Skip to content

A served object cannot stream a reply, so bulk results must fit one 16 MiB frame #16

Description

@YellowSnnowmann

Summary

A served object cannot stream a reply, so any call whose result is large must fit one 16 MiB frame. Inbound payloads have a stream; replies do not. That asymmetry is now load-bearing for a shipped consumer, and it forces callers to guess a size in units they do not control.

Where it bites

Interface::call receives no caller identity and no connection, so the served side has nothing to open a stream back on. Inbound bytes ride a stream opened alongside the call and get flow control and chunking for free; the reply gets MAX_FRAME_LEN (16 MiB, message/codec.rs) and a protocol error if it does not fit:

frame of {n} bytes exceeds the {MAX_FRAME_LEN}-byte cap

The concrete case: the TinyMemory module serves ExportPage, which returns records. The caller asks for a page as a count of records; whether that page fits is a function of bytes it cannot see. In opencompany the operator-facing knob is memory migrate --page-size, default 500, no upper bound — so a company whose chunks came from document ingest can request a page that cannot be framed, and the migration stops on a byte count that names no knob.

We worked around it caller-side (halve the page on a cap refusal, down to one record, then report that a single record cannot cross). That is sound, but it is guessing at bytes through a count, and every consumer that returns a variable-size result will end up writing the same loop.

What would remove it

A reply-stream seam: some way for a served object to answer with a stream rather than a frame — either by handing Interface::call the caller's connection, or by a call_streaming counterpart whose reply is a StreamRef the caller reads.

Then a page is bounded by flow control rather than by the caller's guess, and MAX_FRAME_LEN goes back to being what its comment says it is — a bound on control messages, with anything larger going through crate::stream.

Not urgent, and why it is worth filing anyway

Nothing is broken today: the workaround holds, and 16 MiB is far above a normal page. It is filed because the constraint is invisible from the outside — it looks like a caller bug until you read codec.rs — and because the next module that returns bulk data will hit it and write the same loop. openhuman's own AGENTS.md already records it as a known gap ("A reply-stream seam upstream would remove that half"); this is that note, in the repo that owns the seam.

Related

  • tinybus/crates/tinybus/src/message/codec.rsMAX_FRAME_LEN, and the comment saying oversized payloads belong on crate::stream
  • opencompany#1550 — the module memory driver and its page-halving workaround
  • openhuman AGENTS.md, "Loadable native modules" — "Payloads in and out are not symmetric"

Activity

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