Class: ZK::Election::Candidate

Inherits:
Base
  • Object
show all
Defined in:
lib/zk/election.rb

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

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_ballotsObject (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_electionObject (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

Returns:

  • (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_errorObject

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)

Raises:

  • (NotImplementedError)


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)

Returns:

  • (Boolean)


202
203
204
# File 'lib/zk/election.rb', line 202

def voted? #:nodoc:
  !@leader.nil?
end