Skip to main content

Module subscription_slot

Module subscription_slot 

Source
Expand description

The slot that holds the one inner subscription an operator keeps at a time.

An operator such as Switch or ConcatAll is subscribed to at most one inner observable at a time, and swaps that subscription as the source emits. Subscribing is an external API call, so it must happen with no lock held, which splits every swap into two locked steps around an unlocked one: reserve the slot, subscribe, fill the slot. Between the two steps the inner observable can terminate the operator synchronously and release the slot, so the fill has to be able to give the new subscription back.

SubscriptionSlot is that three-step state machine and nothing else. It carries no lock of its own: it lives inside a model already guarded by the delivery lock — see SubscriptionContext::update — and every method takes &mut self. It also disposes nothing: each method hands the subscription it evicts back to the caller, which passes it to UpdateOutcome::with_drop_outside so it is dropped outside the lock.

§When a slot is the right type

Reserved is the whole of what this type adds. Idle and Active are what an Option<D> already says, so a host that needs only those two keeps its Option. Reach for a slot only when the host must tell “a value is on its way” apart from “nothing is held”, which takes all three of:

  • the value is built by an external call that has to run with the lock released, so the state is observable by someone else while the build is in flight;
  • the slot can be released inside that window, and filling a released slot would install a value that is already dead — in a host that keeps one task alive while there is work to do, that means storing the handle of a task that already stopped, after which no new task is ever started and the queued events stall;
  • there is exactly one such value. Several of them are keyed, and an absent key already means Idle, so each entry collapses back to Option — see the maps in MergeAll and Amb, which run this same reserve/fill protocol without this type.

When the third state is unreachable, a slot only widens the state space with a variant the host’s invariants forbid, which is the opposite of what it is for.

§Why this is not SharedDisposal

The two are the same shape — idle, building, active — but not the same contract, so neither is built on the other:

  • SharedDisposal is itself a disposal, so it needs a terminal Disposed state that absorbs later replacements. A slot needs none: its host stops through the delivery, which stops running updates at all (DeliveryStopped), and whatever is left in the slot is disposed when the model is dropped.
  • SharedDisposal releases its own lock while the builder runs, so a second replace can race the first and it needs a generation id to tell whether a finished build is still current. A slot cannot be raced: the reserve/fill pair is serialized by the delivery lock, and Reserved makes a second reserve unreachable.

Merging them would give every slot a state it never enters and an id it never reads.

Enums§

SubscriptionSlot
The one inner subscription an operator holds at a time.