Skip to content

Commit cfa1614

Browse files
authored
Add small improvements to the Disruptor spec. (#151)
- Add comment on 'Multicast' behaviour. - Add ASSUME for MaxPublished constant. - Add comment on the usage of the 'consumed' variable. - Remove WF from write actions. - Remove bounding of model happening in BeginWrite action. Use a State constraint instead. Use suggestion for liveliness property. - (The last two items are not done yet for the MPMC spec - it's less straight forward to do because of the multiple producers behaviour.) - Add 'state constraint' to feature list for Disruptor spec. - Flip 'Advance' and 'Access' state - they were flipped. - Add state constraint, improve model. - Flip 'Advance' and 'Access' states - they were flipped. - Remove model boundary from 'BeginWrite' action - separates spec and verification. - Fix 'published' variable which had an unused mapping. Signed-off-by: Nicholas Schultz-Møller <[email protected]>
1 parent 7ebd914 commit cfa1614

6 files changed

+86
-41
lines changed

manifest.json

+8-5
Original file line numberDiff line numberDiff line change
@@ -459,11 +459,12 @@
459459
"size": "small",
460460
"mode": "exhaustive search",
461461
"features": [
462+
"state constraint",
462463
"ignore deadlock"
463464
],
464465
"result": "success",
465466
"distinctStates": 112929,
466-
"totalStates": 406505,
467+
"totalStates": 422781,
467468
"stateDepth": 81
468469
},
469470
{
@@ -472,12 +473,13 @@
472473
"size": "small",
473474
"mode": "exhaustive search",
474475
"features": [
476+
"state constraint",
475477
"liveness",
476478
"ignore deadlock"
477479
],
478480
"result": "success",
479481
"distinctStates": 14365,
480-
"totalStates": 42101,
482+
"totalStates": 44581,
481483
"stateDepth": 61
482484
}
483485
]
@@ -494,13 +496,14 @@
494496
"size": "small",
495497
"mode": "exhaustive search",
496498
"features": [
499+
"state constraint",
497500
"liveness",
498501
"ignore deadlock"
499502
],
500503
"result": "success",
501-
"distinctStates": 8153,
502-
"totalStates": 26481,
503-
"stateDepth": 81
504+
"distinctStates": 8496,
505+
"totalStates": 28049,
506+
"stateDepth": 82
504507
}
505508
]
506509
},

specifications/Disruptor/Disruptor_MPMC.cfg

+3
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,8 @@ INVARIANTS
1212
TypeOk
1313
NoDataRaces
1414

15+
CONSTRAINT
16+
StateConstraint
17+
1518
CHECK_DEADLOCK
1619
FALSE

specifications/Disruptor/Disruptor_MPMC.tla

+46-20
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
(* *)
99
(* The model also verifies that no data races occur between the producers *)
1010
(* and consumers and that all consumers eventually read all published *)
11-
(* values. *)
11+
(* values (in a Multicast fashion - i.e. all consumers read all events). *)
1212
(***************************************************************************)
1313

1414
EXTENDS Integers, FiniteSets, Sequences
@@ -22,16 +22,19 @@ CONSTANTS
2222

2323
ASSUME Writers /= {}
2424
ASSUME Readers /= {}
25-
ASSUME Size \in Nat \ {0}
25+
ASSUME Size \in Nat \ {0}
26+
ASSUME MaxPublished \in Nat \ {0}
2627

2728
VARIABLES
2829
ringbuffer,
2930
next_sequence, (* Shared counter for claiming a sequence for a Writer. *)
3031
claimed_sequence, (* Claimed sequence by each Writer. *)
3132
published, (* Encodes whether each slot is published. *)
3233
read, (* Read Cursors. One per Reader. *)
33-
consumed, (* Sequence of all read events by the Readers. *)
34-
pc (* Program Counter for each Writer/Reader. *)
34+
pc, (* Program Counter for each Writer/Reader. *)
35+
consumed (* Sequence of all read events by the Readers. *)
36+
(* This is a history variable used for liveliness *)
37+
(* checking. *)
3538

3639
vars == <<
3740
ringbuffer,
@@ -63,7 +66,12 @@ Range(f) ==
6366
{ f[x] : x \in DOMAIN(f) }
6467

6568
MinReadSequence ==
66-
CHOOSE min \in Range(read) : \A r \in Readers : min <= read[r]
69+
CHOOSE min \in Range(read) :
70+
\A r \in Readers : min <= read[r]
71+
72+
MinClaimedSequence ==
73+
CHOOSE min \in Range(claimed_sequence) :
74+
\A w \in Writers : min <= claimed_sequence[w]
6775

6876
(***************************************************************************)
6977
(* Encode whether an index is published by tracking if the slot was *)
@@ -86,6 +94,20 @@ Publish(sequence) ==
8694
\* Flip whether we're at an even or odd round.
8795
IN published' = [ published EXCEPT ![index] = Xor(TRUE, @) ]
8896

97+
(***************************************************************************)
98+
(* Computes the highest published sequence number that can be read. *)
99+
(* This might seem strange but e.g. a producer P1 can be about to publish *)
100+
(* sequence 5 while producer P2 has published sequence 6 and thus *)
101+
(* consumers can neither read sequence 5 nor 6 (yet). *)
102+
(***************************************************************************)
103+
AvailablePublishedSequence ==
104+
LET guaranteed_published == MinClaimedSequence - 1
105+
candidate_sequences == {guaranteed_published} \union Range(claimed_sequence)
106+
IN CHOOSE max \in candidate_sequences :
107+
IsPublished(max) => ~ \E w \in Writers :
108+
/\ claimed_sequence[w] = max + 1
109+
/\ IsPublished(claimed_sequence[w])
110+
89111
(***************************************************************************)
90112
(* Producer Actions: *)
91113
(***************************************************************************)
@@ -98,10 +120,9 @@ BeginWrite(writer) ==
98120
IN
99121
\* Are we clear of all consumers? (Potentially a full cycle behind).
100122
/\ min_read >= seq - Size
101-
/\ seq < MaxPublished
102123
/\ claimed_sequence' = [ claimed_sequence EXCEPT ![writer] = seq ]
103124
/\ next_sequence' = seq + 1
104-
/\ Transition(writer, Access, Advance)
125+
/\ Transition(writer, Advance, Access)
105126
/\ Buffer!Write(index, writer, seq)
106127
/\ UNCHANGED << consumed, published, read >>
107128

@@ -110,7 +131,7 @@ EndWrite(writer) ==
110131
seq == claimed_sequence[writer]
111132
index == Buffer!IndexOf(seq)
112133
IN
113-
/\ Transition(writer, Advance, Access)
134+
/\ Transition(writer, Access, Advance)
114135
/\ Buffer!EndWrite(index, writer)
115136
/\ Publish(seq)
116137
/\ UNCHANGED << claimed_sequence, next_sequence, consumed, read >>
@@ -125,7 +146,7 @@ BeginRead(reader) ==
125146
index == Buffer!IndexOf(next)
126147
IN
127148
/\ IsPublished(next)
128-
/\ Transition(reader, Access, Advance)
149+
/\ Transition(reader, Advance, Access)
129150
/\ Buffer!BeginRead(index, reader)
130151
\* Track what we read from the ringbuffer.
131152
/\ consumed' = [ consumed EXCEPT ![reader] = Append(@, Buffer!Read(index)) ]
@@ -136,7 +157,7 @@ EndRead(reader) ==
136157
next == read[reader] + 1
137158
index == Buffer!IndexOf(next)
138159
IN
139-
/\ Transition(reader, Advance, Access)
160+
/\ Transition(reader, Access, Advance)
140161
/\ Buffer!EndRead(index, reader)
141162
/\ read' = [ read EXCEPT ![reader] = next ]
142163
/\ UNCHANGED << claimed_sequence, next_sequence, consumed, published >>
@@ -148,11 +169,11 @@ EndRead(reader) ==
148169
Init ==
149170
/\ Buffer!Init
150171
/\ next_sequence = 0
151-
/\ claimed_sequence = [ w \in Writers |-> -1 ]
152-
/\ published = [ i \in 0..Size |-> FALSE ]
153-
/\ read = [ r \in Readers |-> -1 ]
154-
/\ consumed = [ r \in Readers |-> << >> ]
155-
/\ pc = [ a \in Writers \union Readers |-> Access ]
172+
/\ claimed_sequence = [ w \in Writers |-> -1 ]
173+
/\ published = [ i \in 0..Buffer!LastIndex |-> FALSE ]
174+
/\ read = [ r \in Readers |-> -1 ]
175+
/\ consumed = [ r \in Readers |-> << >> ]
176+
/\ pc = [ a \in Writers \union Readers |-> Advance ]
156177

157178
Next ==
158179
\/ \E w \in Writers : BeginWrite(w)
@@ -161,14 +182,18 @@ Next ==
161182
\/ \E r \in Readers : EndRead(r)
162183

163184
Fairness ==
164-
/\ \A w \in Writers : WF_vars(BeginWrite(w))
165-
/\ \A w \in Writers : WF_vars(EndWrite(w))
166185
/\ \A r \in Readers : WF_vars(BeginRead(r))
167186
/\ \A r \in Readers : WF_vars(EndRead(r))
168187

169188
Spec ==
170189
Init /\ [][Next]_vars /\ Fairness
171190

191+
(***************************************************************************)
192+
(* State constraint - bounds model: *)
193+
(***************************************************************************)
194+
195+
StateConstraint == next_sequence <= MaxPublished
196+
172197
(***************************************************************************)
173198
(* Invariants: *)
174199
(***************************************************************************)
@@ -179,7 +204,7 @@ TypeOk ==
179204
/\ Buffer!TypeOk
180205
/\ next_sequence \in Nat
181206
/\ claimed_sequence \in [ Writers -> Int ]
182-
/\ published \in [ 0..Size -> { TRUE, FALSE } ]
207+
/\ published \in [ 0..Buffer!LastIndex -> { TRUE, FALSE } ]
183208
/\ read \in [ Readers -> Int ]
184209
/\ consumed \in [ Readers -> Seq(Nat) ]
185210
/\ pc \in [ Writers \union Readers -> { Access, Advance } ]
@@ -188,8 +213,9 @@ TypeOk ==
188213
(* Properties: *)
189214
(***************************************************************************)
190215

191-
(* Eventually always, consumers must have read all published values. *)
216+
(* Eventually always, consumers must have read all published values. *)
192217
Liveliness ==
193-
<>[] (\A r \in Readers : consumed[r] = [i \in 1..MaxPublished |-> i - 1])
218+
\A r \in Readers : \A i \in 0..(MaxPublished - 1) :
219+
<>[](i \in 0..AvailablePublishedSequence => Len(consumed[r]) >= i + 1 /\ consumed[r][i + 1] = i)
194220

195221
=============================================================================

specifications/Disruptor/Disruptor_MPMC_liveliness.cfg

+3
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,8 @@ INVARIANTS
1515
PROPERTIES
1616
Liveliness
1717

18+
CONSTRAINT
19+
StateConstraint
20+
1821
CHECK_DEADLOCK
1922
FALSE

specifications/Disruptor/Disruptor_SPMC.cfg

+3
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,8 @@ INVARIANTS
1515
PROPERTIES
1616
Liveliness
1717

18+
CONSTRAINT
19+
StateConstraint
20+
1821
CHECK_DEADLOCK
1922
FALSE

specifications/Disruptor/Disruptor_SPMC.tla

+23-16
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
(* *)
99
(* The model also verifies that no data races occur between the producer *)
1010
(* and consumers and that all consumers eventually read all published *)
11-
(* values. *)
11+
(* values (in a Multicast fashion - i.e. all consumers read all events). *)
1212
(* *)
1313
(* To see a data race, try and run the model with two producers. *)
1414
(***************************************************************************)
@@ -24,14 +24,17 @@ CONSTANTS
2424

2525
ASSUME Writers /= {}
2626
ASSUME Readers /= {}
27-
ASSUME Size \in Nat \ {0}
27+
ASSUME Size \in Nat \ {0}
28+
ASSUME MaxPublished \in Nat \ {0}
2829

2930
VARIABLES
3031
ringbuffer,
3132
published, (* Write cursor. One for the producer. *)
3233
read, (* Read cursors. One per consumer. *)
33-
consumed, (* Sequence of all read events by the Readers. *)
34-
pc (* Program Counter of each Writer/Reader. *)
34+
pc, (* Program Counter of each Writer/Reader. *)
35+
consumed (* Sequence of all read events by the Readers. *)
36+
(* This is a history variable used for liveliness *)
37+
(* checking. *)
3538

3639
vars == <<
3740
ringbuffer,
@@ -73,8 +76,7 @@ BeginWrite(writer) ==
7376
IN
7477
\* Are we clear of all consumers? (Potentially a full cycle behind).
7578
/\ min_read >= next - Size
76-
/\ next < MaxPublished
77-
/\ Transition(writer, Access, Advance)
79+
/\ Transition(writer, Advance, Access)
7880
/\ Buffer!Write(index, writer, next)
7981
/\ UNCHANGED << consumed, published, read >>
8082

@@ -83,7 +85,7 @@ EndWrite(writer) ==
8385
next == published + 1
8486
index == Buffer!IndexOf(next)
8587
IN
86-
/\ Transition(writer, Advance, Access)
88+
/\ Transition(writer, Access, Advance)
8789
/\ Buffer!EndWrite(index, writer)
8890
/\ published' = next
8991
/\ UNCHANGED << consumed, read >>
@@ -98,7 +100,7 @@ BeginRead(reader) ==
98100
index == Buffer!IndexOf(next)
99101
IN
100102
/\ published >= next
101-
/\ Transition(reader, Access, Advance)
103+
/\ Transition(reader, Advance, Access)
102104
/\ Buffer!BeginRead(index, reader)
103105
\* Track what we read from the ringbuffer.
104106
/\ consumed' = [ consumed EXCEPT ![reader] = Append(@, Buffer!Read(index)) ]
@@ -109,7 +111,7 @@ EndRead(reader) ==
109111
next == read[reader] + 1
110112
index == Buffer!IndexOf(next)
111113
IN
112-
/\ Transition(reader, Advance, Access)
114+
/\ Transition(reader, Access, Advance)
113115
/\ Buffer!EndRead(index, reader)
114116
/\ read' = [ read EXCEPT ![reader] = next ]
115117
/\ UNCHANGED << consumed, published >>
@@ -121,9 +123,9 @@ EndRead(reader) ==
121123
Init ==
122124
/\ Buffer!Init
123125
/\ published = -1
124-
/\ read = [ r \in Readers |-> -1 ]
125-
/\ consumed = [ r \in Readers |-> << >> ]
126-
/\ pc = [ a \in Writers \union Readers |-> Access ]
126+
/\ read = [ r \in Readers |-> -1 ]
127+
/\ consumed = [ r \in Readers |-> << >> ]
128+
/\ pc = [ a \in Writers \union Readers |-> Advance ]
127129

128130
Next ==
129131
\/ \E w \in Writers : BeginWrite(w)
@@ -132,14 +134,18 @@ Next ==
132134
\/ \E r \in Readers : EndRead(r)
133135

134136
Fairness ==
135-
/\ \A w \in Writers : WF_vars(BeginWrite(w))
136-
/\ \A w \in Writers : WF_vars(EndWrite(w))
137137
/\ \A r \in Readers : WF_vars(BeginRead(r))
138138
/\ \A r \in Readers : WF_vars(EndRead(r))
139139

140140
Spec ==
141141
Init /\ [][Next]_vars /\ Fairness
142142

143+
(***************************************************************************)
144+
(* State constraint - bounds model: *)
145+
(***************************************************************************)
146+
147+
StateConstraint == published < MaxPublished
148+
143149
(***************************************************************************)
144150
(* Invariants: *)
145151
(***************************************************************************)
@@ -159,6 +165,7 @@ NoDataRaces == Buffer!NoDataRaces
159165

160166
(* Eventually always, consumers must have read all published values. *)
161167
Liveliness ==
162-
<>[] (\A r \in Readers : consumed[r] = [i \in 1..MaxPublished |-> i - 1])
168+
\A r \in Readers : \A i \in 0 .. (MaxPublished - 1) :
169+
<>[](i \in 0 .. published => Len(consumed[r]) >= i + 1 /\ consumed[r][i + 1] = i)
163170

164-
=============================================================================
171+
=============================================================================

0 commit comments

Comments
 (0)