From 8de23086d091c340cb895d6c2c260e5761c010e7 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sun, 9 Aug 2026 12:23:20 -0400 Subject: [PATCH] feat(worker): share the completion cache between sink and session --- .../MxAccess/MxAccessSession.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessSession.cs b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessSession.cs index a82e0a6..0bc4690 100644 --- a/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessSession.cs +++ b/src/ZB.MOM.WW.MxGateway.Worker/MxAccess/MxAccessSession.cs @@ -13,6 +13,7 @@ public sealed class MxAccessSession : IDisposable private readonly IMxAccessEventSink eventSink; private readonly MxAccessHandleRegistry handleRegistry; private readonly MxAccessValueCache valueCache; + private readonly MxAccessWriteCompletionCache writeCompletionCache; private bool disposed; private MxAccessSession( @@ -21,6 +22,7 @@ public sealed class MxAccessSession : IDisposable IMxAccessEventSink eventSink, MxAccessHandleRegistry handleRegistry, MxAccessValueCache valueCache, + MxAccessWriteCompletionCache writeCompletionCache, int creationThreadId) { this.mxAccessComObject = mxAccessComObject ?? throw new ArgumentNullException(nameof(mxAccessComObject)); @@ -28,6 +30,7 @@ public sealed class MxAccessSession : IDisposable this.eventSink = eventSink ?? throw new ArgumentNullException(nameof(eventSink)); this.handleRegistry = handleRegistry ?? throw new ArgumentNullException(nameof(handleRegistry)); this.valueCache = valueCache ?? throw new ArgumentNullException(nameof(valueCache)); + this.writeCompletionCache = writeCompletionCache ?? throw new ArgumentNullException(nameof(writeCompletionCache)); CreationThreadId = creationThreadId; } @@ -45,6 +48,14 @@ public sealed class MxAccessSession : IDisposable /// public MxAccessValueCache ValueCache => valueCache; + /// + /// Per-session OnWriteComplete completion cache populated by the event + /// sink. The write command executor consults it after a + /// WriteSecured/WriteSecured2 COM call so the unary reply can carry + /// the correlated completion outcome. + /// + public MxAccessWriteCompletionCache WriteCompletionCache => writeCompletionCache; + /// Creates a WorkerReady message with session metadata. /// Process ID of the worker. /// The populated message. @@ -149,12 +160,22 @@ public sealed class MxAccessSession : IDisposable ? baseSink.ValueCache : new MxAccessValueCache(); + // Share the sink's completion cache the same way (the production + // sink and completion-aware test sinks implement the provider + // seam); fall back to a fresh cache for other fakes — the write + // executor then simply never observes a completion and replies + // unconfirmed. + MxAccessWriteCompletionCache writeCompletionCache = eventSink is IWriteCompletionCacheProvider provider + ? provider.WriteCompletionCache + : new MxAccessWriteCompletionCache(); + return new MxAccessSession( mxAccessComObject, new MxAccessComServer(mxAccessComObject), eventSink, new MxAccessHandleRegistry(), valueCache, + writeCompletionCache, Environment.CurrentManagedThreadId); } catch (Exception exception)