In an RPC system, which call shapes exist beyond the blocking request-reply call, and what does each change for the caller?
answer
- who waits, and for what
- future or callback
- no reply at all
- many messages in one call
basics
~20 sBesides blocking until the reply arrives, a caller can take a future or callback (asynchronous), send one-way or batched calls that get no reply, or stream many messages in one call. Each trades the local-call illusion for concurrency, throughput or incremental data.
solid answer
~50 sThe classic model blocks: one thread of control "logically winds through two processes", in RFC 5531's words - though the RFC adds that the model is only an example and calls may be asynchronous so the client can do useful work while waiting. The shapes: **synchronous** (simplest, but a waiting thread per call and latencies add up serially); **asynchronous** (a future, promise or callback - calls overlap, but completion and failure are handled out of line); **one-way or batched** (no reply per call - ONC RPC batching sends calls the server never answers and ends with an ordinary call to flush the pipeline and get an acknowledgement); and **streaming** (many messages within one call from the client, the server or both, for large or open-ended data processed as it arrives). The further a shape moves from blocking, the less the call resembles a local procedure.
go deeper
Recall the shapes - blocking, asynchronous, one-way, batched, streaming - and what the caller waits for in each.
Explain what each shape changes for the caller: overlapping latency, out-of-line errors, lost outcomes for one-way calls, flushing a batch, incremental stream processing.
Choose a shape for a real workload and defend it against its failure behaviour: what the caller knows when a one-way call, a batch or a stream breaks.
Decide which call shapes an organisation's interfaces should offer by default, balancing simplicity of synchronous calls against throughput and streaming needs.
## The baseline: a blocking call The original RPC model copies a local procedure call. RFC 5531 describes it: the caller sends a call message "and waits (blocks) for a reply message"; one thread of control "logically winds through two processes", and only one of the two is active at a time. This **synchronous** shape is easy to read and reason about, which is why generated stubs default to it. Its costs: - a thread or task sits idle for the whole round trip; - several independent calls made one after another add their latencies; - the caller cannot do anything else while the remote work runs. RFC 5531 immediately adds that this model "is only given as an example" and that the protocol "makes no restrictions on the concurrency model implemented". The other shapes come from relaxing who waits and how many messages flow. ## Asynchronous calls An **asynchronous** stub returns at once with a **future** (or promise) that will hold the result, or takes a **callback** to run when the reply arrives. RFC 5531 names the motive: the client "may do useful work while waiting for the reply". What changes for the caller: - several calls can be in flight together, so independent latencies overlap; - results and errors arrive out of line, so error handling moves into the completion path; - the server's work is unchanged - asynchrony is about the caller's waiting, and how the server schedules calls is its own concurrency model. ## One-way and batched calls A **one-way** call sends a request that gets no reply. The caller learns nothing - not the result, not an error, not even that the call ran - so it fits hints, notifications and telemetry, not work whose outcome matters. JSON-RPC 2.0's notifications are this shape: a request without an `id` to which the server "MUST NOT reply". **Batching** applies the same idea to a sequence. In RFC 5531's description of ONC RPC batching, the client sends an arbitrarily large sequence of calls, "the client never waits for a reply from the server, and the server does not send replies to batch calls". The sequence is usually terminated by an ordinary call "in order to flush the pipeline and get positive acknowledgement". Batching typically uses a reliable byte-stream transport such as TCP; it buys throughput by giving up a reply per call. ## Streaming calls A **streaming** call carries many messages within one call instead of one request and one reply: - **client streaming** - the caller sends a sequence and gets one reply, for uploads or aggregations; - **server streaming** - one request produces a sequence of results, for large or open-ended output; - **bidirectional streaming** - both sides send sequences independently, for interactive sessions. Streams let each side process data as it arrives and keep a long-lived exchange inside one call. They also raise questions a single reply never did: how a consumer slows a fast producer, how a stream ends, and what a failure halfway through means for the messages already handled. How a particular framework names and implements these shapes is that framework's subject. ## Comparing the shapes | Shape | Replies | Caller blocks? | Fits | Gives up | |---|---|---|---|---| | Synchronous | One per call | Yes | Simple request-response logic | Concurrency, overlapping latency | | Asynchronous | One per call | No - future or callback | Fan-out, independent calls | Straight-line error handling | | One-way | None | No | Hints, telemetry, notifications | Any knowledge of the outcome | | Batched | None per call | No, until the closing call | High-volume pipelined calls | Per-call results and errors | | Streaming | A sequence | Varies | Large, open-ended or interactive data | Simple whole-message semantics | ## Choosing a shape 1. Ask whether the caller must act on the outcome; if it must, one-way and batched calls are out. 2. Ask whether results are bounded and small; if not, a stream beats one huge reply. 3. Ask whether the caller has other work or other calls to make meanwhile; if so, prefer an asynchronous stub. 4. Otherwise the synchronous call remains the clearest choice. Whatever the shape, the call is still remote: asynchrony hides waiting, not failure.
- Does an asynchronous stub make the remote work itself run asynchronously?No. It changes only how the caller waits: the stub returns a future or takes a callback instead of blocking. The server may still run each call to completion on one thread. RFC 5531 treats these separately - an asynchronous client, and a server that may create a task per incoming call.
- What does a one-way call cost the caller?All knowledge of the outcome. With no reply there is no result, no error and no confirmation that the server ran the call at all. That suits hints, telemetry and notifications - JSON-RPC 2.0's notifications get no reply even on error - but not work whose result the caller must act on.
- Why does ONC RPC batching end with an ordinary call?Batched calls get no replies, so the client needs some point at which it knows the sequence has been pushed through. RFC 5531 says a batch is usually terminated by a legitimate remote procedure call to flush the pipeline and get positive acknowledgement.
saying these in an interview costs you the question
- An asynchronous RPC call makes the server execute the procedure faster.
- One-way calls still report errors back to the caller, just later.
- Each batched call eventually gets its own reply from the server.
- Making a call asynchronous removes the need to handle remote failure.
- A streaming call is just one big message reassembled before the handler runs.