skip to content

Stubs, Marshalling and Dispatch

A client stub marshals the arguments, a transport carries them and a server skeleton dispatches the call, all to pass for a local call. Interviewers ask what that location transparency hides.

part ofAPI stylesoverview, primer and where to startread it →
on this pageshow

questions

6

In a remote procedure call system, what do the client stub and the server-side skeleton each do during one call?

level: juniorimportance: must knowfreq 45%

answer

  1. looks like a local function
  2. arguments packed into a message
  3. procedure identifier picks the handler
  4. reply matched back by call id

basics

~20 s

The client stub poses as the local procedure: it marshals the arguments into a request and hands it to the transport. The server skeleton unmarshals them, dispatches to the real procedure and marshals the result back.

solid answer

~50 s

An RPC call runs through a pipeline both sides share. The **client stub** has the remote procedure's signature; when called, it marshals the arguments into a request that also names the procedure and carries a call identifier, passes it to the RPC runtime and, in the classic synchronous form, waits for the reply. On the server, the **skeleton** (or a dispatcher plus a per-procedure server stub) reads the procedure identifier, unmarshals the arguments, invokes the real implementation and marshals the result or error into a reply carrying the same identifier. The client stub matches that reply to its call, unmarshals the result and returns it, or raises an error. Both stubs are normally generated from an interface definition, which is what lets the call look local - the stub-based design Birrell and Nelson described in 1984.

code

pseudocode · 18 lines
pseudocode
// client stub for: total = addItems(cartId, items)
function addItems(cartId, items):
    request = marshal(procedure = "addItems", callId = nextCallId(), args = [cartId, items])
    runtime.send(request)
    reply = runtime.awaitReply(request.callId)
    if reply.isError:
        raise RemoteError(reply.error)
    return unmarshal(reply.result)

// server dispatcher
on receive(request):
    handler = registry.lookup(request.procedure)
    if handler is missing:
        runtime.send(errorReply(request.callId, "unknown procedure"))
        return
    args = handler.unmarshalArgs(request.args)
    result = handler.invoke(args)
    runtime.send(reply(request.callId, marshal(result)))

go deeper

for a junior

Walk one call end to end: stub marshals, transport carries, dispatcher picks the handler, skeleton unmarshals and invokes, reply comes back. Naming each piece in order is the answer.

for a middle

Explain what the request must carry - procedure identifier, call identifier, encoded arguments - and why the reply repeats the call identifier when several calls share a connection.

for a senior

Show that you know where the pipeline leaks: latency, partial failure and the missing shared address space are things the stub cannot hide, and they shape interface design.

for a principal

Discuss what an organisation gains and risks by generating stubs that make remote calls look local, and when it should prefer interfaces that make remoteness visible.

## The idea: a remote call dressed as a local one A **remote procedure call (RPC)** lets a program call a procedure that runs in another process, usually on another machine, with the same syntax it would use for a local function. The design that made this practical is usually traced to Birrell and Nelson's 1984 paper *Implementing Remote Procedure Calls*: put a small piece of generated code on each side of the network that translates between "a function call" and "a message", so neither the caller nor the implementation has to touch the network directly. Those pieces are the **stubs**. RFC 5531, the specification of ONC RPC, describes the same model in words that fit every RPC system: "one thread of control logically winds through two processes". The caller sends a **call message** and waits for a **reply message**; the server extracts the parameters, computes the results and sends the reply. ## The pipeline, step by step 1. The application calls `addItems(cartId, items)` on the **client stub**, an object or function with exactly the remote procedure's signature. 2. The stub **marshals** the call: it encodes the procedure identifier, a fresh **call identifier** and the arguments into a request message in the protocol's wire format. 3. The **RPC runtime** hands the bytes to a **transport** (a TCP connection, a datagram, an HTTP request) through a binding that decides how messages travel. 4. On the server, the runtime receives the message and passes it to the **dispatcher**, which reads the procedure identifier and selects the registered handler. 5. The **server stub** or **skeleton** for that procedure **unmarshals** the arguments into local values and invokes the real implementation as an ordinary local call. 6. The skeleton marshals the return value - or an error - into a reply that repeats the call identifier, and the runtime sends it back. 7. The client runtime matches the reply to the waiting call by its identifier; the client stub unmarshals the result and returns it, or raises an error the application can catch. ## Who does what | Component | Side | Job | |---|---|---| | Client stub | Caller | Present the local signature, marshal arguments, unmarshal the result | | RPC runtime | Both | Send and receive messages, match replies to calls | | Binding / transport | Both | Carry the bytes: delimiting, size limits, reliability | | Dispatcher | Callee | Route a request to the handler named by its procedure identifier | | Skeleton / server stub | Callee | Unmarshal arguments, invoke the implementation, marshal the reply | Terminology varies between systems. Some generate one **skeleton** per interface that both dispatches and decodes; others split a generic dispatcher from per-procedure **server stubs**. The division of labour is the same. ## What the messages must carry RFC 5531 lists what the ONC RPC protocol must provide: a **unique specification of the procedure** to be called, a way of **matching responses to requests**, and a way of **authenticating** each side. In ONC RPC that means: - a transaction identifier, `xid`, that the reply repeats - RFC 5531 adds that it serves only to match replies or detect retransmissions, never as a sequence number; - the program, version and procedure numbers (`prog`, `vers`, `proc`) that tell the dispatcher what to run; - a credential and verifier for authentication; - the procedure's parameters, encoded in XDR, the external data representation the protocol uses. Other systems name the procedure with a string or a path instead of numbers, but every one carries some procedure identifier, some correlation, and the encoded arguments. Stubs are normally **generated** from an interface definition so that both sides agree on that encoding; how that definition is managed is a separate subject. ## What the stub cannot hide The stub makes the call look local; it cannot make it behave locally. RFC 5531 itself lists the differences: failures of the server or the network must be handled, the server has no access to the caller's address space, and remote calls run "one or more orders of magnitude slower". Those leaks are why location transparency is a debated goal rather than a solved problem. ## Common confusions - **"Marshalling is only needed across languages."** Even two processes in one language share no memory; arguments always travel as an encoded message. - **"The stub runs the procedure."** The stub only translates; the implementation runs on the server. - **"Replies arrive in call order, so no identifier is needed."** Several calls can be outstanding on one connection, and datagrams can arrive late or twice; the identifier is what pairs them.

  • Why must an RPC request carry a call identifier when the stub already waits for its own reply?
    Because one connection or socket can carry many outstanding calls, and replies may arrive late, out of order or, over datagrams, more than once. The runtime pairs each reply with its call by the identifier. In ONC RPC that is the `xid`; RFC 5531 says the server may use it only as an equality test, to detect retransmissions, never as a sequence number.
  • Is a skeleton the same thing as a dispatcher?
    Not always - it depends on the system's vocabulary. Some generate one skeleton per interface that both routes the call and decodes its arguments; others have a generic dispatcher that routes on the procedure identifier and per-procedure server stubs that decode. Either way the job is to turn a message back into a local call on the implementation.
  • Where do the stubs usually come from?
    They are generated from an interface definition that lists each procedure, its parameters and its result, so the client's encoder and the server's decoder agree by construction. Hand-written stubs are possible, but then nothing but care keeps the two sides in agreement, and a mismatch shows up only at run time as undecodable arguments.

saying these in an interview costs you the question

  • The client stub runs the procedure locally and syncs the result later.
  • Marshalling is only needed when client and server use different languages.
  • Replies always arrive in call order, so a call identifier is unnecessary.
  • The server receives the caller's actual objects, not values decoded from a message.
  • Once a stub is generated, a remote call behaves exactly like a local one.
open as a page

Using Waldo et al.'s 1994 critique of RPC location transparency, what goes wrong when a team designs local objects and decides later which become remote?

level: seniorimportance: must knowfreq 36%

basics

~20 s

Waldo et al. argue local and remote objects differ in latency, memory access, partial failure and concurrency, so an interface designed for local use cannot simply be made remote later; failure and concurrency must be designed in from the start.

open as a page

In an RPC system, why does a caller-supplied list that the remote procedure fills stay empty on the caller's side?

level: juniorimportance: should knowfreq 24%

basics

~20 s

Arguments are marshalled into a message, so the server fills a copy decoded on its own side. Its additions never travel back unless the interface returns them as a result or an output parameter; caller and server share no memory.

open as a page

In an RPC system, which call shapes exist beyond the blocking request-reply call, and what does each change for the caller?

level: middleimportance: should knowfreq 22%

basics

~20 s

Besides 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.

open as a page

When an RPC protocol is bound to a transport, what must the binding supply that the call model itself leaves out?

level: middleimportance: should knowfreq 13%

basics

~20 s

The call model defines messages, not delivery. A binding supplies message boundaries on a byte stream, the transport's size limits, how replies pair with calls and, on datagrams, timeouts, retransmission and duplicate detection; finding the server is separate again.

open as a page

When a request reaches an RPC server, how does the dispatcher choose the procedure, and what can fail before that procedure runs?

level: middleimportance: nice to knowfreq 14%

basics

~20 s

The request names its target - a service or program, often a version, and a procedure - and the dispatcher looks it up. An unknown service, version or procedure, or arguments that will not decode, are reported before any handler runs.

open as a page