Skip to content

onDataOrAborted & onWritableOrAborted, maybeCollectBody #1349

Open

Description

@uNetworkingAB

The async lifetime of a HttpSocket can often be handled by only 1 single callback at any given time. onAborted is only needed for when you neither wait for a body, nor wait for backpressure (that is, you are just waiting for something else).

onDataOrAborted & onWritableOrAborted, collectBodyOrAborted are decent names but they could need some thinking. Maybe they are too long. In any case, they would register the onAborted handler for you, and deliver abortion events to the onData or onWritable handler so you only need 1 callback at any given time.

onDataOrFail is an alternative, onDataV3 is another alternative onMaybeData is another

Activity

  1. changed the title [-]onDataOrAbort & onWritableOrAbort, collectBodyOrAbort[/-] [+]onDataOrAborted & onWritableOrAborted, collectBodyOrAborted[/+] on Oct 4, 2026
  2. changed the title [-]onDataOrAborted & onWritableOrAborted, collectBodyOrAborted[/-] [+]onDataOrAborted & onWritableOrAborted, maybeCollectBody[/+] on Oct 4, 2026
  3. nigrosimone commented on Oct 4, 2026

    @nigrosimone
    Contributor

    I like it, it is the onStream idea but one per event, and the C++ lib stay as it is. Some points to decide:

    1. How the abort arrives. onDataV2 can use chunk === null, onWritable offset === -1 (return value ignored). collectBody already uses null for a body over maxSize, and there you can still answer 413, after an abort you cannot: so it needs another value, undefined or a second argument.
    2. After the last chunk. The usual handler reads the body, awaits a db and answers. If the abort keeps going to the last ...OrAborted handler until the response ends, as you wrote in onAborted + onData -> new single onStream callback #1245 for onStream, that handler needs no onAborted at all. If not, it still needs onAborted for the await, and the gain is small.
    3. An onAborted already set. The wrapper can keep one abort dispatcher that calls the current ...OrAborted handler and also the onAborted one, so nothing is overwritten (the problem of March in Add HTTP body reader helper #1159).
    4. Names. I would keep the event first and short: onDataOrAbort, onWritableOrAbort, collectBodyOrAbort. collectJson (POC: collectJson, the body parsed by JSON::Parse, to a callback or a promise #1348) can follow the same rule.

    All of it is in the wrapper, nothing in C++. If you want, I can make a PR.

  4. uNetworkingAB commented on Oct 4, 2026

    @uNetworkingAB
    ContributorAuthor

    Regarding onStream, in v21 you could entirely remove onAborted and just pass optional chunk, or optional offset in onData or onWritable.

    Then you just change the rule that you must attach onAborted handler to: you must attach either onData or onWritable if you go async.

  5. uNetworkingAB commented on Oct 4, 2026

    @uNetworkingAB
    ContributorAuthor

    So if you just want to wait, you would attach onData since that one maps well to "receiving FIN or RST" in Unix

  6. nigrosimone commented on Oct 4, 2026

    @nigrosimone
    Contributor

    Ok for v21. Then onData, onDataV2, collectBody and onWritable install the abort themselves, and their handler gets undefined (chunk, body or offset).

    Two questions:

    1. A handler with both onData and onWritable (an upload while the response has backpressure): which one gets the abort? I would call both, once each.
    2. After the last chunk, the abort still goes to onData until the response ends, right? That is what lets "read the body, await a db, answer" use one callback.
  7. uNetworkingAB commented on Oct 4, 2026

    @uNetworkingAB
    ContributorAuthor

    Both gets aborts and aborts are delivered even after isLast.

    v21 is breaking change so it would not even have onDataV2, it would just be onData, onWritable.

    You can even see the misdesign of onAborted in this example I made myself: https://github.com/uNetworking/uWebSockets/blob/e2ec2d7af011203420e782fb649d27934fc2bdea/examples/Crc32.cpp#L47

    See how I need to allocate a shared_ptr for isAborted that's insane.

  8. ValoChet commented on Oct 6, 2026

    @ValoChet
    Contributor

    Why is res->onAborted() even mandatory?
    Most simple async cases will only use it to set an isAborted boolean.

    Can't we just create that isAborted boolean, or isFinished, and make the res->onAborted() optional, only used when we need to clear things up?

    In c++ this could be: [](auto *res, auto *req, const bool *isAborted) using overloads
    In JS it would ideally be: res.isAborted

  9. uNetworkingAB commented on Oct 6, 2026

    @uNetworkingAB
    ContributorAuthor

    Most simple async cases will only use it to set an isAborted boolean.
    Can't we just create that isAborted boolean

    You are pressing on a fundamental difference between a GC language and a RAII language. In GC languages, you wouldn't even have isAborted you would just throw an exception if you tried to write to an aborted socket. In other words, GC languages are "poll based", you just do whatever you want and handle the exceptions when you "poll" their state. In Rust, C++ which are RAII languages, everything is "event based", you get the event that an object has been destructed, and from that point you cannot even store the isAborted because where do you store isAborted = true if the object has been destructed? So the claim that most simply set an isAborted is not really true - it's true in JS because JS is a script with GC, but in Rust, C++ you would handle the event of destruction by removing your async operation at that very point.

    The problem here is that onAborted is very non-elegant because you would rather want it inside onData so you don't need to capture a shared_ptr from onAborted, also because you typically either wait for data, backpressure OR aborted

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions