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
}
// 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 {
state protoimpl.MessageState `protogen:"open.v1"`
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
}
// Same statuses correlation as WriteCommand.
type Write2Command struct {
state protoimpl.MessageState `protogen:"open.v1"`
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.
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"`
// Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
// replies the worker holds the reply for a bounded window (default 1.5 s,
// MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
// MXAccess OnWriteComplete callback and copies its status rows here, so
// statuses[0] carries the real MXAccess commit outcome (success OR failure)
// while protocol_status/hresult still describe command acceptance only.
// Empty statuses on a write reply means the completion did not arrive
// Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
// WRITE_SECURED2 replies the worker holds the reply for a bounded window
// (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
// the matching MXAccess OnWriteComplete callback and copies its status rows
// here, so statuses[0] carries the real MXAccess commit outcome (success OR
// failure) while protocol_status/hresult still describe command acceptance
// only. Empty statuses on a write reply means the completion did not arrive
// within the window — the write is unconfirmed, not failed. Correlation is
// best-effort per (server_handle, item_handle): MXAccess's callback carries
// no transaction id, so concurrent writes to the same item within the
// 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"`
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:
@@ -24235,6 +24235,12 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
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}
*/
public static final class WriteCommand extends
@@ -24527,6 +24533,12 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
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}
*/
public static final class Builder extends
@@ -25036,6 +25048,10 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
int getUserId();
}
/**
* <pre>
* Same statuses correlation as WriteCommand.
* </pre>
*
* Protobuf type {@code mxaccess_gateway.v1.Write2Command}
*/
public static final class Write2Command extends
@@ -25370,6 +25386,10 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
return builder;
}
/**
* <pre>
* Same statuses correlation as WriteCommand.
* </pre>
*
* Protobuf type {@code mxaccess_gateway.v1.Write2Command}
*/
public static final class Builder extends
@@ -50513,18 +50533,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -50533,18 +50554,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
getStatusesList();
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <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);
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -50571,18 +50594,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
int getStatusesCount();
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -50591,18 +50615,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
getStatusesOrBuilderList();
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <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_;
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -51355,18 +51381,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -51378,18 +51405,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -51400,18 +51428,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -51422,18 +51451,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54156,18 +54186,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54181,18 +54212,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54206,18 +54238,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54231,18 +54264,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54263,18 +54297,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54292,18 +54327,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54323,18 +54359,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54355,18 +54392,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54384,18 +54422,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54413,18 +54452,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54443,18 +54483,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54471,18 +54512,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54499,18 +54541,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54521,18 +54564,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54546,18 +54590,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54572,18 +54617,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54594,18 +54640,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
@@ -54617,18 +54664,19 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
}
/**
* <pre>
* Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
* replies the worker holds the reply for a bounded window (default 1.5 s,
* MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
* MXAccess OnWriteComplete callback and copies its status rows here, so
* statuses[0] carries the real MXAccess commit outcome (success OR failure)
* while protocol_status/hresult still describe command acceptance only.
* Empty statuses on a write reply means the completion did not arrive
* Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
* WRITE_SECURED2 replies the worker holds the reply for a bounded window
* (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
* the matching MXAccess OnWriteComplete callback and copies its status rows
* here, so statuses[0] carries the real MXAccess commit outcome (success OR
* failure) while protocol_status/hresult still describe command acceptance
* only. Empty statuses on a write reply means the completion did not arrive
* within the window the write is unconfirmed, not failed. Correlation is
* best-effort per (server_handle, item_handle): MXAccess's callback carries
* no transaction id, so concurrent writes to the same item within the
* 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>
*
* <code>repeated .mxaccess_gateway.v1.MxStatusProxy statuses = 7;</code>
+13 -8
View File
@@ -241,6 +241,9 @@ message ActivateCommand {
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 {
int32 server_handle = 1;
int32 item_handle = 2;
@@ -248,6 +251,7 @@ message WriteCommand {
int32 user_id = 4;
}
// Same statuses correlation as WriteCommand.
message Write2Command {
int32 server_handle = 1;
int32 item_handle = 2;
@@ -531,18 +535,19 @@ message MxCommandReply {
// transport failures.
optional int32 hresult = 5;
MxValue return_value = 6;
// Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
// replies the worker holds the reply for a bounded window (default 1.5 s,
// MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
// MXAccess OnWriteComplete callback and copies its status rows here, so
// statuses[0] carries the real MXAccess commit outcome (success OR failure)
// while protocol_status/hresult still describe command acceptance only.
// Empty statuses on a write reply means the completion did not arrive
// Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
// WRITE_SECURED2 replies the worker holds the reply for a bounded window
// (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
// the matching MXAccess OnWriteComplete callback and copies its status rows
// here, so statuses[0] carries the real MXAccess commit outcome (success OR
// failure) while protocol_status/hresult still describe command acceptance
// only. Empty statuses on a write reply means the completion did not arrive
// within the window — the write is unconfirmed, not failed. Correlation is
// best-effort per (server_handle, item_handle): MXAccess's callback carries
// no transaction id, so concurrent writes to the same item within the
// 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;
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)
- [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
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
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
1.5 s, `0` disables; conveyed to the worker via
`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
`statuses[0]`; the `OnWriteComplete` event still streams unchanged (nothing
swallowed, nothing synthesized).
- **Scope: secured writes only.** Plain `Write`/`Write2` and bulk writes stay
fire-and-forget — the wait would add a device round-trip per write to
high-rate supervisory loops.
- **Scope: all four unary write kinds; bulk writes stay fire-and-forget.**
The first cut correlated `WriteSecured`/`WriteSecured2` only, but OtOpcUa's
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
`(hItem, statuses)` — no transaction id — so concurrent writes to the same
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: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: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: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`. |
+5 -4
View File
@@ -431,9 +431,10 @@ Core commands:
- `AuthenticateUser`
- `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
`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
(`MxGateway:Worker:WriteCompletionWaitMilliseconds`, default 1.5 s, `0`
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.
Correlation is best-effort per `(server_handle, item_handle)` — the callback
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
fire-and-forget: waiting there would add a device round-trip of latency to
window can swap rows. The bulk write commands stay
fire-and-forget: waiting per entry would add a device round-trip of latency to
high-rate supervisory write loops.
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}")]
public sealed partial class WriteCommand : pb::IMessage<WriteCommand>
#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}")]
public sealed partial class Write2Command : pb::IMessage<Write2Command>
#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);
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>
/// Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
/// replies the worker holds the reply for a bounded window (default 1.5 s,
/// MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
/// MXAccess OnWriteComplete callback and copies its status rows here, so
/// statuses[0] carries the real MXAccess commit outcome (success OR failure)
/// while protocol_status/hresult still describe command acceptance only.
/// Empty statuses on a write reply means the completion did not arrive
/// Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
/// WRITE_SECURED2 replies the worker holds the reply for a bounded window
/// (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
/// the matching MXAccess OnWriteComplete callback and copies its status rows
/// here, so statuses[0] carries the real MXAccess commit outcome (success OR
/// failure) while protocol_status/hresult still describe command acceptance
/// only. Empty statuses on a write reply means the completion did not arrive
/// within the window — the write is unconfirmed, not failed. Correlation is
/// best-effort per (server_handle, item_handle): MXAccess's callback carries
/// no transaction id, so concurrent writes to the same item within the
/// 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>
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
@@ -241,6 +241,9 @@ message ActivateCommand {
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 {
int32 server_handle = 1;
int32 item_handle = 2;
@@ -248,6 +251,7 @@ message WriteCommand {
int32 user_id = 4;
}
// Same statuses correlation as WriteCommand.
message Write2Command {
int32 server_handle = 1;
int32 item_handle = 2;
@@ -531,18 +535,19 @@ message MxCommandReply {
// transport failures.
optional int32 hresult = 5;
MxValue return_value = 6;
// Correlated per-item outcome rows. For WRITE_SECURED / WRITE_SECURED2
// replies the worker holds the reply for a bounded window (default 1.5 s,
// MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for the matching
// MXAccess OnWriteComplete callback and copies its status rows here, so
// statuses[0] carries the real MXAccess commit outcome (success OR failure)
// while protocol_status/hresult still describe command acceptance only.
// Empty statuses on a write reply means the completion did not arrive
// Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
// WRITE_SECURED2 replies the worker holds the reply for a bounded window
// (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
// the matching MXAccess OnWriteComplete callback and copies its status rows
// here, so statuses[0] carries the real MXAccess commit outcome (success OR
// failure) while protocol_status/hresult still describe command acceptance
// only. Empty statuses on a write reply means the completion did not arrive
// within the window the write is unconfirmed, not failed. Correlation is
// best-effort per (server_handle, item_handle): MXAccess's callback carries
// no transaction id, so concurrent writes to the same item within the
// 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;
string diagnostic_message = 8;
@@ -851,6 +851,9 @@ public sealed class MxAccessCommandExecutorTests
FakeMxAccessComObjectFactory factory = new(fakeComObject);
using StaRuntime runtime = CreateRuntime();
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);
MxCommandReply reply = await session.DispatchAsync(CreateWriteCommand(
@@ -874,6 +877,8 @@ public sealed class MxAccessCommandExecutorTests
FakeMxAccessComObjectFactory factory = new(fakeComObject);
using StaRuntime runtime = CreateRuntime();
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);
DateTime timestamp = new(2026, 5, 19, 12, 0, 0, DateTimeKind.Utc);
@@ -952,7 +957,7 @@ public sealed class MxAccessCommandExecutorTests
FakeMxAccessComObject fakeComObject = new(registerHandle: 82);
FakeMxAccessComObjectFactory factory = new(fakeComObject);
CompletionCacheEventSink sink = new();
fakeComObject.OnWriteSecuredCallback = () =>
fakeComObject.OnWriteCallback = () =>
sink.WriteCompletionCache.Record(82, 820, CreateCompletionRows(detail: 4321));
using StaRuntime runtime = CreateRuntime();
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
// guaranteed to be "newer" — no fixed sleep racing the STA thread.
using System.Threading.ManualResetEventSlim comCallReached = new(initialState: false);
fakeComObject.OnWriteSecuredCallback = () => comCallReached.Set();
fakeComObject.OnWriteCallback = () => comCallReached.Set();
using StaRuntime runtime = CreateRuntime();
using MxAccessStaSession session = new(runtime, factory, sink);
session.WriteCompletionTimeout = TimeSpan.FromSeconds(10);
@@ -1068,7 +1073,7 @@ public sealed class MxAccessCommandExecutorTests
FakeMxAccessComObject fakeComObject = new(registerHandle: 86);
FakeMxAccessComObjectFactory factory = new(fakeComObject);
CompletionCacheEventSink sink = new();
fakeComObject.OnWriteSecuredCallback = () =>
fakeComObject.OnWriteCallback = () =>
sink.WriteCompletionCache.Record(86, 860, CreateCompletionRows(detail: 2222));
using StaRuntime runtime = CreateRuntime();
using MxAccessStaSession session = new(runtime, factory, sink);
@@ -1085,25 +1090,110 @@ public sealed class MxAccessCommandExecutorTests
}
/// <summary>
/// Verifies plain Write stays fire-and-forget: even with a huge completion
/// timeout configured and no completion source, the reply returns
/// immediately (guarded well under the configured wait) with empty
/// statuses — only the secured write kinds enter the bounded wait.
/// Verifies plain Write correlates the same way as WriteSecured
/// (fast-completion edge): OtOpcUa's dominant FreeAccess path goes out
/// as MX_COMMAND_KIND_WRITE, so its reply must carry the OnWriteComplete
/// rows too.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task DispatchAsync_Write_DoesNotWaitForCompletion()
public async Task DispatchAsync_Write_WhenCompletionArrivesDuringComCall_ReturnsStatuses()
{
FakeMxAccessComObject fakeComObject = new(registerHandle: 87);
FakeMxAccessComObjectFactory factory = new(fakeComObject);
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 MxAccessStaSession session = new(runtime, factory, sink);
session.WriteCompletionTimeout = TimeSpan.FromSeconds(30);
await session.StartAsync(workerProcessId: 1234);
Task<MxCommandReply> pending = session.DispatchAsync(CreateWriteCommand(
"plain-write-no-wait", serverHandle: 87, itemHandle: 870, value: 1, userId: 5));
Task<MxCommandReply> pending = session.DispatchAsync(CreateWriteBulkCommand(
"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)));
Assert.Same(pending, completed);
@@ -2004,12 +2094,13 @@ public sealed class MxAccessCommandExecutorTests
private readonly List<string> operationNames = new();
/// <summary>
/// Invoked at the end of a successful WriteSecured/WriteSecured2 —
/// stands in for MXAccess committing synchronously and delivering
/// OnWriteComplete while the COM call is still on the stack, so
/// tests can exercise the fast-completion ordering edge.
/// Invoked at the end of a successful Write/Write2/WriteSecured/
/// WriteSecured2 — stands in for MXAccess committing synchronously
/// and delivering OnWriteComplete while the COM call is still on
/// the stack, so tests can exercise the fast-completion ordering
/// edge on every correlated write kind.
/// </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>
/// <param name="registerHandle">Return value for Register method.</param>
@@ -2285,6 +2376,7 @@ public sealed class MxAccessCommandExecutorTests
WriteUserId = userId;
WriteThreadId = Environment.CurrentManagedThreadId;
ThrowIfWriteFailureConfigured(itemHandle);
OnWriteCallback?.Invoke();
}
/// <inheritdoc />
@@ -2303,6 +2395,7 @@ public sealed class MxAccessCommandExecutorTests
WriteUserId = userId;
WriteThreadId = Environment.CurrentManagedThreadId;
ThrowIfWriteFailureConfigured(itemHandle);
OnWriteCallback?.Invoke();
}
/// <inheritdoc />
@@ -2321,7 +2414,7 @@ public sealed class MxAccessCommandExecutorTests
WriteValue = value;
WriteThreadId = Environment.CurrentManagedThreadId;
ThrowIfWriteFailureConfigured(itemHandle);
OnWriteSecuredCallback?.Invoke();
OnWriteCallback?.Invoke();
}
/// <inheritdoc />
@@ -2342,7 +2435,7 @@ public sealed class MxAccessCommandExecutorTests
WriteTimestamp = timestamp;
WriteThreadId = Environment.CurrentManagedThreadId;
ThrowIfWriteFailureConfigured(itemHandle);
OnWriteSecuredCallback?.Invoke();
OnWriteCallback?.Invoke();
}
private void ThrowIfWriteFailureConfigured(int itemHandle)
@@ -427,13 +427,28 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
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(
writeCommand.ServerHandle,
writeCommand.ItemHandle,
variantConverter.ConvertToComValue(writeCommand.Value),
writeCommand.UserId);
return CreateOkReply(command);
MxCommandReply reply = CreateOkReply(command);
AwaitWriteCompletion(
reply,
completionCache,
writeCommand.ServerHandle,
writeCommand.ItemHandle,
completionBaseline);
return reply;
}
private MxCommandReply ExecuteWrite2(StaCommand command)
@@ -454,6 +469,12 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
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(
write2Command.ServerHandle,
write2Command.ItemHandle,
@@ -461,7 +482,14 @@ public sealed class MxAccessCommandExecutor : IStaCommandExecutor
variantConverter.ConvertToComValue(write2Command.TimestampValue),
write2Command.UserId);
return CreateOkReply(command);
MxCommandReply reply = CreateOkReply(command);
AwaitWriteCompletion(
reply,
completionCache,
write2Command.ServerHandle,
write2Command.ItemHandle,
completionBaseline);
return reply;
}
private MxCommandReply ExecuteWriteSecured(StaCommand command)