fix(worker): wire alarm command handler and STA poll loop (Gap 1 + Gap 2)
Gap 1 — WorkerPipeSession now passes `eq => new AlarmCommandHandler(eq)` as
the alarmCommandHandlerFactory in all three places it constructs
MxAccessStaSession (two convenience constructors and InitializeMxAccessAsync).
Previously the parameterless MxAccessStaSession() set the factory to null,
so every SubscribeAlarms / AcknowledgeAlarm / QueryActiveAlarms command
returned "alarm consumer not configured" in a deployed worker.
- Added internal `MxAccessStaSession(Func<MxAccessEventQueue, IAlarmCommandHandler>?)`
constructor that builds all defaults but accepts a factory.
- Added public `MxAccessStaSession(StaRuntime, factory, eventQueue, alarmFactory?)`
4-arg overload to complete the constructor chain.
Gap 2 — WnWrapAlarmConsumer now disables its internal threadpool Timer
(pollIntervalMilliseconds=0 in the default constructor). MxAccessStaSession
starts a `RunAlarmPollLoopAsync` background task that sleeps off-STA then
calls `staRuntime.InvokeAsync(() => handler.PollOnce())` at 500ms intervals.
This satisfies the ThreadingModel=Apartment requirement of wwAlarmConsumerClass:
every GetXmlCurrentAlarms2 call now runs on the worker's STA.
- Added `PollOnce()` to `IMxAccessAlarmConsumer`, `AlarmDispatcher`,
`IAlarmCommandHandler`, and `AlarmCommandHandler`.
- Poll loop cancelled and awaited before alarm handler disposal in both
ShutdownGracefullyAsync and Dispose.
Tests: 4 new tests in MxAccessStaSessionTests verify that
- SubscribeAlarms reaches the handler when the factory is wired (Gap 1)
- SubscribeAlarms returns InvalidRequest without a factory (regression guard)
- PollOnce is called on the STA thread within 3s (Gap 2)
- The poll loop stops after Dispose (Gap 2 lifecycle)
All fake IMxAccessAlarmConsumer / IAlarmCommandHandler test implementations
updated with no-op PollOnce() to satisfy the new interface member.
Worker tests: 199 passed / 1 pre-existing failure / 4 skipped (was 195/1/4).
Server tests: 308 passed / 0 failures (unchanged).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -63,8 +63,16 @@ public sealed class WnWrapAlarmConsumer : IMxAccessAlarmConsumer
|
||||
private bool subscribed;
|
||||
private bool disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Production constructor — creates the wnwrap COM object on the
|
||||
/// current thread (must be the worker's STA) and disables the
|
||||
/// internal <see cref="Timer"/> (<c>pollIntervalMilliseconds=0</c>).
|
||||
/// Polling is driven externally by the STA via
|
||||
/// <c>StaRuntime.InvokeAsync(() => consumer.PollOnce())</c> so
|
||||
/// that every COM call stays on the STA that owns the apartment.
|
||||
/// </summary>
|
||||
public WnWrapAlarmConsumer()
|
||||
: this(new wwAlarmConsumerClass(), DefaultPollIntervalMilliseconds, DefaultMaxAlarmsPerFetch)
|
||||
: this(new wwAlarmConsumerClass(), pollIntervalMilliseconds: 0, DefaultMaxAlarmsPerFetch)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user