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, #synchronize, #vote_basename
Constructor Details
#initialize(client, name, opts = {}) ⇒ Candidate
Returns a new instance of Candidate.
183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/zk/election.rb', line 183 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
241 242 243 |
# File 'lib/zk/election.rb', line 241 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
255 256 257 258 259 260 261 262 263 264 265 266 267 268 |
# File 'lib/zk/election.rb', line 255 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)
310 311 312 313 314 315 |
# File 'lib/zk/election.rb', line 310 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)
321 322 323 |
# File 'lib/zk/election.rb', line 321 def fire_losing_callbacks! safe_call(*@loser_callbacks) end |
#fire_winning_callbacks! ⇒ Object (protected)
317 318 319 |
# File 'lib/zk/election.rb', line 317 def fire_winning_callbacks! safe_call(*@winner_callbacks) end |
#get_ballots ⇒ Object (protected)
return the list of ephemeral vote nodes
246 247 248 249 250 |
# File 'lib/zk/election.rb', line 246 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)
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 301 302 303 304 305 306 307 308 |
# File 'lib/zk/election.rb', line 276 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.register(next_ballot) do |event| if event.node_deleted? logger.debug { "#{next_ballot} was deleted, voting, #{@data.inspect}" } @zk.defer { 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}" } @zk.defer { 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" } @zk.defer { vote! } end end end |
#handle_winning_election ⇒ Object (protected)
270 271 272 273 274 |
# File 'lib/zk/election.rb', line 270 def handle_winning_election @leader = true fire_winning_callbacks! acknowledge_win! end |
#leader? ⇒ Boolean
197 198 199 |
# File 'lib/zk/election.rb', line 197 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)
215 216 217 |
# File 'lib/zk/election.rb', line 215 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)
223 224 225 |
# File 'lib/zk/election.rb', line 223 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.
208 209 210 |
# File 'lib/zk/election.rb', line 208 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
231 232 233 234 235 236 237 |
# File 'lib/zk/election.rb', line 231 def vote! 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)
202 203 204 |
# File 'lib/zk/election.rb', line 202 def voted? #:nodoc: !@leader.nil? end |