Compare commits

..

4 Commits

Author SHA1 Message Date
Joseph Doherty 91d8715c74 docs: write-completion correlation covers all four unary write kinds
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Failing after 1m51s
ci / java (push) Successful in 2m16s
ci / portable (push) Successful in 8m26s
2026-08-09 19:47:04 -04:00
Joseph Doherty 794c44246a chore(clients): regenerate Go/Java bindings and Rust vendored proto for the plain-write statuses comments 2026-08-09 19:47:04 -04:00
Joseph Doherty b0e65d4f31 feat(worker): correlate OnWriteComplete onto plain Write/Write2 replies (06/S-1 follow-up)
OtOpcUa's dominant FreeAccess write path goes out as MX_COMMAND_KIND_WRITE,
not WriteSecured — the original 06/S-1 brief mis-scoped the correlation, so
a refused plain write was invisible on the unary reply (verified live on
windev 2026-08-09). ExecuteWrite/ExecuteWrite2 now use the same pre-call
version baseline + bounded pump-wait as the secured kinds. Bulk writes stay
fire-and-forget.
2026-08-09 19:47:00 -04:00
Joseph Doherty b948e6975e feat: correlate OnWriteComplete onto WriteSecured/WriteSecured2 unary replies (OtOpcUa 06/S-1)
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Failing after 1m9s
ci / java (push) Successful in 2m3s
ci / portable (push) Successful in 7m20s
2026-08-09 12:52:18 -04:00
10 changed files with 484 additions and 285 deletions
@@ -2553,6 +2553,9 @@ func (x *ActivateCommand) GetItemHandle() int32 {
return 0 return 0
} }
// The unary reply's statuses field carries the correlated OnWriteComplete
// outcome when it arrives within the worker's bounded wait — see
// MxCommandReply.statuses.
type WriteCommand struct { type WriteCommand struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
ServerHandle int32 `protobuf:"varint,1,opt,name=server_handle,json=serverHandle,proto3" json:"server_handle,omitempty"` ServerHandle int32 `protobuf:"varint,1,opt,name=server_handle,json=serverHandle,proto3" json:"server_handle,omitempty"`
@@ -2621,6 +2624,7 @@ func (x *WriteCommand) GetUserId() int32 {
return 0 return 0
} }
// Same statuses correlation as WriteCommand.
type Write2Command struct { type Write2Command struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
ServerHandle int32 `protobuf:"varint,1,opt,name=server_handle,json=serverHandle,proto3" json:"server_handle,omitempty"` ServerHandle int32 `protobuf:"varint,1,opt,name=server_handle,json=serverHandle,proto3" json:"server_handle,omitempty"`
@@ -4583,18 +4587,19 @@ type MxCommandReply struct {
// transport failures. // transport failures.
Hresult *int32 `protobuf:"varint,5,opt,name=hresult,proto3,oneof" json:"hresult,omitempty"` Hresult *int32 `protobuf:"varint,5,opt,name=hresult,proto3,oneof" json:"hresult,omitempty"`
ReturnValue *MxValue `protobuf:"bytes,6,opt,name=return_value,json=returnValue,proto3" json:"return_value,omitempty"` ReturnValue *MxValue `protobuf:"bytes,6,opt,name=return_value,json=returnValue,proto3" json:"return_value,omitempty"`
// Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 // Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
// replies the worker holds the reply for a bounded window (default 1.5 s, // WRITE_SECURED2 replies the worker holds the reply for a bounded window
// MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching // (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
// MXAccess OnWriteComplete callback and copies its status rows here, so // the matching MXAccess OnWriteComplete callback and copies its status rows
// statuses[0] carries the real MXAccess commit outcome (success OR failure) // here, so statuses[0] carries the real MXAccess commit outcome (success OR
// while protocol_status/hresult still describe command acceptance only. // failure) while protocol_status/hresult still describe command acceptance
// Empty statuses on a write reply means the completion did not arrive // only. Empty statuses on a write reply means the completion did not arrive
// within the window — the write is unconfirmed, not failed. Correlation is // within the window — the write is unconfirmed, not failed. Correlation is
// best-effort per (server_handle, item_handle): MXAccess's callback carries // best-effort per (server_handle, item_handle): MXAccess's callback carries
// no transaction id, so concurrent writes to the same item within the // no transaction id, so concurrent writes to the same item within the
// window can swap rows. The OnWriteComplete event still flows on the event // window can swap rows. The OnWriteComplete event still flows on the event
// stream unchanged. Other command kinds leave this field as before. // stream unchanged. Bulk write kinds and all non-write kinds leave this
// field as before.
Statuses []*MxStatusProxy `protobuf:"bytes,7,rep,name=statuses,proto3" json:"statuses,omitempty"` Statuses []*MxStatusProxy `protobuf:"bytes,7,rep,name=statuses,proto3" json:"statuses,omitempty"`
DiagnosticMessage string `protobuf:"bytes,8,opt,name=diagnostic_message,json=diagnosticMessage,proto3" json:"diagnostic_message,omitempty"` DiagnosticMessage string `protobuf:"bytes,8,opt,name=diagnostic_message,json=diagnosticMessage,proto3" json:"diagnostic_message,omitempty"`
// Types that are valid to be assigned to Payload: // Types that are valid to be assigned to Payload:
@@ -24235,6 +24235,12 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
int getUserId(); int getUserId();
} }
/** /**
* <pre>
* The unary reply's statuses field carries the correlated OnWriteComplete
* outcome when it arrives within the worker's bounded wait see
* MxCommandReply.statuses.
* </pre>
*
* Protobuf type {@code mxaccess_gateway.v1.WriteCommand} * Protobuf type {@code mxaccess_gateway.v1.WriteCommand}
*/ */
public static final class WriteCommand extends public static final class WriteCommand extends
@@ -24527,6 +24533,12 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
return builder; return builder;
} }
/** /**
* <pre>
* The unary reply's statuses field carries the correlated OnWriteComplete
* outcome when it arrives within the worker's bounded wait see
* MxCommandReply.statuses.
* </pre>
*
* Protobuf type {@code mxaccess_gateway.v1.WriteCommand} * Protobuf type {@code mxaccess_gateway.v1.WriteCommand}
*/ */
public static final class Builder extends public static final class Builder extends
@@ -25036,6 +25048,10 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
int getUserId(); int getUserId();
} }
/** /**
* <pre>
* Same statuses correlation as WriteCommand.
* </pre>
*
* Protobuf type {@code mxaccess_gateway.v1.Write2Command} * Protobuf type {@code mxaccess_gateway.v1.Write2Command}
*/ */
public static final class Write2Command extends public static final class Write2Command extends
@@ -25370,6 +25386,10 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
return builder; return builder;
} }
/** /**
* <pre>
* Same statuses correlation as WriteCommand.
* </pre>
*
* Protobuf type {@code mxaccess_gateway.v1.Write2Command} * Protobuf type {@code mxaccess_gateway.v1.Write2Command}
*/ */
public static final class Builder extends public static final class Builder extends
@@ -50513,18 +50533,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -50533,18 +50554,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
getStatusesList(); getStatusesList();
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -50552,18 +50574,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
mxaccess_gateway.v1.MxaccessGateway.MxStatusProxy getStatuses(int index); mxaccess_gateway.v1.MxaccessGateway.MxStatusProxy getStatuses(int index);
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -50571,18 +50594,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
int getStatusesCount(); int getStatusesCount();
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -50591,18 +50615,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
getStatusesOrBuilderList(); getStatusesOrBuilderList();
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -51333,18 +51358,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
private java.util.List<mxaccess_gateway.v1.MxaccessGateway.MxStatusProxy> statuses_; private java.util.List<mxaccess_gateway.v1.MxaccessGateway.MxStatusProxy> statuses_;
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -51355,18 +51381,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -51378,18 +51405,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -51400,18 +51428,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -51422,18 +51451,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54156,18 +54186,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54181,18 +54212,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54206,18 +54238,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54231,18 +54264,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54263,18 +54297,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54292,18 +54327,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54323,18 +54359,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54355,18 +54392,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54384,18 +54422,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54413,18 +54452,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54443,18 +54483,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54471,18 +54512,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54499,18 +54541,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54521,18 +54564,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54546,18 +54590,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54572,18 +54617,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54594,18 +54640,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54617,18 +54664,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
} }
/** /**
* <pre> * <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 * Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* replies the worker holds the reply for a bounded window (default 1.5 s, * WRITE_SECURED2 replies the worker holds the reply for a bounded window
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching * (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* MXAccess OnWriteComplete callback and copies its status rows here, so * the matching MXAccess OnWriteComplete callback and copies its status rows
* statuses[0] carries the real MXAccess commit outcome (success OR failure) * here, so statuses[0] carries the real MXAccess commit outcome (success OR
* while protocol_status/hresult still describe command acceptance only. * failure) while protocol_status/hresult still describe command acceptance
* Empty statuses on a write reply means the completion did not arrive * only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is * within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries * best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the * no transaction id, so concurrent writes to the same item within the
* window can swap rows. The OnWriteComplete event still flows on the event * window can swap rows. The OnWriteComplete event still flows on the event
* stream unchanged. Other command kinds leave this field as before. * stream unchanged. Bulk write kinds and all non-write kinds leave this
* field as before.
* </pre> * </pre>
* *
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code> * <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
+13 -8
View File
@@ -241,6 +241,9 @@ message ActivateCommand {
int32 item_handle = 2; int32 item_handle = 2;
} }
// The unary reply's statuses field carries the correlated OnWriteComplete
// outcome when it arrives within the worker's bounded wait — see
// MxCommandReply.statuses.
message WriteCommand { message WriteCommand {
int32 server_handle = 1; int32 server_handle = 1;
int32 item_handle = 2; int32 item_handle = 2;
@@ -248,6 +251,7 @@ message WriteCommand {
int32 user_id = 4; int32 user_id = 4;
} }
// Same statuses correlation as WriteCommand.
message Write2Command { message Write2Command {
int32 server_handle = 1; int32 server_handle = 1;
int32 item_handle = 2; int32 item_handle = 2;
@@ -531,18 +535,19 @@ message MxCommandReply {
// transport failures. // transport failures.
optional int32 hresult = 5; optional int32 hresult = 5;
MxValue return_value = 6; MxValue return_value = 6;
// Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 // Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
// replies the worker holds the reply for a bounded window (default 1.5 s, // WRITE_SECURED2 replies the worker holds the reply for a bounded window
// MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching // (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
// MXAccess OnWriteComplete callback and copies its status rows here, so // the matching MXAccess OnWriteComplete callback and copies its status rows
// statuses[0] carries the real MXAccess commit outcome (success OR failure) // here, so statuses[0] carries the real MXAccess commit outcome (success OR
// while protocol_status/hresult still describe command acceptance only. // failure) while protocol_status/hresult still describe command acceptance
// Empty statuses on a write reply means the completion did not arrive // only. Empty statuses on a write reply means the completion did not arrive
// within the window — the write is unconfirmed, not failed. Correlation is // within the window — the write is unconfirmed, not failed. Correlation is
// best-effort per (server_handle, item_handle): MXAccess's callback carries // best-effort per (server_handle, item_handle): MXAccess's callback carries
// no transaction id, so concurrent writes to the same item within the // no transaction id, so concurrent writes to the same item within the
// window can swap rows. The OnWriteComplete event still flows on the event // window can swap rows. The OnWriteComplete event still flows on the event
// stream unchanged. Other command kinds leave this field as before. // stream unchanged. Bulk write kinds and all non-write kinds leave this
// field as before.
repeated MxStatusProxy statuses = 7; repeated MxStatusProxy statuses = 7;
string diagnostic_message = 8; string diagnostic_message = 8;
+10 -5
View File
@@ -534,7 +534,7 @@ against the live MXAccess attribute set.
- [Alarm Client Discovery — Subtag provider](./AlarmClientDiscovery.md) - [Alarm Client Discovery — Subtag provider](./AlarmClientDiscovery.md)
- [gRPC Contract — provider_status and degraded fields](./Grpc.md) - [gRPC Contract — provider_status and degraded fields](./Grpc.md)
## Secured-Write Completion Correlation ## Write Completion Correlation
MXAccess writes are fire-and-forget: the toolkit call returns before the MXAccess writes are fire-and-forget: the toolkit call returns before the
Galaxy commit, and the per-item outcome only exists in the later Galaxy commit, and the per-item outcome only exists in the later
@@ -542,7 +542,8 @@ Galaxy commit, and the per-item outcome only exists in the later
proved worker-side command acceptance only, forcing consumers (OtOpcUa's proved worker-side command acceptance only, forcing consumers (OtOpcUa's
GalaxyDriver) to report every write as provisionally good. GalaxyDriver) to report every write as provisionally good.
For `WriteSecured`/`WriteSecured2` the worker now holds the unary reply for a For the unary write kinds (`Write`/`Write2`/`WriteSecured`/`WriteSecured2`)
the worker now holds the unary reply for a
bounded window (`MxGateway:Worker:WriteCompletionWaitMilliseconds`, default bounded window (`MxGateway:Worker:WriteCompletionWaitMilliseconds`, default
1.5 s, `0` disables; conveyed to the worker via 1.5 s, `0` disables; conveyed to the worker via
`MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS`) and copies the matching `MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS`) and copies the matching
@@ -565,9 +566,13 @@ callback's status rows onto `MxCommandReply.statuses`. Key choices, argued in
acceptance; the MXAccess outcome (success or failure) rides only in acceptance; the MXAccess outcome (success or failure) rides only in
`statuses[0]`; the `OnWriteComplete` event still streams unchanged (nothing `statuses[0]`; the `OnWriteComplete` event still streams unchanged (nothing
swallowed, nothing synthesized). swallowed, nothing synthesized).
- **Scope: secured writes only.** Plain `Write`/`Write2` and bulk writes stay - **Scope: all four unary write kinds; bulk writes stay fire-and-forget.**
fire-and-forget — the wait would add a device round-trip per write to The first cut correlated `WriteSecured`/`WriteSecured2` only, but OtOpcUa's
high-rate supervisory loops. dominant FreeAccess write path goes out as plain `Write` (2026-08-09 live
verification, 06/S-1) — a refused plain write was invisible on the reply.
Plain `Write`/`Write2` now correlate identically. Bulk writes keep
fire-and-forget replies: waiting per entry would add a device round-trip per
item to high-rate supervisory loops.
- **Best-effort correlation.** The callback carries only - **Best-effort correlation.** The callback carries only
`(hItem, statuses)` — no transaction id — so concurrent writes to the same `(hItem, statuses)` — no transaction id — so concurrent writes to the same
item within the window can swap rows; benign for the serialized single-write item within the window can swap rows; benign for the serialized single-write
+1 -1
View File
@@ -114,7 +114,7 @@ launch CWD (SEC-01, SEC-33).
| `MxGateway:Worker:StartupProbeRetryAttempts` | `3` | Number of retry attempts for transient worker startup probe failures before pipe connection and handshake continue. | | `MxGateway:Worker:StartupProbeRetryAttempts` | `3` | Number of retry attempts for transient worker startup probe failures before pipe connection and handshake continue. |
| `MxGateway:Worker:StartupProbeRetryDelayMilliseconds` | `250` | Delay between transient startup probe retry attempts. | | `MxGateway:Worker:StartupProbeRetryDelayMilliseconds` | `250` | Delay between transient startup probe retry attempts. |
| `MxGateway:Worker:PipeConnectAttemptTimeoutMilliseconds` | `2000` | Per-attempt timeout used by the worker named-pipe connect retry path. The overall pipe connection still stays under the startup budget. | | `MxGateway:Worker:PipeConnectAttemptTimeoutMilliseconds` | `2000` | Per-attempt timeout used by the worker named-pipe connect retry path. The overall pipe connection still stays under the startup budget. |
| `MxGateway:Worker:WriteCompletionWaitMilliseconds` | `1500` | Bounded wait the worker holds a `WriteSecured`/`WriteSecured2` reply for the matching MXAccess `OnWriteComplete` callback, so the reply's `statuses` carry the real commit outcome. `0` disables the wait (pure fire-and-forget replies). Must be `>= 0`. The gateway conveys the value to the worker via the `MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS` environment variable. Consumers that time their own writes must budget above this wait: OtOpcUa's GalaxyDriver wraps gateway writes in a 2 s Tier A resilience timeout, so a deployment raising this option past ~2000 must raise that driver `ResilienceConfig` write timeout in step or slow-but-successful commits surface as consumer-side failures. | | `MxGateway:Worker:WriteCompletionWaitMilliseconds` | `1500` | Bounded wait the worker holds a unary write reply (`Write`/`Write2`/`WriteSecured`/`WriteSecured2`; bulk writes excluded) for the matching MXAccess `OnWriteComplete` callback, so the reply's `statuses` carry the real commit outcome. `0` disables the wait (pure fire-and-forget replies). Must be `>= 0`. The gateway conveys the value to the worker via the `MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS` environment variable. Consumers that time their own writes must budget above this wait: OtOpcUa's GalaxyDriver wraps gateway writes in a 2 s Tier A resilience timeout, so a deployment raising this option past ~2000 must raise that driver `ResilienceConfig` write timeout in step or slow-but-successful commits surface as consumer-side failures. |
| `MxGateway:Worker:ShutdownTimeoutSeconds` | `10` | Grace period for worker shutdown before the gateway treats shutdown as failed and may kill the worker process tree. | | `MxGateway:Worker:ShutdownTimeoutSeconds` | `10` | Grace period for worker shutdown before the gateway treats shutdown as failed and may kill the worker process tree. |
| `MxGateway:Worker:HeartbeatIntervalSeconds` | `5` | Worker heartbeat send interval and gateway heartbeat check cadence input. | | `MxGateway:Worker:HeartbeatIntervalSeconds` | `5` | Worker heartbeat send interval and gateway heartbeat check cadence input. |
| `MxGateway:Worker:HeartbeatGraceSeconds` | `15` | Maximum age of the last worker heartbeat before the gateway faults the worker. This must be greater than or equal to `HeartbeatIntervalSeconds`. | | `MxGateway:Worker:HeartbeatGraceSeconds` | `15` | Maximum age of the last worker heartbeat before the gateway faults the worker. This must be greater than or equal to `HeartbeatIntervalSeconds`. |
+5 -4
View File
@@ -431,9 +431,10 @@ Core commands:
- `AuthenticateUser` - `AuthenticateUser`
- `ArchestrAUserToId` - `ArchestrAUserToId`
**Secured-write completion correlation.** MXAccess writes are fire-and-forget **Write completion correlation.** MXAccess writes are fire-and-forget
at the toolkit level — the per-item outcome only exists in the later at the toolkit level — the per-item outcome only exists in the later
`OnWriteComplete` callback. For `WriteSecured` and `WriteSecured2` the worker `OnWriteComplete` callback. For the unary write kinds — `Write`, `Write2`,
`WriteSecured`, `WriteSecured2` — the worker
therefore holds the unary reply for a bounded window therefore holds the unary reply for a bounded window
(`MxGateway:Worker:WriteCompletionWaitMilliseconds`, default 1.5 s, `0` (`MxGateway:Worker:WriteCompletionWaitMilliseconds`, default 1.5 s, `0`
disables) and, when the matching callback arrives, copies its status rows onto disables) and, when the matching callback arrives, copies its status rows onto
@@ -444,8 +445,8 @@ empty `statuses` means unconfirmed (the callback missed the window), never
failed. The `OnWriteComplete` event still flows on the event stream unchanged. failed. The `OnWriteComplete` event still flows on the event stream unchanged.
Correlation is best-effort per `(server_handle, item_handle)` — the callback Correlation is best-effort per `(server_handle, item_handle)` — the callback
carries no transaction id, so concurrent writes to the same item within the carries no transaction id, so concurrent writes to the same item within the
window can swap rows. Plain `Write`/`Write2` and the bulk write commands stay window can swap rows. The bulk write commands stay
fire-and-forget: waiting there would add a device round-trip of latency to fire-and-forget: waiting per entry would add a device round-trip of latency to
high-rate supervisory write loops. high-rate supervisory write loops.
Bulk variants (single gRPC round-trip carries the full list, the worker Bulk variants (single gRPC round-trip carries the full list, the worker
@@ -8012,6 +8012,11 @@ namespace ZB.MOM.WW.MxGateway.Contracts.Proto {
} }
/// <summary>
/// The unary reply's statuses field carries the correlated OnWriteComplete
/// outcome when it arrives within the worker's bounded wait — see
/// MxCommandReply.statuses.
/// </summary>
[global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")] [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
public sealed partial class WriteCommand : pb::IMessage<WriteCommand> public sealed partial class WriteCommand : pb::IMessage<WriteCommand>
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
@@ -8330,6 +8335,9 @@ namespace ZB.MOM.WW.MxGateway.Contracts.Proto {
} }
/// <summary>
/// Same statuses correlation as WriteCommand.
/// </summary>
[global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")] [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
public sealed partial class Write2Command : pb::IMessage<Write2Command> public sealed partial class Write2Command : pb::IMessage<Write2Command>
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
@@ -17215,18 +17223,19 @@ namespace ZB.MOM.WW.MxGateway.Contracts.Proto {
= pb::FieldCodec.ForMessage(58, global::ZB.MOM.WW.MxGateway.Contracts.Proto.MxStatusProxy.Parser); = pb::FieldCodec.ForMessage(58, global::ZB.MOM.WW.MxGateway.Contracts.Proto.MxStatusProxy.Parser);
private readonly pbc::RepeatedField<global::ZB.MOM.WW.MxGateway.Contracts.Proto.MxStatusProxy> statuses_ = new pbc::RepeatedField<global::ZB.MOM.WW.MxGateway.Contracts.Proto.MxStatusProxy>(); private readonly pbc::RepeatedField<global::ZB.MOM.WW.MxGateway.Contracts.Proto.MxStatusProxy> statuses_ = new pbc::RepeatedField<global::ZB.MOM.WW.MxGateway.Contracts.Proto.MxStatusProxy>();
/// <summary> /// <summary>
/// Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 /// Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
/// replies the worker holds the reply for a bounded window (default 1.5 s, /// WRITE_SECURED2 replies the worker holds the reply for a bounded window
/// MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching /// (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
/// MXAccess OnWriteComplete callback and copies its status rows here, so /// the matching MXAccess OnWriteComplete callback and copies its status rows
/// statuses[0] carries the real MXAccess commit outcome (success OR failure) /// here, so statuses[0] carries the real MXAccess commit outcome (success OR
/// while protocol_status/hresult still describe command acceptance only. /// failure) while protocol_status/hresult still describe command acceptance
/// Empty statuses on a write reply means the completion did not arrive /// only. Empty statuses on a write reply means the completion did not arrive
/// within the window — the write is unconfirmed, not failed. Correlation is /// within the window — the write is unconfirmed, not failed. Correlation is
/// best-effort per (server_handle, item_handle): MXAccess's callback carries /// best-effort per (server_handle, item_handle): MXAccess's callback carries
/// no transaction id, so concurrent writes to the same item within the /// no transaction id, so concurrent writes to the same item within the
/// window can swap rows. The OnWriteComplete event still flows on the event /// window can swap rows. The OnWriteComplete event still flows on the event
/// stream unchanged. Other command kinds leave this field as before. /// stream unchanged. Bulk write kinds and all non-write kinds leave this
/// field as before.
/// </summary> /// </summary>
[global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
@@ -241,6 +241,9 @@ message ActivateCommand {
int32 item_handle = 2; int32 item_handle = 2;
} }
// The unary reply's statuses field carries the correlated OnWriteComplete
// outcome when it arrives within the worker's bounded wait see
// MxCommandReply.statuses.
message WriteCommand { message WriteCommand {
int32 server_handle = 1; int32 server_handle = 1;
int32 item_handle = 2; int32 item_handle = 2;
@@ -248,6 +251,7 @@ message WriteCommand {
int32 user_id = 4; int32 user_id = 4;
} }
// Same statuses correlation as WriteCommand.
message Write2Command { message Write2Command {
int32 server_handle = 1; int32 server_handle = 1;
int32 item_handle = 2; int32 item_handle = 2;
@@ -531,18 +535,19 @@ message MxCommandReply {
// transport failures. // transport failures.
optional int32 hresult = 5; optional int32 hresult = 5;
MxValue return_value = 6; MxValue return_value = 6;
// Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2 // Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
// replies the worker holds the reply for a bounded window (default 1.5 s, // WRITE_SECURED2 replies the worker holds the reply for a bounded window
// MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching // (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
// MXAccess OnWriteComplete callback and copies its status rows here, so // the matching MXAccess OnWriteComplete callback and copies its status rows
// statuses[0] carries the real MXAccess commit outcome (success OR failure) // here, so statuses[0] carries the real MXAccess commit outcome (success OR
// while protocol_status/hresult still describe command acceptance only. // failure) while protocol_status/hresult still describe command acceptance
// Empty statuses on a write reply means the completion did not arrive // only. Empty statuses on a write reply means the completion did not arrive
// within the window the write is unconfirmed, not failed. Correlation is // within the window the write is unconfirmed, not failed. Correlation is
// best-effort per (server_handle, item_handle): MXAccess's callback carries // best-effort per (server_handle, item_handle): MXAccess's callback carries
// no transaction id, so concurrent writes to the same item within the // no transaction id, so concurrent writes to the same item within the
// window can swap rows. The OnWriteComplete event still flows on the event // window can swap rows. The OnWriteComplete event still flows on the event
// stream unchanged. Other command kinds leave this field as before. // stream unchanged. Bulk write kinds and all non-write kinds leave this
// field as before.
repeated MxStatusProxy statuses = 7; repeated MxStatusProxy statuses = 7;
string diagnostic_message = 8; string diagnostic_message = 8;
@@ -851,6 +851,9 @@ public sealed class MxAccessCommandExecutorTests
FakeMxAccessComObjectFactory factory = new(fakeComObject); FakeMxAccessComObjectFactory factory = new(fakeComObject);
using StaRuntime runtime = CreateRuntime(); using StaRuntime runtime = CreateRuntime();
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink()); using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
// No completion source in this test — disable the bounded reply wait
// so the forwarding assertions don't pay the default 1.5 s timeout.
session.WriteCompletionTimeout = TimeSpan.Zero;
await session.StartAsync(workerProcessId: 1234); await session.StartAsync(workerProcessId: 1234);
MxCommandReply reply = await session.DispatchAsync(CreateWriteCommand( MxCommandReply reply = await session.DispatchAsync(CreateWriteCommand(
@@ -874,6 +877,8 @@ public sealed class MxAccessCommandExecutorTests
FakeMxAccessComObjectFactory factory = new(fakeComObject); FakeMxAccessComObjectFactory factory = new(fakeComObject);
using StaRuntime runtime = CreateRuntime(); using StaRuntime runtime = CreateRuntime();
using MxAccessStaSession session = new(runtime, factory, new NoopEventSink()); using MxAccessStaSession session = new(runtime, factory, new NoopEventSink());
// Same rationale as the Write forwarding test above.
session.WriteCompletionTimeout = TimeSpan.Zero;
await session.StartAsync(workerProcessId: 1234); await session.StartAsync(workerProcessId: 1234);
DateTime timestamp = new(2026, 5, 19, 12, 0, 0, DateTimeKind.Utc); DateTime timestamp = new(2026, 5, 19, 12, 0, 0, DateTimeKind.Utc);
@@ -952,7 +957,7 @@ public sealed class MxAccessCommandExecutorTests
FakeMxAccessComObject fakeComObject = new(registerHandle: 82); FakeMxAccessComObject fakeComObject = new(registerHandle: 82);
FakeMxAccessComObjectFactory factory = new(fakeComObject); FakeMxAccessComObjectFactory factory = new(fakeComObject);
CompletionCacheEventSink sink = new(); CompletionCacheEventSink sink = new();
fakeComObject.OnWriteSecuredCallback = () => fakeComObject.OnWriteCallback = () =>
sink.WriteCompletionCache.Record(82, 820, CreateCompletionRows(detail: 4321)); sink.WriteCompletionCache.Record(82, 820, CreateCompletionRows(detail: 4321));
using StaRuntime runtime = CreateRuntime(); using StaRuntime runtime = CreateRuntime();
using MxAccessStaSession session = new(runtime, factory, sink); using MxAccessStaSession session = new(runtime, factory, sink);
@@ -989,7 +994,7 @@ public sealed class MxAccessCommandExecutorTests
// baseline is committed and a Record from the test thread is // baseline is committed and a Record from the test thread is
// guaranteed to be "newer" — no fixed sleep racing the STA thread. // guaranteed to be "newer" — no fixed sleep racing the STA thread.
using System.Threading.ManualResetEventSlim comCallReached = new(initialState: false); using System.Threading.ManualResetEventSlim comCallReached = new(initialState: false);
fakeComObject.OnWriteSecuredCallback = () => comCallReached.Set(); fakeComObject.OnWriteCallback = () => comCallReached.Set();
using StaRuntime runtime = CreateRuntime(); using StaRuntime runtime = CreateRuntime();
using MxAccessStaSession session = new(runtime, factory, sink); using MxAccessStaSession session = new(runtime, factory, sink);
session.WriteCompletionTimeout = TimeSpan.FromSeconds(10); session.WriteCompletionTimeout = TimeSpan.FromSeconds(10);
@@ -1068,7 +1073,7 @@ public sealed class MxAccessCommandExecutorTests
FakeMxAccessComObject fakeComObject = new(registerHandle: 86); FakeMxAccessComObject fakeComObject = new(registerHandle: 86);
FakeMxAccessComObjectFactory factory = new(fakeComObject); FakeMxAccessComObjectFactory factory = new(fakeComObject);
CompletionCacheEventSink sink = new(); CompletionCacheEventSink sink = new();
fakeComObject.OnWriteSecuredCallback = () => fakeComObject.OnWriteCallback = () =>
sink.WriteCompletionCache.Record(86, 860, CreateCompletionRows(detail: 2222)); sink.WriteCompletionCache.Record(86, 860, CreateCompletionRows(detail: 2222));
using StaRuntime runtime = CreateRuntime(); using StaRuntime runtime = CreateRuntime();
using MxAccessStaSession session = new(runtime, factory, sink); using MxAccessStaSession session = new(runtime, factory, sink);
@@ -1085,25 +1090,110 @@ public sealed class MxAccessCommandExecutorTests
} }
/// <summary> /// <summary>
/// Verifies plain Write stays fire-and-forget: even with a huge completion /// Verifies plain Write correlates the same way as WriteSecured
/// timeout configured and no completion source, the reply returns /// (fast-completion edge): OtOpcUa's dominant FreeAccess path goes out
/// immediately (guarded well under the configured wait) with empty /// as MX_COMMAND_KIND_WRITE, so its reply must carry the OnWriteComplete
/// statuses — only the secured write kinds enter the bounded wait. /// rows too.
/// </summary> /// </summary>
/// <returns>A task that represents the asynchronous operation.</returns> /// <returns>A task that represents the asynchronous operation.</returns>
[Fact] [Fact]
public async Task DispatchAsync_Write_DoesNotWaitForCompletion() public async Task DispatchAsync_Write_WhenCompletionArrivesDuringComCall_ReturnsStatuses()
{ {
FakeMxAccessComObject fakeComObject = new(registerHandle: 87); FakeMxAccessComObject fakeComObject = new(registerHandle: 87);
FakeMxAccessComObjectFactory factory = new(fakeComObject); FakeMxAccessComObjectFactory factory = new(fakeComObject);
CompletionCacheEventSink sink = new(); CompletionCacheEventSink sink = new();
fakeComObject.OnWriteCallback = () =>
sink.WriteCompletionCache.Record(87, 870, CreateCompletionRows(detail: 5555));
using StaRuntime runtime = CreateRuntime();
using MxAccessStaSession session = new(runtime, factory, sink);
// Hermetic: don't inherit MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS
// from the test runner's environment.
session.WriteCompletionTimeout = TimeSpan.FromSeconds(10);
await session.StartAsync(workerProcessId: 1234);
MxCommandReply reply = await session.DispatchAsync(CreateWriteCommand(
"plain-write-fast", serverHandle: 87, itemHandle: 870, value: 1, userId: 5));
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
Assert.True(reply.HasHresult);
Assert.Equal(0, reply.Hresult);
MxStatusProxy row = Assert.Single(reply.Statuses);
Assert.Equal(5555, row.Detail);
Assert.Equal(MxStatusCategory.Ok, row.Category);
}
/// <summary>
/// Verifies the plain-Write timeout fallback mirrors the secured one:
/// no completion within the bounded wait returns protocol OK with EMPTY
/// statuses — unconfirmed, never a synthesized failure row.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task DispatchAsync_Write_WhenNoCompletion_TimesOutWithEmptyStatusesAndOkProtocol()
{
FakeMxAccessComObject fakeComObject = new(registerHandle: 88);
FakeMxAccessComObjectFactory factory = new(fakeComObject);
CompletionCacheEventSink sink = new();
using StaRuntime runtime = CreateRuntime();
using MxAccessStaSession session = new(runtime, factory, sink);
session.WriteCompletionTimeout = TimeSpan.FromMilliseconds(100);
await session.StartAsync(workerProcessId: 1234);
MxCommandReply reply = await session.DispatchAsync(CreateWriteCommand(
"plain-write-timeout", serverHandle: 88, itemHandle: 880, value: 1, userId: 5));
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
Assert.True(reply.HasHresult);
Assert.Equal(0, reply.Hresult);
Assert.Empty(reply.Statuses);
}
/// <summary>
/// Verifies Write2 correlates the same way as Write (fast-completion
/// edge).
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task DispatchAsync_Write2_WhenCompletionArrivesDuringComCall_ReturnsStatuses()
{
FakeMxAccessComObject fakeComObject = new(registerHandle: 89);
FakeMxAccessComObjectFactory factory = new(fakeComObject);
CompletionCacheEventSink sink = new();
fakeComObject.OnWriteCallback = () =>
sink.WriteCompletionCache.Record(89, 890, CreateCompletionRows(detail: 6666));
using StaRuntime runtime = CreateRuntime();
using MxAccessStaSession session = new(runtime, factory, sink);
session.WriteCompletionTimeout = TimeSpan.FromSeconds(10);
await session.StartAsync(workerProcessId: 1234);
MxCommandReply reply = await session.DispatchAsync(CreateWrite2Command(
"plain-write2-fast", serverHandle: 89, itemHandle: 890, value: 1,
timestamp: new DateTime(2026, 8, 9, 12, 0, 0, DateTimeKind.Utc), userId: 6));
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
Assert.Equal(6666, Assert.Single(reply.Statuses).Detail);
}
/// <summary>
/// Verifies WriteBulk stays fire-and-forget: even with a huge completion
/// timeout configured and no completion source, the reply returns
/// immediately with per-entry results — bulk writes never enter the
/// bounded wait (latency for high-rate loops).
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task DispatchAsync_WriteBulk_DoesNotWaitForCompletion()
{
FakeMxAccessComObject fakeComObject = new(registerHandle: 90);
FakeMxAccessComObjectFactory factory = new(fakeComObject);
CompletionCacheEventSink sink = new();
using StaRuntime runtime = CreateRuntime(); using StaRuntime runtime = CreateRuntime();
using MxAccessStaSession session = new(runtime, factory, sink); using MxAccessStaSession session = new(runtime, factory, sink);
session.WriteCompletionTimeout = TimeSpan.FromSeconds(30); session.WriteCompletionTimeout = TimeSpan.FromSeconds(30);
await session.StartAsync(workerProcessId: 1234); await session.StartAsync(workerProcessId: 1234);
Task<MxCommandReply> pending = session.DispatchAsync(CreateWriteCommand( Task<MxCommandReply> pending = session.DispatchAsync(CreateWriteBulkCommand(
"plain-write-no-wait", serverHandle: 87, itemHandle: 870, value: 1, userId: 5)); "bulk-write-no-wait", serverHandle: 90, entries: new[] { (itemHandle: 900, value: 1, userId: 5) }));
Task completed = await Task.WhenAny(pending, Task.Delay(TimeSpan.FromSeconds(5))); Task completed = await Task.WhenAny(pending, Task.Delay(TimeSpan.FromSeconds(5)));
Assert.Same(pending, completed); Assert.Same(pending, completed);
@@ -2004,12 +2094,13 @@ public sealed class MxAccessCommandExecutorTests
private readonly List<string> operationNames = new(); private readonly List<string> operationNames = new();
/// <summary> /// <summary>
/// Invoked at the end of a successful WriteSecured/WriteSecured2 — /// Invoked at the end of a successful Write/Write2/WriteSecured/
/// stands in for MXAccess committing synchronously and delivering /// WriteSecured2 — stands in for MXAccess committing synchronously
/// OnWriteComplete while the COM call is still on the stack, so /// and delivering OnWriteComplete while the COM call is still on
/// tests can exercise the fast-completion ordering edge. /// the stack, so tests can exercise the fast-completion ordering
/// edge on every correlated write kind.
/// </summary> /// </summary>
public Action? OnWriteSecuredCallback { get; set; } public Action? OnWriteCallback { get; set; }
/// <summary>Initializes a fake MXAccess COM object with the given handles and optional exceptions.</summary> /// <summary>Initializes a fake MXAccess COM object with the given handles and optional exceptions.</summary>
/// <param name="registerHandle">Return value for Register method.</param> /// <param name="registerHandle">Return value for Register method.</param>
@@ -2285,6 +2376,7 @@ public sealed class MxAccessCommandExecutorTests
WriteUserId = userId; WriteUserId = userId;
WriteThreadId = Environment.CurrentManagedThreadId; WriteThreadId = Environment.CurrentManagedThreadId;
ThrowIfWriteFailureConfigured(itemHandle); ThrowIfWriteFailureConfigured(itemHandle);
OnWriteCallback?.Invoke();
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -2303,6 +2395,7 @@ public sealed class MxAccessCommandExecutorTests
WriteUserId = userId; WriteUserId = userId;
WriteThreadId = Environment.CurrentManagedThreadId; WriteThreadId = Environment.CurrentManagedThreadId;
ThrowIfWriteFailureConfigured(itemHandle); ThrowIfWriteFailureConfigured(itemHandle);
OnWriteCallback?.Invoke();
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -2321,7 +2414,7 @@ public sealed class MxAccessCommandExecutorTests
WriteValue = value; WriteValue = value;
WriteThreadId = Environment.CurrentManagedThreadId; WriteThreadId = Environment.CurrentManagedThreadId;
ThrowIfWriteFailureConfigured(itemHandle); ThrowIfWriteFailureConfigured(itemHandle);
OnWriteSecuredCallback?.Invoke(); OnWriteCallback?.Invoke();
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -2342,7 +2435,7 @@ public sealed class MxAccessCommandExecutorTests
WriteTimestamp = timestamp; WriteTimestamp = timestamp;
WriteThreadId = Environment.CurrentManagedThreadId; WriteThreadId = Environment.CurrentManagedThreadId;
ThrowIfWriteFailureConfigured(itemHandle); ThrowIfWriteFailureConfigured(itemHandle);
OnWriteSecuredCallback?.Invoke(); OnWriteCallback?.Invoke();
} }
private void ThrowIfWriteFailureConfigured(int itemHandle) private void ThrowIfWriteFailureConfigured(int itemHandle)
@@ -427,13 +427,28 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
return CreateInvalidRequestReply(command, "Write command value is required."); return CreateInvalidRequestReply(command, "Write command value is required.");
} }
// Same pre-call baseline rule as ExecuteWriteSecured: plain Write is
// also fire-and-forget in MXAccess, and its commit outcome only exists
// in the later OnWriteComplete callback.
MxAccessWriteCompletionCache completionCache = session.WriteCompletionCache;
ulong completionBaseline = completionCache.CurrentVersion(
writeCommand.ServerHandle,
writeCommand.ItemHandle);
session.Write( session.Write(
writeCommand.ServerHandle, writeCommand.ServerHandle,
writeCommand.ItemHandle, writeCommand.ItemHandle,
variantConverter.ConvertToComValue(writeCommand.Value), variantConverter.ConvertToComValue(writeCommand.Value),
writeCommand.UserId); writeCommand.UserId);
return CreateOkReply(command); MxCommandReply reply = CreateOkReply(command);
AwaitWriteCompletion(
reply,
completionCache,
writeCommand.ServerHandle,
writeCommand.ItemHandle,
completionBaseline);
return reply;
} }
private MxCommandReply ExecuteWrite2(StaCommand command) private MxCommandReply ExecuteWrite2(StaCommand command)
@@ -454,6 +469,12 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
return CreateInvalidRequestReply(command, "Write2 command timestamp value is required."); return CreateInvalidRequestReply(command, "Write2 command timestamp value is required.");
} }
// Same pre-call baseline rule as ExecuteWriteSecured.
MxAccessWriteCompletionCache completionCache = session.WriteCompletionCache;
ulong completionBaseline = completionCache.CurrentVersion(
write2Command.ServerHandle,
write2Command.ItemHandle);
session.Write2( session.Write2(
write2Command.ServerHandle, write2Command.ServerHandle,
write2Command.ItemHandle, write2Command.ItemHandle,
@@ -461,7 +482,14 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
variantConverter.ConvertToComValue(write2Command.TimestampValue), variantConverter.ConvertToComValue(write2Command.TimestampValue),
write2Command.UserId); write2Command.UserId);
return CreateOkReply(command); MxCommandReply reply = CreateOkReply(command);
AwaitWriteCompletion(
reply,
completionCache,
write2Command.ServerHandle,
write2Command.ItemHandle,
completionBaseline);
return reply;
} }
private MxCommandReply ExecuteWriteSecured(StaCommand command) private MxCommandReply ExecuteWriteSecured(StaCommand command)