In an RPC system, why does a caller-supplied list that the remote procedure fills stay empty on the caller's side?
answer
- no shared address space
- arguments travel as copies
- only declared results come back
- output parameter or remote reference
basics
~20 sArguments 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.
solid answer
~50 sA local call can pass a reference, so caller and callee touch the same list in one address space. An RPC stub can only send bytes: it marshals the list's current contents, and the server's skeleton builds a new list from them. The procedure appends to that copy, and when it returns only what the interface declares as a result is marshalled back. RFC 5531 states the rule for ONC RPC: the server has no access to the client's address space, so hidden arguments cannot be passed as global variables or returned as side effects. The fix belongs in the interface - return the items, or declare an output parameter the stub copies back - or, in systems that support remote references, pass a reference whose every access is itself a remote call. This is the memory-access difference Waldo et al. (1994) name between local and remote calls.
go deeper
Recall that arguments travel as copies: the server changes its own decoded copy, and only declared results come back to the caller.
Contrast by value, copy in and copy out, and remote references, and say what the caller observes after the call in each.
Spot interfaces that rely on argument mutation, shared globals or object identity before they are made remote, and redesign them to return results explicitly.
Weigh remote references against copied values for shared state across services, considering latency, failure and object lifetime across an organisation's interfaces.
## The local version works by sharing In a single process, passing a list to a function usually passes a **reference**: the caller and the callee both point at the same object in the same **address space**. When the callee appends items, the caller sees them, because there is only one list. Much ordinary code relies on that: "fill this buffer", "add your results to this collection", "set this field on the object I gave you". ## What the stub actually does to the argument An RPC stub cannot send a reference to the caller's memory - the server runs in another process with its own memory. So the call proceeds like this: 1. At call time the **client stub marshals** the list's current contents into the request message. 2. The **server skeleton unmarshals** those bytes into a brand-new list in the server's memory. 3. The procedure appends to **that** list - the copy. 4. When the procedure returns, the skeleton marshals **only what the interface declares as output** - the return value and any output parameters. 5. The client stub unmarshals that reply. The caller's original list was never part of it, so it is unchanged. RFC 5531 names the rule directly among the ways remote calls differ from local ones: "since the server does not have access to the client's address space, hidden arguments cannot be passed as global variables or returned as side effects". Waldo et al.'s *A Note on Distributed Computing* (1994) lists the same thing as one of its four differences between local and remote computing: a different **model of memory access**. ## Three ways an argument can cross | Passing style | What crosses the network | What the caller sees afterwards | |---|---|---| | By value (copy in) | The argument's contents, once | Nothing the server changed | | Copy in, copy out | Contents out, the final value back in the reply | The final value, written back after the reply arrives | | Remote reference | An identifier for an object that stays where it lives | Every change - but each access is a remote call | **Copy in, copy out** (also called copy-restore) is what an output or in-out parameter gives you: the stub overwrites the caller's variable with the value from the reply. It is not the same as sharing - the caller sees only the final state, not intermediate changes, and if the same object is passed as two parameters, the two copies may be restored independently. A **remote reference** keeps the object on one side and hands the other side a handle. State really is shared, but every method call through the handle pays a round trip and can fail, and the system must decide when the remote object may be released. ## Consequences beyond lists - **Global variables and side effects** set by the procedure stay on the server. - **Identity** does not survive: the server's copy is a different object, so identity comparisons with the caller's objects are meaningless. - **Aliasing** may not survive either: whether two arguments that refer to the same object arrive as one object or two depends on the marshalling format and implementation. - **Cost** grows with what is reachable: marshalling a large object graph to change one field sends the whole graph. - Why a raw in-memory pointer cannot be sent at all is a general serialization question; the RPC consequence is the copy semantics above. ## Designing the interface instead - Return what the procedure produces: `items = findItems(query)` rather than `findItems(query, outList)`. - Where an interface definition supports it, declare output parameters explicitly so both stubs agree on what comes back. - Keep side effects on the server visible as explicit operations rather than mutations of arguments. - Use remote references only where shared, long-lived state is the point, and budget for their latency and failure. ## Common confusions - "The server writes into my memory, just slowly" - it never sees the caller's memory at all. - "The network lost the changes" - nothing was lost; nothing was ever sent back. - "A remote reference is as cheap as a local one" - each access is a remote call.
- If the interface adds an output parameter, does the caller see changes as they happen?No. An output or in-out parameter is copy-restore: the stub writes the final value into the caller's variable after the reply arrives. The caller never sees intermediate states, and code that relied on two parameters aliasing the same object still behaves differently from the local version.
- How does passing a remote reference differ from passing a copy?With a remote reference the object stays on one side and the other side receives a handle. Changes are shared because there is only one object, but every method call through the handle is itself a remote call, with its latency and its possibility of failure, and the system must decide when the object can be released.
Posting a photocopy of a form to an office: the clerk can fill in the copy, but your original stays blank unless they post something back to you.
saying these in an interview costs you the question
- The server writes into the caller's memory, only more slowly than a local call.
- The list stays empty because the network dropped the server's changes.
- Global variables set by a remote procedure are visible to the caller afterwards.
- An output parameter means caller and server share the same object during the call.
- Passing a remote reference makes every access as cheap as a local one.