binance.py Walkthrough
The examples/binance.py script is the canonical end-to-end demo of the worker pipeline. It runs 2 workers × 5 connections = 10 concurrent WebSocket subscriptions to Binance's depth streams, dispatches the relevant commands to each worker, and publishes aggregated orderbook updates to the worker's response topic. This page walks through what the script does and why, line by line at the section level.
What binance.py does
- Discovers the worker's nupkg URL by reading
build_version.txtnext to the locally-built nupkg and constructing the URL fromconfig.json'sscript_url_template. - Asserts the runtime (the host's arch/OS) is in the nupkg's
.supported-ridssidecar (the runtime arch check — fails fast with a clear error if the local RID isn't supported). - Creates two workers via the WorkManager's
CreateWorkergRPC, both pointing at the same nupkg URL. - Starts the workers via
StartWorkergRPC. - For each of 10 connections (5 per worker), sends three commands in sequence:
create— opens a WebSocket connection to Binance's combined-stream endpointsubscribe— subscribes the open connection to a chunk of currency-pair depth streamspublish— starts the worker publishing orderbook updates for the open connection
- Listens on the response topic for command results. Each command is sent with a
correlationidso the matching response can be identified. - Reports when setup is complete (
=== Setup complete. Listening on ... ===). The script then exits; the workers keep running in the WorkManager.
Configuration
config.json (in the worker root) provides:
script_url_template— the URL pattern for the nupkg. Default: the Gitea NuGet feed athttps://nuget.haenerconsulting.com/....mime_type—application/x-dotnet-dll(the script targets the managed engine).topic— the WorkManager command topic the script publishes to (command.websocketmanagercontroller).
The handler flow
The script doesn't talk to the worker directly — it goes through the WorkManager via the API gateway, which routes to the appropriate engine (managed in this case). Each command is a CloudEvent published to the topic; the WM dispatches it to the worker instance; the worker dispatches the command in its handler; the result is published back on the response topic.
The script is a client of the platform, not a participant. The interesting code is in the worker handler (WebSocketManagerControllerLogic.HandleCommandAsync in the worker's source) — the script just orchestrates the orchestration.
Where the workers do their work
After the script exits, the workers keep running in the WorkManager. The WM is the long-lived process; the workers are the entities that hold the WebSocket connections to Binance and publish orderbook updates. The script is a one-shot setup; the workers are persistent.
This matches the platform's overall pattern: short-lived clients (scripts, tests, ad-hoc commands) interact with long-lived services (the WM, the API gateway) that hold state and connections.
Why this is the canonical demo
- End-to-end: covers the full path from script → API gateway → Dapr pub/sub → WorkManager → worker → WebSocketManager → Binance → back.
- Multi-instance: runs 2 workers and 10 connections concurrently, exercising the WM's per-worker
AssemblyLoadContextand the per-connection WebSocket state inside the WebSocketManager. - Content-addressed versioning: the script reads the version from the locally-built nupkg's
build_version.txtand downloads the matching version from the Gitea feed. No version is hard-coded. - Error surface: if the WM is down, the gRPC call fails with a clear
Gateway invoke failed: ...message. If the nupkg can't be fetched, the script exits before creating any worker. If the runtime arch doesn't match, the runtime check raises with a clearUnsupportedWorkerRuntimeError.
What the script doesn't do
- It doesn't manage the workers after setup. Once
=== Setup complete ===is printed, the script exits. Killing the script does NOT kill the workers — they're in the WorkManager. To stop them, you'd need to callDeleteWorkerfor each (the example doesn't demonstrate this; seescripts/delete_worker.pyin the worker root). - It doesn't subscribe to the orderbook output stream. The workers publish to
act.exchange.binance.orderbook.update(their default), but the script doesn't read that stream. The orderbook is consumable by other services via the API gateway's pubsub RPCs. - It doesn't run on the worker's host. The script can run anywhere — it talks to the WorkManager via the API gateway's gRPC port (
localhost:5002by default).
The handler protocol in detail
Each command in binance.py follows the WebSocketManagerController command protocol:
| Command | Required fields | Effect |
|---|---|---|
create |
url, auto_reconnect, api_host |
Open a WebSocket connection to the url (typically wss://stream.binance.com:9443/stream); return a connection id |
subscribe |
id, params (array of stream names like btcusdt@depth@100ms) |
Subscribe the connection to the streams; subsequent messages are forwarded to the worker's act.exchange.binance.* pubsub topic |
publish |
id, topic |
Start the worker publishing the connection's inbound messages to topic |
unsubscribe |
id, params |
Unsubscribes a chunk of streams from the connection |
stop_publish |
id |
Stops publishing (but keeps the connection open) |
send_raw |
id, message, content_type |
Sends a raw payload to the upstream (used for SUBSCRIBE/UNSUBSCRIBE in JSON-RPC form) |
list |
(none) | Lists active connections |
disconnect |
id |
Closes a single connection |
stop |
(none) | Closes all connections and stops the worker |
The script uses create, subscribe, publish for setup and disconnect would be the inverse. The other commands are useful for runtime introspection and control.
See also
- Getting Started — how to run the script end-to-end
- Worker Author Guide — the command protocol and handler shape
- Engines — how the WM dispatches the commands to the worker