Why must a weak handle be promoted to a temporary owning handle before use rather than only tested for liveness?
answer
- check-then-act, one window
- the answer is already history
- not only a threading problem
- raise the count if non-zero
- own it for the whole operation
basics
~20 sBecause liveness can change between the test and the read. Promotion takes a temporary owning handle in one indivisible step when the count is still above zero, so the object cannot be destroyed mid-use; a bare is-alive test leaves a window open.
solid answer
~50 sA test through a weak handle answers a question about the past: the object was alive when you asked. It says nothing about the moment you dereference. Between the two, the last owning handle can be dropped — by another thread, or, in a single-threaded program, by work the use itself triggers, such as a callback that removes the entry from the collection that owned it. Promotion closes that window by combining the test and the claim: it raises the owning count in one indivisible step *provided* the count is not already zero, and hands back a temporary owning handle on success or nothing on failure. While the caller holds that handle, destruction cannot happen. The correct shape is therefore promote, branch once on the result, and use the owning handle for the whole operation.
code
pseudocode · 12 lines// wrong: the answer is stale by the time it is used
if weak.is_alive():
address = weak.raw_target()
do_work(address) // owner may have dropped it by now
// right: test and claim in one indivisible step
temp = promote(weak) // increments the owning count only if it is non-zero
if temp == NONE:
handle_absent() // gone for good; no retry will help
else:
do_work(temp) // cannot be destroyed while temp is held
release_owning(temp)go deeper
Remember that a weak handle is used by turning it into a temporary owning handle, and that the result may be nothing. Never take the bare address out of a weak handle and keep it.
Explain the window: a liveness test describes the instant it was asked, so the test and the claim must be one indivisible step that raises the count only when it is above zero.
Produce the single-threaded case — a callback during the use drops the last owning handle and destruction runs inline — and show the promote-once-around-the-operation shape that avoids it.
Decide where the boundary between observation and temporary ownership sits in a codebase, since a promoted handle held too long silently turns an observing edge into a retaining one.
## Two operations, one window Write the naive use out and the bug is visible: 1. Ask the weak handle whether its object is still there. It says yes. 2. Take the raw address it refers to. 3. Read through that address. Steps 1 and 3 are separated in time, and nothing in between holds the object. The answer from step 1 is a statement about the instant it was asked, and it is already history by step 3. This is the classic check-then-act shape, and the weak handle's whole promise — *never yield a destroyed object* — cannot be kept by a design that reads it this way. ## It is not only a threading problem Multiple threads are the obvious way to lose the race: another thread drops the last owning handle between your test and your read. But the window is real in a single-threaded program too, and that is the version interviewers use to separate people who have reasoned about it from people who have memorised "weak means check first": - You test the weak handle for an entry, find it alive, and take an address. - You call something with it — a notification, a callback, a re-entrant update. - That call removes the entry from the collection that owned it, dropping the last owning handle. The object is destroyed **immediately**, because counting releases at the moment of the last drop. - Control returns and your remaining work reads destroyed storage. No concurrency was involved. The object died inside your own call stack. ## What promotion does Promotion fuses the question and the claim into a single indivisible step: *raise the owning count, but only if it is not already zero.* The two outcomes are total and there is no third: | outcome | meaning | what the caller gets | |---|---|---| | success | the count was above zero and is now one higher | a temporary owning handle, valid until the caller releases it | | failure | the count had already reached zero | nothing; the object is gone for good | On success the caller is an owner for the duration. Nothing anyone else does can destroy the object while that handle is held — the worst that happens is that the caller's own release is the one that takes the count to zero, and then destruction runs at a point the caller controls. ## The shape that follows from this - **Promote once, not per field.** Promoting, reading one field, releasing, promoting again, reading a second field gives you two snapshots of possibly different worlds. Promote once around the whole operation. - **Promote outside a loop.** A loop body that promotes on every iteration pays for it repeatedly and still gives no consistency across iterations. - **Branch once.** The absent path is taken at the promotion, not scattered through the body, because after a successful promotion the object cannot vanish. - **Release promptly.** A temporary owning handle held longer than the operation is no longer temporary, and you have quietly converted an observing edge into an owning one — precisely the retention the weak handle was chosen to avoid. ## What promotion does not give you It is an existence guarantee, not a lock. A promoted handle guarantees that the object is still there; it says nothing about whether its contents are being changed at the same time, and it does not exclude anyone else. Treating a successful promotion as mutual exclusion is a separate bug in a concurrent program, and a misreading of what the count means: the count records how many owners there are, not who is currently using the object. It also does not resurrect. A failed promotion is final — the count reached zero, the object was destroyed, and no number of retries will produce a different answer. The right response is the absent path: re-fetch from the source of truth, clear the selection, or drop the notification. ## The interview signal A candidate who says "check the weak handle before using it" has the vocabulary. A candidate who explains *why the check and the use must be one step*, and can produce the single-threaded re-entrancy example, has actually built something on a counted graph.
- If a promotion fails, is it ever worth retrying?No. Failure means the owning count had already reached zero and the object was destroyed, which is a terminal state — the count cannot rise again from zero. Retrying loops on an answer that will not change. The correct response is the absent path: re-fetch from whatever still owns the data, clear the reference, or skip the work.
- Does holding a promoted handle stop other code from modifying the object?No. Promotion is an existence guarantee, not mutual exclusion. It guarantees the object is still there for as long as you hold the handle; concurrent modification of its contents is a separate concern needing its own synchronisation. Reading a successful promotion as a lock is a distinct bug.
- Why is promoting inside a loop body usually the wrong shape?Because it pays the cost on every iteration and still gives no consistency: the object could be destroyed between two iterations, so each one sees a possibly different world and the absent path is scattered through the body. Promote once outside the loop, branch on the result there, and use the owning handle throughout.
saying these in an interview costs you the question
- Tests a weak handle, then reads through the raw address it gave
- Believes the window only exists when several threads are involved
- Treats a successful promotion as mutual exclusion over the object
- Retries a failed promotion hoping the object comes back
- Promotes per field, producing an inconsistent mix of snapshots