Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions docs/spec/concurrency_and_lifetimes.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,15 +403,15 @@ the "work" column below is for a synchronous handler unless it says otherwise.
| `LocalBackend::cancelPending` | **G2** | The error is posted to each pending call's `cbExec`; the action's later result reaches nobody | Not waited for: a synchronous action runs to its end. A Task handler is asked to stop | M |
| `SimulatedRemoteBackend::cancelPending` | **G2** | As above | The server's handler runs to its end; nothing crosses the wire | M |
| `SynchronousBackendAdapter::cancelPending` | **G2** for the binds and promotes it produced; **G0** on return for the wrapped backend's calls | Its own completions are rejected at once. The wrapped backend's `cancelPending` is queued on the adapter's control strand, behind whatever was queued before it | A bind still queued never reaches the wrapped backend; one already running is not recalled | M |
| `SocketBackend::cancelPending` | **G0** on return; **G2** once the I/O loop runs it | Posted to the loop, which settles both pending tables | The server keeps executing; its reply finds no pending entry | R |
| `QtWebSocketBackend::cancelPending` | **G2** | On the Qt thread: executes, control calls and never-sent registrations are settled; fire-and-forget deregisters are dropped, having no one to tell | The server keeps executing | R |
| `SocketBackend::cancelPending` | **G0** on return; **G2** once the I/O loop runs it | Posted to the loop, which settles both pending tables | A Task handler on a server that advertised `cancel` is asked to stop (measured, through `~Bridge`, over a real loopback); otherwise the server keeps executing and its reply finds no pending entry | R (G); M (work) |
| `QtWebSocketBackend::cancelPending` | **G2** | On the Qt thread: executes, control calls and never-sent registrations are settled; fire-and-forget deregisters are dropped, having no one to tell | A Task handler on a server that advertised `cancel` is asked to stop (measured, through `~Bridge`, against a real `QtWebSocketServer`); otherwise the server keeps executing | R (G); M (work) |
| `~Bridge` | **G2** for its calls | Calls waiting for a bind and the backend's pending calls end with `BridgeDestroyedError`, posted | **G4** with a `LocalBackend`: the backend is destroyed with the bridge, and drains its strands, so the destructor returns only once the running action has ended. A Task handler is asked to stop first | M |
| `Bridge::switchBackend` | **G2** for the outgoing backend's calls | `BackendChangedError`, posted | The outgoing backend is destroyed inside the switch: with a `LocalBackend`, the switch waits for its running actions | M |
| `Bridge::setExecuteDeadline` | **G0** for calls already made | Applies to calls made after it. When a call's deadline fires, the call ends as in G2, with `ClientTimeoutError` | A synchronous action runs to its end; a Task handler is asked to stop | M |
| A stop on `BridgeHandler::execute(action, stop)`'s token | **G2**, from any thread | `OperationCancelled` is posted to the call's `cbExec` by the thread that requested the stop; the call's own outcome, later, reaches nobody. A token already stopped rejects the call the same way without dispatching it | Asked to stop, not waited for: a Task handler on a `LocalBackend` sees the stop. A synchronous action runs to its end. **On a remote backend the call is only abandoned**: no `cancel` crosses the wire, so the server's handler runs to its end and its reply is dropped (measured with `SimulatedRemoteBackend`). The call stays counted as pending until that reply, as for a fired deadline | M |
| `Bridge::setExecuteDeadline` | **G0** for calls already made | Applies to calls made after it. When a call's deadline fires, the call ends as in G2, with `ClientTimeoutError` | A synchronous action runs to its end; a Task handler is asked to stop — over `SocketBackend` or `QtWebSocketBackend` too, through a `cancel`, when the server advertised it (measured over both) | M |
| A stop on `BridgeHandler::execute(action, stop)`'s token | **G2**, from any thread | `OperationCancelled` is posted to the call's `cbExec` by the thread that requested the stop; the call's own outcome, later, reaches nobody. A token already stopped rejects the call the same way without dispatching it | Asked to stop, not waited for: a Task handler on a `LocalBackend` sees the stop. A synchronous action runs to its end. **On a remote backend that sends no `cancel` the call is only abandoned**: the server's handler runs to its end and its reply is dropped (measured with `SimulatedRemoteBackend`). Over `SocketBackend` or `QtWebSocketBackend`, against a server that advertised `cancel`, the stop on the call's stop source is relayed as a `cancel` (read, not measured for this verb). The call stays counted as pending until that reply, as for a fired deadline | M |
| `BridgeHandler::unsubscribe<R>()` | **G1** | A delivery already posted still runs after it returns; no new one is scheduled. Pair it with a `CallbackScope` for G3 | n/a | M |
| `CallbackScope::requestStop()`, `reset()`, `~CallbackScope` | **G3** on the delivery executor; **G1** from another thread | A gated callback posted but not yet run is refused when it runs. From another thread the check and the body are not atomic, so a callback that has passed its check still runs — advisory, and not measured as a race | Never waits for a callback in its body (measured). Stops every call a gated continuation is attached to | M |
| A stop on a coroutine suspended in `co_await completion` | **G2** for the await | The coroutine's resumption with `OperationCancelled` is posted to the executor it suspended on; the await's handlers are refused, so the call's own outcome, later, does not reach it | The call is asked to stop, through the awaiter's scope linked to its stop source: a Task handler on a `LocalBackend` sees the stop (measured). Not waited for. A synchronous action runs to its end; a remote server's handler runs on | M |
| A stop on a coroutine suspended in `co_await completion` | **G2** for the await | The coroutine's resumption with `OperationCancelled` is posted to the executor it suspended on; the await's handlers are refused, so the call's own outcome, later, does not reach it | The call is asked to stop, through the awaiter's scope linked to its stop source: a Task handler on a `LocalBackend` sees the stop (measured). Not waited for. A synchronous action runs to its end. A remote server's handler runs on, unless the backend relays the call's stop as a `cancel` (read, not measured for this verb) | M |
| `TimeoutScheduler::cancel` | **G0** on return; **G3** for a callback not yet started, once the loop has run the cancel | The cancel is posted to the loop: on return the callback still holds its captures | Never waits for a callback already running | M |
| `~TimeoutScheduler` | **G4** | Every pending callback is dropped unfired | Returns only once no callback of the scheduler is running | M |
| `LimitPolicy::executeTimeout` (`RemoteServer`) | **G2** for the reply | `err "timeout"` is sent; the handler's own reply, later, is dropped — one reply per call | A synchronous handler runs to its end; a Task handler is asked to stop | M |
Expand All @@ -424,9 +424,6 @@ the "work" column below is for a synchronous handler unless it says otherwise.

### What no verb does today

- **No cancel crosses the wire.** A remote call is settled locally only; the
server finishes the handler and its reply is dropped. There is no `cancel`
envelope.
- **`Completion` has no cancel API of its own.** A call is cancelled through
the token given to `execute(action, stop)`, a stopped scope a continuation is
gated by, a stopped `co_await`, or the execute deadline.
Expand Down
25 changes: 5 additions & 20 deletions docs/spec/core/backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -1627,30 +1627,15 @@ when it does (measured on both backends). `QtWebSocketBackend` sends nothing fro
destructor's sweep — its socket is already aborted there — so a backend
destroyed without its owner's `cancelPending` cancels nothing.

### Cancellation-policy rows for the remote backends
### Where the remote backends' cancel rows are measured

The policy table in
[concurrency_and_lifetimes.md](../concurrency_and_lifetimes.md#every-cancel-verb-measured)
marks the remote backends' rows read, not measured, and gives "the server
keeps executing" for the work they abandon. What those rows are, and which
parts are measured, is:

- `SocketBackend::cancelPending` — **G0** on return; **G2** once the I/O loop
runs it (unchanged). Work: a Task handler on a server that advertised
`"cancel"` is asked to stop; otherwise the server keeps executing.
**Measured** over a real loopback (`SocketServer` with `SocketBackend`) for
the stop, through `~Bridge`.
- `QtWebSocketBackend::cancelPending` — **G2** (unchanged). Work: as above.
**Measured** in the Qt suite against a real `QtWebSocketServer`, through
`~Bridge`.
- `Bridge::setExecuteDeadline` over either backend — the deadline's stop
reaches the server's Task handler through a cancel. **Measured** for both
over a real loopback.

The G-levels of these verbs are unchanged and are still read, not measured:
the tests above measure the work column only. The measuring tests are in
gives the remote backends' rows. Their *work* column — a Task handler on a
server that advertised `"cancel"` is asked to stop — is measured over a real
loopback, through `~Bridge` and through the execute deadline, in
`tests/net/test_socket_backend.cpp` and `tests/qt/test_qt_websocket.cpp`
(`[cancel]`).
(`[cancel]`). Their G-levels are read, not measured.

## Lifetime & ownership

Expand Down
Loading