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
import std/atomics
type
NodeObj = object
value: int
next: Atomic[Managed[ref NodeObj]]
Node = ref NodeObj
proc main() =
var manager = initDebraManager[4](../../examples)
setGlobalManager(addr manager)
let handle = registerThread(manager)
# Basic pin/unpin cycle
echo "=== Basic Pin/Unpin ==="
block:
let pinned = unpinned(handle).pin()
echo "Thread pinned at epoch: ", pinned.epoch
# Do work while pinned...
let node = managed Node(value: 42)
echo "Created node: ", node.value
let unpinResult = pinned.unpin()
case unpinResult.kind:
of uUnpinned:
echo "Normal unpin"
of uNeutralized:
echo "Was neutralized - acknowledging"
discard unpinResult.neutralized.acknowledge()
# Multiple pin/unpin cycles
echo ""
echo "=== Multiple Cycles ==="
for i in 1..3:
let pinned = unpinned(handle).pin()
echo "Cycle ", i, ": pinned at epoch ", pinned.epoch
# Retire a node
let node = managed Node(value: i * 100)
let ready = retireReady(pinned)
discard ready.retire(node)
let unpinResult = pinned.unpin()
case unpinResult.kind:
of uUnpinned:
echo "Cycle ", i, ": unpinned normally"
of uNeutralized:
echo "Cycle ", i, ": neutralized"
discard unpinResult.neutralized.acknowledge()
# Advance epoch between cycles
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? Pinning blocks epoch advancement and prevents reclamation. Long critical sections waste memory.
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