Class: ZK::Election::Candidate
Overview
This class is for registering candidates in the leader election. This instance will participate in votes for becoming the leader and will be notified in the case where it needs to take over.
if data is given, it will be used as the content of both our ballot and the leader acknowledgement node if and when we become the leader.
Instance Attribute Summary
Attributes inherited from Base
#root_election_node, #vote_path, #zk
Instance Method Summary collapse
-
#acknowledge_win! ⇒ Object
protected
the inauguration, as it were.
-
#check_election_results! ⇒ Object
protected
if
watch_next
is true, we register a watcher for the next-lowest index number in the list of ballots. - #clear_next_node_ballot_sub! ⇒ Object protected
- #fire_losing_callbacks! ⇒ Object protected
- #fire_winning_callbacks! ⇒ Object protected
-
#get_ballots ⇒ Object
protected
return the list of ephemeral vote nodes.
- #handle_losing_election(our_idx, ballots) ⇒ Object protected
- #handle_winning_election ⇒ Object protected
-
#initialize(client, name, opts = {}) ⇒ Candidate
constructor
A new instance of Candidate.
- #leader? ⇒ Boolean
-
#on_losing_election(&block) ⇒ Object
When we lose the election and are relegated to the shadows, waiting for the leader to make one small misstep, where we can finally claim what is rightfully ours! MWUAHAHAHAHAHA(cough).
-
#on_takeover_error ⇒ Object
These procs should be run in the case of an error when trying to assume the leadership role.
-
#on_winning_election(&block) ⇒ Object
When we win the election, we will call the procs registered using this method.
-
#vote! ⇒ Object
volunteer to become the leader.
-
#voted? ⇒ Boolean
true if leader has been determined at least once (used in tests).
Methods inherited from Base
#cast_ballot!, #create_root_path!, #digit, #leader_ack_path, #leader_acked?, #leader_data, #on_leader_ack, #root_vote_path, #safe_call, #vote_basename
Constructor Details
#initialize(client, name, opts = {}) ⇒ Candidate
Returns a new instance of Candidate.
175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/z_k/election.rb', line 175 def initialize(client, name, opts={}) super(client, name, opts) opts = DEFAULT_OPTS.merge(opts) @leader = nil @data = opts[:data] || '' @vote_path = nil @winner_callbacks = [] @loser_callbacks = [] @next_node_ballot_sub = nil # the subscription for next-node failure end |
Instance Method Details
#acknowledge_win! ⇒ Object (protected)
the inauguration, as it were
233 234 235 |
# File 'lib/z_k/election.rb', line 233 def acknowledge_win! @zk.create(leader_ack_path, @data, :ephemeral => true) rescue Exceptions::NodeExists end |
#check_election_results! ⇒ Object (protected)
if watch_next
is true, we register a watcher for the next-lowest index number in the list of ballots
247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/z_k/election.rb', line 247 def check_election_results! #return if leader? # we already know we're the leader ballots = get_ballots() our_idx = ballots.index(vote_basename) if our_idx == 0 # if we have the lowest number logger.info { "ZK: We have become leader, data: #{@data.inspect}" } handle_winning_election else logger.info { "ZK: we are not the leader, data: #{@data.inspect}" } handle_losing_election(our_idx, ballots) end end |
#clear_next_node_ballot_sub! ⇒ Object (protected)
302 303 304 305 306 307 |
# File 'lib/z_k/election.rb', line 302 def clear_next_node_ballot_sub! if @next_node_ballot_sub @next_node_ballot_sub.unsubscribe @next_node_ballot_sub = nil end end |
#fire_losing_callbacks! ⇒ Object (protected)
313 314 315 |
# File 'lib/z_k/election.rb', line 313 def fire_losing_callbacks! safe_call(*@loser_callbacks) end |
#fire_winning_callbacks! ⇒ Object (protected)
309 310 311 |
# File 'lib/z_k/election.rb', line 309 def fire_winning_callbacks! safe_call(*@winner_callbacks) end |
#get_ballots ⇒ Object (protected)
return the list of ephemeral vote nodes
238 239 240 241 242 |
# File 'lib/z_k/election.rb', line 238 def get_ballots @zk.children(root_vote_path).grep(/^ballot/).tap do |ballots| ballots.sort! {|a,b| digit(a) <=> digit(b) } end end |
#handle_losing_election(our_idx, ballots) ⇒ Object (protected)
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 |
# File 'lib/z_k/election.rb', line 268 def handle_losing_election(our_idx, ballots) @leader = false on_leader_ack do fire_losing_callbacks! next_ballot = File.join(root_vote_path, ballots[our_idx - 1]) logger.info { "ZK: following #{next_ballot} for changes, #{@data.inspect}" } @next_node_ballot_sub ||= @zk.watcher.register(next_ballot) do |event| if event.node_deleted? logger.debug { "#{next_ballot} was deleted, voting, #{@data.inspect}" } vote! else # this takes care of the race condition where the leader ballot would # have been deleted before we could re-register to receive updates # if zk.stat returns false, it means the path was deleted unless @zk.exists?(next_ballot, :watch => true) logger.debug { "#{next_ballot} was deleted (detected on re-watch), voting, #{@data.inspect}" } vote! end end end # this catches a possible race condition, where the leader has died before # our callback has fired. In this case, retry and do this procedure again unless @zk.stat(next_ballot, :watch => true).exists? logger.debug { "#{@data.inspect}: the node #{next_ballot} did not exist, retrying" } vote! end end end |
#handle_winning_election ⇒ Object (protected)
262 263 264 265 266 |
# File 'lib/z_k/election.rb', line 262 def handle_winning_election @leader = true fire_winning_callbacks! acknowledge_win! end |
#leader? ⇒ Boolean
189 190 191 |
# File 'lib/z_k/election.rb', line 189 def leader? false|@leader end |
#on_losing_election(&block) ⇒ Object
When we lose the election and are relegated to the shadows, waiting for the leader to make one small misstep, where we can finally claim what is rightfully ours! MWUAHAHAHAHAHA(cough)
207 208 209 |
# File 'lib/z_k/election.rb', line 207 def on_losing_election(&block) @loser_callbacks << block end |
#on_takeover_error ⇒ Object
These procs should be run in the case of an error when trying to assume the leadership role. This should probably be a “hara-kiri” or STONITH type procedure (i.e. kill the candidate)
215 216 217 |
# File 'lib/z_k/election.rb', line 215 def on_takeover_error #:nodoc: raise NotImplementedError end |
#on_winning_election(&block) ⇒ Object
When we win the election, we will call the procs registered using this method.
200 201 202 |
# File 'lib/z_k/election.rb', line 200 def on_winning_election(&block) @winner_callbacks << block end |
#vote! ⇒ Object
volunteer to become the leader. if we win, on_winning_election blocks will be called, otherwise, wait for next election
data
will be placed in the znode representing our vote
223 224 225 226 227 228 229 |
# File 'lib/z_k/election.rb', line 223 def vote! @mutex.synchronize do clear_next_node_ballot_sub! cast_ballot!(@data) unless @vote_path check_election_results! end end |
#voted? ⇒ Boolean
true if leader has been determined at least once (used in tests)
194 195 196 |
# File 'lib/z_k/election.rb', line 194 def voted? #:nodoc: !@leader.nil? end |