@@ -31,7 +31,6 @@ const LeaderElection = function (broadcastChannel, options) {
31
31
// things to clean up
32
32
this . _unl = [ ] ; // _unloads
33
33
this . _lstns = [ ] ; // _listeners
34
- this . _invs = [ ] ; // _intervals
35
34
this . _dpL = ( ) => { } ; // onduplicate listener
36
35
this . _dpLC = false ; // true when onduplicate called
37
36
@@ -61,7 +60,10 @@ LeaderElection.prototype = {
61
60
* false if not.
62
61
* @async
63
62
*/
64
- applyOnce ( ) {
63
+ applyOnce (
64
+ // true if the applyOnce() call came from the fallbackInterval cycle
65
+ isFromFallbackInterval
66
+ ) {
65
67
if ( this . isLeader ) {
66
68
return sleep ( 0 , true ) ;
67
69
}
@@ -126,16 +128,29 @@ LeaderElection.prototype = {
126
128
}
127
129
} ;
128
130
this . broadcastChannel . addEventListener ( 'internal' , handleMessage ) ;
131
+
132
+ /**
133
+ * If the applyOnce() call came from the fallbackInterval,
134
+ * we can assume that the election runs in the background and
135
+ * not critical process is waiting for it.
136
+ * When this is true, we give the other intances
137
+ * more time to answer to messages in the election cycle.
138
+ * This makes it less likely to elect duplicate leaders.
139
+ * But also it takes longer which is not a problem because we anyway
140
+ * run in the background.
141
+ */
142
+ const waitForAnswerTime = isFromFallbackInterval ? this . _options . responseTime * 4 : this . _options . responseTime ;
143
+
129
144
const applyPromise = _sendMessage ( this , 'apply' ) // send out that this one is applying
130
145
. then ( ( ) => Promise . race ( [
131
- sleep ( this . _options . responseTime / 2 ) ,
146
+ sleep ( waitForAnswerTime ) ,
132
147
stopCriteriaPromise . then ( ( ) => Promise . reject ( new Error ( ) ) )
133
148
] ) )
134
149
// send again in case another instance was just created
135
150
. then ( ( ) => _sendMessage ( this , 'apply' ) )
136
151
// let others time to respond
137
152
. then ( ( ) => Promise . race ( [
138
- sleep ( this . _options . responseTime / 2 ) ,
153
+ sleep ( waitForAnswerTime ) ,
139
154
stopCriteriaPromise . then ( ( ) => Promise . reject ( new Error ( ) ) )
140
155
] ) )
141
156
. catch ( ( ) => { } )
@@ -177,8 +192,6 @@ LeaderElection.prototype = {
177
192
die ( ) {
178
193
this . _lstns . forEach ( listener => this . broadcastChannel . removeEventListener ( 'internal' , listener ) ) ;
179
194
this . _lstns = [ ] ;
180
- this . _invs . forEach ( interval => clearInterval ( interval ) ) ;
181
- this . _invs = [ ] ;
182
195
this . _unl . forEach ( uFn => uFn . remove ( ) ) ;
183
196
this . _unl = [ ] ;
184
197
@@ -207,7 +220,6 @@ function _awaitLeadershipOnce(leaderElector) {
207
220
return ;
208
221
}
209
222
resolved = true ;
210
- clearInterval ( interval ) ;
211
223
leaderElector . broadcastChannel . removeEventListener ( 'internal' , whenDeathListener ) ;
212
224
res ( true ) ;
213
225
}
@@ -219,15 +231,32 @@ function _awaitLeadershipOnce(leaderElector) {
219
231
}
220
232
} ) ;
221
233
222
- // try on fallbackInterval
223
- const interval = setInterval ( ( ) => {
224
- leaderElector . applyOnce ( ) . then ( ( ) => {
225
- if ( leaderElector . isLeader ) {
226
- finish ( ) ;
227
- }
228
- } ) ;
229
- } , leaderElector . _options . fallbackInterval ) ;
230
- leaderElector . _invs . push ( interval ) ;
234
+ /**
235
+ * Try on fallbackInterval
236
+ * @recursive
237
+ */
238
+ const tryOnFallBack = ( ) => {
239
+ return sleep ( leaderElector . _options . fallbackInterval )
240
+ . then ( ( ) => {
241
+ if ( leaderElector . isDead || resolved ) {
242
+ return ;
243
+ }
244
+ if ( leaderElector . isLeader ) {
245
+ finish ( ) ;
246
+ } else {
247
+ return leaderElector
248
+ . applyOnce ( true )
249
+ . then ( ( ) => {
250
+ if ( leaderElector . isLeader ) {
251
+ finish ( ) ;
252
+ } else {
253
+ tryOnFallBack ( ) ;
254
+ }
255
+ } ) ;
256
+ }
257
+ } ) ;
258
+ } ;
259
+ tryOnFallBack ( ) ;
231
260
232
261
// try when other leader dies
233
262
const whenDeathListener = msg => {
0 commit comments