Repository navigation
Expand file tree
/
Copy pathTwophase4_run.lean
More file actions
114 lines (99 loc) · 3.38 KB
/
Copy pathTwophase4_run.lean
File metadata and controls
114 lines (99 loc) · 3.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/-
A randomised simulation of the Two-phase commit protocol.
Copyright (c) 2025 Igor Konnov
Released under MIT license as described in the file LICENSE.
Authors: Igor Konnov, 2025
-/
import Mathlib.Data.Finset.Basic
import Mathlib.Data.Finset.Fold
import Twophase.Functional
import Twophase.System
-- An instance of four resource managers.
inductive RM
| RM1
| RM2
| RM3
| RM4
deriving Repr, DecidableEq, Hashable, Inhabited
def mkAction (action_no: Nat) (rm_no: Nat): @Action RM :=
let rm := match rm_no with
| 0 => RM.RM1
| 1 => RM.RM2
| 2 => RM.RM3
| _ => RM.RM4
match action_no with
| 0 => Action.TMCommit
| 1 => Action.TMAbort
| 2 => Action.TMRcvPrepared rm
| 3 => Action.RMPrepare rm
| 4 => Action.RMChooseToAbort rm
| 5 => Action.RMRcvCommitMsg rm
| _ => Action.RMRcvAbortMsg rm
def help: String :=
"❌ Arguments: <number of runs> <number of steps> <invariant> <seed>"
def parseNat (s: String): IO Nat :=
match s.toNat? with
| some k => pure k
| none => panic! help
-- This is a false invariant to demonstrate that TM can abort.
def noAbortEx (s: ProtocolState RM): Bool :=
s.tmState ≠ TMState.Aborted
-- This is a false invariant to demonstrate that TM can commit.
def noCommitEx (s: ProtocolState RM): Bool :=
s.tmState ≠ TMState.Committed
-- This is a false invariant for a trickier property:
-- Even though all resource managers are prepared, the TM can still abort.
def noAbortOnAllPreparedEx (s: ProtocolState RM): Bool :=
s.tmState = TMState.Aborted → s.tmPrepared ≠ s.all
-- the main invariant of the protocol, namely, that resource managers cannot disagree
def consistentInv (s: ProtocolState RM): Bool :=
let existsAborted :=
∅ ≠ (Finset.filter (fun rm => s.rmState.get? rm = RMState.Aborted) s.all)
let existsCommitted :=
∅ ≠ (Finset.filter (fun rm => s.rmState.get? rm = RMState.Committed) s.all)
¬existsAborted ∨ ¬existsCommitted
def main (args: List String): IO UInt32 := do
-- parse the arguments
let mut maxSamples := 10000
let mut maxSteps := 10
let mut seed := 0
let mut inv := fun (_: ProtocolState RM) => true
match args with
| [ maxSamples_s, maxSteps_s, inv_s, seed_s ] =>
maxSamples ← parseNat maxSamples_s
maxSteps ← parseNat maxSteps_s
seed ← parseNat seed_s
match inv_s with
| "noAbortEx" => inv := noAbortEx
| "noCommitEx" => inv := noCommitEx
| "noAbortOnAllPreparedEx" => inv := noAbortOnAllPreparedEx
| "consistentInv" => inv := consistentInv
| _ =>
IO.eprintln help;
return 1
| _ =>
IO.eprintln help;
return 1
-- run a loop of `maxSamples`
let mut rng := mkStdGen seed
for trial in [0:maxSamples] do
let mut state := init [ RM.RM1, RM.RM2, RM.RM3, RM.RM4 ]
-- run a loop of `maxSteps` steps
let mut trace: List (@Action RM) := []
for _ in [0:maxSteps] do
let (action_no, next_rng) := randNat rng 0 6
let (rm_no, next_rng) := randNat next_rng 0 3
rng := next_rng
let action := mkAction action_no rm_no
match next state action with
| some new_state =>
state := new_state
trace := action::trace
| none => pure ()
-- check our invariant
if ¬inv state then
IO.println s!"❌ Counterexample found after {trial} trials"
for (a, i) in trace.reverse.zipIdx 0 do
IO.println s!"#{i}: {repr a}"
return 1
return 0