One volume. One writer. Ever.
A block volume is a single disk. Two machines writing to it at once destroys the filesystem, so shc lets exactly one node hold it. The hard part is a node that disappears: it may still be alive and still writing. So the volume does not move until the old node has provably stopped writing. Watch it happen.
Simulated walkthrough. node-a holds a block volume and is writing to it; node-b is not allowed to mount it. node-a is cut off from the cluster but keeps writing for about ten seconds, until two consecutive lease renewals fail and it stops itself: containers stopped, filesystem unmounted, buffers flushed. The cluster leader nevertheless waits a full forty-five seconds of agreement between two independent signals before it lets the volume move. Only then does the volume attach to node-b, at a new fence generation, which permanently locks out any older mount request from node-a.
simulated — no daemon, no kernel, no cloud API; it plays by itself and loops. It mirrors internal/subsystems/impl/volume_self_fence.go and internal/subsystems/impl/volume_failover.go at their real constants. The honest mechanism behind the plain words: the writer renews a lease every 5s and self-fences after two consecutive failed renews (~10s) — not at lease-TTL expiry — stopping bound containers, then umount -l, then blockdev --flushbufs. That loop ignores every config flag, because stopping a partitioned writer is correct on its own. Separately the leader sweeps every 15s and advances a dwell only while two independent signals agree — the durable node.cluster_state (fence_pending/unreachable) and the leader's own in-memory health poller; a healthy or absent poller reading calls clearDwell, so a fresh leader that has never polled the node starts the 45s over. Three consecutive agreeing ticks = 45s, and only then does a force-detach proof (a provably expired lease, or a STONITH confirm the default NoopFencer never gives) authorise the move. The ordering is asserted at compile time: lease TTL 15s + grace ~10s + the local fence must finish inside the 45s dwell. The leader is the single monotonic writer of fence_generation; it bumps it before the new mount, so a stale gen-7 mount request is refused with X409035VOLFNC (409). Automatic rebind ships off (storage.volumes.auto_failover defaults false) — with it off the node still self-fences and the volume waits for an operator's shc volume rebind. Volume ids, node ids and device paths here are illustrative; the mount path shape, commands and error codes are real. Clock runs at ×3. Docs: /guide/getting-started/