Submit and Completion¶
Execution topic table of contents · Spec table of contents · Next: 02. Handler Turn And Execution Gate
This document defines when a ZLink Framework Messaging/Worker call completes, when a one-way submit is accepted, what confirms completion for a request, and the structure the runtime uses to confirm that completion exactly once. The order in which handlers run, and the scope a
Yieldreleases, belongs to Handler Turn And Execution Gate; what cancellation and shutdown do to work already accepted belongs to Cancellation And Shutdown. Spot timer completion belongs to Spot Timer, and the relationship between the Core byte HWM and the Application Job Queue belongs to Application Job Queue And Backpressure.
1. Messaging/Worker Call Overview and Scope¶
When an application calls a Messaging call builder or a Worker call builder, that call completes with exactly one terminator this document defines. This document defines what each terminator treats as completion, how far a one-way submit waits for admission, whether a request completes with a reply, an error, a timeout, a cancellation, or a shutdown, and the internal structure that confirms that completion.
This section's naming rules apply to Messaging call builders and to Worker call builders
returned by RunCpuWorker/RunIoWorker. Messaging calls include Framework Send/Request/
Publish/Reply, Spot/Actor Send/Request,
Stream Connector Send/Request/Wait, and
HTTP request. Here a Spot is a logical instance with an address and state, reachable at the
same ID even when the executing node changes, and a Stream Connector is a client library that
connects to the server Framework's STREAM model to exchange packets. They do not apply to
network topology/endpoint/node connection, Host/runtime/
client configuration, handler/Channel membership/codec/security/retry registration, or
object lifecycle builders. Direct methods that don't return a builder, such as
RelayAsync(...), are also out of scope.
2. Completion Meaning per Terminator and Per-Language Names¶
A call object offers only the terminator that fits its operation kind. Actor Model §6.2 owns Actor Create/GetOrCreate single-use rules, duplicate-option handling, and terminal re-invocation errors. Per-language interfaces project the names and types of this contract. For other operations, each per-language interface defines single-use rules, repeated-option handling, and terminal re-invocation errors.
| terminator | completion meaning after acceptance | owner turn |
|---|---|---|
| one-way async terminal | Completes with no return data if source-local admission, the source-side acceptance boundary, succeeds, or with an exception on failure | Does not make the current turn wait unless awaited |
| general async terminal | Waits until the request, worker, or create's application result reaches a terminal state | Holds the current execution object's handler turn until the completion continuation finishes |
| synchronous blocking terminator | Blocks the calling thread until the application result (or one-way admission). Fails immediately with InvalidOperation in a runtime execution context (§4 F2-a) |
Application-thread only — holds no runtime turn or gate |
Yield |
Submits the operation, then releases the shared Spot turn — the unit in which a callback occupies the execution gate from an application queue to run — while waiting for the application result | The completion continuation re-acquires the same Spot gate and resumes in a new turn |
The synchronous blocking terminator name follows the binding policy
async-coroutine-policy §6
— Java submit_sync(), .NET Submit(), C++ submit() (Kotlin adds none and exposes the Java surface's
submit_sync()). Node.js provides no synchronous blocking terminator (§4.1).
The general async terminal name per language is .NET Async, C++ async, Java/Node.js submit,
and the dedicated Kotlin wrapper's await. An immediate submit that returns no async
completion uses Submit/submit. Only the terminal that actually releases the shared Spot
gate uses the name Yield/yield.
The binding rule for isolation between submissions also applies to asynchronous completion representations returned by the Framework. When an asynchronous terminator covered by this document returns an admission or application result, directly or by transforming a binding result, completion-state changes, cancellation, consumption, or detachment applied to one call must not propagate to another call's completion state or ability to consume its result through a shared returned representation or shared state behind it. This also applies to shared representations of already-completed results. Progress of other calls due to normal resource reclamation follows the existing admission and lifecycle contracts.
Verification uses the completion representations and outcomes actually exposed by Framework terminators to check that shared state does not contaminate results already returned for other calls or results of later calls. It does not require the Framework to expose binding result-object structures or a Backpressured result.
The layer returning the completion representation owns its isolation. Cancellation and shutdown §3 owns the boundaries between cancellation of Framework queue waits, caller-wait cancellation of binding operations, and late-completion cleanup. Isolating a returned representation must not remove an existing cancellation connection for a pending stage or introduce binding operation state, registries, or resubmission logic into the Framework.
Handler turn and execution gate §16
owns the execution contexts and calls that offer Yield.
What claim a Yield keeps and what gate alone it releases is defined by
Handler Turn And Execution Gate 「3. Gate And Claim On Yield」.
Actor Join is not a terminator this section covers — the completion boundary for Defer()
is defined by
Handler Turn And Execution Gate 「5. Actor Join And The Defer() Completion Boundary」.
3. Worker Offload¶
- CPU work and async I/O work are submitted to a bounded worker scheduler owned by the Framework.
- A CPU execution slot is occupied only while an application CPU callback is actually running. Async I/O does not occupy a CPU execution slot while waiting for an operating-system, transport, or Store completion.
- I/O admission and completion bookkeeping also use bounded resources, but a full CPU worker
queue does not turn an already-submitted I/O completion into
CapacityExceeded. - More I/O operations than the configured CPU-worker thread count may wait for completion. Their count is bounded by separate internal I/O admission, not by CPU execution slots or CPU queue length.
- This isolation contract does not require a separate public I/O thread-count or queue setting. A language runtime may implement it with native async I/O, an event loop, or a completion executor.
- A worker call keeps the type of the application result it computes, and in the permitted
SpotWide/Instance contexts, that same result can be awaited withYield. - Completion is
CapacityExceededif the queue is full,DeadlineExceeded— the Framework exception raised when an operation's completion condition isn't met by its allowed deadline — if the deadline is exceeded, andInternalFailureif the work itself fails. - Work that finishes late, after a timeout or cancellation, does not produce a second terminal result.
4. One-Way Submit — The Admission Boundary¶
Send, publish, bound session send, session Actor relay, and explicit STREAM send/reply provide an async submit terminator and the synchronous blocking terminator of the §2 table, with no nonblocking try family. The only alternative that gives nonblocking completion is a callback, which is complex and confusing, so the synchronous terminator is blocking only. There is no normal-completion value; completion only means the source-local admission boundary defined by the operation family accepted the message. Remote handler execution, subscriber receipt, remote Spot queue acceptance, or application callback completion are not awaited.
4.1 Node.js Provides No Synchronous Blocking Terminator¶
The Node.js runtime is single-threaded. Blocking that thread leaves the framework with no way to deliver the completion. When a request's target handler lives in the same process, the caller would have to produce the very reply it is waiting for, and the call deadlocks. Java, .NET, and C++ do not have this problem because the execution context that carries completions is separate from the calling thread (Java uses a dedicated platform-thread pump).
Whether the target is local or remote is not always known at submit time, so a "block only when
remote" rule is not available either. A promise that cannot be kept does not belong on the
surface — the Node.js framework surface has no synchronous blocking terminator. Node
applications use the async terminator (submit(...) returning a Promise).
This decision applies to the framework surface only. The Node binding keeps its submit_sync()
(async-coroutine-policy §6)
— the binding does not depend on the framework runtime to deliver completions.
The synchronous blocking terminator cannot be called from a runtime execution context (F2-a).
Blocking on a handler turn, Spot turn, or state lane would wait for completion while holding the
gate and deadlock. By the same principle as State Ownership And Lanes §5
(completion before return) and handler turn §2, calling a
synchronous terminator from a runtime execution context fails immediately with InvalidOperation. The
synchronous terminator is for application threads (main, tests, scripts).
The transport-facing stream write used from a session callback (synchronous bool return) is not a
call of this section; it follows the transport execution-context contract of
STREAM session.
| Target kind | admission boundary |
|---|---|
| Remote target | Local transport queue |
| Local target | The matching mailbox or relay queue |
| Classic fanout — a separate PUB/SUB path that sends events only to targets that finished connecting and subscribing — /STREAM | The matching socket queue |
Global Spot/Actor send waits from the current Ready authority resolve through this source-local admission.
%%{init: {'sequence': {'actorFontSize': '18px', 'messageFontSize': '18px', 'noteFontSize': '18px', 'boxMargin': 8, 'width': 140}, 'themeVariables': {'fontSize': '18px'}}}%%
sequenceDiagram
participant App as Application
participant Src as Source-local admission
participant T as Target-kind queue
App->>Src: one-way call submit
alt Global Spot/Actor send
Src->>Src: current Ready authority resolve
end
Src->>T: admission boundary (see table)
alt Acceptance succeeds
T-->>App: Completes normally with no return data
else Acceptance fails
T-->>App: Completes as an exception (see §5 error classification)
end
Note over App,T: Remote handler execution, subscriber receipt, and<br/>remote queue acceptance are not awaited
5. Backpressure and Error Classification¶
When local Framework capacity is unavailable, the Framework waits up to that family's send
timeout. When a binding operation waits on Core HWM, Core owns the retry and completes the
per-operation completion awaitable (the binding result object's admitted). The Framework does not
create a separate readiness callback, retry waiter, or separate binding adapter, and follows these rules.
Backpressured— an internal state in which a send path or queue's capacity is temporarily unavailable — is not a public terminal result.- If capacity becomes available first, the message is submitted exactly once and completes normally.
- Timeout, shutdown, and cancellation races during Framework queue waiting before binding handoff follow Cancellation and shutdown §3. Binding-operation cancellation and native-completion cleanup reference the ownership boundary there.
- If the internal bounded waiter capacity is fully used, a new payload is not held — the
call completes immediately with
DeadlineExceeded. - Even at this hard overload boundary, the
Backpressuredstatus is not exposed, nor is the message submitted later.
| Failure | Error classification |
|---|---|
| No Actor authority | NotFound |
| No Spot authority | NotFound |
| No Mesh or eligible Server | NotFound |
| No route available | Unavailable |
| Admission deadline expired | DeadlineExceeded |
| Runtime not accepting new admission | ShuttingDown |
| Same call's terminal invoked twice | InvalidOperation |
Pending admission keeps the caller-specified Node RID, global Spot/Actor ID, and session binding token. A select-one Channel in a RouteMesh — the scope in which multiple MeshNodes participate to exchange node and Channel messages — or ClientServer picks one current eligible member of the same ChannelName immediately before starting the first binding operation. It may choose another eligible member only while checking route eligibility or source-local admission before any binding operation has started.
Starting the binding operation fixes the selected target. Core owns HWM retry and completion; the Framework does not reselect for capacity or resubmit the binding operation. There is no automatic resubmission after completion.
6. Logical Multicast and Classic Fanout¶
Logical Multicast follows these rules.
- It fixes a target snapshot when the operation starts and attempts each target exactly once.
- If the operation itself cannot be submitted to the local executor, it waits up to the send timeout.
- Once the bounded worker and source-local capacity have been secured to start the transaction, the public terminal completes normally with no return data, and per-target submission continues internally.
- Once started, an individual target's failure does not roll back the whole publish or turn it into an exceptional completion.
- Per-target acceptance/failure results are not surfaced as a public return value or as publish-only monitoring values.
- It completes normally even with zero targets.
Classic fanout completes normally once the publisher socket queue accepts the message, even with no subscribers. Subscriber count and receipt are not exposed as a public result.
7. Admission Deadline — Owner and Value Rules¶
The one-way admission deadline is owned by the outbound socket or a MeshNode — a runtime node that participates in a RouteMesh to send or receive messages — that the operation actually uses.
| Operation family | deadline owner | Default rule |
|---|---|---|
| RouteMesh node/channel, Spot, Actor | The selected MeshNode's ROUTER send timeout | Includes global object route resolve time; 1 second if unset |
| ClientServer | The client's DEALER send timeout | 1 second if unset |
| Logical Multicast | The selected MeshNode ROUTER's per-target send timeout | Applies to each remote target of a committed publish transaction |
| classic fanout | The publisher socket's send timeout | 1 second if unset |
| bound session/session Actor relay | The Framework socket's send timeout | Same deadline even if the local/remote Actor route changes |
| STREAM send/reply | The matching STREAM socket's send timeout | The reply does not use the caller's request timeout |
The Framework's public send timeout follows these value rules.
- It must be a finite duration whose value, rounded up to milliseconds, falls in
1..INT_MAX. - A positive sub-millisecond value rounds up to 1ms.
0, negative values, infinity, and values above the upper bound are rejected no later than host startup, and are never silently substituted with a valid default.- If no value is specified, that family's 1-second default is chosen.
- An existing public root fallback, if present, applies with the same meaning, but that does not mean every language must add the same root option.
- If a runtime setter exists, an invalid value is rejected immediately at the setter call.
A STREAM one-way send call provides an optional per-call admission-timeout modifier. This value is not reply wait time; it is the maximum time that send can wait for acceptance by the STREAM transport queue.
- If omitted, the matching STREAM socket's send timeout is used.
- If specified, the earlier of the socket timeout and the per-call timeout is used. A per-call value never extends the socket timeout.
- Validation and millisecond rounding use the same
1..INT_MAXrules above. - If the deadline wins, the call completes once with
DeadlineExceeded; later capacity does not admit or replay that send. - This modifier does not apply to a STREAM reply call. A reply uses the socket send timeout and the one-shot token contract.
- Races between per-language cancellation and timeout follow Cancellation and shutdown §3.
8. STREAM Reply Token¶
Bound session and session Actor relay do not retroactively turn a remote failure that occurs after the local relay has accepted the message into a failure of the same submit, and do not automatically replay it.
The one-shot reply token rules for STREAM reply are:
- The request sequence and the token are preserved when the call is created.
- The first valid terminator invocation atomically claims and consumes the token before the transport admission attempt.
- Even if that terminator completes with a
DeadlineExceeded, cancellation, or runtime shutdown exception, the token cannot be reused. - If two calls made from the same token race, only the one that wins the claim starts transport admission; the other ends as an exceptional completion with no transport attempt.
- The caller's request timeout is not carried on the reply wire, so it is not used as the STREAM reply's admission deadline.
- Even if a late-accepted reply doesn't match on the client's correlation, the transport admission result does not become the request's result.
9. Request Completion — The Completion Race and Timeout Budget¶
The caller of a request observes exactly one result: whichever of reply, remote error, timeout, cancellation, or shutdown is decided first. Timeout and cancellation end the caller's wait, but do not roll back work the remote handler already started. A late reply for a Framework service operation is not redelivered to a closed correlation. Binding-operation lifetime and native completion cleanup follow the ownership boundary in Cancellation and shutdown §3.
%%{init: {'flowchart': {'nodeSpacing': 32, 'rankSpacing': 40, 'padding': 8, 'wrappingWidth': 180}, 'themeVariables': {'fontSize': '18px'}}}%%
flowchart LR
R["Request starts"] --> W{"Which result is decided first?"}
W -->|reply| C1["Completes normally"]
W -->|remote error| C2["Completes as an exception"]
W -->|timeout| C3["Completes as an exception<br/>(remote handler is not rolled back)"]
W -->|cancellation| C4["Completes as an exception"]
W -->|shutdown| C5["Completes as an exception"]
C1 -.->|any other result that arrives late| D["Discarded, only correlation state is cleaned up"]
C2 -.-> D
C3 -.-> D
C4 -.-> D
C5 -.-> D
The global object request timeout covers the current Ready authority resolve, outbound admission, handler, and reply as a whole. A source only passes the remaining time, after subtracting what earlier stages used, to the next stage. Since there is no receipt proving a remote target did not accept the request, it is not automatically resubmitted to a different owner after a timeout or connection failure.
How a request sent within the same handler turn releases and reacquires the gate while waiting is defined by Handler Turn And Execution Gate 「4. Waiting And Returning Within The Same Turn」.
When reply, timeout, cancellation, and Spot shutdown race, only the terminal result decided first is used. If the Spot has already terminated, or a new generation was created under the same Spot ID, a late reply from the previous activation is not delivered to the new Spot. There is no automatic resend to a different RouteMesh member, ClientServer server, or send path after a target connection closes or times out.
10. Operation Identity and Where Completion Happens (Implementation)¶
Each call has one completion slot, and several paths compete for it. Only the path that claims it releases the caller's wait. The losing paths do nothing.
%%{init: {'flowchart': {'nodeSpacing': 32, 'rankSpacing': 40, 'padding': 8, 'wrappingWidth': 180}, 'themeVariables': {'fontSize': '18px'}}}%%
flowchart LR
R["response arrives"] --> S["completion slot<br/>(one)"]
T["timeout"] --> S
C["cancellation"] --> S
D["shutdown"] --> S
X["disconnect"] --> S
S --> W["only the path that claimed it<br/>finalizes the caller"]
Atomically taking an entry from the in-progress call table is the completion contention point. The response, timeout, cancellation, and shutdown paths all try to take the same entry. Only the successful path gains completion authority; the others observe that the call is already complete and stop. This operation confirms completion authority and removes the in-progress call together. It therefore needs neither a separate completion marker nor a second slot reservation. Every completion path uses the same approach so that adding a path cannot introduce a different contention rule.
A service-wire request preserves two different values. Both are Framework-internal values and are not exposed to the application.
| Value | Form | Responsibility |
|---|---|---|
OperationId |
{ high: u64, low: u64 } |
Terminal-deduplication identity for one operation. It stays unchanged through relocation and reply relay |
ReplyRouteId |
non-zero u64 |
Connects a terminal reply to a pending entry within the source lifecycle. It does not replace operation identity |
An operation that expects a terminal result cannot have an OperationId with both words
zero.
Registries and durable completion records preserve both words. Using only the low
word as a key can make two different operations appear to be the same entry.
A
ReplyRouteId is also unique among requests pending in one source-owner lifecycle, but it
does not by itself decide terminal deduplication after relocation.
The sending runtime first creates OperationId/ReplyRouteId and only submits to
transport after registering the pending completion entry, a slot in the completion dispatcher that delivers completion callbacks, and reply route.
This is so that even an immediate in-process response cannot be processed before
registration, avoiding the need for a separate early-response map and the race-handling that
would cross-check that map with the pending table. The wire request preserves the two values
in separate fields; neither value is used as an alias for the other.
%%{init: {'sequence': {'actorFontSize': '18px', 'messageFontSize': '18px', 'noteFontSize': '18px', 'boxMargin': 8, 'width': 140}, 'themeVariables': {'fontSize': '18px'}}}%%
sequenceDiagram
participant S as Source runtime
participant P as Completion and reply-route registry
participant T as Transport
S->>S: create OperationId and ReplyRouteId
S->>P: register full OperationId, reply route, and dispatcher slot
S->>T: submit the registered request
T-->>P: terminal reply arrives
P->>P: atomically take the registered entry
P-->>S: deliver completion in a new execution turn
11. The Execution Turn of the Completion Callback (Implementation)¶
An application callback does not run inside the lock held while confirming completion. If the callback calls back into the runtime, it would require the same lock, causing a deadlock. Timer cancellation and payload cleanup also occur outside the lock.
Releasing the lock and immediately invoking the callback on the same call stack is still insufficient. It lets application code re-enter the runtime before the current transport response or timeout handling has returned. The completion callback is placed on a process-shared completion dispatcher and runs on a new execution turn after the current handling returns.
The order is as follows.
- Confirm completion authority.
- Release the lock.
- Enqueue the callback on the dispatcher.
- Run the callback on a new execution turn.
If the terminal winner takes the in-progress call table entry and admission to the dispatcher then fails, the application completion is lost. The runtime therefore reserves a completion dispatcher slot when it accepts the operation. That reservation remains until the callback returns. The combined number of in-progress operations and callbacks waiting or running on the dispatcher cannot exceed 4,096, so the callback queue cannot grow without bound.
If no slot can be reserved, the operation is rejected with CapacityExceeded before the
request is sent. Once an operation is accepted, completion enqueue has no reject or drop
path.
The dispatcher uses a process-shared lane instead of creating a thread per callback, and shutdown drains every accepted callback.
An exception from one callback does not stop later callbacks from running.
12. Once Accepted, It Is Never Resent¶
Once transport has accepted a message, whether the target has executed it is unknowable. Resending to a different target in this state can cause double execution.
The runtime never automatically resends after acceptance. This holds even if the connection drops (Transport Liveness 「5. Ready And Failure Determination」). The application can start a new call, and at that point the risk of duplicate execution is judged by the application.
This rule requires distinguishing "failure after sending" from "failure before sending."
| Failure timing | May it be resent |
|---|---|
| Before transport accepts | Yes. It's certain the target never received it |
| After transport accepts | No. Whether it executed is unknowable |
13. The Completion Point of a Call That Does Not Wait for a Reply¶
A call that does not wait for a reply completes normally at the moment this process's send path accepts the message. Whether the remote queue received it or the handler executed it can't be known from this result (Framework API 「12. Spot, Actor, And STREAM Owner」).
"Local acceptance" and "transport acceptance" are not separate events. In this product the send path is the socket's send queue, so both terms refer to the same completion boundary. Documentation and code comments use the single term send acceptance.
14. Failures Are Not Classified by String (Implementation)¶
The completion path must distinguish cancellation, timeout, and shutdown. This distinction decides the result the caller receives.
Judging cancellation by running a regex against the error message string causes the classification to change silently when the message wording changes. Conversely, a business error whose message contains "cancel" is misclassified as cancellation and swallowed.
Failure is classified by type or a dedicated value. The message string is for humans to read, not a branch condition. The error kind value itself is owned by the Framework Error Model; this document only defines which completion path picks that value.
15. Consuming Binding Send Terminals (Implementation)¶
When the framework runtime consumes a binding's HWM-managed send family
(PAIR send, routed send, Received.send()), it uses the result object of the
async terminal (C++ async(), .NET Async(), submit() elsewhere) (F1). It
decides immediately from the returned result, waits on admitted only when
result == BACKPRESSURED, and consumes reply for a request. This implements "wait
only when blocked at HWM" precisely — until now a single stage could not distinguish
admission from reply, so three-stage backpressure
waiting was imprecise. The framework's public terminal does not expose backpressure
(§5, Backpressured is not a public result); this consumption lives only in the framework's
internal implementation. Core send-completion notification drives the completion, so the
framework doesn't wrap it in a separate executor or offload. The contracts for the binding
terminal names, return types, and result object are owned by
the binding routed-transfer contract and asynchronous completion surface policy
— this section owns only the framework's consumption rule.
The binding's synchronous blocking terminator (§2, §4) is an application-thread-only public
surface. When implementing a public synchronous contract (the completion-before-return that
State Ownership And Lanes §5
requires), waiting at HWM saturation is an observable property of that public contract, not a
violation. The path that observed immediate backpressure through a sync DONTWAIT terminal is
removed — the admission result comes from the async terminal result object's result.
Publish is HWM-free and uses a synchronous terminal. Raw reply depends on peer
topology. A RouteMesh ROUTER-ROUTER reply is HWM-free on the separate Completion
connection. A raw reply from a ClientServer ROUTER to a Client DEALER is subject
to HWM, PAUSED, and SNDTIMEO admission on the single Application connection,
so it can end as BACKPRESSURED. The binding spec owns the synchronous one-shot
terminal in both cases. After a raw reply enters the Framework completion queue,
the Application Job Queue permit bypass and first-terminal rules continue to
apply.
Framework Typed Session Reply¶
The framework's typed Session reply is not a surface that turns the raw binding reply into an async terminator. The framework runtime owns typed serialization and the per-request one-shot reply token; the terminator atomically claims the token and then waits for source-local admission. A second reply on the same token ends as an exceptional completion without attempting transport. Token claim rules are owned by §8.
| Framework language | Typed Session reply terminal | Completion expression |
|---|---|---|
| C++ | .reply_packet(...).async() |
co_await-able framework task |
| .NET | .Reply(...).Async(ct) |
ValueTask |
| Java | .reply(...).submit() |
CompletionStage<Void> |
| Kotlin | .reply(...).await() |
suspending Unit |
| Node | .reply(...).submit(signal?) |
Promise<void> |
In languages that use the submit name, the return type and
owning layer distinguish it from the raw binding reply (a synchronous
one-shot).
16. Per-Language Representation¶
The common contract does not mandate a specific async type name. This document owns completion ordering, cancellation, and error semantics; each language's per-language interface owns the specific return type and error representation.
| Language | General async completion | Synchronous blocking | Returning the Spot turn | per-language interface owner |
|---|---|---|---|---|
| .NET | Async(...) returns ValueTask or ValueTask<T> |
Submit(...) |
Yield(...) |
per-language interface index |
| Java | submit(...) returns CompletionStage<T> |
submit_sync(...) |
yield(...) |
Channel messaging |
| Kotlin | Uses the dedicated call wrapper's suspending await() |
the Java surface's submit_sync(...) |
The dedicated wrapper's yield() |
Channel messaging |
| Node.js | submit(...) returns Promise<T> |
not provided (§4.1) | yield(...) |
interface index |
| C++ | async(...) returns task_t<T> |
submit(...) |
yield(...) |
framework interfaces |
The synchronous blocking terminator fails with InvalidOperation in a runtime execution context (§4 F2-a).
Each per-language interface fixes the return type, cancellation argument, and callback or coroutine representation per terminator. Even when the language's standard idiom differs, the same operation's completion timing, ordering, and error classification do not change.
Because C++'s task_t starts the operation when it's called, async() can be used in the
following two ways depending on whether the result is consumed. The two lines below show two
distinct single-use calls.
sendCall.async(); // Starts the operation only, with no result.
auto reply = co_await requestCall.async(); // Awaits the async application reply.
No overload differing only by return type is created. The C++ Messaging call wrapper offers both
the async task_t<T> async() and the synchronous blocking submit() (the two terminators have
different names, so they are not overloads). submit() is application-thread only and fails with
InvalidOperation in a runtime execution context (§4 F2-a). A callback overload can be provided as
submit(callback) since its parameter list differs.
17. Verification Requirements¶
The following are confirmed from the public surface alone (each language's terminator return type, returned error kind, one-way submit's normal/exceptional completion, a request's reply/error/timeout/cancellation/shutdown completion, and STREAM reply token claim results). Each item corresponds to one test. Conditions confirmable only through internal structure — that there is one completion-confirmation approach within the runtime, and when the dispatcher slot is reserved — are owned, with their rules, by §10/§11 and are not repeated here.
Submit and admission
- A one-way call completes with no return data once the admission boundary (§4 table) accepts it, and completes with one value from the §5 error classification table on failure.
- A send whose local capacity is unavailable waits up to the family send timeout; if
capacity becomes available first it's submitted exactly once and completes normally; if
the timeout is decided first it completes with
DeadlineExceeded. - A call submitted while the bounded waiter capacity is full completes immediately with
DeadlineExceeded, with no wait. - Logical Multicast completes normally with no return data even with zero targets, and an individual target's failure after starting does not change the public return value.
- Classic fanout publish completes normally once the publisher socket queue accepts it, even with no subscribers.
Deadline and reply token
- Setting the send timeout to
0, a negative value, infinity, or a value above the upper bound is rejected at host startup or at the setter call. - If a STREAM send call's admission-timeout modifier expires before the socket timeout, the
call completes with
DeadlineExceeded, and the same send is not later admitted or replayed. - If two calls made from the same STREAM reply token are submitted at the same time, only one starts transport admission; the other ends as an exceptional completion with no transport attempt.
Request completion
- A request completes with whichever of reply, remote error, timeout, cancellation, or shutdown is decided first, and the remaining results are not delivered to the caller.
- A reply from a previous activation that arrives after the Spot has terminated, or after a new generation was created, is not delivered to the new Spot.
- The same request is not automatically resubmitted to a different owner after a timeout or connection failure.
- In ClientServer DEALER-ROUTER, if earlier one-way DATA or
PAUSED/HWM delays a reply until the configured request timeout is decided first, the request completes once with timeout and the late reply doesn't complete the caller again. - In RouteMesh ROUTER-ROUTER, a raw reply for an already-started request can
progress on the separate Completion connection while the Application Job
Queue is
PAUSED.
Raw reply admission
- A raw reply to a DEALER peer is subject to HWM,
PAUSED, andSNDTIMEOon the single Application connection, so it can beBACKPRESSURED. - A raw reply to a ROUTER peer retains HWM-free admission on the separate Completion connection.
Completion confirmation and the ban on resending
-
Even if a response, timeout, cancellation, and shutdown occur at the same time for the same operation, the caller completes exactly once. Binding cancellation observations reference Binding async execution model §7.
-
The completion callback runs on a new execution turn, not the call stack at the moment of confirmation.
- If no slot can be reserved within the combined in-progress-operation and dispatcher limit,
the request is rejected with
CapacityExceededbefore it is sent. - Even if the connection drops after transport has accepted the message, the runtime does not resend to a different target.
- A result completed by cancellation, timeout, or shutdown is distinguished by a dedicated type or value, not by the error message string.
Execution topic table of contents · Spec table of contents · Next: 02. Handler Turn And Execution Gate