Skip to content

Instantly share code, notes, and snippets.

@vietj
Created July 20, 2026 19:34
Show Gist options
  • Select an option

  • Save vietj/75d37c1033ac584b45162ce91b8e5c3f to your computer and use it in GitHub Desktop.

Select an option

Save vietj/75d37c1033ac584b45162ce91b8e5c3f to your computer and use it in GitHub Desktop.
Claude generated RFC for gRPC over Vert.x Event Bus
gRPC / Vert.x Event Bus Vert.x gRPC Project
Internet-Draft Eclipse Vert.x
Intended status: Experimental 20 July 2026
Expires: 21 January 2027
gRPC Transport over the Vert.x Event Bus
draft-vertx-grpc-eventbus-transport-00
Abstract
This document specifies an experimental transport that carries gRPC
calls over the Eclipse Vert.x event bus in place of HTTP/2. When a
gRPC client and server run within the same Vert.x application or
cluster, this transport lets calls be made with the generated stubs
over the event bus that is already present, without standing up an
HTTP server, and lets those calls travel across a clustered event bus
alongside other verticles. Unary calls reuse the existing Vert.x
service-proxy request/reply shape; streaming calls add a small
framing protocol, multiplexed over one long-lived consumer per
endpoint, with message-counted flow control and an explicit liveness
mechanism.
Status of This Memo
This Internet-Draft is submitted in full conformance with the
provisions of BCP 78 and BCP 79.
Internet-Drafts are working documents of the Internet Engineering
Task Force (IETF). Note that other groups may also distribute working
documents as Internet-Drafts. The list of current Internet-Drafts is
at https://datatracker.ietf.org/drafts/current/.
Internet-Drafts are draft documents valid for a maximum of six months
and may be updated, replaced, or obsoleted by other documents at any
time. It is inappropriate to use Internet-Drafts as reference
material or to cite them other than as "work in progress."
This Internet-Draft will expire on 21 January 2027.
Copyright Notice
Copyright (c) 2026 IETF Trust and the persons identified as the
document authors. All rights reserved.
This document is subject to BCP 78 and the IETF Trust's Legal
Provisions Relating to IETF Documents
(https://trustee.ietf.org/license-info) in effect on the date of
publication of this document. Please review these documents
Vert.x gRPC Expires 21 January 2027 [Page 1]
Internet-Draft gRPC over the Vert.x Event Bus July 2026
carefully, as they describe your rights and restrictions with respect
to this document.
Table of Contents
1. Introduction ...................................................2
1.1. Goals .....................................................3
1.2. Relationship to the HTTP/2 Transport ......................3
2. Conventions and Terminology ....................................3
2.1. Requirements Language .....................................4
2.2. Terminology ...............................................4
3. Transport Model ................................................4
4. Unary Calls ....................................................5
5. Streaming Calls ................................................5
5.1. Stream Establishment ......................................5
5.2. Private Addresses and Multiplexing ........................6
5.3. Full-Duplex Frame Exchange ................................6
5.4. Ordering Guarantees .......................................7
5.5. Stream Termination ........................................8
5.6. Behaviour on a Clustered Event Bus ........................8
6. Transport Frames ...............................................8
6.1. Frame Envelope ............................................8
6.2. Frame Variants ............................................9
6.3. Message Encoding ..........................................9
6.4. Frame Schema .............................................10
7. Delivery Header Fields ........................................11
8. Flow Control ..................................................12
9. Liveness ......................................................12
9.1. Active Detection (Delivery Failure) ......................13
9.2. Passive Detection (Idle Timeout) .........................13
9.3. Heartbeats ...............................................13
9.4. Multiplexing and Head-of-Line Blocking ...................13
10. Configuration ................................................14
11. Security Considerations ......................................15
12. IANA Considerations ..........................................15
13. Open Questions and Future Work ...............................16
14. References ...................................................16
14.1. Normative References ....................................17
14.2. Informative References ..................................17
Author's Address ..................................................18
1. Introduction
This document describes a transport that carries gRPC calls over the
Eclipse Vert.x event bus rather than over HTTP/2. The event bus is a
Vert.x gRPC Expires 21 January 2027 [Page 2]
Internet-Draft gRPC over the Vert.x Event Bus July 2026
message bus rather than a byte stream, so gRPC streaming requires a
small protocol on top of it to bridge the gap. Unary calls do not,
and are mapped directly onto the event bus request/reply primitive.
This transport is experimental. The wire format described here is a
proposal under active development, and a given prototype MAY diverge
from it in detail. This document is a design in progress rather than
a finished specification, and its intended status is Experimental.
1.1. Goals
A primary goal is that application code need not change between the
HTTP/2 transport and this transport. The generated stubs and the
GrpcServerRequest, GrpcServerResponse, and GrpcClientRequest types
are shared with the HTTP/2 transport; ideally only the factory
differs.
// server
EventBusGrpcServer.server(vertx).onSuccess(server -> {
server.addService(GreeterGrpcService.of(new GreeterService() {
...
}));
});
// client
EventBusGrpcClient.client(vertx).onSuccess(client -> {
GreeterClient greeter = GreeterGrpcClient.create(client);
greeter.sayHello(
HelloRequest.newBuilder().setName("World").build());
});
1.2. Relationship to the HTTP/2 Transport
This transport mirrors the gRPC wire protocol [GRPC-HTTP2] at the
call level: method names, metadata, leading and trailing metadata,
status, and the unary and streaming call types all retain their gRPC
meaning. It replaces only the framing layer. Flow control is modelled
on the HTTP/2 WINDOW_UPDATE mechanism [RFC9113], applied at the
message level rather than the octet level (Section 8).
2. Conventions and Terminology
Vert.x gRPC Expires 21 January 2027 [Page 3]
Internet-Draft gRPC over the Vert.x Event Bus July 2026
2.1. Requirements Language
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
"OPTIONAL" in this document are to be interpreted as described in BCP
14 [RFC2119] [RFC8174] when, and only when, they appear in all
capitals, as shown here.
2.2. Terminology
Endpoint: a gRPC client or server participating in this transport.
Each endpoint owns exactly one private address.
Service address: the event bus address at which a service is
registered. On a clustered bus it is registered by
every server node that hosts the service.
Private address: a unique event bus address minted by an endpoint at
startup, on which it registers a single long-lived
consumer and through which it multiplexes all of its
streams.
Stream: a single streaming call, identified on a given private
address by a stream_id assigned by the endpoint that consumes
on that address.
Frame: a TransportFrame (Section 6), the unit of exchange after a
streaming call has been established.
Wire format: the encoding of gRPC messages and of the frame envelope;
either PROTOBUF (protobuf binary, the default) or JSON.
3. Transport Model
The transport splits into two shapes, selected by the method's call
type. A unary method uses a plain event bus request/reply (Section
4). A server-streaming, client-streaming, or bidirectional method
uses an establishment handshake followed by full-duplex frame
exchange (Section 5).
The call type is known to both peers in advance and MUST NOT be
negotiated on the wire. The ServiceMethod carries the call type(),
filled in by the generated stub on the client and read by the server,
so each side opens the call in the correct shape without guessing.
Vert.x gRPC Expires 21 January 2027 [Page 4]
Internet-Draft gRPC over the Vert.x Event Bus July 2026
There is no negotiation or marker on the wire indicating call type.
4. Unary Calls
A unary call MUST be a single event bus request() and its reply. The
request carries:
* the method name in the "action" delivery header;
* the wire format in the "grpc-wire-format" delivery header;
* request metadata as delivery headers prefixed with "__header__.";
* the encoded request message as the event bus body.
The reply carries the encoded response message as its body, response
metadata as "__header__." prefixed delivery headers, and trailing
metadata as "__trailer__." prefixed delivery headers.
This is deliberately identical to the shape a Vert.x service proxy
uses, so that a unary call and a service-proxy call remain
interchangeable on the bus. A unary method MUST NOT use the streaming
establishment handshake.
5. Streaming Calls
5.1. Stream Establishment
Because the event bus has no notion of a stream, a streaming call is
opened with an HTTP-upgrade-style request/reply carried in delivery
headers, in the same manner as a unary call, with an empty body on
each side. Triggering establishment on the request headers, rather
than on the first message, allows a call that only sends headers, or
one that wishes to receive before it sends, to open.
The client MUST send the opening request() to the service address.
The request headers MUST carry:
* "action", the method;
* "grpc-wire-format", the wire format;
* "grpc-client-address", the client's private address;
Vert.x gRPC Expires 21 January 2027 [Page 5]
Internet-Draft gRPC over the Vert.x Event Bus July 2026
* "grpc-client-stream-id", the stream_id the client has assigned to
this call for its own inbound direction;
* any request metadata, as "__header__." prefixed headers.
The server looks the method up, sets the call up, registers it in its
stream map, and MUST reply with:
* "grpc-server-address", the server's private address;
* "grpc-server-stream-id", the stream_id the server has assigned for
its own inbound direction;
* "grpc-initial-window", the initial flow-control window (Section 8).
The reply is the "go" signal and nothing more. It MUST be sent before
the handler runs and therefore MUST NOT carry response metadata.
Response leading metadata is instead delivered later as a Headers
frame (Section 6.2) ahead of the first response message, so that it
reflects whatever the handler set.
5.2. Private Addresses and Multiplexing
Every endpoint MUST mint exactly one private address at startup,
register a single long-lived consumer on it, and multiplex all of its
streams through that one consumer. Each frame carries the destination
endpoint's stream_id, and the receiver demultiplexes it back to the
correct call through a map keyed by stream_id.
Each endpoint assigns the stream_ids for its own inbound direction,
so two endpoints sharing a private-address relationship can never
choose colliding ids. The establishment handshake is where the two
peers exchange the ids they have each chosen.
Because the consumer is long-lived and per-endpoint, opening or
closing a stream is a map insertion or removal only. There is no per-
call consumer registration or unregistration, which on a cluster
would otherwise broadcast registration churn to every node.
5.3. Full-Duplex Frame Exchange
Once established, the call is full duplex. Every subsequent unit of
the call MUST be a TransportFrame (Section 6) sent to the peer's
Vert.x gRPC Expires 21 January 2027 [Page 6]
Internet-Draft gRPC over the Vert.x Event Bus July 2026
private address with fire-and-forget send(). The first request
message follows as an ordinary Message frame, identical to every
later message. Both directions number their messages independently.
The exchange for a bidirectional call proceeds as follows.
Client Server
| |
| request: action, grpc-wire-format, grpc-client-address, |
| grpc-client-stream-id |
|----------------------------------------------------------->|
| (type is streaming; assign |
| grpc-server-stream-id; register |
| in the stream map) |
| reply: grpc-server-address, grpc-server-stream-id, |
| grpc-initial-window |
|<-----------------------------------------------------------|
| |
| ===== full duplex from here; TransportFrames ===== |
| |
| WindowUpdate (grant the server its window) |
|----------------------------------------------------------->|
| Message (stream_sequence 1, first request message) |
|----------------------------------------------------------->|
| Headers (response leading metadata) |
|<-----------------------------------------------------------|
| Message (stream_sequence 1) |
|<-----------------------------------------------------------|
| Message (stream_sequence 2) |
|----------------------------------------------------------->|
| HalfClose (client done sending) |
|----------------------------------------------------------->|
| Message (stream_sequence 2) |
|<-----------------------------------------------------------|
| Trailers (status) |
|<-----------------------------------------------------------|
| (both ends drop the stream; the call is done) |
| |
5.4. Ordering Guarantees
The handshake is careful about ordering. A peer MUST NOT begin
sending frames until the receiving consumer is actually registered;
the establishing side therefore waits for that registration to
complete first. In addition, the server's send window MUST start at
Vert.x gRPC Expires 21 January 2027 [Page 7]
Internet-Draft gRPC over the Vert.x Event Bus July 2026
zero, so the server cannot send a response before the client has
registered its consumer and granted a window.
Together these two rules close a race in which, on a local event bus,
the whole round trip could otherwise run synchronously before the
application has attached its response handler.
5.5. Stream Termination
A stream is removed from the map when it ends by Trailers or by
Cancel. A stream is also given up locally when a frame to the peer
cannot be delivered, or when its idle timeout elapses (Section 9), so
that a peer that has left the cluster does not leak the registration.
A frame that arrives for a stream_id no longer present in the map
(one still in flight when the stream was torn down) MUST be dropped.
When an endpoint is closed, it MUST terminate its remaining streams
rather than dropping them silently: each remaining stream is sent a
Cancel frame so the peer is notified, and is then removed from the
map.
5.6. Behaviour on a Clustered Event Bus
On a clustered bus, the service address is registered by every server
node, so the opening request() round-robins and lands on one node.
The private address in that node's reply is unique to it, so every
subsequent frame for the stream pins to that node. Because the
consumer is long-lived and per-endpoint, and because ids are assigned
per inbound direction, no per-call registration is broadcast to the
cluster. A future stream timeout would likewise be a plain map
removal.
6. Transport Frames
6.1. Frame Envelope
Every unit of a streaming call after establishment is a
TransportFrame. The envelope MUST be encoded in the call's wire
format: protobuf binary by default, or JSON when the call runs in
JSON mode. The envelope travels as the event bus body, and the format
is named in the frame's "grpc-wire-format" delivery header so the
Vert.x gRPC Expires 21 January 2027 [Page 8]
Internet-Draft gRPC over the Vert.x Event Bus July 2026
receiver knows how to read it.
A frame consists of a small header, stream_id and stream_sequence,
plus exactly one variant. The stream_sequence is per-stream,
monotonic, and MUST advance on Message frames only.
6.2. Frame Variants
The variant is exactly one of the following.
Message: a message payload, in either direction.
WindowUpdate: a flow-control credit, in either direction (Section 8).
HalfClose: sent by the client to end the request stream.
Headers: sent by the server, response leading metadata, ordered ahead
of the first response message.
Trailers: sent by the server to terminate the call; also carries the
gRPC status, since the response stream MUST end with one.
Cancel: sent by either side for abnormal termination.
Heartbeat: sent by either side as a liveness signal on an otherwise
idle stream (Section 9).
The Headers and Trailers frames are thin: the metadata itself rides
as "__header__." and "__trailer__." prefixed delivery headers, and
these frames only mark where in the stream that metadata belongs.
6.3. Message Encoding
The transport MUST NOT re-encode messages. The gRPC encoder has
already produced the message bytes, so Message.payload carries them
verbatim and the receiver hands them straight back to the decoder;
there is no second codec in the middle.
The envelope, however, follows the call's wire format. In protobuf
mode it is binary. In JSON mode the frame is a JSON object, so its
shape is readable on the bus (useful with an event bus interceptor,
for example to observe frames or drop one to test how a call reacts).
Within a JSON frame the payload remains the opaque message bytes,
carried as base64 rather than re-encoded.
Vert.x gRPC Expires 21 January 2027 [Page 9]
Internet-Draft gRPC over the Vert.x Event Bus July 2026
6.4. Frame Schema
The normative schema is as follows.
syntax = "proto3";
package io.vertx.grpc.eventbus.transport.v1alpha;
option java_package =
"io.vertx.grpc.eventbus.transport.v1alpha";
option java_multiple_files = true;
message TransportFrame {
// Destination endpoint's id for this call; demuxes on the
// shared address.
uint64 stream_id = 1;
// Per-stream, monotonic; advances on Message frames only.
uint64 stream_sequence = 2;
oneof frame {
Message message = 3; // payload, either direction
WindowUpdate window_update = 4; // flow-control credit
HalfClose half_close = 5; // client->server, req. end
Trailers trailers = 6; // server->client, ends call
Cancel cancel = 7; // either dir., abnormal end
Headers headers = 8; // server->client, resp. meta
Heartbeat heartbeat = 9; // either dir., liveness
}
}
// Server to client, response initial metadata, ordered ahead of
// the first response message. The metadata itself rides as
// "__header__." prefixed delivery headers.
message Headers {
}
// A message payload, either direction. The serialized message in
// the call's wire format, carried verbatim.
message Message {
bytes payload = 1;
}
// Flow-control credit, either direction: grants the peer "delta"
// more messages to send. Counted in messages, after HTTP/2's
Vert.x gRPC Expires 21 January 2027 [Page 10]
Internet-Draft gRPC over the Vert.x Event Bus July 2026
// WINDOW_UPDATE.
message WindowUpdate {
uint32 delta = 1;
}
// Client to server, end of the request stream (half close).
message HalfClose {
}
// Server to client, terminates the call. Trailing metadata rides
// as "__trailer__." prefixed delivery headers.
message Trailers {
uint32 status = 1; // gRPC status code
string status_message = 2;
}
// Either direction, abnormal termination.
message Cancel {
uint32 status = 1; // typically CANCELLED or DEADLINE_EXCEEDED
string reason = 2;
}
// Either direction, a liveness signal keeping an otherwise idle
// stream alive so the peer's idle timeout does not fire. Carries
// no payload.
message Heartbeat {
}
7. Delivery Header Fields
gRPC semantics that the encoder and decoder do not own ride as event
bus delivery headers, mapped the same way for unary and streaming
calls. The frame schema of Section 6 carries only what streaming
genuinely adds on top. The delivery header fields are:
action: the method name.
grpc-wire-format: the wire format of the body (PROTOBUF or JSON).
grpc-client-address: the client's private address (establishment).
grpc-client-stream-id: the client's inbound stream_id
(establishment).
grpc-server-address: the server's private address (establishment).
Vert.x gRPC Expires 21 January 2027 [Page 11]
Internet-Draft gRPC over the Vert.x Event Bus July 2026
grpc-server-stream-id: the server's inbound stream_id
(establishment).
grpc-initial-window: the initial flow-control window (establishment).
__header__.<name>: a request or response metadata entry.
__trailer__.<name>: a trailing metadata entry.
8. Flow Control
The event bus send() primitive is fire-and-forget: it returns
immediately whether or not the peer is keeping up, so there is no
built-in backpressure. This transport therefore carries a window of
its own, counted in messages rather than octets, modelled on HTTP/2's
WINDOW_UPDATE [RFC9113], Section 6.9.
Each side starts with the window its peer granted during
establishment and MUST spend one credit per Message frame it sends,
stopping at zero. As the receiving application consumes messages, the
receiver sends a WindowUpdate frame carrying a delta, and the sender
adds that delta to its window.
This is expressed through the Vert.x WriteStream contract. A zero
window makes writeQueueFull() return true, and an arriving
WindowUpdate fires the drainHandler, so a generated Pipe or any well-
behaved producer behaves as it does over HTTP/2.
A producer that ignores writeQueueFull(), such as a tight loop of
response.write(...), MUST nonetheless remain safe. The stream buffers
the excess messages once the window is spent and holds back the
terminating frame until they drain, so that nothing is lost or
reordered.
On a local event bus this provides real backpressure, since a paused
consumer would otherwise buffer and eventually drop. On a clustered
event bus the window is the only mechanism pushing back at all,
because a send() is already on the wire the moment it is issued.
9. Liveness
HTTP/2 relies on TCP to notice a dead peer. The event bus has no such
connection, so this transport detects an unreachable peer in two ways
Vert.x gRPC Expires 21 January 2027 [Page 12]
Internet-Draft gRPC over the Vert.x Event Bus July 2026
and, in either case, gives the stream up rather than retrying, since
a retry would race the teardown.
9.1. Active Detection (Delivery Failure)
Every frame is sent to the peer's private address. On a point-to-
point send(), a missing consumer fails the write with NO_HANDLERS.
When a frame cannot be delivered, the sending side MUST give the
stream up: it fails the read side and any pending writes so the
application is notified, and unbinds the stream from its map so
nothing leaks.
9.2. Passive Detection (Idle Timeout)
To cover a stream on which the peer stops sending without a delivery
failure (for instance a node that vanished while this side was only
receiving), each side that consumes a stream arms an idle timer and
resets it on every received frame. If nothing arrives within the idle
timeout, that side MUST give the stream up in the same way.
9.3. Heartbeats
To keep a genuinely idle but healthy stream from tripping the idle
timer, the producing side sends Heartbeat frames while its outbound
half is open. A heartbeat is itself a send(), so it doubles as the
producer's own liveness check: if the consumer is gone, the heartbeat
fails to deliver and the producer gives up.
Heartbeats follow the call direction, so a heartbeat flows only where
messages would. On a server-streaming call the server heartbeats and
the client times out; on a client-streaming call the client
heartbeats and the server times out; a bidirectional call does both;
a unary call uses neither, as it is a plain request/reply. Both
timers are off by default and are enabled per endpoint (Section 10).
The idle timeout SHOULD be a small multiple of the peer's heartbeat
interval, so that an occasional late heartbeat does not fail a live
stream.
9.4. Multiplexing and Head-of-Line Blocking
Because every stream on an endpoint shares one consumer, that
consumer MUST NOT be paused: pausing it would stall every stream
Vert.x gRPC Expires 21 January 2027 [Page 13]
Internet-Draft gRPC over the Vert.x Event Bus July 2026
behind a slow one, which is head-of-line blocking. The per-stream
window is therefore the only backpressure available. A slow reader
pushes back only by withholding WindowUpdate credit on its own
stream, while the shared consumer keeps draining the others.
10. Configuration
Both endpoints take an options object. All options are OPTIONAL and
default to the values below.
EventBusGrpcServer.server(vertx,
new EventBusGrpcServerOptions()
.setSupportedWireFormats(EnumSet.of(WireFormat.PROTOBUF))
.setHeartbeatInterval(30_000)
.setIdleTimeout(90_000));
EventBusGrpcClient.client(vertx,
new EventBusGrpcClientOptions()
.setWireFormat(WireFormat.JSON)
.setHeartbeatInterval(30_000)
.setIdleTimeout(90_000));
maxConcurrentStreams (server, default 1000): caps the number of
streams one server multiplexes at once. An open
beyond the cap MUST be rejected with
RESOURCE_EXHAUSTED, so that a flood of opens
cannot grow the demux map without bound.
supportedWireFormats (server, default [PROTOBUF, JSON]): limits which
wire formats the server accepts. A request in an
unsupported format MUST be rejected with
UNIMPLEMENTED.
wireFormat (client, default PROTOBUF): the default wire format for
the client's requests. A call MAY override it with
request.format(...), and the generated stubs'
create(client, WireFormat.JSON) does exactly that. The
override wins.
heartbeatInterval (client and server, milliseconds, default 0,
disabled): how often the endpoint sends Heartbeat
frames on streams where it produces messages
(Section 9).
idleTimeout (client and server, milliseconds, default 0, disabled):
Vert.x gRPC Expires 21 January 2027 [Page 14]
Internet-Draft gRPC over the Vert.x Event Bus July 2026
how long the endpoint waits without receiving any frame
on a stream where it consumes messages before giving the
stream up. It SHOULD be a small multiple of the peer's
heartbeatInterval.
11. Security Considerations
This transport inherits the security properties of the underlying
Vert.x event bus and adds no message-level authentication, integrity,
or confidentiality of its own. Deployments that carry sensitive data,
in particular clustered deployments whose bus traverses a network,
SHOULD secure the event bus itself (for example with TLS and cluster
authentication).
An endpoint's private address functions as a capability. Any party
able to send to that address, and able to guess or observe a live
stream_id on it, can inject Message, Cancel, or other frames into a
call, or forge a WindowUpdate. A frame for an unknown stream_id is
dropped, which limits blind injection, but this is not a substitute
for bus-level access control. Private addresses SHOULD be treated as
secrets and MUST NOT be exposed to untrusted parties.
In JSON mode the frame envelope is human-readable on the bus, which
is convenient for interceptor-based debugging but widens the exposure
of call structure to anyone with bus visibility. Operators SHOULD
weigh this when choosing a wire format for sensitive traffic.
Several resource-exhaustion vectors are bounded by design:
maxConcurrentStreams caps the demux map and rejects excess opens with
RESOURCE_EXHAUSTED; the per-stream flow-control window bounds
receiver buffering. Note, however, that a local producer which
ignores writeQueueFull() buffers excess messages on its own side
(Section 8); an application that does this with untrusted input can
exhaust its own memory. Misconfigured liveness timers (an idleTimeout
that is not a sufficient multiple of the peer's heartbeatInterval)
can cause healthy streams to be torn down, a self-inflicted denial of
service.
12. IANA Considerations
This document has no IANA actions. The delivery header field names
defined in Section 7 are namespaced within the Vert.x event bus and
are not registered in any IANA registry. Were this transport to be
standardized, coordination of those field names would be revisited at
Vert.x gRPC Expires 21 January 2027 [Page 15]
Internet-Draft gRPC over the Vert.x Event Bus July 2026
that time.
13. Open Questions and Future Work
This is an experiment, and several pieces are deliberately out of
scope. The directions they would take are noted here.
Session identity and resumption. Streams are already multiplexed over
one long-lived private consumer per
endpoint and told apart by
stream_id, so there is no per-call
registration churn. The private
address doubles as the endpoint's
session token; it is minted per
process and dies with it, so a
restarted endpoint has a new address
and stale frames hit a dead one. A
distinct, stable session_id would be
needed only to tie streams across a
reconnect, or for session-level flow
control, and is deferred until then.
Session-level flow control. HTTP/2 has a second, connection-wide
window above the per-stream one, capping
total buffering across a connection's
streams. The analog would be a session-
wide window across all streams sharing an
endpoint's private address, paired with
the session_id above.
Resumption. The stream_sequence on each Message leaves room for a
dropped client to reconnect and ask the server to replay
everything after the last sequence it saw, in the spirit
of the Last-Event-ID resumption of [MCP]. The reconnect
handshake and a bounded replay buffer are the missing
pieces. Surviving a node failure, rather than just a
dropped connection, would additionally require the
session state in a shared or durable store, which could
be an SPI with a local default and, for example, a Redis
backend.
14. References
Vert.x gRPC Expires 21 January 2027 [Page 16]
Internet-Draft gRPC over the Vert.x Event Bus July 2026
14.1. Normative References
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
Requirement Levels", BCP 14, RFC 2119,
DOI 10.17487/RFC2119, March 1997,
<https://www.rfc-editor.org/info/rfc2119>.
[RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
2119 Key Words", BCP 14, RFC 8174,
DOI 10.17487/RFC8174, May 2017,
<https://www.rfc-editor.org/info/rfc8174>.
14.2. Informative References
[RFC7540] Belshe, M., Peon, R., and M. Thomson, Ed., "Hypertext
Transfer Protocol Version 2 (HTTP/2)", RFC 7540,
DOI 10.17487/RFC7540, May 2015,
<https://www.rfc-editor.org/info/rfc7540>. Obsoleted
by RFC 9113; sections 5.2 and 6.9 are the model for the
message-counted window used here.
[RFC9113] Thomson, M., Ed. and C. Benfield, Ed., "HTTP/2",
RFC 9113, DOI 10.17487/RFC9113, June 2022,
<https://www.rfc-editor.org/info/rfc9113>.
[GRPC-HTTP2]
"gRPC over HTTP2", the gRPC wire protocol this design
mirrors at the call level,
<https://github.com/grpc/grpc/blob/master/doc/
PROTOCOL-HTTP2.md>.
[MCP] "Model Context Protocol, Streamable HTTP transport", the
source of the session and Last-Event-ID resumption ideas
in Section 13, <https://modelcontextprotocol.io/
specification>.
[REACTIVE-STREAMS]
"Reactive Streams", the demand-signalling model
(request(n)) that Vert.x's ReadStream.fetch and this
window scheme both follow,
<https://www.reactive-streams.org/>.
[RSOCKET] "RSocket Protocol", a message-oriented protocol whose
REQUEST_N frame is close prior art for message-counted
flow control, <https://rsocket.io/about/protocol>.
Vert.x gRPC Expires 21 January 2027 [Page 17]
Internet-Draft gRPC over the Vert.x Event Bus July 2026
Author's Address
Eclipse Vert.x gRPC Contributors (editor)
Eclipse Foundation
Email: [email protected]
Vert.x gRPC Expires 21 January 2027 [Page 18]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment