Workers
What is a Worker?¶
Workers are long-lived processes that execute FuncNodes graphs. Each worker owns:
- A NodeSpace (graph state, groups, properties).
- An isolated environment (virtualenv by default) and dependency set.
- A data directory (
~/.funcnodes/workers/worker_<uuid>/) containingnodespace.json, uploadedfiles/, optionalpyproject.toml, andworker.log. - A WebSocket/HTTP server that exposes RPC commands and large-payload endpoints.
Workers are started and supervised by the Workermanager, but you can also launch them directly via funcnodes worker start.
Runtime loops & health¶
The worker event loop runs several recurring tasks:
- NodeSpaceLoop — drains pending triggers (
NodeSpace.await_done) on a short interval (default 5 ms). - SaveLoop — writes process/runstate files and persists the graph when
request_save()is flagged. - LocalWorkerLookupLoop — discovers external worker classes in
data_path/local_scripts. - HeartbeatLoop — optional; if
required_heartbeatis set and noheartbeat()RPC arrives in time, the worker stops itself.
Defaults can be tuned via worker config (e.g., nodespace_delay, save_delay) for responsiveness vs. CPU/disk usage.
RPC surface (WebSocket JSON)¶
Clients send {"type":"cmd","cmd":<name>,"kwargs":{...}}; worker replies with result or error. Common commands:
- Identity/meta:
uuid,name,get_meta,heartbeat - State:
full_state,get_nodes,get_edges,get_groups,view_state,get_save_state - Mutations:
update_node,update_group,group_nodes,remove_group,clear,save,load_data,export_worker,import_worker - Library/modules:
get_library,get_worker_dependencies,get_plugin_keys,get_plugin,add_package_dependency,remove_package_dependency - External tooling:
list_local_workers,start_local_worker,stop_local_worker,upload
Ping/pong is built in ({"type":"ping"} → {"type":"pong"}) and is how the UI detects liveness.
Messaging & large payloads¶
- Standard messages travel over WebSockets as JSON.
- Messages larger than
MESSAGE_SIZE_BEFORE_REQUEST(default 1 MB) are staged in memory; the worker sends alarge_messagestub and exposes a temporary HTTP endpoint/message/<msg_id>for retrieval. - Binary streams (e.g., image frames) are chunked with headers
chunk=<i>/<n>to avoid blocking the socket. - Uploads use
POST /upload/and are forcibly rooted tofiles/inside the worker’s data dir; attempts to traverse elsewhere are rejected.
Lifecycle & files on disk¶
When running, the worker writes:
worker_<uuid>.json— config (host/port, env paths, flags)worker_<uuid>.p— PID file for liveness detectionworker_<uuid>.runstate— human-readable status (“starting…”, “running”, etc.)
Shutting down clears PID/runstate and flushes a final save. Exports bundle config, state, optional pyproject.toml, and files/ into a ZIP for backup/migration.
Isolation & performance¶
- Process/thread offload: Nodes can set
separate_thread=Trueorseparate_process=Trueto avoid blocking the event loop. - Message size caps:
MAX_DATA_SIZE(default 10 GB) protects memory; adjust via env if needed. - Logging: Per-worker rotating file handler (~100 KB × 5). Change location/level via config.
Security considerations¶
- Workers do not implement authentication. In production, front them with an authenticated proxy (e.g., nginx/Traefik) and keep ports non-public. File writes are constrained to
files/, but you should still sandbox network access and enforce upload size limits at the proxy. - TLS termination is not provided by the worker itself; the
sslfield in the config defaults toFalseand the WebSocket loop always starts plain HTTP. Terminate TLS in your proxy/load balancer instead.
Environments & dependencies¶
- New workers create their own virtualenv unless started with
--not-in-venv; sharing the interpreter is possible but increases the risk of version conflicts. - The worker config carries
update_on_startupflags (defaultTrueforfuncnodes,funcnodes-core,funcnodes-worker) so core packages can be upgraded automatically when a worker starts. - Additional packages installed via
funcnodes worker ... modules install ...are tracked per worker inpackage_dependencies; isolated envs let different workers pin incompatible versions safely.
Subprocess/offload options¶
@NodeDecorator(..., separate_thread=True)runs the wrapped function in a thread;separate_process=Truewraps it in aProcessPoolExecutor(funcnodes_core.utils.functions.make_run_in_new_process).- Workers expose an optional
subprocess_monitorhost/port in their config; if set, heavy external commands can be supervised by thesubprocess_monitorservice. FuncNodes itself does not enforce per-node resource limits—rely on the monitor/OS for that.