IV Time and Flow

Stateful Operations

A call is addressed to a component. Whichever replica is free takes it, and the caller neither knows nor cares which one. This is what lets a component scale, restart, and move, and it depends on operations being stateless.

Occasionally something exists in exactly one process: an open connection to a device, a media stream being produced, a session held in memory. To reach it, a call has to arrive at that process and no other.

§Addressing a process

An operation declared stateful is served by every process of its component under that process’s own name:

operations:
  watch:
    type: computation
    stateful: true

A process learns its name from the context and gives it to whoever will need it later, usually by storing it in state. A caller then names the process the call is for:

// in the process that holds the stream
const instance = context.instance

// in any caller
await context.remote.streams.watch({ input: { id }, instance })

The rest of the call is unchanged. It has a contract, it is validated and traced, and it is addressed by name.

§What an addressed call gives up

An ordinary call reaches a component, and a component comes back. If one replica stops, another takes the call. If none is running, the call waits.

An addressed call reaches one process. When that process is gone, no replica can answer in its place, and nothing says the process will return. The guarantees that rest on substitutability no longer apply:

It can end without an answer. An addressed call waits for a bounded time. If nothing answers, the call is abandoned, and the caller cannot tell whether it ran.

It can find nobody. A call to a name no process holds is refused outright, and did not run.

It cannot be deferred. A task or a delayed call is taken later by whichever process is free, which contradicts naming one.

OutcomeMeaning for the caller
The replyThe process answered.
Nobody holds the nameThe call did not run.
AbandonedThe wait ended; the call may have run, and may still.

Both failures are exceptions. They are also ordinary events in the life of such a system, since processes come and go, and a caller of a stateful operation is written to expect them.

§Keeping the exception small

stateful is a declared departure from the model, like unmanaged. The manifest shows which operations are tied to a process, and everything else in the component keeps its guarantees.

The usual design keeps durable facts in entity state and uses the stateful operation only for the live resource. A streams component stores which process holds each stream. Its ordinary operations create, list, and end streams, reliably. Only watch is stateful. When the process holding a stream disappears, the stored state is what lets the rest of the system notice and recover.

§Process memory

The same need appears on a smaller scale as memory that outlives a single call: a parsed dictionary, a pool of connections. A component can ask for a state aspect, an object that belongs to one process of the component and is shared by its operations there.

Such memory is a cache, not state in the sense of State. Each replica has its own, it is lost when the process stops, and nothing in it is visible to the runtime. Whatever has to survive, or be seen by another replica, belongs in an entity.

Search
Search the documentation