Pin/Unpin Protocol¶
Understanding the pin/unpin lifecycle for critical sections.
State Machine¶
Overview¶
The pin/unpin protocol marks critical sections where threads access lock-free data structures. Pinning prevents reclamation of objects from the current epoch.
Pin Operation¶
When you call pin():
- Load global epoch: Read the current global epoch counter
- Clear neutralization: Reset the neutralization flag
- Store epoch: Write epoch to thread's epoch slot
- Set pinned flag: Mark thread as pinned
Unpin Operation¶
When you call unpin():
- Clear pinned flag: Mark thread as no longer pinned
- Check neutralization: Read the neutralization flag
- Return result: Either
UnpinnedorNeutralizedstate
Critical Section Example¶
The following example demonstrates pin/unpin patterns and neutralization handling:
# examples/pin_unpin.nim
## Pin/unpin lifecycle example with neutralization handling.
import debra
type
NodeObj = object
value: int
Node = ref NodeObj
proc main() =
var manager = initDebraManager[4](../../examples)
setGlobalManager(addr manager)
let handle = registerThread(manager)
# Basic pin/unpin cycle (no retire). The unpin result discriminates
# between a normal unpin and a neutralization; the latter happens when
# `neutralizeStalled` signaled this thread while it was pinned.
echo "=== Basic Pin/Unpin ==="
block:
let pinned = unpinned(handle).pin()
echo "Thread pinned at epoch: ", pinned.epoch
let unpinResult = pinned.unpin()
case unpinResult.kind
of uUnpinned:
echo "Normal unpin"
of uNeutralized:
echo "Was neutralized - acknowledging"
discard unpinResult.neutralized.acknowledge()
# Multiple pin/retire/unpin cycles using the high-level `withPin` sugar.
# `withPin` injects `it: var RetireReady[MT]` and unpins on scope exit
# (including raises). `retain` GC-pins the ref; `releaseDestructor` is
# the matching destructor that runs at reclamation time.
echo ""
echo "=== Multiple Cycles (withPin) ==="
let dtor = releaseDestructor[NodeObj](../../examples)
for i in 1 .. 3:
handle.withPin:
echo "Cycle ", i, ": pinned"
let node = retain Node(value: i * 100)
it.retire(cast[pointer](../../examples/node), dtor)
# Advance epoch between cycles so reclamation can make progress.
manager.advance()
echo ""
echo "Pin/unpin example completed"
when isMainModule:
main()
What You Can Do While Pinned¶
- Read shared memory from lock-free structures
- Perform CAS operations to modify shared state
- Retire objects that have been removed
- Access multiple structures in same critical section
What You Cannot Do While Pinned¶
Avoid these while pinned:
- Blocking operations: Don't hold locks, wait on condition variables
- Long computations: Keep critical sections short
- I/O operations: Don't do file/network I/O while pinned
- Sleeping: Don't call sleep or delay functions
Why? While a thread is pinned, objects retired by other threads cannot be reclaimed. Long critical sections delay reclamation and cause memory to accumulate in limbo bags.
Typestate Guarantees¶
The typestate system enforces:
- Cannot retire unpinned: Must be in
Pinnedstate to retire objects - Cannot double-pin: Once pinned, cannot pin again without unpinning
- Must acknowledge neutralization: Cannot pin after neutralization without acknowledging
These are compile-time guarantees - if it compiles, the protocol is correct.
Performance Considerations¶
Pin/unpin overhead:
- Pin: ~10-20ns (1 atomic load, 1 atomic store, 2 relaxed stores)
- Unpin: ~5-10ns (1 relaxed store, 1 atomic load)
This is negligible compared to lock-free operation costs.
Next Steps¶
- Learn about retiring objects
- Understand neutralization
- See integration examples