grpc-eio: fix concurrent request body for grpcio/grpc-core interop
Prior to this commit, Grpc_eio.Client.call called Promise.await on the
HTTP/2 response before returning write_body to the handler. This caused
a deadlock when connecting to grpcio (Python), grpc-core (C++), or any
other gRPC server that withholds response HEADERS until it has received
the complete client request stream.
Root cause: get_response_and_bodies blocked at Promise.await response
(client.ml:42 pre-fix), preventing the handler from ever calling
write_body, while the server waited for END_STREAM before dispatching
its handler — mutual deadlock.
Fix: change Rpc.handler from
H2.Body.Writer.t -> H2.Body.Reader.t -> 'a
to
H2.Body.Writer.t -> H2.Body.Reader.t Eio.Promise.t -> 'a
The call function now invokes the handler immediately with write_body
(before response headers arrive) and passes read_body_p, a promise that
resolves once the server sends its HEADERS frame. Bidirectional streaming
is restructured with two concurrent Eio.Fiber.both fibers:
- Send side: starts immediately, writes the request body, then closes
- Recv side: awaits read_body_p, then drives grpc_recv_streaming
This is consistent with how flush_headers_immediately:true (already
present) ensures the HEADERS frame is sent immediately, but the prior
code still blocked on the response before writing any DATA frames.
Verified against the gRPC over HTTP/2 protocol specification
(https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md) and
RFC 9113 §5.1: a conforming server may defer response HEADERS until
END_STREAM is received; the client must not assume otherwise.
A new integration test (lib/grpc-eio/test/test_concurrent_handler.ml)
exercises this scenario in-process: a mock server withholds HEADERS
until the full request is received, and verifies the call completes
without deadlock.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2cbfa5
-
Feb 21 07:25 +00:00