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)