Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F99923180
leader-election.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Mon, Jan 27, 07:28
Size
6 KB
Mime Type
text/x-java
Expires
Wed, Jan 29, 07:28 (2 d)
Engine
blob
Format
Raw Data
Handle
23850780
Attached To
rOACCT Open Access Compliance Check Tool (OACCT)
leader-election.js
View Options
import
{
sleep
,
randomToken
}
from
'./util.js'
;
import
unload
from
'unload'
;
var
LeaderElection
=
function
LeaderElection
(
channel
,
options
)
{
this
.
_channel
=
channel
;
this
.
_options
=
options
;
this
.
isLeader
=
false
;
this
.
isDead
=
false
;
this
.
token
=
randomToken
();
this
.
_isApl
=
false
;
// _isApplying
this
.
_reApply
=
false
;
// things to clean up
this
.
_unl
=
[];
// _unloads
this
.
_lstns
=
[];
// _listeners
this
.
_invs
=
[];
// _intervals
this
.
_dpL
=
function
()
{};
// onduplicate listener
this
.
_dpLC
=
false
;
// true when onduplicate called
};
LeaderElection
.
prototype
=
{
applyOnce
:
function
applyOnce
()
{
var
_this
=
this
;
if
(
this
.
isLeader
)
return
Promise
.
resolve
(
false
);
if
(
this
.
isDead
)
return
Promise
.
resolve
(
false
);
// do nothing if already running
if
(
this
.
_isApl
)
{
this
.
_reApply
=
true
;
return
Promise
.
resolve
(
false
);
}
this
.
_isApl
=
true
;
var
stopCriteria
=
false
;
var
recieved
=
[];
var
handleMessage
=
function
handleMessage
(
msg
)
{
if
(
msg
.
context
===
'leader'
&&
msg
.
token
!=
_this
.
token
)
{
recieved
.
push
(
msg
);
if
(
msg
.
action
===
'apply'
)
{
// other is applying
if
(
msg
.
token
>
_this
.
token
)
{
// other has higher token, stop applying
stopCriteria
=
true
;
}
}
if
(
msg
.
action
===
'tell'
)
{
// other is already leader
stopCriteria
=
true
;
}
}
};
this
.
_channel
.
addEventListener
(
'internal'
,
handleMessage
);
var
ret
=
_sendMessage
(
this
,
'apply'
)
// send out that this one is applying
.
then
(
function
()
{
return
sleep
(
_this
.
_options
.
responseTime
);
})
// let others time to respond
.
then
(
function
()
{
if
(
stopCriteria
)
return
Promise
.
reject
(
new
Error
());
else
return
_sendMessage
(
_this
,
'apply'
);
}).
then
(
function
()
{
return
sleep
(
_this
.
_options
.
responseTime
);
})
// let others time to respond
.
then
(
function
()
{
if
(
stopCriteria
)
return
Promise
.
reject
(
new
Error
());
else
return
_sendMessage
(
_this
);
}).
then
(
function
()
{
return
beLeader
(
_this
);
})
// no one disagreed -> this one is now leader
.
then
(
function
()
{
return
true
;
})[
"catch"
](
function
()
{
return
false
;
})
// apply not successfull
.
then
(
function
(
success
)
{
_this
.
_channel
.
removeEventListener
(
'internal'
,
handleMessage
);
_this
.
_isApl
=
false
;
if
(
!
success
&&
_this
.
_reApply
)
{
_this
.
_reApply
=
false
;
return
_this
.
applyOnce
();
}
else
return
success
;
});
return
ret
;
},
awaitLeadership
:
function
awaitLeadership
()
{
if
(
/* _awaitLeadershipPromise */
!
this
.
_aLP
)
{
this
.
_aLP
=
_awaitLeadershipOnce
(
this
);
}
return
this
.
_aLP
;
},
set
onduplicate
(
fn
)
{
this
.
_dpL
=
fn
;
},
die
:
function
die
()
{
var
_this2
=
this
;
if
(
this
.
isDead
)
return
;
this
.
isDead
=
true
;
this
.
_lstns
.
forEach
(
function
(
listener
)
{
return
_this2
.
_channel
.
removeEventListener
(
'internal'
,
listener
);
});
this
.
_invs
.
forEach
(
function
(
interval
)
{
return
clearInterval
(
interval
);
});
this
.
_unl
.
forEach
(
function
(
uFn
)
{
uFn
.
remove
();
});
return
_sendMessage
(
this
,
'death'
);
}
};
function
_awaitLeadershipOnce
(
leaderElector
)
{
if
(
leaderElector
.
isLeader
)
return
Promise
.
resolve
();
return
new
Promise
(
function
(
res
)
{
var
resolved
=
false
;
function
finish
()
{
if
(
resolved
)
{
return
;
}
resolved
=
true
;
clearInterval
(
interval
);
leaderElector
.
_channel
.
removeEventListener
(
'internal'
,
whenDeathListener
);
res
(
true
);
}
// try once now
leaderElector
.
applyOnce
().
then
(
function
()
{
if
(
leaderElector
.
isLeader
)
{
finish
();
}
});
// try on fallbackInterval
var
interval
=
setInterval
(
function
()
{
leaderElector
.
applyOnce
().
then
(
function
()
{
if
(
leaderElector
.
isLeader
)
{
finish
();
}
});
},
leaderElector
.
_options
.
fallbackInterval
);
leaderElector
.
_invs
.
push
(
interval
);
// try when other leader dies
var
whenDeathListener
=
function
whenDeathListener
(
msg
)
{
if
(
msg
.
context
===
'leader'
&&
msg
.
action
===
'death'
)
{
leaderElector
.
applyOnce
().
then
(
function
()
{
if
(
leaderElector
.
isLeader
)
finish
();
});
}
};
leaderElector
.
_channel
.
addEventListener
(
'internal'
,
whenDeathListener
);
leaderElector
.
_lstns
.
push
(
whenDeathListener
);
});
}
/**
* sends and internal message over the broadcast-channel
*/
function
_sendMessage
(
leaderElector
,
action
)
{
var
msgJson
=
{
context
:
'leader'
,
action
:
action
,
token
:
leaderElector
.
token
};
return
leaderElector
.
_channel
.
postInternal
(
msgJson
);
}
export
function
beLeader
(
leaderElector
)
{
leaderElector
.
isLeader
=
true
;
var
unloadFn
=
unload
.
add
(
function
()
{
return
leaderElector
.
die
();
});
leaderElector
.
_unl
.
push
(
unloadFn
);
var
isLeaderListener
=
function
isLeaderListener
(
msg
)
{
if
(
msg
.
context
===
'leader'
&&
msg
.
action
===
'apply'
)
{
_sendMessage
(
leaderElector
,
'tell'
);
}
if
(
msg
.
context
===
'leader'
&&
msg
.
action
===
'tell'
&&
!
leaderElector
.
_dpLC
)
{
/**
* another instance is also leader!
* This can happen on rare events
* like when the CPU is at 100% for long time
* or the tabs are open very long and the browser throttles them.
* @link https://github.com/pubkey/broadcast-channel/issues/414
* @link https://github.com/pubkey/broadcast-channel/issues/385
*/
leaderElector
.
_dpLC
=
true
;
leaderElector
.
_dpL
();
// message the lib user so the app can handle the problem
_sendMessage
(
leaderElector
,
'tell'
);
// ensure other leader also knows the problem
}
};
leaderElector
.
_channel
.
addEventListener
(
'internal'
,
isLeaderListener
);
leaderElector
.
_lstns
.
push
(
isLeaderListener
);
return
_sendMessage
(
leaderElector
,
'tell'
);
}
function
fillOptionsWithDefaults
(
options
,
channel
)
{
if
(
!
options
)
options
=
{};
options
=
JSON
.
parse
(
JSON
.
stringify
(
options
));
if
(
!
options
.
fallbackInterval
)
{
options
.
fallbackInterval
=
3000
;
}
if
(
!
options
.
responseTime
)
{
options
.
responseTime
=
channel
.
method
.
averageResponseTime
(
channel
.
options
);
}
return
options
;
}
export
function
createLeaderElection
(
channel
,
options
)
{
if
(
channel
.
_leaderElector
)
{
throw
new
Error
(
'BroadcastChannel already has a leader-elector'
);
}
options
=
fillOptionsWithDefaults
(
options
,
channel
);
var
elector
=
new
LeaderElection
(
channel
,
options
);
channel
.
_befC
.
push
(
function
()
{
return
elector
.
die
();
});
channel
.
_leaderElector
=
elector
;
return
elector
;
}
Event Timeline
Log In to Comment