Skip to content

Fix/4085 ws json rpc batch - #4135

Open
Parikalp-Bhardwaj wants to merge 3 commits into
alloy-rs:mainfrom
Parikalp-Bhardwaj:fix/4085-ws-json-rpc-batch
Open

Fix/4085 ws json rpc batch#4135
Parikalp-Bhardwaj wants to merge 3 commits into
alloy-rs:mainfrom
Parikalp-Bhardwaj:fix/4085-ws-json-rpc-batch

Conversation

@Parikalp-Bhardwaj

Copy link
Copy Markdown

Summary

Related #4085.

RpcClient::new_batch() correctly creates a RequestPacket::Batch, but the pubsub frontend was splitting that batch into individual requests before passing them to the transport.

As a result, a batch containing multiple requests was sent as multiple WebSocket messages instead of a single JSON-RPC batch array.

This PR preserves the batch through the pubsub layer so WebSocket and IPC transports send it as one wire-level packet, while still tracking each request independently by JSON-RPC ID.

Problem

Previously, PubSubFrontend::send_packet() handled a batch like this:

RequestPacket::Batch(reqs) =>
    try_join_all(reqs.into_iter().map(|req| self.send(req)))
        .map_ok(ResponsePacket::Batch)
        .boxed(),

Calling self.send() for every request created a separate PubSubInstruction::Request, so the original batch was lost before reaching the backend.

For a batch containing two requests, the flow was effectively:

RequestPacket::Batch([A, B])
        ↓
send(A) + send(B)
        ↓
two backend messages
        ↓
two WebSocket frames

With this change:

RequestPacket::Batch([A, B])
        ↓
one PubSubInstruction::Batch
        ↓
one RequestPacket::Batch
        ↓
one serialized JSON-RPC array
        ↓
one WebSocket frame

Changes

Preserve batches in the pubsub frontend

PubSubFrontend::send_packet() now creates an InFlight entry and response receiver for each request, but sends all of the requests to the service using a single batch instruction.

Each request remains independently tracked, so response handling continues to use JSON-RPC IDs rather than array position.

Dispatch batches as one packet

The pubsub service now handles the batch as a single RequestPacket::Batch.

The existing RequestPacket serialization is reused, so the batch is serialized as one JSON array and dispatched to the backend once.

Each InFlight request is still registered individually with the RequestManager.

Handle batch responses

WebSocket and IPC previously expected each incoming message to deserialize into a single PubSubItem.

A new PubSubItems helper supports both:

  • a single response or subscription notification
  • an array of responses

Batch response elements are forwarded individually to the existing frontend handling, where they are matched to pending requests by JSON-RPC ID.

This also means responses can arrive in a different order from the requests without affecting the corresponding futures.

IPC behavior

Because WebSocket and IPC share the pubsub frontend, IPC now preserves JSON-RPC batches on the wire as well.

Single-request behavior remains unchanged.

Reconnect behavior

Reconnect behavior is intentionally unchanged.

Pending requests are still reissued individually after reconnect. Preserving the original batch grouping across reconnects would introduce separate retry and idempotency concerns and is outside the scope of this fix.

This PR does not provide transactional batch semantics.

Empty batches

Existing empty-batch behavior is preserved.

RequestPacket::Batch(vec![])

returns an empty ResponsePacket::Batch without sending anything to the backend.

Tests

Added WebSocket regression tests covering:

  • multiple calls from new_batch() are sent as one WebSocket text frame containing a JSON array
  • batch responses returned in reverse order are routed to the correct futures by request ID
  • an empty batch does not send a WebSocket frame

Added JSON-RPC tests covering PubSubItems deserialization for:

  • a single response object
  • a batch response array
  • a subscription notification

The original #4085 behavior is also covered by the regression test: restoring the previous split behavior causes the WebSocket batch test to fail because the server receives individual JSON objects instead of one array.

Files changed

  • crates/json-rpc/src/notification.rs — add PubSubItems support for single items and arrays
  • crates/json-rpc/src/lib.rs — export PubSubItems
  • crates/pubsub/src/ix.rs — add the internal batch instruction
  • crates/pubsub/src/frontend.rs — preserve batch grouping and individual response receivers
  • crates/pubsub/src/service.rs — serialize and dispatch batches once while tracking requests individually
  • crates/transport-ws/src/lib.rs — handle incoming batch responses
  • crates/transport-ws/Cargo.toml — add the workspace alloy-json-rpc dependency
  • crates/transport-ipc/src/lib.rs — handle incoming PubSubItems
  • crates/rpc-client/tests/it/ws.rs — add WebSocket batch regression tests
  • crates/rpc-client/Cargo.toml — add test dependencies required by the local WebSocket server

@Parikalp-Bhardwaj

Copy link
Copy Markdown
Author

Hi @mattsse could you please review it?

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

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant