17 KiB
MXAccess Worker Instance Detailed Design
Purpose
An MXAccess worker instance is the compatibility boundary around one installed MXAccess COM object. It runs as a disposable .NET Framework 4.8 x86 process, owns one dedicated STA thread, pumps Windows/COM messages, executes MXAccess commands on that STA, and forwards MXAccess events back to the gateway.
The worker's job is not to make MXAccess nicer. Its job is to preserve direct MXAccess behavior while making that behavior available to modern clients through the gateway.
Runtime
- Target runtime: .NET Framework 4.8.
- Language: C#.
- Platform target: x86 by default.
- Process lifetime: one worker per gateway session.
- Public network listeners: none.
- Gateway IPC: one named pipe with protobuf-framed messages.
- COM apartment: one dedicated STA thread.
Style guides:
Build And Test
Build the SDK-style worker project with the .NET SDK MSBuild entry point. The project targets .NET Framework 4.8, but the SDK resolver comes from the .NET SDK installation:
dotnet msbuild src\MxGateway.Worker\MxGateway.Worker.csproj /restore /p:Configuration=Debug /p:Platform=x86
docs/toolchain-links.md records the Visual Studio MSBuild executable for
classic .NET Framework and COM interop builds:
& "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe" src\MxGateway.Worker\MxGateway.Worker.csproj /p:Configuration=Debug /p:Platform=x86
Run the worker tests with the same platform target:
dotnet test src\MxGateway.Worker.Tests\MxGateway.Worker.Tests.csproj -p:Platform=x86
The only MXAccess interop reference belongs in MxGateway.Worker. Gateway and
test projects may reference the worker project for metadata and scaffold tests,
but they must not reference ArchestrA.MXAccess.dll directly.
Responsibilities
The worker owns:
- connection to the gateway pipe,
- protocol hello and readiness reporting,
- STA thread creation and teardown,
- COM initialization on the STA,
- MXAccess COM object creation,
- MXAccess event sink wiring,
- command dispatch on the STA,
- MXAccess handle and advise state tracking,
- value/status/HRESULT capture,
- conversion to worker protobuf DTOs,
- event sequencing,
- heartbeat reporting,
- graceful shutdown.
The worker does not own:
- public gRPC API,
- client authentication,
- cross-session routing,
- worker process supervision,
- remote TLS,
- policy decisions for other sessions.
Process Bootstrap
Expected command-line arguments:
--session-id <sessionId>
--pipe-name <pipeName>
--protocol-version <version>
Expected protected environment values:
MXGATEWAY_WORKER_NONCE=<random nonce>
MXGATEWAY_WORKER_LOG_CONTEXT=<optional context>
Startup sequence:
- Parse command-line arguments.
- Configure minimal logging.
- Validate required values are present.
- Connect to the gateway named pipe.
- Exchange
WorkerHelloandGatewayHello. - Validate protocol version, session id, and nonce.
- Start the STA runtime.
- Create the MXAccess COM object on the STA.
- Attach MXAccess event handlers on the STA.
- Send
WorkerReady. - Start pipe read, pipe write, heartbeat, and shutdown coordination loops.
If validation fails before MXAccess creation, exit quickly with a non-zero exit
code. If MXAccess creation fails, send WorkerFault when possible and exit.
Internal Components
MxGateway.Worker
Program
Bootstrap
WorkerOptions
WorkerHost
Ipc
PipeClient
FrameReader
FrameWriter
WorkerProtocol
Sta
StaRuntime
StaCommandQueue
MessagePump
StaWatchdog
MxAccess
MxAccessSession
MxAccessCommandDispatcher
MxAccessEventSink
MxAccessHandleRegistry
Conversion
VariantConverter
SafeArrayConverter
StatusProxyConverter
HResultMapper
Threading Model
main thread
-> parse args
-> configure host
-> coordinate shutdown
pipe reader thread/task
-> read WorkerEnvelope frames
-> validate protocol
-> enqueue commands or control messages
pipe writer thread/task
-> serialize WorkerEnvelope frames
-> write replies, events, heartbeats, faults
STA thread
-> CoInitializeEx(APARTMENTTHREADED)
-> create MXAccess COM object
-> attach event handlers
-> pump Windows/COM messages
-> execute queued commands
-> detach events and release COM on shutdown
watchdog/heartbeat task
-> observe STA responsiveness
-> send heartbeat or fault
No MXAccess method may execute outside the STA thread. Do not use Task.Run
around COM calls. Do not let event handlers perform pipe writes.
STA Runtime
The STA runtime is the most important part of the worker.
Startup:
- Create a dedicated
Thread. - Set apartment state to
ApartmentState.STA. - Start the thread.
- Inside the thread, initialize COM.
- Create the MXAccess COM object.
- Attach event handlers.
- Signal ready to the worker host.
- Enter the message pump.
Shutdown:
- Mark the command queue as completing.
- Drain or reject pending commands according to shutdown mode.
- Optionally issue MXAccess cleanup calls for active handles.
- Detach event handlers.
- Release COM references.
- Uninitialize COM.
- Exit the thread.
Message Pump
The STA must pump Windows messages while also processing queued commands. A blocking queue that prevents message pumping is not acceptable.
Required loop shape:
while not shutdown:
while command queue has work:
execute one command on STA
MsgWaitForMultipleObjectsEx(
command_event,
timeout,
QS_ALLINPUT,
MWMO_INPUTAVAILABLE)
while PeekMessage:
TranslateMessage
DispatchMessage
The command queue should signal a Win32 event or equivalent wait handle so the STA can wake without busy-waiting.
The loop should update a heartbeat timestamp after:
- successfully pumping messages,
- starting a command,
- finishing a command,
- processing an MXAccess event.
COM Creation
The MXAccess analysis source at C:\Users\dohertj2\Desktop\mxaccess identifies
the installed COM target:
- interop assembly:
C:\Program Files (x86)\ArchestrA\Framework\Bin\ArchestrA.MXAccess.dll - assembly identity:
ArchestrA.MxAccess, Version=3.2.0.0, PublicKeyToken=23106a86e706d0ae - COM class:
ArchestrA.MxAccess.LMXProxyServerClass - CLSID:
{C30B52F5-2CB5-4760-AF0A-3A344A7EB5DC} - ProgID:
LMXProxy.LMXProxyServer.1 - version-independent ProgID:
LMXProxy.LMXProxyServer - registered server:
C:\Program Files (x86)\ArchestrA\Framework\Bin\LmxProxy.dll - registry view:
HKCR\Wow6432Node\CLSID\{C30B52F5-2CB5-4760-AF0A-3A344A7EB5DC} - threading model:
Apartment
The worker should reference the interop assembly and instantiate
LMXProxyServerClass on the dedicated STA thread. Keep the ProgID and assembly
path configurable for diagnostics, but this COM class is the v1 default.
Creation rules:
- Create COM object only on the STA.
- Attach event handlers only on the STA.
- Keep the COM reference private to the STA runtime.
- Never marshal the raw COM object to pipe reader/writer threads.
- Capture COM creation HRESULT or exception details.
If COM creation fails, the worker should send a structured fault with:
- fault category,
- exception type,
- HRESULT when available,
- COM class or ProgID attempted,
- worker process id,
- session id.
Event Sink
The worker must subscribe to every public MXAccess event family:
OnDataChangeOnWriteCompleteOperationCompleteOnBufferedDataChange
Forward these event families only when the native MXAccess COM object raises
them. Do not synthesize OperationComplete from write completion or command
status. OnBufferedDataChange must be represented in the protocol now, but
multi-sample payload conversion should remain capture-validated; preserve raw
metadata whenever conversion is incomplete.
Event handling rules:
- Event handlers are expected to run on the STA.
- Assign a monotonic worker event sequence.
- Convert event args to
WorkerEvent. - Include value, quality, timestamp, handles, status arrays, and raw status details when available.
- Preserve raw event payload metadata for unsupported buffered or completion-only shapes.
- Enqueue to the outbound event queue.
- Return quickly to preserve message pumping.
If event conversion throws, catch it inside the event handler, enqueue a
structured WorkerFault or diagnostic event, and keep the worker alive only if
the fault policy allows it.
Command Queue
The pipe reader converts WorkerCommand messages into StaCommand entries.
Each entry should include:
- correlation id,
- method name,
- method-specific request payload,
- enqueue timestamp,
- cancellation marker,
- reply completion path.
The STA command dispatcher:
- Dequeues one command.
- Checks whether shutdown has started.
- Calls the matching MXAccess method.
- Captures return values, out parameters, status arrays, and HRESULT.
- Converts results to
WorkerCommandReply. - Enqueues the reply to the pipe writer.
The STA should execute one command at a time. MXAccess command ordering must be preserved for one worker.
Command Dispatch Surface
Phase 1 commands:
RegisterUnregisterAddItemRemoveItem
Phase 2 event commands:
AdviseUnAdviseAdviseSupervisory
Full surface:
AddItem2AddBufferedItemSetBufferedUpdateIntervalSuspendActivateWriteWrite2WriteSecuredWriteSecured2AuthenticateUserArchestrAUserToId
Diagnostics:
PingGetSessionStateGetWorkerInfoDrainEventsShutdownWorker
Implement method-specific dispatch instead of a generic string method invoker. Parity tests need stable command-specific request and reply shapes.
Handle Registry
The worker should track MXAccess state for diagnostics and cleanup, while still treating MXAccess as the authority.
Suggested tracked state:
- registered server handles,
- item handles,
- item names and context,
- server handle for each item,
- advise state,
- buffered item state,
- authenticated user ids if needed,
- last command touching each handle.
Rules:
- Do not invent handles.
- Do not rewrite handles returned by MXAccess.
- Preserve invalid-handle behavior from MXAccess.
- Preserve cross-server handle behavior from MXAccess.
- Use registry state for cleanup and diagnostics, not semantic correction.
Value Conversion
VariantConverter should convert COM values into the protobuf MxValue union.
Supported scalar projections:
- bool,
- int32,
- int64,
- float,
- double,
- string,
- timestamp,
- raw fallback.
Supported arrays:
- bool array,
- int32 array,
- float array,
- double array,
- string array,
- timestamp array,
- raw fallback.
Rules:
- Preserve null and empty values distinctly when MXAccess exposes a distinction.
- Preserve array rank and dimensions when available.
- Preserve original variant type metadata.
- If conversion is lossy, include the best typed value plus raw diagnostic metadata.
- Do not throw away values just because they are awkward.
Credential-bearing values must not be logged.
Status And HRESULT Capture
MXSTATUS_PROXY arrays must be represented explicitly. Do not collapse status
arrays into a single success flag.
For every command reply, capture:
- protocol success/failure,
- method name,
- correlation id,
- COM HRESULT if available,
- thrown exception HRESULT if available,
- MXAccess return value if any,
- method-specific out parameters,
- status array,
- diagnostic message safe for logs.
If a COM call throws, map the exception into a command reply instead of crashing the worker, unless the exception indicates process corruption or the configured policy says to fail the session.
Cancellation
Worker cancellation is cooperative at the queue boundary.
Rules:
- If a
WorkerCancelarrives before a command starts, mark the command canceled and reply or drop according to protocol policy. - If a command is already executing on the STA, do not attempt to abort the COM call.
- When the COM call returns after gateway cancellation, send the reply only if the gateway still wants late replies; otherwise log and discard.
- Hard cancellation is process kill by the gateway.
Outbound Queues
The worker should use bounded outbound queues for replies, events, heartbeats, and faults.
Priority order when writing:
- faults,
- command replies,
- shutdown acknowledgements,
- heartbeats,
- events.
Event overflow policy defaults to fail-fast for parity testing. If the event queue fills:
- Capture overflow metrics.
- Send
WorkerFaultif possible. - Stop accepting new commands.
- Let the gateway close or kill the worker.
Production coalescing may be added later, but it must be explicit and tested. Do not drop or coalesce events in v1.
Heartbeat And Watchdog
The worker heartbeat should prove that:
- pipe writer is alive,
- worker host is alive,
- STA has recently pumped or completed work.
Heartbeat payload should include:
- worker process id,
- session id,
- current state,
- last STA activity timestamp,
- pending command count,
- outbound event queue depth,
- event sequence,
- current command correlation id if any.
The STA watchdog should warn when:
- one command exceeds its expected duration,
- the STA has not pumped messages within the heartbeat grace period,
- event queue depth remains high.
The worker can report the problem, but the gateway owns the final kill decision.
Shutdown
Graceful shutdown sequence:
- Pipe reader receives
WorkerShutdown. - Worker host marks shutdown requested.
- Reject new commands.
- Let current STA command finish if within timeout.
- Optionally run MXAccess cleanup:
UnAdvise,RemoveItem,Unregister.
- Detach event handlers.
- Release COM object until reference count reaches zero when possible.
- Stop pipe reader and writer.
- Exit process with success code.
If shutdown wedges, the gateway kills the process. The worker should be written so process kill does not corrupt other sessions.
Fault Handling
Worker fault categories:
InvalidArgumentsGatewayAuthenticationFailedProtocolMismatchProtocolViolationPipeDisconnectedMxAccessCreationFailedMxAccessCommandFailedMxAccessEventConversionFailedStaHungQueueOverflowShutdownTimeout
Fault payload should include:
- category,
- session id,
- correlation id when command-specific,
- command method when command-specific,
- HRESULT when available,
- exception type when available,
- safe diagnostic message.
Do not include raw credentials or full secured-write values.
Security
The worker should trust only the launching gateway after validating:
- expected session id,
- expected protocol version,
- nonce,
- pipe identity where available.
It should not expose any network listener. It should not accept commands from arbitrary local processes.
Credential-bearing commands must keep credential data out of:
- command line,
- logs,
- metrics labels,
- exception messages,
- crash dumps when avoidable.
Observability
Worker logs should include:
- startup arguments except secrets,
- protocol version,
- gateway handshake result,
- MXAccess COM creation result,
- command start/end with correlation id,
- HRESULT/status summary,
- event family and sequence,
- queue overflow,
- STA watchdog warnings,
- shutdown path.
Metrics can be emitted through the gateway or exposed as worker heartbeat fields. The worker does not need its own public metrics endpoint.
Testing Strategy
Worker tests that do not require installed MXAccess:
- frame reader/writer,
- protocol validation,
- command queue ordering,
- STA command scheduling with a fake COM object,
- message-pump wake behavior where practical,
- value conversion,
- status conversion,
- event conversion from fake event args,
- shutdown state transitions,
- queue overflow behavior.
Live MXAccess tests:
- COM creation on STA,
RegisterandUnregister,AddItemandRemoveItem,Adviseand oneOnDataChange,- write completion behavior,
- secured write behavior,
- buffered data-change behavior,
- invalid handle behavior.
- no synthesized
OperationCompletewhen native MXAccess does not raise it. - raw metadata preservation for buffered payloads that cannot yet be fully converted.
Live tests should be opt-in and clearly marked because they depend on installed MXAccess COM and provider state.
Initial Implementation Slice
The first worker slice should implement:
- Argument parsing and pipe connection.
- Protocol hello and nonce validation.
- STA thread startup.
- COM initialization and MXAccess object creation.
- Message pump with command wake event.
WorkerReady.- Shutdown command.
Register,AddItem, andAdvise.- Event sink for one
OnDataChange. - Basic value/status conversion.
- Event model coverage for
OperationCompleteandOnBufferedDataChangewithout synthesized events. - Fault reporting.
This slice proves the worker can preserve the core MXAccess requirements: single-process isolation, STA ownership, message pumping, command execution, and event delivery.