Skip to content

Cancellation: one kind, and a write says how much it has taken - #42

Merged
pgit merged 5 commits into
masterfrom
cancellation
Nov 8, 2026
Merged

pgit merged 5 commits into
masterfrom
cancellation

Conversation

@pgit

@pgit pgit commented Nov 8, 2026

Copy link
Copy Markdown
Owner

A cancelled operation now promises one thing on both runtimes: it completes right away, with operation_canceled, and a write reports how much of its buffer it has taken, so that the body can still be finished where the message is still usable.

Changes

  • Only terminal cancellation cancels a parked operation on ASIO (65febe7). on_cancel() ignores a request for partial or total alone, which is what Asio asks of an operation that can't guarantee what those types promise, and what its own coroutines do by default. cancel_after sends terminal and || sends all three, so neither is affected.

  • A write says how much of its buffer it has taken (f82a6d7). API change: Write is void(error_code, size_t), and write() / write_eof() yield IoTask<size_t>. On success that is all of the buffer; after a cancellation it is what the protocol has taken:

    • nghttp2 v1: what it has copied
    • nghttp2 v2 and HTTP/3: what is staged, or handed over by reference
    • HTTP/1.1: the body bytes that reached the connection

    On HTTP/2, cancelling write_eof() before nghttp2 has seen the end now takes the end back, as HTTP/3 already did. So write_eof(buffer + n) finishes the body after any cancelled write, on every protocol.

  • Tests for what a cancelled operation promises (c9c53c4):

    • partial/total alone leave a read running, and terminal completes it
    • a cancelled read_some() can be followed by reading the rest of the body
    • an 8 MiB write cancelled while the server isn't reading reports n, and write_eof(buffer + n) then gets the whole body across intact (HTTP/1.1 skips this one, because a cancelled write there ends the connection)
  • docs/cancellation.rst (14556f7): a page on cancelling, abandoning and draining. It covers what a cancelled operation promises, what each operation leaves behind, how writes differ by protocol, and how to finish a body after a cancelled write. The programmer's guide and end-of-body.rst now link to it.

  • test/test_runtime.cpp (df5c9dd): delay() can be cancelled on both runtimes, when_either() doesn't wait for the delay it cancels, and on ASIO partial/total alone don't reach the timer.

Testing

build/ (ASIO) and build-corosio/ pass the new runtime tests. CI runs the full matrix.

🤖 Generated with Claude Code

pgit and others added 5 commits November 8, 2026 19:53
There is one kind of cancellation: it completes the operation right away, whatever that leaves
behind, the same as a stop request on COROSIO. That is Asio's terminal type. A request for partial
or total alone promises a state that a cancelled write cannot always keep, so on_cancel() now
ignores it, which is what Asio asks of an operation that does not support a type, and what its own
coroutines do by default. cancel_after sends terminal and || sends all three, so neither notices.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A cancelled write completes right away, and so far said nothing but that. Where the message is
still usable afterwards, the caller cannot go on without knowing where the write stopped, so Write
is void(error_code, size_t) now, and write() and write_eof() yield IoTask<size_t>: all of the
buffer on success, none when the entry ladder answers, and otherwise what the writer has taken --
copied by nghttp2 version 1, copied in Staged and framed or handed to the stack in ZeroCopy on
HTTP/2 version 2 and HTTP/3, and over HTTP/1.1 the body bytes that reached the connection, which
h1::write_some() counts among the header and chunk framing Beast hands out. On COROSIO,
Writer::write_some() passes that on, where it reported 0 for any error.

Cancelling write_eof() before nghttp2 has been told about the end now takes the end back on HTTP/2,
as HTTP/3 already did, so that write_eof(buffer + n) finishes the body after any cancelled write, on
every protocol. Until now a data write after it failed with broken_pipe there, and only a bare re-end
was possible.

The callers in the library, the programs, the tests and the snippets take the count; send() counts
what was actually taken.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On ASIO, a request for partial or total cancellation alone leaves a read running, and terminal
completes it. A cancelled read_some() can be followed by reading the rest of the body. A write
cancelled while the server is not reading says how much of an 8 MiB body it has taken, and
write_eof(buffer + n) then gets the whole body across intact, after write() as after write_eof().
HTTP/1.1 skips the last two, as its cancelled write ends the connection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A page of its own for what used to be a list of cases in the guide. There are three ways to stop
something in progress: cancelling one operation, which completes right away, abandoning a message or
session by destroying its handle, and draining, the ordinary operations that finish what was
started and may wait for the peer. The page has what a cancelled operation promises, what each one
leaves behind, how writes differ by protocol, and how to finish a body after a cancelled write.

The guide points there now, and end-of-body.rst says what cancelling write_eof() does to the end
of the body since it is taken back where the stack has not been given it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new file for what runtime.hpp offers generic code, with the same expectations on both runtimes.
A delay cancelled from outside completes right away with operation_canceled: on ASIO through a
terminal signal on its completion's slot, on COROSIO through its stop token. when_either() does not
wait for the delay it cancels. On ASIO, partial or total alone does not reach the timer, since
co_spawn() lets only terminal cancellation through by default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pgit
pgit merged commit 2ab497b into master Nov 8, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant