@@ -45,9 +45,9 @@ VARIABLE elections
45
45
VARIABLE allServers
46
46
47
47
\* The members of the server at that time.
48
- VARIABLE votersOfServer
49
-
50
- nodeVars == << allServers , votersOfServer >>
48
+ VARIABLE voters
49
+ VARIABLE nextVoters
50
+ nodeVars == << allServers , voters , nextVoters >>
51
51
----
52
52
\* The following variables are all per server (functions with domain Servers).
53
53
@@ -58,7 +58,7 @@ VARIABLE state
58
58
\* The candidate the server voted for in its current term, or
59
59
\* Nil if it hasn't voted for any.
60
60
VARIABLE votedFor
61
- serverVars == << currentTerm , state , votedFor , allServers , votersOfServer >>
61
+ serverVars == << currentTerm , state , votedFor , allServers , voters , nextVoters >>
62
62
63
63
\* The set of requests that can go into the log
64
64
VARIABLE clientRequests
@@ -95,6 +95,7 @@ VARIABLE nextIndex
95
95
\* The latest entry that each follower has acknowledged is the same as the
96
96
\* leader's. This is used to calculate commitIndex on the leader.
97
97
VARIABLE matchIndex
98
+
98
99
leaderVars == << nextIndex , matchIndex , elections >>
99
100
100
101
\* End of per server variables.
@@ -110,6 +111,13 @@ vars == <<messages, serverVars, candidateVars, leaderVars, logVars>>
110
111
\* important property is that every quorum overlaps with every other.
111
112
Quorum ( Server ) == { i \in SUBSET ( Server ) : Cardinality ( i ) * 2 > Cardinality ( Server ) }
112
113
114
+ BothQuorum ( old , new ) == IF old /= { } /\ new /= { } THEN
115
+ { x [ 1 ] \cup x [ 2 ] : x \in ( Quorum ( old ) \X Quorum ( new ) ) }
116
+ ELSE IF new = { } THEN
117
+ Quorum ( old )
118
+ ELSE IF old = { } THEN
119
+ Quorum ( new )
120
+ ELSE { }
113
121
\* The term of the last entry in a log, or 0 if the log is empty.
114
122
LastTerm ( xlog ) == IF Len ( xlog ) = 0 THEN 0 ELSE xlog [ Len ( xlog ) ] . term
115
123
@@ -138,13 +146,13 @@ GetLatestConfig(i) ==
138
146
LET length == Len ( log [ i ] )
139
147
maxIndex == GetMaxConfigIndex ( i , length )
140
148
IN IF maxIndex = 0 THEN InitialServers
141
- ELSE log [ i ] [ maxIndex ] . value
149
+ ELSE log [ i ] [ maxIndex ]
142
150
\* Get the latest commit config of server i.
143
151
GetLatestCommitConfig ( i ) ==
144
152
LET length == commitIndex [ i ]
145
153
maxIndex == GetMaxConfigIndex ( i , length )
146
154
IN IF maxIndex = 0 THEN InitialServers
147
- ELSE log [ i ] [ maxIndex ] . value
155
+ ELSE log [ i ] [ maxIndex ]
148
156
149
157
----
150
158
\* Define initial values for all variables
@@ -155,10 +163,11 @@ InitServerVars == /\ allServers = InitialServers \cup NewServers
155
163
/\ currentTerm = [ i \in allServers |-> 1 ]
156
164
/\ state = [ i \in allServers |-> Follower ]
157
165
/\ votedFor = [ i \in allServers |-> Nil ]
158
- /\ votersOfServer =
166
+ /\ voters =
159
167
LET initalVoters == InitialServers
160
168
newVoters == ( InitialServers \cup NewServers ) \ InitialServers
161
169
IN [ i \in initalVoters |-> initalVoters ] @@ [ i \in newVoters |-> { } ]
170
+ /\ nextVoters = [ i \in allServers |-> { } ]
162
171
163
172
InitCandidateVars == /\ votesResponded = [ i \in allServers |-> { } ]
164
173
/\ votesGranted = [ i \in allServers |-> { } ]
@@ -195,7 +204,7 @@ Timeout(i) == /\ state[i] \in {Follower, Candidate}
195
204
196
205
\* Candidate i sends j a RequestVote request.
197
206
RequestVote ( i , j ) ==
198
- /\ j \in votersOfServer [ i ]
207
+ /\ j \in ( voters [ i ] \cup nextVoters [ i ] )
199
208
/\ state [ i ] = Candidate
200
209
/\ j \notin votesResponded [ i ]
201
210
/\ Send ( [ mtype |-> RequestVoteRequest ,
@@ -212,7 +221,7 @@ RequestVote(i, j) ==
212
221
AppendEntries ( i , j ) ==
213
222
/\ i /= j
214
223
/\ state [ i ] = Leader
215
- /\ j \in votersOfServer [ i ]
224
+ /\ j \in ( voters [ i ] \cup nextVoters [ i ] )
216
225
/\ LET prevLogIndex == nextIndex [ i ] [ j ] - 1
217
226
prevLogTerm == IF prevLogIndex > 0 THEN
218
227
log [ i ] [ prevLogIndex ] . term
@@ -221,7 +230,7 @@ AppendEntries(i, j) ==
221
230
\* Send up to 1 entry, constrained by the end of the log.
222
231
lastEntry == Min ( { Len ( log [ i ] ) , nextIndex [ i ] [ j ] } )
223
232
entries == SubSeq ( log [ i ] , nextIndex [ i ] [ j ] , lastEntry )
224
- IN /\ Len ( entries ) > 0 /\ matchIndex [ i ] [ j ] < Len ( log [ i ] )
233
+ IN /\ Len ( entries ) > 0 /\ matchIndex [ i ] [ j ] < lastEntry
225
234
/\ Send ( [ mtype |-> AppendEntriesRequest ,
226
235
mterm |-> currentTerm [ i ] ,
227
236
mprevLogIndex |-> prevLogIndex ,
@@ -235,8 +244,9 @@ AppendEntries(i, j) ==
235
244
\* Candidate i transitions to leader.
236
245
BecomeLeader ( i ) ==
237
246
/\ state [ i ] = Candidate
238
- /\ votesGranted [ i ] \in Quorum ( votersOfServer [ i ] )
247
+ /\ votesGranted [ i ] \in BothQuorum ( voters [ i ] , nextVoters [ i ] )
239
248
/\ state ' = [ state EXCEPT ! [ i ] = Leader ]
249
+ \*/\ PrintT(i \cup "became leader")
240
250
/\ nextIndex ' = [ nextIndex EXCEPT ! [ i ] =
241
251
[ j \in allServers |-> Len ( log [ i ] ) + 1 ] ]
242
252
/\ matchIndex ' = [ matchIndex EXCEPT ! [ i ] =
@@ -249,31 +259,18 @@ BecomeLeader(i) ==
249
259
evoterLog |-> voterLog [ i ] ] }
250
260
/\ UNCHANGED << messages , currentTerm , votedFor , candidateVars , logVars , nodeVars >>
251
261
252
- \* Leader i receives a client request to add v to the log.
253
- ClientRequest ( i ) ==
254
- /\ state [ i ] = Leader
255
- /\ clientRequests < MaxClientRequests
256
- /\ LET entry == [ term |-> currentTerm [ i ] ,
257
- type |-> ValueEntry ,
258
- value |-> clientRequests ]
259
- newLog == Append ( log [ i ] , entry )
260
- IN /\ log ' = [ log EXCEPT ! [ i ] = newLog ]
261
- /\ clientRequests ' = clientRequests + 1
262
- /\ UNCHANGED << messages , serverVars , candidateVars ,
263
- leaderVars , commitIndex , committedLog , committedLogDecrease , nodeVars >>
264
-
265
262
\* Leader i advances its commitIndex.
266
263
\* This is done as a separate step from handling AppendEntries responses,
267
264
\* in part to minimize atomic regions, and in part so that leaders of
268
265
\* single-server clusters are able to mark entries committed.
269
266
AdvanceCommitIndex ( i ) ==
270
267
/\ state [ i ] = Leader
271
268
/\ LET \* The set of servers that agree up through index.
272
- Agree ( index ) == { i } \cup { k \in allServers :
269
+ Agree ( index ) == { i } \cup { k \in ( voters [ i ] \cup nextVoters [ i ] ) :
273
270
matchIndex [ i ] [ k ] >= index }
274
271
\* The maximum indexes for which a quorum agrees
275
272
agreeIndexes == { index \in 1 .. Len ( log [ i ] ) :
276
- Agree ( index ) \in Quorum ( votersOfServer [ i ] ) }
273
+ Agree ( index ) \in BothQuorum ( voters [ i ] , nextVoters [ i ] ) }
277
274
\* New value for commitIndex'[i]
278
275
newCommitIndex ==
279
276
IF /\ agreeIndexes /= { }
@@ -283,49 +280,64 @@ AdvanceCommitIndex(i) ==
283
280
ELSE
284
281
commitIndex [ i ]
285
282
newCommittedLog ==
286
- IF newCommitIndex > 1
283
+ IF newCommitIndex >= 1
287
284
THEN
288
285
[ j \in 1 .. newCommitIndex |-> log [ i ] [ j ] ]
289
286
ELSE
290
287
<< >>
291
- IN /\ commitIndex ' = [ commitIndex EXCEPT ! [ i ] = newCommitIndex ]
288
+ IN /\ commitIndex [ i ] /= newCommitIndex
289
+ /\ commitIndex ' = [ commitIndex EXCEPT ! [ i ] = newCommitIndex ]
292
290
/\ committedLogDecrease ' = \/ ( newCommitIndex < Len ( committedLog ) )
293
291
\/ \E j \in 1 .. Len ( committedLog ) : committedLog [ j ] /= newCommittedLog [ j ]
294
292
/\ committedLog ' = newCommittedLog
295
- /\ UNCHANGED << messages , serverVars , candidateVars , leaderVars , log , clientRequests >>
293
+ \*/\ PrintT(Len(log[i])\cup newCommitIndex)
294
+ /\ UNCHANGED << messages , serverVars , candidateVars , leaderVars , log , clientRequests , nodeVars >>
296
295
297
296
AdvanceCommitConfig ( i ) ==
298
297
/\ GetMaxConfigIndex ( i , commitIndex [ i ] ) > 0
299
298
/\ LET config == GetLatestCommitConfig ( i )
300
- IN IF votersOfServer [ i ] /= config . newConf
299
+ IN IF voters [ i ] /= config . newConf
301
300
THEN /\ \/ \* leader not in the new configuration.
302
301
\* switch to Follwer.
303
302
/\ i \notin config . newConf
304
303
/\ state [ i ] = Leader
305
304
/\ state ' = [ state EXCEPT ! [ i ] = Follower ]
306
305
\/ /\ i \in config . newConf
307
306
/\ UNCHANGED << state >>
308
- /\ votersOfServer ' = [ votersOfServer EXCEPT ! [ i ] = config . newConf ]
309
- ELSE UNCHANGED << votersOfServer >>
310
- /\ UNCHANGED << currentTerm , votedFor , candidateVars , leaderVars , logVars , nodeVars >>
307
+ /\ voters ' = [ voters EXCEPT ! [ i ] = config . newConf ]
308
+ /\ nextVoters ' = [ nextVoters EXCEPT ! [ i ] = { } ]
309
+ ELSE UNCHANGED << voters , state , nextVoters >>
310
+ /\ UNCHANGED << messages , currentTerm , votedFor , candidateVars , leaderVars , logVars , allServers >>
311
311
312
312
313
313
\* Leader i add a group servers to cluster.
314
314
SetNodes ( i , newNodes ) ==
315
315
/\ state [ i ] = Leader
316
316
/\ \* for reducing the state space, just verify once.
317
317
Len ( SelectSeq ( log [ i ] , LAMBDA logEntry : logEntry . type = ConfigEntry ) ) = 0
318
- /\ LET oldConfig == votersOfServer [ i ]
318
+ /\ LET oldConfig == voters [ i ]
319
319
entry == [ term |-> currentTerm [ i ] ,
320
320
type |-> ConfigEntry ,
321
321
newConf |-> newNodes ,
322
322
oldConf |-> oldConfig ]
323
- transitionConfig == oldConfig \cup newNodes
324
323
newLog == Append ( log [ i ] , entry )
325
- IN /\ log ' = [ log EXCEPT ! [ i ] = newLog ]
326
- /\ votersOfServer = [ votersOfServer EXCEPT ! [ i ] = transitionConfig ]
327
- /\ UNCHANGED << state , leaderVars , logVars , candidateVars , allServers >>
324
+ IN /\ log ' = [ log EXCEPT ! [ i ] = Append ( @ , entry ) ]
325
+ /\ nextVoters ' = [ nextVoters EXCEPT ! [ i ] = newNodes ]
326
+ /\ UNCHANGED << messages , state , votedFor , currentTerm , candidateVars ,
327
+ leaderVars , commitIndex , committedLog , committedLogDecrease , allServers , voters , clientRequests >>
328
328
329
+ \* Leader i receives a client request to add v to the log.
330
+ ClientRequest ( i ) ==
331
+ /\ state [ i ] = Leader
332
+ /\ clientRequests < MaxClientRequests
333
+ /\ LET entry == [ term |-> currentTerm [ i ] ,
334
+ type |-> ValueEntry ,
335
+ value |-> clientRequests ]
336
+ newLog == Append ( log [ i ] , entry )
337
+ IN /\ log ' = [ log EXCEPT ! [ i ] = newLog ]
338
+ /\ clientRequests ' = clientRequests + 1
339
+ /\ UNCHANGED << messages , serverVars , candidateVars ,
340
+ leaderVars , commitIndex , committedLog , committedLogDecrease , nodeVars >>
329
341
----
330
342
\* Message handlers
331
343
\* i = recipient, j = sender, m = message
@@ -393,12 +405,12 @@ HandleAppendEntriesRequest(i, j, m) ==
393
405
msource |-> i ,
394
406
mdest |-> j ] ,
395
407
m )
396
- /\ UNCHANGED << serverVars , logVars >>
408
+ /\ UNCHANGED << serverVars , logVars , nodeVars >>
397
409
\/ \* return to follower state
398
410
/\ m . mterm = currentTerm [ i ]
399
411
/\ state [ i ] = Candidate
400
412
/\ state ' = [ state EXCEPT ! [ i ] = Follower ]
401
- /\ UNCHANGED << currentTerm , votedFor , logVars , messages >>
413
+ /\ UNCHANGED << currentTerm , votedFor , logVars , messages , nodeVars >>
402
414
\/ \* accept request
403
415
/\ m . mterm = currentTerm [ i ]
404
416
/\ state [ i ] = Follower
@@ -422,22 +434,22 @@ HandleAppendEntriesRequest(i, j, m) ==
422
434
msource |-> i ,
423
435
mdest |-> j ] ,
424
436
m )
425
- /\ UNCHANGED << serverVars , log , clientRequests , committedLog , committedLogDecrease >>
437
+ /\ UNCHANGED << serverVars , log , clientRequests , committedLog , committedLogDecrease , nodeVars >>
426
438
\/ \* conflict: remove 1 entry
427
439
/\ m . mentries /= << >>
428
440
/\ Len ( log [ i ] ) >= index
429
441
/\ log [ i ] [ index ] . term /= m . mentries [ 1 ] . term
430
442
/\ LET new == [ index2 \in 1 .. ( Len ( log [ i ] ) - 1 ) |->
431
443
log [ i ] [ index2 ] ]
432
444
IN log ' = [ log EXCEPT ! [ i ] = new ]
433
- /\ UNCHANGED << serverVars , commitIndex , messages , clientRequests , committedLog , committedLogDecrease >>
445
+ /\ UNCHANGED << serverVars , commitIndex , messages , clientRequests , committedLog , committedLogDecrease , nodeVars >>
434
446
\/ \* no conflict: append entry
435
447
/\ m . mentries /= << >>
436
448
/\ Len ( log [ i ] ) = m . mprevLogIndex
437
449
/\ log ' = [ log EXCEPT ! [ i ] =
438
450
Append ( log [ i ] , m . mentries [ 1 ] ) ]
439
- /\ UNCHANGED << serverVars , commitIndex , messages , clientRequests , committedLog , committedLogDecrease >>
440
- /\ UNCHANGED << candidateVars , leaderVars , nodeVars >>
451
+ /\ UNCHANGED << serverVars , commitIndex , messages , clientRequests , committedLog , committedLogDecrease , nodeVars >>
452
+ /\ UNCHANGED << candidateVars , leaderVars >>
441
453
442
454
\* Servers i receives an AppendEntries response from server j with
443
455
\* m.mterm = currentTerm[i].
@@ -493,20 +505,21 @@ Next ==
493
505
\* Leader election
494
506
\/ \E i , j \in allServers : RequestVote ( i , j )
495
507
\/ \E i \in allServers : BecomeLeader ( i )
508
+ \* Network
509
+ \/ \E m \in messages : Receive ( m )
510
+ \/ \/ /\ \A i \in allServers : state [ i ] = Follower
511
+ /\ \E i \in allServers : Timeout ( i )
512
+ \/ /\ \E i \in allServers : state [ i ] /= Follower
513
+ /\ UNCHANGED << serverVars , candidateVars , messages , logVars , leaderVars , nodeVars >>
496
514
\* Client request
497
515
\/ \E i \in allServers : ClientRequest ( i )
498
516
\* Config changed with joint consensus
499
- \/ \E i \in InitialServers : SetNodes ( i , NewServers )
517
+ \/ \E i \in allServers : SetNodes ( i , NewServers )
500
518
\* Log replica
501
519
\/ \E i \in allServers : AdvanceCommitIndex ( i )
502
520
\/ \E i \in allServers : AdvanceCommitConfig ( i )
503
521
\/ \E i , j \in allServers : AppendEntries ( i , j )
504
- \* Network
505
- \/ \E m \in messages : Receive ( m )
506
- \/ \/ /\ \A i \in allServers : state [ i ] = Follower
507
- /\ \E i \in allServers : Timeout ( i )
508
- \/ /\ \E i \in allServers : state [ i ] /= Follower
509
- /\ UNCHANGED << serverVars , candidateVars , messages , leaderVars , logVars , nodeVars >>
522
+
510
523
511
524
512
525
\* The specification must start with the initial state and transition according
@@ -518,23 +531,18 @@ Spec == Init /\ [][Next]_vars
518
531
519
532
MoreThanOneLeader ==
520
533
Cardinality ( { i \in allServers : state [ i ] = Leader } ) > 1
534
+
521
535
522
536
TransitionPhaseChecker ==
523
- \A i \in allServers : LET inTransition == /\ commitIndex [ i ] < GetMaxConfigIndex ( i , Len ( log [ i ] ) )
537
+ \A i \in allServers : LET inTransition == /\ commitIndex [ i ] > 1
538
+ /\ commitIndex [ i ] < GetMaxConfigIndex ( i , Len ( log [ i ] ) )
524
539
/\ GetMaxConfigIndex ( i , Len ( log [ i ] ) ) > 0
525
540
configEntry == GetLatestConfig ( i )
526
541
IN IF inTransition THEN
527
542
IF state [ i ] = Leader THEN
528
- votersOfServer [ i ] = configEntry . oldConf \cup configEntry . newConf
529
- ELSE votersOfServer [ i ] = configEntry . oldConf
530
- ELSE TRUE
531
- CommittedPhaseChecker ==
532
- \A i \in allServers : LET committed == /\ commitIndex [ i ] > 0
533
- /\ GetMaxConfigIndex ( i , commitIndex [ i ] ) > 0
534
- /\ commitIndex [ i ] > GetMaxConfigIndex ( i , commitIndex [ i ] )
535
- configEntry == GetLatestCommitConfig ( i )
536
- IN IF committed THEN
537
- votersOfServer [ i ] = configEntry . newConf
543
+ /\ voters [ i ] = configEntry . oldConf
544
+ /\ nextVoters [ i ] = configEntry . newConf
545
+ ELSE /\ voters [ i ] = configEntry . oldConf
538
546
ELSE TRUE
539
547
540
548
===============================================================================
0 commit comments