Skip to content

Park pulls awaiting registration instead of holding a server thread - #1026

Open
jndevanshu wants to merge 4 commits into
mainfrom
fix/issue-888-parked-pull-waiters
Open

jndevanshu wants to merge 4 commits into
mainfrom
fix/issue-888-parked-pull-waiters

Conversation

@jndevanshu

@jndevanshu jndevanshu commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

When a producer receives a PullStream for a read it has not registered yet, it waits up to kPullRegistrationGrace (5 s) for NotifyForRead. That wait ran on the control server's handler thread, and the gRPC handler ignored the caller's deadline and cancellation. A burst of early, expired or bogus pulls from one consumer therefore held one server thread each for the full grace. That starved pulls from every other consumer, and they are the ones the producer could have answered right away.

A producer that receives a PullStream for a read it has not registered yet
waits up to kPullRegistrationGrace (5 s) for NotifyForRead. That wait ran on
the control server's handler thread, and the gRPC handler ignored the
caller's deadline and cancellation, so a burst of early, expired or bogus
pulls from one consumer tied up one thread each for the full grace and
starved pulls from every other consumer (issue #888).

The gRPC PullStream is now a callback-API reactor. A pull whose read is not
registered is parked as an entry with no thread behind it, and is answered:
  - by NotifyForRead as soon as the read is registered;
  - with a rejection when the grace or the consumer's deadline passes,
    whichever is first, by a single expiry thread;
  - with a rejection as soon as the consumer cancels the call;
  - with "stopping" on shutdown, before the server is stopped, since a
    callback server's shutdown waits for every outstanding call.

Parked pulls are capped at kMaxPullWaitersPerPeer (64) per peer transport
address and kMaxPullWaiters (4096) in total. Over a cap a pull is rejected
at once, so one consumer cannot use up the room every other consumer needs.

ControlPlaneHandler gains OnPullStreamAsync/CancelPullStream, whose defaults
call OnPullStream inline. The TCP backend, which is being retired, keeps
its blocking path through a wrapper over the same parking logic.
GrpcPuller used GrpcControlPlaneBackend::SendPullRequestAsync, which is not
on main. It now drives the generated stub's callback API directly, with a
ClientContext per call, so tests can also cancel pulls explicitly. It no
longer builds a StatusOr from an OK status, which aborted.

The producer gives up on a parked pull at the consumer's deadline, so its
rejection and the consumer's own DEADLINE_EXCEEDED race. The deadline test
accepts either, and still requires the pull to be released well before the
registration grace.
The pull rejections were only tested over TCP, where the backend refuses
an oversized pull from its header before the handler sees it, so the
handler's own block-count check was never reached. Validation of a pull
that was parked and then resolved by NotifyForRead was not tested at all.

New gRPC tests cover the oversized pull (rejected without parking), the
unregistered, duplicate and empty block lists against a registered read
and against a parked one on registration, a registration that has
already expired (before and after the pull), and a duplicate pull.
Main's fault-injection hooks (62e98ea) were placed in the synchronous
gRPC PullStream and the blocking registration wait, which this branch
replaced. They keep their meaning in the new code:
  - grpc_control_plane.pull.reply is applied in PullStreamReactor::Respond,
    replacing a successful handler answer with the injected error, on every
    path that answers a pull.
  - kv_cache_manager.pull.register_wait delays BeginPullStream after the
    block-count check, ahead of the registration lookup.
The pull.accepted and pull.spawn hooks merged cleanly into LaunchPull,
inside its try, so an injected throw still becomes a rejection.
@jndevanshu jndevanshu self-assigned this Sep 24, 2026
@jndevanshu
jndevanshu marked this pull request as ready for review September 24, 2026 00:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant