Repository navigation
Add StaleViewShrink: safe contraction (mutual inclusion) on stale views - #243
topeuph-ai wants to merge 2 commits into
Conversation
muenchnerkindl
left a comment
There was a problem hiding this comment.
Thank you for your contribution. At this point, I haven't run TLC yet, but I have a few questions concerning the motivations and the specification.
| @@ -0,0 +1,46 @@ | |||
| # StaleViewShrink: safe contraction on stale views | |||
There was a problem hiding this comment.
Can you give (here and/or in the TLA+ module) some references to where the problem and the algorithm come from? It strikes me that time is assumed to be global for all nodes, which is not a standard assumption is distributed computing.
|
|
||
| N nodes each hold a shared resource (here, one sector of a distributed hash | ||
| table). Any of them may decide to stop holding it, but at least R must always | ||
| remain. That is *l-mutual inclusion*, the mirror image of k-exclusion. There is |
There was a problem hiding this comment.
Rename l-mutual inclusion to R-mutual inclusion? Also because you have an (upper-case) L below.
| + (IF Rule = "patient" THEN Cardinality(Nodes) * (D + 2 * L + 2) ELSE 0) | ||
|
|
||
| VARIABLES t, \* current tick | ||
| lag, \* lag[n]: node n's view lag (fixed per behaviour) |
There was a problem hiding this comment.
Perhaps point out that TLC will check for all (type-correct and bounded by L) values of lag, otherwise one may wonder why it is not a CONSTANT parameter of the specification given that it never changes.
|
|
||
| Spec == Init /\ [][Next]_vars | ||
|
|
||
| TypeOK == /\ t \in 0..Horizon |
There was a problem hiding this comment.
Stylistically, I suggest moving TypeOK further up in the module, right after the variable declarations: this helps fixing the mindset of readers. More importantly, it should include a conjunct for every declared variable (lag and recent are missing).
|
|
||
| \* Node n's view at tick t: the state published at tick t - lag[n]. | ||
| View(n) == IF t >= lag[n] | ||
| THEN recent[Len(recent) - lag[n] + 1] |
There was a problem hiding this comment.
Perhaps note that t >= lag[n] ensures that Len(recent) - lag[n] + 1 is a valid index into recent.
| higher == { m \in its : m > n } | ||
| IN IF Rule = "tb" | ||
| THEN IF Cardinality((hs \ {n}) \ lower) >= R THEN "go" ELSE "cancel" | ||
| ELSE LET eff == Cardinality((hs \ {n}) \ (its \ {n})) |
There was a problem hiding this comment.
Explain what eff represents.
| ELSE IF higher # {} /\ lower = {} THEN "wait" | ||
| ELSE "cancel" | ||
|
|
||
| Tick == |
There was a problem hiding this comment.
This is a global action – stated differently, it is taken synchronously by all nodes, given that they all share the same global time. Its definition makes use of the global system state, rather than the local views of each node. I would appreciate seeing an explanation in which sense it still can be understood as each node taking a step based on the information that is available to it alone: obviously, the problem has a much simpler solution in a centralized solution where full information is available.
|
Thanks for the review. I've restructured the spec along those lines in ddc7240. The problem comes from dynamic storage-arc sizing in Holochain's kitsune2 DHT (holochain/kitsune2#160). The rules, with a Python checker and a Rust implementation, are in ValiChord/polite-shrink; as far as I know they aren't published elsewhere. The closest literature I found is Kakugawa, "Mutual inclusion in asynchronous message-passing distributed systems", JPDC 77 (2015), and Kamei & Kakugawa, arXiv:1807.05674, whose solutions use quorums and permission messages. Both are now cited in the README and the module header. On global time: each node now acts in its own steps, I've also renamed it R-mutual inclusion (noting the l- name used in the literature), moved The verdicts are unchanged. Both V6 models pass, now with 16,848 and 43,348 distinct states (previously 65,001 and 245,146, since view ages are no longer part of the state), and TLC still finds the same descending-ID cascade for V3 and fails the short-wait control. The 3–5 node configurations I tried earlier agree, every model runs in under 3 s, and the manifest, table and small-model checks pass locally. |
Per-node Announce/Recheck steps, an explicit clock, view age chosen afresh at every re-check, references. Verdicts unchanged; V6 state counts now 16,848 and 43,348. Co-Authored-By: Claude Opus 5.5 <[email protected]>
N nodes share a resource and any may leave, but at least R must remain, with no coordinator and each node acting on a view up to L ticks old. Compares a lowest-ID tie-break with a "patient lowest" rule. Four models: two pass (Safety and Progress), one fails instructively (TLC finds a staggered cascade that breaks the tie-break), and one control fails because the wait is shorter than the staleness. An atomic-read model of the same protocol passes the broken rule; modelling staleness exposes it. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Ceri John <[email protected]>
Addresses review: each node acts in its own steps, Announce(n) and Recheck(n), reading only its own variables and a view published 1..L ticks earlier, chosen afresh at every re-check. Time is an explicit clock advanced by AdvanceTime once every due re-check has happened (Abadi-Lamport style); nodes never read it. This also removes the per-node fixed lag. TypeOK follows the variables and covers all of them; comments on the snapshots index and on eff; renamed to R-mutual inclusion; references to the origin and to the literature. Results unchanged: both V6 models pass (16,848 and 43,348 distinct states), the V3 cascade and the short-wait control violate Safety. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Ceri John <[email protected]>
9597578 to
ddc7240
Compare
Adds
specifications/StaleViewShrink: a coordinator-free "leave only if R remain" protocol (l-mutual inclusion, the mirror of k-exclusion) in which each node acts on a view of the world up toLticks old. It compares a lowest-ID tie-break with a "patient lowest" rule.StaleShrink.cfgSafetyandProgress)StaleShrinkV6Wait2x.cfgStaleShrinkV3Cascade.cfgSafetyviolated: TLC finds a staggered cascade that breaks the tie-breakStaleShrinkV6TooShort.cfgSafetyviolated: control, with the wait shorter than the stalenessWhy it may be instructive: an atomic-read model of the same protocol passes the broken rule, and even a bare re-check with no tie-break. Modelling staleness explicitly is what exposes the cascade.
All models run in under 30 s (1–23 s single-worker). Checked locally with
check_manifest_schema.py,check_manifest_files.py,check_markdown_table.pyandcheck_small_models.py(TLC 2.19).manifest.jsonwas written by hand:generate_manifest.pywould not run on Windows (tree_sitterLanguage()overflow). Licence: Apache-2.0, as in the source repository (https://github.com/ValiChord/polite-shrink), which also has a Python checker that agrees with every verdict here and a Rust implementation.Written by Ceri John with AI assistance (Claude, Anthropic).
🤖 Generated with Claude Code