Skip to content

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//native/lib.so), or 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