Virtufin Workers
A C#/.NET 10 worker source package for the Virtufin WorkManager. Each worker is a self-contained nupkg consumable by the WM's DotNetDllEngine (managed, in-process) or NativeDllEngine (NativeAOT, in-process), with the engine selected at CreateWorker time by the MIME type.
The canonical worker is WebSocketManagerController — a self-driving binance orderbook subscriber. It demonstrates the full pattern: a shared handler logic, a managed shim, a NativeAOT shim, content-addressed versioning, and the IGatewayClient transport abstraction.
Key Features
| Feature | Description |
|---|---|
| Shared handler logic | One .cs file compiled into both managed and AOT projects — the bulk of the worker code is identical regardless of which engine loads it. |
IGatewayClient abstraction |
Workers call await gateway.InvokeAsync("websocketmanager", "Connect", request) — the adapter routes to managed gRPC or the host's C-ABI gateway callback. |
| Three packaging modes | managed (lib/net10.0/.dll), native (runtimes/both (single nupkg containing both layouts). |
| Content-addressed versioning | The build script produces a deterministic, byte-stable nupkg whose filename encodes the SHA-256 short hash. Re-publishes are no-ops when source is unchanged. |
| NativeAOT-ready | The AOT csproj sets <PublishAot>true</PublishAot> + per-RID <ObjCopyName>; the build script runs dotnet publish inside the dotnet-crossbuild:10.0 Docker image (clang + cross-binutils). |
| Runtime arch check | Each nupkg ships a .supported-rids sidecar manifest. Consumers (e.g. binance.py) read it before CreateWorker to assert the local RID is supported. |
Worker Layout
WebSocketManagerController/
├── config/
│ └── config.json # script_url_template, topic, mime_type, etc.
├── examples/
│ ├── binance.py # the worked example; subscribes to Binance depth
│ ├── alpaca.py # Alpaca variant
│ └── settings.binance.json
├── src/
│ ├── Virtufin.Worker.WebSocketManagerController.Managed/
│ │ ├── *.csproj # OutputType=Library, <PackageId>=...
│ │ ├── WebSocketManagerController.cs # managed shim (~30 LOC)
│ │ ├── WebSocketManagerControllerLogic.cs # shared logic (~700 LOC)
│ │ └── Command.cs # command enum
│ └── Virtufin.Worker.WebSocketManagerController.Native/
│ ├── *.Native.csproj # <PublishAot>, <RuntimeIdentifiers>=linux-x64,linux-arm64
│ └── WebSocketManagerControllerNative.cs # AOT shim with [UnmanagedCallersOnly]
├── scripts/
│ ├── build_managed.py # dotnet publish the managed csproj
│ ├── build_aot.py # dotnet publish -r <RID> --publish-aot
│ ├── build_both.sh # produce a nupkg with BOTH layouts
│ ├── publish.py # push the nupkg to the Gitea NuGet feed
│ └── smoke_test_aot.py # verify the AOT nupkg is well-formed
└── @common/ # shared with the @common scripts in the repo root
└── scripts/
├── build_managed.py # the canonical managed build (shared helpers)
├── build_aot.py # the canonical AOT build
└── publish.py # the canonical publish
Build & Run
The three packaging modes:
# 1. managed-only (lib/net10.0/*.dll)
python3 scripts/build_managed.py
python3 scripts/publish.py
# 2. NativeAOT-only (runtimes/<rid>/native/lib*.so) — requires Docker
python3 scripts/build_aot.py
python3 scripts/publish.py
# 3. both — single nupkg supporting either engine
python3 scripts/build_both.sh
python3 scripts/publish.py
Then load and run:
# 3. Run the binance example against the local WM + API + Dapr
python3 examples/binance.py
Get Started
- Overview — What workers are and how the WM loads them
- Getting Started — Build, publish, and run the binance example end-to-end
- Worker Author Guide — Author a new worker in the recommended shape
- Engines — DotNetDll vs NativeDll: how each loads, how the IGatewayClient routes
- Packaging Modes — managed / native / both, and which to pick