skip to content

Why can an open file handle or network connection held by a value not be carried across a serialization boundary?

level: middleimportance: nice to knowfreq 32%

answer

  1. a handle is a token, not the resource
  2. it indexes a per-process table
  3. the live state is in the table entry
  4. same number, different table, wrong resource
  5. write the durable key, re-acquire there

basics

~20 s

A handle is a token naming an entry in a table the operating system keeps for one specific process. It names a live relationship, not data, so writing its number out ships an index into a table the reader does not have.

solid answer

~40 s

A handle is not the resource; it is a small token that indexes a table maintained **for the process that opened it**. Behind the entry sits live state the bytes cannot carry: a current position, buffers, credentials, a connected peer, an exclusive claim. Serializing the token therefore ships a number whose meaning is the table, and the reader has a different table. The failure is not always a clean error either — the same number may be a perfectly valid entry in the reader's table, naming an unrelated resource, which turns a dead reference into a silent read of the wrong thing. What crosses the boundary instead is the **durable key**: the path, the address, the identifier from which the reader can acquire its own handle, if it is entitled to.

go deeper

for a junior

Recall that an open handle names a live resource of one process rather than holding data, so it is not something an encoded value can carry to another process.

for a middle

Explain the mechanism: the token indexes a per-process table, the interesting state lives in the table entry, and the reader has a different table in which that same number may mean something else.

for a senior

Show the silent-failure case and the fix: write the durable key, have the reader acquire its own handle, and let re-acquisition fail visibly at the point of use rather than pretending access transferred.

for a principal

Treat it as a trust boundary question. Anything that would carry the writer's access across the boundary must be re-established under the reader's own identity, and the wire contract should make that impossible to skip.

## What a handle actually is When a program opens a file or a connection, it does not receive the resource. It receives a **token** — commonly a small integer or an opaque reference — that the operating system or runtime uses to look the resource up in a table it maintains **for that process**. The interesting state lives in the table entry, not in the token: - the current position within the file, and any buffered data not yet flushed; - the identity and permissions under which the resource was opened; - for a connection, the peer it is connected to and the state of that conversation; - any exclusive claim the open established, which other holders are being kept out of. None of that is inside the value being encoded. The value holds a number; the meaning of the number is entirely in a table that belongs to one process and disappears when it exits. ## Why writing the number out is worse than useless The obvious failure is that the reader has no such entry and the reference is dead. The worse failure is the quiet one: 1. The writer's table has entry 7 pointing at a report file it opened. 2. The number 7 is encoded into the bytes like any other integer. 3. The reader opens its own resources during startup, and **its** table also has an entry 7 — pointing at something else entirely. 4. The decoded value now holds a valid-looking handle to the wrong resource, and nothing reports an error. A dead reference fails loudly. A valid number naming a different resource fails silently, and it fails at whatever the reader does next — read, write, or close. This is the case that turns a serialization mistake into data corruption. ## The same argument, wider The reasoning is not special to files and sockets. It applies to **anything in a value whose meaning depends on a counterpart being alive**: - a borrowed entry from a pool of connections; - a held lock or any other exclusive claim, which is a promise made to other holders in one process; - an in-process cache whose entries are only valid while the things they refer to are; - a subscription or callback registration, which names a receiver that exists only where it was registered. Each of these is a *relationship*, and a relationship cannot be copied by copying a number. Serialization moves **content**; a relationship has to be re-established, by the side that will actually use it. ## What crosses the boundary instead The replacement is always the same shape: write the **durable key**, and let the reader open its own. | Held in memory | What is written | What the reader does | |---|---|---| | An open file handle | the path and, if needed, the position | opens it itself, under its own permissions | | A live connection | the address of the peer | connects itself, or takes one from its own pool | | A pooled resource | the identifier of what was needed | borrows from the pool available to it | | A held exclusive claim | nothing | acquires the claim itself, or fails to | Two consequences follow, and both are the point rather than an inconvenience: - **Re-acquisition can fail.** The path may be gone, the peer unreachable, the claim held by someone else. That failure is honest: it surfaces at the reader, where the resource is actually needed, instead of pretending the writer's access transferred. - **Authorisation is re-evaluated.** The writer's open happened under the writer's identity. The reader opening the same path is checked against the reader's. Shipping a handle would have smuggled the writer's access across the boundary, which is exactly what you do not want. ## Saying it in an interview The sentence that carries the whole idea is: **a handle names a live relationship in one process's table, not data**, so it has no meaning on the other side of any boundary — not a process, not a machine, and least of all a year. The good follow-up is that the reader must re-acquire, may legitimately fail, and is checked in its own right when it does.

  • Why is a stale handle that still resolves more dangerous than one that fails?
    Because nothing signals the mistake. If the number happens to be a valid entry in the reader's own table, the value looks intact and the next read, write or close lands on an unrelated resource. A reference that simply fails is caught immediately at the point of use; one that silently resolves corrupts whatever it touches.
  • What does a reader gain from re-acquiring the resource rather than inheriting a handle?
    Two things. The failure becomes honest — if the path is gone or the peer unreachable, that surfaces where the resource is actually needed. And authorisation is re-evaluated against the reader's own identity rather than smuggling the writer's access across the boundary with the bytes.
  • Does the same reasoning apply to a lock the value is holding?
    Yes, and more sharply. A lock is a promise made to other holders inside one process about exclusive access; there is no number to write that could carry that promise elsewhere. The encoded value omits it, and the reader has to acquire the corresponding claim itself — which it may not be granted.

saying these in an interview costs you the question

  • Thinks the handle's number still names the same resource elsewhere
  • Believes the file position and buffers travel with the handle
  • Says handles cannot be encoded because they are not numbers
  • Expects the writer's permissions to transfer with the bytes
  • Treats a connection from a pool as ordinary copyable field data