Programming · free software · the open webSubscribe via RSS

Promise pipelining removes a wait, not a dependency

Promise pipelining lets a client send a call against a result that has not arrived yet. It can collapse sequential network waits into one round trip, while preserving the dependency and its failure modes.

The directory and file are remote objects

Imagine a client holding a remote directory capability. It calls open("notes.txt"), which will resolve to a file capability, and then wants to call read(0, 4096) on that file. The second operation depends on the first: until the server resolves the name, the client cannot know whether the file exists or which object it denotes. With ordinary request-then-wait behavior, the client waits for open to return before sending read. That dependency creates two sequential network waits, even though the client already knows the next operation it wants to request.

Promise pipelining keeps the unresolved result usable as the target of another call to the same server. The RPC model lets a promise expose methods corresponding to its eventual result for the purpose of making further server calls; those calls can be sent without waiting for the first result. This is a protocol capability, not simply a local promise completing early. [1]

One flight can carry a dependent request

The conceptual trace describes three steps: request an open, form a read request through the unresolved file result, then await the read result. The assignment to file represents a future remote capability, not a file object already returned to the client. Calling read through it constructs a request whose target is tied to the outcome of open. Awaiting the final result is where the client needs the returned data. This is illustrative pseudocode, not a claim about any particular language binding’s method names or syntax.

On the wire, the client can send both requests before receiving the open response. The server processes the open and then interprets the dependent read against the resulting object. If the file is found, the read can return its requested range. If opening fails, there is no valid file target for the read, so the dependent operation cannot produce successful file contents; the failure must propagate through the chain. The optimization overlaps waiting and request delivery. It does not make the server’s result known in advance. [1]

# Conceptual RPC sequence; not a language binding API.
file = directory.open("notes.txt")   # unresolved remote capability
contents = file.read(0, 4096)         # can be sent before open resolves
await contents
Illustrative protocol or diagnostic sketch · Download example

Count round trips, not calls

Assume one network round trip takes 80 milliseconds and the server work is small compared with that delay. In the sequential design, open consumes one round trip and read another: roughly 160 milliseconds of network waiting before the data arrives. Under pipelining, both requests travel in the same outbound period and the response arrives after the server has processed their dependency, for roughly one round trip, or 80 milliseconds in this simplified model. There are still two remote operations and their server-side order still matters; only the client’s wait between them disappears.

The distinction matters when describing performance. Pipelining does not eliminate propagation delay, server processing, queueing, transfer time, or the possibility of a disconnect. It removes the extra client-visible round trip caused by waiting for an intermediate result before issuing a request that is already predictable. The RPC documentation describes chaining calls in this way and reducing a multi-step interaction to one network round trip. [1]

Keep the interface, account for uncertainty

Without pipelining, designers sometimes combine open and read into a special readByPath method to avoid the intermediate wait. That can be a sound operation if the domain calls for it, but it also moves path interpretation and combined behavior into the service API. Pipelining lets an interface keep distinct directory and file objects while a client composes calls across them. The RPC design uses promises precisely because remote calls have latency and unreliability; making the API asynchronous preserves those facts instead of hiding them. [1]

The tradeoff is that a client must reason about a dependency it has not observed resolve yet. It can queue the follow-on request, but it cannot safely use the returned file data or make a local decision based on the opened object until the relevant result is available. Errors and disconnections still need handling, and a speculative pipeline may have sent work that becomes unnecessary if an earlier operation fails. Pipelining saves a wait when the next request is known; it does not remove the causal link between the requests. [1]

References

  1. Cap’n Proto RPC protocol

Suggest a correction · All posts