pontoon 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/lib/pontoon.rb +536 -0
 - data/lib/pontoon/goliath.rb +248 -0
 - metadata +115 -0
 
    
        checksums.yaml
    ADDED
    
    | 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ---
         
     | 
| 
      
 2 
     | 
    
         
            +
            SHA1:
         
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 3d547ad0a54b22d03d8862391c8dfacdffb59fa0
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: f0140570f9bc1ad3c756748bf3e2d56ccb8972d7
         
     | 
| 
      
 5 
     | 
    
         
            +
            SHA512:
         
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 4dfe51bc72db1c90793fd6da0f69bdbca28a2b71360134dbb222bc27b8deed8960b2fde0a71f37222b55154d4f3dbf2260f753cd4ebc27a91005b452f6440ad9
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: ed25f5d79a2f4d5526424c3c38fb1a9c59519c42461ac82755f86e8b640714288f633ca5479dfff4c544f9e966041a977daf7ee670acda569734ff563dcccee6
         
     | 
    
        data/lib/pontoon.rb
    ADDED
    
    | 
         @@ -0,0 +1,536 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'delegate'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Pontoon
         
     | 
| 
      
 4 
     | 
    
         
            +
              Config = Struct.new(:rpc_provider, :async_provider, :election_timeout, :election_splay, :update_interval, :heartbeat_interval)
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              class Cluster
         
     | 
| 
      
 7 
     | 
    
         
            +
                attr_reader :node_ids
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def initialize(*node_ids)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @node_ids = node_ids
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def quorum
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @node_ids.count / 2 + 1 # integer division rounds down
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              class LogEntry
         
     | 
| 
      
 19 
     | 
    
         
            +
                attr_reader :term, :index, :command
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def initialize(term, index, command)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @term, @index, @command = term, index, command
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def ==(other)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  [:term, :index, :command].all? do |attr|
         
     | 
| 
      
 27 
     | 
    
         
            +
                    self.send(attr) == other.send(attr)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                def eql?(other)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  self == other
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                def hash
         
     | 
| 
      
 36 
     | 
    
         
            +
                  [:term, :index, :command].reduce(0) do |h, attr|
         
     | 
| 
      
 37 
     | 
    
         
            +
                    h ^= self.send(attr)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
              end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
              class Log < DelegateClass(Array)
         
     | 
| 
      
 43 
     | 
    
         
            +
                def last(*args)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  self.any? ? super(*args) : LogEntry.new(nil, nil, nil)
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
              class PersistentState #= Struct.new(:current_term, :voted_for, :log)
         
     | 
| 
      
 49 
     | 
    
         
            +
                attr_reader :current_term, :voted_for, :log
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 52 
     | 
    
         
            +
                  @current_term = 0
         
     | 
| 
      
 53 
     | 
    
         
            +
                  @voted_for = nil
         
     | 
| 
      
 54 
     | 
    
         
            +
                  @log = Log.new([])
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                def current_term=(new_term)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  raise 'cannot restart an old term' unless @current_term < new_term
         
     | 
| 
      
 59 
     | 
    
         
            +
                  @current_term = new_term
         
     | 
| 
      
 60 
     | 
    
         
            +
                  @voted_for = nil
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                def voted_for=(new_votee)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  raise 'cannot change vote for this term' unless @voted_for.nil?
         
     | 
| 
      
 65 
     | 
    
         
            +
                  @voted_for = new_votee
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                def log=(new_log)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  @log = Log.new(new_log)
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
              end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
              class TemporaryState
         
     | 
| 
      
 74 
     | 
    
         
            +
                attr_reader :commit_index
         
     | 
| 
      
 75 
     | 
    
         
            +
                attr_accessor :leader_id
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                def initialize(commit_index, leader_id)
         
     | 
| 
      
 78 
     | 
    
         
            +
                  @commit_index, @leader_id = commit_index, leader_id
         
     | 
| 
      
 79 
     | 
    
         
            +
                end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                def commit_index=(new_commit_index)
         
     | 
| 
      
 82 
     | 
    
         
            +
                  raise 'cannot uncommit log entries' unless @commit_index.nil? || @commit_index <= new_commit_index
         
     | 
| 
      
 83 
     | 
    
         
            +
                  @commit_index = new_commit_index
         
     | 
| 
      
 84 
     | 
    
         
            +
                end
         
     | 
| 
      
 85 
     | 
    
         
            +
              end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
              class LeadershipState
         
     | 
| 
      
 88 
     | 
    
         
            +
                def followers
         
     | 
| 
      
 89 
     | 
    
         
            +
                  @followers ||= {}
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                attr_reader :update_timer
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                def initialize(update_interval)
         
     | 
| 
      
 95 
     | 
    
         
            +
                  @update_timer = Timer.new(update_interval)
         
     | 
| 
      
 96 
     | 
    
         
            +
                end
         
     | 
| 
      
 97 
     | 
    
         
            +
              end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
              FollowerState = Struct.new(:next_index, :succeeded)
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
              RequestVoteRequest = Struct.new(:term, :candidate_id, :last_log_index, :last_log_term)
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
              #class RequestVoteRequest < Struct.new(:term, :candidate_id, :last_log_index, :last_log_term)
         
     | 
| 
      
 104 
     | 
    
         
            +
              #  def term; @term.to_i; end
         
     | 
| 
      
 105 
     | 
    
         
            +
              #  def last_log_index; @last_log_index.to_i; end
         
     | 
| 
      
 106 
     | 
    
         
            +
              #  def last_log_term; @last_log_term.to_i; end
         
     | 
| 
      
 107 
     | 
    
         
            +
              #end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
              RequestVoteResponse = Struct.new(:term, :vote_granted)
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
              #class RequestVoteResponse < Struct.new(:term, :vote_granted)
         
     | 
| 
      
 112 
     | 
    
         
            +
              #  def term; @term.to_i; end
         
     | 
| 
      
 113 
     | 
    
         
            +
              #end
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
              AppendEntriesRequest = Struct.new(:term, :leader_id, :prev_log_index, :prev_log_term, :entries, :commit_index)
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
              AppendEntriesResponse = Struct.new(:term, :success)
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
              CommandRequest = Struct.new(:command)
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
              CommandResponse = Struct.new(:success)
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
              class RpcProvider
         
     | 
| 
      
 124 
     | 
    
         
            +
                def request_votes(request, cluster)
         
     | 
| 
      
 125 
     | 
    
         
            +
                  raise "Your RpcProvider subclass must implement #request_votes"
         
     | 
| 
      
 126 
     | 
    
         
            +
                end
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                def append_entries(request, cluster)
         
     | 
| 
      
 129 
     | 
    
         
            +
                  raise "Your RpcProvider subclass must implement #append_entries"
         
     | 
| 
      
 130 
     | 
    
         
            +
                end
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                def append_entries_to_follower(request, node_id)
         
     | 
| 
      
 133 
     | 
    
         
            +
                  raise "Your RpcProvider subclass must implement #append_entries_to_follower"
         
     | 
| 
      
 134 
     | 
    
         
            +
                end
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
                def command(request, node_id)
         
     | 
| 
      
 137 
     | 
    
         
            +
                  raise "Your RpcProvider subclass must implement #command"
         
     | 
| 
      
 138 
     | 
    
         
            +
                end
         
     | 
| 
      
 139 
     | 
    
         
            +
              end
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
              class AsyncProvider
         
     | 
| 
      
 142 
     | 
    
         
            +
                def await
         
     | 
| 
      
 143 
     | 
    
         
            +
                  raise "Your AsyncProvider subclass must implement #await"
         
     | 
| 
      
 144 
     | 
    
         
            +
                end
         
     | 
| 
      
 145 
     | 
    
         
            +
              end
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
              class Timer
         
     | 
| 
      
 148 
     | 
    
         
            +
                def initialize(interval, splay=0.0)
         
     | 
| 
      
 149 
     | 
    
         
            +
                  @interval = interval.to_f
         
     | 
| 
      
 150 
     | 
    
         
            +
                  @splay = splay.to_f
         
     | 
| 
      
 151 
     | 
    
         
            +
                  @start = Time.now - @interval + (rand * @splay)
         
     | 
| 
      
 152 
     | 
    
         
            +
                end
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                def splayed_interval
         
     | 
| 
      
 155 
     | 
    
         
            +
                  (@interval + (rand * @splay))#.tap {|t|STDOUT.write("\nsplayed interval is #{t}\n")}
         
     | 
| 
      
 156 
     | 
    
         
            +
                end
         
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
                def reset!
         
     | 
| 
      
 159 
     | 
    
         
            +
                  @start = Time.now + splayed_interval
         
     | 
| 
      
 160 
     | 
    
         
            +
                  #STDOUT.write("\ntimer will elapse at #{timeout.strftime('%H:%M:%S:%L')} (timeout is #{timeout.class})\n")
         
     | 
| 
      
 161 
     | 
    
         
            +
                end
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
                def timeout
         
     | 
| 
      
 164 
     | 
    
         
            +
                  @start + @interval
         
     | 
| 
      
 165 
     | 
    
         
            +
                end
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
                def timed_out?
         
     | 
| 
      
 168 
     | 
    
         
            +
                  #STDOUT.write("\ntime is #{Time.now.strftime('%M:%S:%L')}\n")
         
     | 
| 
      
 169 
     | 
    
         
            +
                  Time.now > timeout
         
     | 
| 
      
 170 
     | 
    
         
            +
                end
         
     | 
| 
      
 171 
     | 
    
         
            +
              end
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
              class Node
         
     | 
| 
      
 174 
     | 
    
         
            +
                attr_reader :id
         
     | 
| 
      
 175 
     | 
    
         
            +
                attr_reader :role
         
     | 
| 
      
 176 
     | 
    
         
            +
                attr_reader :config
         
     | 
| 
      
 177 
     | 
    
         
            +
                attr_reader :cluster
         
     | 
| 
      
 178 
     | 
    
         
            +
                attr_reader :persistent_state
         
     | 
| 
      
 179 
     | 
    
         
            +
                attr_reader :temporary_state
         
     | 
| 
      
 180 
     | 
    
         
            +
                attr_reader :election_timer
         
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
                FOLLOWER_ROLE = 0
         
     | 
| 
      
 183 
     | 
    
         
            +
                CANDIDATE_ROLE = 1
         
     | 
| 
      
 184 
     | 
    
         
            +
                LEADER_ROLE = 2
         
     | 
| 
      
 185 
     | 
    
         
            +
             
     | 
| 
      
 186 
     | 
    
         
            +
                def initialize(id, config, cluster, commit_handler=nil, &block)
         
     | 
| 
      
 187 
     | 
    
         
            +
                  @id = id
         
     | 
| 
      
 188 
     | 
    
         
            +
                  @role = FOLLOWER_ROLE
         
     | 
| 
      
 189 
     | 
    
         
            +
                  @config = config
         
     | 
| 
      
 190 
     | 
    
         
            +
                  @cluster = cluster
         
     | 
| 
      
 191 
     | 
    
         
            +
                  @persistent_state = PersistentState.new
         
     | 
| 
      
 192 
     | 
    
         
            +
                  @temporary_state = TemporaryState.new(nil, nil)
         
     | 
| 
      
 193 
     | 
    
         
            +
                  @election_timer = Timer.new(config.election_timeout, config.election_splay)
         
     | 
| 
      
 194 
     | 
    
         
            +
                  @commit_handler = commit_handler || (block.to_proc if block_given?)
         
     | 
| 
      
 195 
     | 
    
         
            +
                end
         
     | 
| 
      
 196 
     | 
    
         
            +
             
     | 
| 
      
 197 
     | 
    
         
            +
                def update
         
     | 
| 
      
 198 
     | 
    
         
            +
                  return if @updating
         
     | 
| 
      
 199 
     | 
    
         
            +
                  @updating = true
         
     | 
| 
      
 200 
     | 
    
         
            +
                  indent = "\t" * (@id.to_i % 3)
         
     | 
| 
      
 201 
     | 
    
         
            +
                  #STDOUT.write("\n\n#{indent}update #{@id}, role #{@role}, log length #{@persistent_state.log.count}\n\n")
         
     | 
| 
      
 202 
     | 
    
         
            +
                  case @role
         
     | 
| 
      
 203 
     | 
    
         
            +
                  when FOLLOWER_ROLE
         
     | 
| 
      
 204 
     | 
    
         
            +
                    follower_update
         
     | 
| 
      
 205 
     | 
    
         
            +
                  when CANDIDATE_ROLE
         
     | 
| 
      
 206 
     | 
    
         
            +
                    candidate_update
         
     | 
| 
      
 207 
     | 
    
         
            +
                  when LEADER_ROLE
         
     | 
| 
      
 208 
     | 
    
         
            +
                    leader_update
         
     | 
| 
      
 209 
     | 
    
         
            +
                  end
         
     | 
| 
      
 210 
     | 
    
         
            +
                  @updating = false
         
     | 
| 
      
 211 
     | 
    
         
            +
                end
         
     | 
| 
      
 212 
     | 
    
         
            +
             
     | 
| 
      
 213 
     | 
    
         
            +
                def follower_update
         
     | 
| 
      
 214 
     | 
    
         
            +
                  if @election_timer.timed_out?
         
     | 
| 
      
 215 
     | 
    
         
            +
                    #STDOUT.write("follower node #{@id} election timed out at #{Time.now.strftime('%H:%M:%S:%L')}\n")
         
     | 
| 
      
 216 
     | 
    
         
            +
                    @role = CANDIDATE_ROLE
         
     | 
| 
      
 217 
     | 
    
         
            +
                    candidate_update
         
     | 
| 
      
 218 
     | 
    
         
            +
                  end
         
     | 
| 
      
 219 
     | 
    
         
            +
                end
         
     | 
| 
      
 220 
     | 
    
         
            +
             
     | 
| 
      
 221 
     | 
    
         
            +
                protected :follower_update
         
     | 
| 
      
 222 
     | 
    
         
            +
             
     | 
| 
      
 223 
     | 
    
         
            +
                def candidate_update
         
     | 
| 
      
 224 
     | 
    
         
            +
                  if @election_timer.timed_out?
         
     | 
| 
      
 225 
     | 
    
         
            +
                    #STDOUT.write("candidate node #{@id} election timed out at #{Time.now.strftime('%H:%M:%S:%L')}\n")
         
     | 
| 
      
 226 
     | 
    
         
            +
                    @persistent_state.current_term += 1
         
     | 
| 
      
 227 
     | 
    
         
            +
                    @persistent_state.voted_for = @id
         
     | 
| 
      
 228 
     | 
    
         
            +
                    reset_election_timeout
         
     | 
| 
      
 229 
     | 
    
         
            +
                    last_log_entry = @persistent_state.log.last
         
     | 
| 
      
 230 
     | 
    
         
            +
                    log_index = last_log_entry ? last_log_entry.index : nil
         
     | 
| 
      
 231 
     | 
    
         
            +
                    log_term = last_log_entry ? last_log_entry.term : nil
         
     | 
| 
      
 232 
     | 
    
         
            +
                    request = RequestVoteRequest.new(@persistent_state.current_term, @id, log_index, log_term)
         
     | 
| 
      
 233 
     | 
    
         
            +
                    votes_for = 1 # candidate always votes for self
         
     | 
| 
      
 234 
     | 
    
         
            +
                    votes_against = 0
         
     | 
| 
      
 235 
     | 
    
         
            +
                    quorum = @cluster.quorum
         
     | 
| 
      
 236 
     | 
    
         
            +
                    #STDOUT.write("\n\t\t#{@id} requests votes for term #{@persistent_state.current_term}\n\n")
         
     | 
| 
      
 237 
     | 
    
         
            +
                    @config.rpc_provider.request_votes(request, @cluster) do |voter_id, request, response|
         
     | 
| 
      
 238 
     | 
    
         
            +
                      #STDOUT.write("\n\t\t#{@id} receives vote #{response.vote_granted} from #{voter_id}\n\n")
         
     | 
| 
      
 239 
     | 
    
         
            +
                      elected = nil # no majority result yet
         
     | 
| 
      
 240 
     | 
    
         
            +
                      if request.term != @persistent_state.current_term
         
     | 
| 
      
 241 
     | 
    
         
            +
                        # this is a response to an out-of-date request, just ignore it
         
     | 
| 
      
 242 
     | 
    
         
            +
                      elsif response.term > @persistent_state.current_term
         
     | 
| 
      
 243 
     | 
    
         
            +
                        @role = FOLLOWER_ROLE
         
     | 
| 
      
 244 
     | 
    
         
            +
                        elected = false
         
     | 
| 
      
 245 
     | 
    
         
            +
                      elsif response.vote_granted
         
     | 
| 
      
 246 
     | 
    
         
            +
                        votes_for += 1
         
     | 
| 
      
 247 
     | 
    
         
            +
                        elected = true if votes_for >= quorum
         
     | 
| 
      
 248 
     | 
    
         
            +
                      else
         
     | 
| 
      
 249 
     | 
    
         
            +
                        votes_against += 1
         
     | 
| 
      
 250 
     | 
    
         
            +
                        elected = false if votes_against >= quorum
         
     | 
| 
      
 251 
     | 
    
         
            +
                      end
         
     | 
| 
      
 252 
     | 
    
         
            +
                      #STDOUT.write("\n\t\t#{@id} receives vote #{response.vote_granted}, elected is #{elected.inspect}\n\n")
         
     | 
| 
      
 253 
     | 
    
         
            +
                      elected
         
     | 
| 
      
 254 
     | 
    
         
            +
                    end
         
     | 
| 
      
 255 
     | 
    
         
            +
                    if votes_for >= quorum
         
     | 
| 
      
 256 
     | 
    
         
            +
                      #STDOUT.write("\n#{@id} becomes leader for term #{@persistent_state.current_term}\n\n")
         
     | 
| 
      
 257 
     | 
    
         
            +
                      @role = LEADER_ROLE
         
     | 
| 
      
 258 
     | 
    
         
            +
                      establish_leadership
         
     | 
| 
      
 259 
     | 
    
         
            +
                    else
         
     | 
| 
      
 260 
     | 
    
         
            +
                      #STDOUT.write("\n\t\t#{@id} not elected leader (for #{votes_for}, against #{votes_against})\n\n")
         
     | 
| 
      
 261 
     | 
    
         
            +
                    end
         
     | 
| 
      
 262 
     | 
    
         
            +
                  end
         
     | 
| 
      
 263 
     | 
    
         
            +
                end
         
     | 
| 
      
 264 
     | 
    
         
            +
             
     | 
| 
      
 265 
     | 
    
         
            +
                protected :candidate_update
         
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
      
 267 
     | 
    
         
            +
                def leader_update
         
     | 
| 
      
 268 
     | 
    
         
            +
                  #STDOUT.write("\nLEADER UPDATE BEGINS\n")
         
     | 
| 
      
 269 
     | 
    
         
            +
                  if @leadership_state.update_timer.timed_out?
         
     | 
| 
      
 270 
     | 
    
         
            +
                    @leadership_state.update_timer.reset!
         
     | 
| 
      
 271 
     | 
    
         
            +
                    send_heartbeats
         
     | 
| 
      
 272 
     | 
    
         
            +
                  end
         
     | 
| 
      
 273 
     | 
    
         
            +
                  if @leadership_state.followers.any?
         
     | 
| 
      
 274 
     | 
    
         
            +
                    new_commit_index = @leadership_state.followers.values.
         
     | 
| 
      
 275 
     | 
    
         
            +
                        select { |follower_state| follower_state.succeeded }.
         
     | 
| 
      
 276 
     | 
    
         
            +
                        map { |follower_state| follower_state.next_index - 1 }.
         
     | 
| 
      
 277 
     | 
    
         
            +
                        sort[@cluster.quorum - 1]
         
     | 
| 
      
 278 
     | 
    
         
            +
                  else
         
     | 
| 
      
 279 
     | 
    
         
            +
                    new_commit_index = @persistent_state.log.size - 1
         
     | 
| 
      
 280 
     | 
    
         
            +
                  end
         
     | 
| 
      
 281 
     | 
    
         
            +
                  handle_commits(new_commit_index)
         
     | 
| 
      
 282 
     | 
    
         
            +
                  #STDOUT.write("\nLEADER UPDATE ENDS\n")
         
     | 
| 
      
 283 
     | 
    
         
            +
                end
         
     | 
| 
      
 284 
     | 
    
         
            +
             
     | 
| 
      
 285 
     | 
    
         
            +
                protected :leader_update
         
     | 
| 
      
 286 
     | 
    
         
            +
             
     | 
| 
      
 287 
     | 
    
         
            +
                def handle_commits(new_commit_index)
         
     | 
| 
      
 288 
     | 
    
         
            +
                  #STDOUT.write("\nnode #{@id} handle_commits(new_commit_index = #{new_commit_index}) (@temporary_state.commit_index = #{@temporary_state.commit_index}\n")
         
     | 
| 
      
 289 
     | 
    
         
            +
                  return if new_commit_index == @temporary_state.commit_index
         
     | 
| 
      
 290 
     | 
    
         
            +
                  next_commit = @temporary_state.commit_index.nil? ? 0 : @temporary_state.commit_index + 1
         
     | 
| 
      
 291 
     | 
    
         
            +
                  while next_commit <= new_commit_index
         
     | 
| 
      
 292 
     | 
    
         
            +
                    @commit_handler.call(@persistent_state.log[next_commit].command) if @commit_handler
         
     | 
| 
      
 293 
     | 
    
         
            +
                    @temporary_state.commit_index = next_commit
         
     | 
| 
      
 294 
     | 
    
         
            +
                    next_commit += 1
         
     | 
| 
      
 295 
     | 
    
         
            +
                    #STDOUT.write("\n\tnode #{@id} handle_commits(new_commit_index = #{new_commit_index}) (new @temporary_state.commit_index = #{@temporary_state.commit_index}\n")
         
     | 
| 
      
 296 
     | 
    
         
            +
                  end
         
     | 
| 
      
 297 
     | 
    
         
            +
                end
         
     | 
| 
      
 298 
     | 
    
         
            +
             
     | 
| 
      
 299 
     | 
    
         
            +
                protected :handle_commits
         
     | 
| 
      
 300 
     | 
    
         
            +
             
     | 
| 
      
 301 
     | 
    
         
            +
                def establish_leadership
         
     | 
| 
      
 302 
     | 
    
         
            +
                  @leadership_state = LeadershipState.new(@config.update_interval)
         
     | 
| 
      
 303 
     | 
    
         
            +
                  @temporary_state.leader_id = @id
         
     | 
| 
      
 304 
     | 
    
         
            +
                  @cluster.node_ids.each do |node_id|
         
     | 
| 
      
 305 
     | 
    
         
            +
                    next if node_id == @id
         
     | 
| 
      
 306 
     | 
    
         
            +
                    follower_state = (@leadership_state.followers[node_id] ||= FollowerState.new)
         
     | 
| 
      
 307 
     | 
    
         
            +
                    follower_state.next_index = @persistent_state.log.size
         
     | 
| 
      
 308 
     | 
    
         
            +
                    follower_state.succeeded = false
         
     | 
| 
      
 309 
     | 
    
         
            +
                  end
         
     | 
| 
      
 310 
     | 
    
         
            +
                  send_heartbeats
         
     | 
| 
      
 311 
     | 
    
         
            +
                end
         
     | 
| 
      
 312 
     | 
    
         
            +
             
     | 
| 
      
 313 
     | 
    
         
            +
                protected :establish_leadership
         
     | 
| 
      
 314 
     | 
    
         
            +
             
     | 
| 
      
 315 
     | 
    
         
            +
                def send_heartbeats
         
     | 
| 
      
 316 
     | 
    
         
            +
                  #STDOUT.write("\nnode #{@id} sending heartbeats at #{Time.now.strftime('%H:%M:%S:%L')}\n")
         
     | 
| 
      
 317 
     | 
    
         
            +
                  last_log_entry = @persistent_state.log.last
         
     | 
| 
      
 318 
     | 
    
         
            +
                  log_index = last_log_entry ? last_log_entry.index : nil
         
     | 
| 
      
 319 
     | 
    
         
            +
                  log_term = last_log_entry ? last_log_entry.term : nil
         
     | 
| 
      
 320 
     | 
    
         
            +
                  request = AppendEntriesRequest.new(
         
     | 
| 
      
 321 
     | 
    
         
            +
                      @persistent_state.current_term,
         
     | 
| 
      
 322 
     | 
    
         
            +
                      @id,
         
     | 
| 
      
 323 
     | 
    
         
            +
                      log_index,
         
     | 
| 
      
 324 
     | 
    
         
            +
                      log_term,
         
     | 
| 
      
 325 
     | 
    
         
            +
                      [],
         
     | 
| 
      
 326 
     | 
    
         
            +
                      @temporary_state.commit_index)
         
     | 
| 
      
 327 
     | 
    
         
            +
             
     | 
| 
      
 328 
     | 
    
         
            +
                  @config.rpc_provider.append_entries(request, @cluster) do |node_id, response|
         
     | 
| 
      
 329 
     | 
    
         
            +
                    append_entries_to_follower(node_id, request, response)
         
     | 
| 
      
 330 
     | 
    
         
            +
                  end
         
     | 
| 
      
 331 
     | 
    
         
            +
                end
         
     | 
| 
      
 332 
     | 
    
         
            +
             
     | 
| 
      
 333 
     | 
    
         
            +
                protected :send_heartbeats
         
     | 
| 
      
 334 
     | 
    
         
            +
             
     | 
| 
      
 335 
     | 
    
         
            +
                def append_entries_to_follower(node_id, request, response)
         
     | 
| 
      
 336 
     | 
    
         
            +
                  if @role != LEADER_ROLE
         
     | 
| 
      
 337 
     | 
    
         
            +
                    # we lost the leadership
         
     | 
| 
      
 338 
     | 
    
         
            +
                  elsif response.success
         
     | 
| 
      
 339 
     | 
    
         
            +
                    #STDOUT.write("\nappend_entries_to_follower #{node_id} request #{request.pretty_inspect} succeeded\n")
         
     | 
| 
      
 340 
     | 
    
         
            +
                    @leadership_state.followers[node_id].next_index = (request.prev_log_index || -1) + request.entries.count + 1
         
     | 
| 
      
 341 
     | 
    
         
            +
                    @leadership_state.followers[node_id].succeeded = true
         
     | 
| 
      
 342 
     | 
    
         
            +
                  elsif response.term <= @persistent_state.current_term
         
     | 
| 
      
 343 
     | 
    
         
            +
                    #STDOUT.write("\nappend_entries_to_follower #{node_id} request failed (#{request.pretty_inspect}) and responded with #{response.pretty_inspect}\n")
         
     | 
| 
      
 344 
     | 
    
         
            +
                    @config.rpc_provider.append_entries_to_follower(request, node_id) do |node_id, response|
         
     | 
| 
      
 345 
     | 
    
         
            +
                      if @role == LEADER_ROLE # make sure leadership wasn't lost since the request
         
     | 
| 
      
 346 
     | 
    
         
            +
                        #STDOUT.write("\nappend_entries_to_follower #{node_id} callback...\n")
         
     | 
| 
      
 347 
     | 
    
         
            +
                        prev_log_index = (request.prev_log_index.nil? || request.prev_log_index <= 0) ? nil : request.prev_log_index - 1
         
     | 
| 
      
 348 
     | 
    
         
            +
                        prev_log_term = nil
         
     | 
| 
      
 349 
     | 
    
         
            +
                        entries = @persistent_state.log
         
     | 
| 
      
 350 
     | 
    
         
            +
                        unless prev_log_index.nil?
         
     | 
| 
      
 351 
     | 
    
         
            +
                          prev_log_term = @persistent_state.log[prev_log_index].term
         
     | 
| 
      
 352 
     | 
    
         
            +
                          entries = @persistent_state.log.slice((prev_log_index + 1)..-1)
         
     | 
| 
      
 353 
     | 
    
         
            +
                        end
         
     | 
| 
      
 354 
     | 
    
         
            +
                        next_request = AppendEntriesRequest.new(
         
     | 
| 
      
 355 
     | 
    
         
            +
                            @persistent_state.current_term,
         
     | 
| 
      
 356 
     | 
    
         
            +
                            @id,
         
     | 
| 
      
 357 
     | 
    
         
            +
                            prev_log_index,
         
     | 
| 
      
 358 
     | 
    
         
            +
                            prev_log_term,
         
     | 
| 
      
 359 
     | 
    
         
            +
                            entries,
         
     | 
| 
      
 360 
     | 
    
         
            +
                            @temporary_state.commit_index)
         
     | 
| 
      
 361 
     | 
    
         
            +
                        #STDOUT.write("\nappend_entries_to_follower #{node_id} request #{request.pretty_inspect} failed...\n")
         
     | 
| 
      
 362 
     | 
    
         
            +
                        #STDOUT.write("sending updated request #{next_request.pretty_inspect}\n")
         
     | 
| 
      
 363 
     | 
    
         
            +
                        @config.rpc_provider.append_entries_to_follower(next_request, node_id) do |node_id, response|
         
     | 
| 
      
 364 
     | 
    
         
            +
                          append_entries_to_follower(node_id, next_request, response)
         
     | 
| 
      
 365 
     | 
    
         
            +
                        end
         
     | 
| 
      
 366 
     | 
    
         
            +
                      end
         
     | 
| 
      
 367 
     | 
    
         
            +
                    end
         
     | 
| 
      
 368 
     | 
    
         
            +
                  end
         
     | 
| 
      
 369 
     | 
    
         
            +
                end
         
     | 
| 
      
 370 
     | 
    
         
            +
             
     | 
| 
      
 371 
     | 
    
         
            +
                protected :append_entries_to_follower
         
     | 
| 
      
 372 
     | 
    
         
            +
             
     | 
| 
      
 373 
     | 
    
         
            +
                def handle_request_vote(request)
         
     | 
| 
      
 374 
     | 
    
         
            +
                  #STDOUT.write("\nnode #{@id} handling vote request from #{request.candidate_id} (request.last_log_index: #{request.last_log_index}, vs #{@persistent_state.log.last.index}\n")
         
     | 
| 
      
 375 
     | 
    
         
            +
                  response = RequestVoteResponse.new
         
     | 
| 
      
 376 
     | 
    
         
            +
                  response.term = @persistent_state.current_term
         
     | 
| 
      
 377 
     | 
    
         
            +
                  response.vote_granted = false
         
     | 
| 
      
 378 
     | 
    
         
            +
             
     | 
| 
      
 379 
     | 
    
         
            +
                  return response if request.term < @persistent_state.current_term
         
     | 
| 
      
 380 
     | 
    
         
            +
             
     | 
| 
      
 381 
     | 
    
         
            +
                  @temporary_state.leader_id = nil if request.term > @persistent_state.current_term
         
     | 
| 
      
 382 
     | 
    
         
            +
             
     | 
| 
      
 383 
     | 
    
         
            +
                  step_down_if_new_term(request.term)
         
     | 
| 
      
 384 
     | 
    
         
            +
             
     | 
| 
      
 385 
     | 
    
         
            +
                  if FOLLOWER_ROLE == @role
         
     | 
| 
      
 386 
     | 
    
         
            +
                    if @persistent_state.voted_for == request.candidate_id
         
     | 
| 
      
 387 
     | 
    
         
            +
                      response.vote_granted = true
         
     | 
| 
      
 388 
     | 
    
         
            +
                    elsif @persistent_state.voted_for.nil?
         
     | 
| 
      
 389 
     | 
    
         
            +
                      if @persistent_state.log.empty?
         
     | 
| 
      
 390 
     | 
    
         
            +
                        # this node has no log so it can't be ahead
         
     | 
| 
      
 391 
     | 
    
         
            +
                        @persistent_state.voted_for = request.candidate_id
         
     | 
| 
      
 392 
     | 
    
         
            +
                        response.vote_granted = true
         
     | 
| 
      
 393 
     | 
    
         
            +
                      elsif request.last_log_term == @persistent_state.log.last.term &&
         
     | 
| 
      
 394 
     | 
    
         
            +
                          (request.last_log_index || -1) < @persistent_state.log.last.index
         
     | 
| 
      
 395 
     | 
    
         
            +
                        # candidate's log is incomplete compared to this node
         
     | 
| 
      
 396 
     | 
    
         
            +
                      elsif (request.last_log_term || -1) < @persistent_state.log.last.term
         
     | 
| 
      
 397 
     | 
    
         
            +
                        # candidate's log is incomplete compared to this node
         
     | 
| 
      
 398 
     | 
    
         
            +
                      else
         
     | 
| 
      
 399 
     | 
    
         
            +
                        @persistent_state.voted_for = request.candidate_id
         
     | 
| 
      
 400 
     | 
    
         
            +
                        response.vote_granted = true
         
     | 
| 
      
 401 
     | 
    
         
            +
                      end
         
     | 
| 
      
 402 
     | 
    
         
            +
                    end
         
     | 
| 
      
 403 
     | 
    
         
            +
                    reset_election_timeout if response.vote_granted
         
     | 
| 
      
 404 
     | 
    
         
            +
                  end
         
     | 
| 
      
 405 
     | 
    
         
            +
             
     | 
| 
      
 406 
     | 
    
         
            +
                  response
         
     | 
| 
      
 407 
     | 
    
         
            +
                end
         
     | 
| 
      
 408 
     | 
    
         
            +
             
     | 
| 
      
 409 
     | 
    
         
            +
                def handle_append_entries(request)
         
     | 
| 
      
 410 
     | 
    
         
            +
                  #STDOUT.write("\n\nnode #{@id} handle_append_entries: #{request.entries.pretty_inspect}\n\n") #if request.prev_log_index.nil?
         
     | 
| 
      
 411 
     | 
    
         
            +
                  response = AppendEntriesResponse.new
         
     | 
| 
      
 412 
     | 
    
         
            +
                  response.term = @persistent_state.current_term
         
     | 
| 
      
 413 
     | 
    
         
            +
                  response.success = false
         
     | 
| 
      
 414 
     | 
    
         
            +
             
     | 
| 
      
 415 
     | 
    
         
            +
                  #STDOUT.write("\n\nnode #{@id} handle_append_entries for term #{request.term} (current is #{@persistent_state.current_term})\n")# if request.prev_log_index.nil?
         
     | 
| 
      
 416 
     | 
    
         
            +
                  return response if request.term < @persistent_state.current_term
         
     | 
| 
      
 417 
     | 
    
         
            +
                  #STDOUT.write("\n\nnode #{@id} handle_append_entries stage 2\n") if request.prev_log_index.nil?
         
     | 
| 
      
 418 
     | 
    
         
            +
             
     | 
| 
      
 419 
     | 
    
         
            +
                  step_down_if_new_term(request.term)
         
     | 
| 
      
 420 
     | 
    
         
            +
             
     | 
| 
      
 421 
     | 
    
         
            +
                  reset_election_timeout
         
     | 
| 
      
 422 
     | 
    
         
            +
             
     | 
| 
      
 423 
     | 
    
         
            +
                  @temporary_state.leader_id = request.leader_id
         
     | 
| 
      
 424 
     | 
    
         
            +
             
     | 
| 
      
 425 
     | 
    
         
            +
                  abs_log_index = abs_log_index_for(request.prev_log_index, request.prev_log_term)
         
     | 
| 
      
 426 
     | 
    
         
            +
                  return response if abs_log_index.nil? && !request.prev_log_index.nil? && !request.prev_log_term.nil?
         
     | 
| 
      
 427 
     | 
    
         
            +
                  #STDOUT.write("\n\nnode #{@id} handle_append_entries stage 3\n") if request.prev_log_index.nil?
         
     | 
| 
      
 428 
     | 
    
         
            +
                  if @temporary_state.commit_index &&
         
     | 
| 
      
 429 
     | 
    
         
            +
                      abs_log_index &&
         
     | 
| 
      
 430 
     | 
    
         
            +
                      abs_log_index < @temporary_state.commit_index
         
     | 
| 
      
 431 
     | 
    
         
            +
                    raise "Cannot truncate committed logs; @temporary_state.commit_index = #{@temporary_state.commit_index}; abs_log_index = #{abs_log_index}"
         
     | 
| 
      
 432 
     | 
    
         
            +
                  end
         
     | 
| 
      
 433 
     | 
    
         
            +
             
     | 
| 
      
 434 
     | 
    
         
            +
                  truncate_and_update_log(abs_log_index, request.entries)
         
     | 
| 
      
 435 
     | 
    
         
            +
             
     | 
| 
      
 436 
     | 
    
         
            +
                  return response unless update_commit_index(request.commit_index)
         
     | 
| 
      
 437 
     | 
    
         
            +
                  #STDOUT.write("\n\nnode #{@id} handle_append_entries stage 4\n") if request.prev_log_index.nil?
         
     | 
| 
      
 438 
     | 
    
         
            +
             
     | 
| 
      
 439 
     | 
    
         
            +
                  response.success = true
         
     | 
| 
      
 440 
     | 
    
         
            +
                  response
         
     | 
| 
      
 441 
     | 
    
         
            +
                end
         
     | 
| 
      
 442 
     | 
    
         
            +
             
     | 
| 
      
 443 
     | 
    
         
            +
                def handle_command(request)
         
     | 
| 
      
 444 
     | 
    
         
            +
                  response = CommandResponse.new(false)
         
     | 
| 
      
 445 
     | 
    
         
            +
                  case @role
         
     | 
| 
      
 446 
     | 
    
         
            +
                  when FOLLOWER_ROLE
         
     | 
| 
      
 447 
     | 
    
         
            +
                    await_leader
         
     | 
| 
      
 448 
     | 
    
         
            +
                    if @role == LEADER_ROLE
         
     | 
| 
      
 449 
     | 
    
         
            +
                      handle_command(request)
         
     | 
| 
      
 450 
     | 
    
         
            +
                    else
         
     | 
| 
      
 451 
     | 
    
         
            +
                      # forward the command to the leader
         
     | 
| 
      
 452 
     | 
    
         
            +
                      response = @config.rpc_provider.command(request, @temporary_state.leader_id)
         
     | 
| 
      
 453 
     | 
    
         
            +
                    end
         
     | 
| 
      
 454 
     | 
    
         
            +
                  when CANDIDATE_ROLE
         
     | 
| 
      
 455 
     | 
    
         
            +
                    await_leader
         
     | 
| 
      
 456 
     | 
    
         
            +
                    response = handle_command(request)
         
     | 
| 
      
 457 
     | 
    
         
            +
                  when LEADER_ROLE
         
     | 
| 
      
 458 
     | 
    
         
            +
                    last_log = @persistent_state.log.last
         
     | 
| 
      
 459 
     | 
    
         
            +
                    log_entry = LogEntry.new(@persistent_state.current_term, last_log.index ? last_log.index + 1 : 0, request.command)
         
     | 
| 
      
 460 
     | 
    
         
            +
                    @persistent_state.log << log_entry
         
     | 
| 
      
 461 
     | 
    
         
            +
                    await_consensus(log_entry)
         
     | 
| 
      
 462 
     | 
    
         
            +
                    response = CommandResponse.new(true)
         
     | 
| 
      
 463 
     | 
    
         
            +
                  end
         
     | 
| 
      
 464 
     | 
    
         
            +
                  response
         
     | 
| 
      
 465 
     | 
    
         
            +
                end
         
     | 
| 
      
 466 
     | 
    
         
            +
             
     | 
| 
      
 467 
     | 
    
         
            +
                def await_consensus(log_entry)
         
     | 
| 
      
 468 
     | 
    
         
            +
                  @config.async_provider.await do
         
     | 
| 
      
 469 
     | 
    
         
            +
                    persisted_log_entry = @persistent_state.log[log_entry.index]
         
     | 
| 
      
 470 
     | 
    
         
            +
                    !@temporary_state.commit_index.nil? &&
         
     | 
| 
      
 471 
     | 
    
         
            +
                        @temporary_state.commit_index >= log_entry.index &&
         
     | 
| 
      
 472 
     | 
    
         
            +
                        persisted_log_entry.term == log_entry.term &&
         
     | 
| 
      
 473 
     | 
    
         
            +
                        persisted_log_entry.command == log_entry.command
         
     | 
| 
      
 474 
     | 
    
         
            +
                  end
         
     | 
| 
      
 475 
     | 
    
         
            +
                end
         
     | 
| 
      
 476 
     | 
    
         
            +
             
     | 
| 
      
 477 
     | 
    
         
            +
                protected :await_consensus
         
     | 
| 
      
 478 
     | 
    
         
            +
             
     | 
| 
      
 479 
     | 
    
         
            +
                def await_leader
         
     | 
| 
      
 480 
     | 
    
         
            +
                  if @temporary_state.leader_id.nil?
         
     | 
| 
      
 481 
     | 
    
         
            +
                    @role = CANDIDATE_ROLE
         
     | 
| 
      
 482 
     | 
    
         
            +
                  end
         
     | 
| 
      
 483 
     | 
    
         
            +
                  @config.async_provider.await do
         
     | 
| 
      
 484 
     | 
    
         
            +
                    @role != CANDIDATE_ROLE && !@temporary_state.leader_id.nil?
         
     | 
| 
      
 485 
     | 
    
         
            +
                  end
         
     | 
| 
      
 486 
     | 
    
         
            +
                end
         
     | 
| 
      
 487 
     | 
    
         
            +
             
     | 
| 
      
 488 
     | 
    
         
            +
                protected :await_leader
         
     | 
| 
      
 489 
     | 
    
         
            +
             
     | 
| 
      
 490 
     | 
    
         
            +
                def step_down_if_new_term(request_term)
         
     | 
| 
      
 491 
     | 
    
         
            +
                  if request_term > @persistent_state.current_term
         
     | 
| 
      
 492 
     | 
    
         
            +
                    @persistent_state.current_term = request_term
         
     | 
| 
      
 493 
     | 
    
         
            +
                    @role = FOLLOWER_ROLE
         
     | 
| 
      
 494 
     | 
    
         
            +
                  end
         
     | 
| 
      
 495 
     | 
    
         
            +
                end
         
     | 
| 
      
 496 
     | 
    
         
            +
             
     | 
| 
      
 497 
     | 
    
         
            +
                protected :step_down_if_new_term
         
     | 
| 
      
 498 
     | 
    
         
            +
             
     | 
| 
      
 499 
     | 
    
         
            +
                def reset_election_timeout
         
     | 
| 
      
 500 
     | 
    
         
            +
                  @election_timer.reset!
         
     | 
| 
      
 501 
     | 
    
         
            +
                end
         
     | 
| 
      
 502 
     | 
    
         
            +
             
     | 
| 
      
 503 
     | 
    
         
            +
                protected :reset_election_timeout
         
     | 
| 
      
 504 
     | 
    
         
            +
             
     | 
| 
      
 505 
     | 
    
         
            +
                def abs_log_index_for(prev_log_index, prev_log_term)
         
     | 
| 
      
 506 
     | 
    
         
            +
                  @persistent_state.log.rindex { |log_entry| log_entry.index == prev_log_index && log_entry.term == prev_log_term }
         
     | 
| 
      
 507 
     | 
    
         
            +
                end
         
     | 
| 
      
 508 
     | 
    
         
            +
             
     | 
| 
      
 509 
     | 
    
         
            +
                protected :abs_log_index_for
         
     | 
| 
      
 510 
     | 
    
         
            +
             
     | 
| 
      
 511 
     | 
    
         
            +
                def truncate_and_update_log(abs_log_index, entries)
         
     | 
| 
      
 512 
     | 
    
         
            +
                  log = @persistent_state.log
         
     | 
| 
      
 513 
     | 
    
         
            +
                  if abs_log_index.nil?
         
     | 
| 
      
 514 
     | 
    
         
            +
                    log = []
         
     | 
| 
      
 515 
     | 
    
         
            +
                  elsif log.length == abs_log_index + 1
         
     | 
| 
      
 516 
     | 
    
         
            +
                    # no truncation required, past log is the same
         
     | 
| 
      
 517 
     | 
    
         
            +
                  else
         
     | 
| 
      
 518 
     | 
    
         
            +
                    log = log.slice(0..abs_log_index)
         
     | 
| 
      
 519 
     | 
    
         
            +
                  end
         
     | 
| 
      
 520 
     | 
    
         
            +
                  #STDOUT.write("\n\nentries is: #{entries.pretty_inspect}\n\n")
         
     | 
| 
      
 521 
     | 
    
         
            +
                  log = log.concat(entries) unless entries.empty?
         
     | 
| 
      
 522 
     | 
    
         
            +
                  @persistent_state.log = log
         
     | 
| 
      
 523 
     | 
    
         
            +
                end
         
     | 
| 
      
 524 
     | 
    
         
            +
             
     | 
| 
      
 525 
     | 
    
         
            +
                protected :truncate_and_update_log
         
     | 
| 
      
 526 
     | 
    
         
            +
             
     | 
| 
      
 527 
     | 
    
         
            +
                def update_commit_index(new_commit_index)
         
     | 
| 
      
 528 
     | 
    
         
            +
                  #STDOUT.write("\n\n%%%%%%%%%%%%%%%%%%%%% node #{@id} update_commit_index(new_commit_index = #{new_commit_index})\n")
         
     | 
| 
      
 529 
     | 
    
         
            +
                  return false if @temporary_state.commit_index && @temporary_state.commit_index > new_commit_index
         
     | 
| 
      
 530 
     | 
    
         
            +
                  handle_commits(new_commit_index)
         
     | 
| 
      
 531 
     | 
    
         
            +
                  true
         
     | 
| 
      
 532 
     | 
    
         
            +
                end
         
     | 
| 
      
 533 
     | 
    
         
            +
             
     | 
| 
      
 534 
     | 
    
         
            +
                protected :update_commit_index
         
     | 
| 
      
 535 
     | 
    
         
            +
              end
         
     | 
| 
      
 536 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,248 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative '../pontoon'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'goliath'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Pontoon
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Goliath
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                def self.log(message)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  #STDOUT.write("\n\n")
         
     | 
| 
      
 10 
     | 
    
         
            +
                  #STDOUT.write(message)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  #STDOUT.write("\n\n")
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                class HttpJsonRpcResponder < ::Goliath::API
         
     | 
| 
      
 15 
     | 
    
         
            +
                  use ::Goliath::Rack::Render, 'json'
         
     | 
| 
      
 16 
     | 
    
         
            +
                  use ::Goliath::Rack::Validation::RequestMethod, %w(POST)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  use ::Goliath::Rack::Params
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def initialize(node)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @node = node
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  HEADERS = { 'Content-Type' => 'application/json' }
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  def response(env)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    case env['REQUEST_PATH']
         
     | 
| 
      
 27 
     | 
    
         
            +
                    when '/request_vote'
         
     | 
| 
      
 28 
     | 
    
         
            +
                      handle_errors { request_vote_response(env['params']) }
         
     | 
| 
      
 29 
     | 
    
         
            +
                    when '/append_entries'
         
     | 
| 
      
 30 
     | 
    
         
            +
                      handle_errors { append_entries_response(env['params']) }
         
     | 
| 
      
 31 
     | 
    
         
            +
                    when '/command'
         
     | 
| 
      
 32 
     | 
    
         
            +
                      handle_errors { command_response(env['params']) }
         
     | 
| 
      
 33 
     | 
    
         
            +
                    else
         
     | 
| 
      
 34 
     | 
    
         
            +
                      error_response(404, 'not found')
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  def request_vote_response(params)
         
     | 
| 
      
 39 
     | 
    
         
            +
                    #STDOUT.write("\nnode #{@node.id} received request_vote from #{params['candidate_id']}, term #{params['term']}\n")
         
     | 
| 
      
 40 
     | 
    
         
            +
                    request = Pontoon::RequestVoteRequest.new(
         
     | 
| 
      
 41 
     | 
    
         
            +
                        params['term'],
         
     | 
| 
      
 42 
     | 
    
         
            +
                        params['candidate_id'],
         
     | 
| 
      
 43 
     | 
    
         
            +
                        params['last_log_index'],
         
     | 
| 
      
 44 
     | 
    
         
            +
                        params['last_log_term'])
         
     | 
| 
      
 45 
     | 
    
         
            +
                    response = @node.handle_request_vote(request)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    [200, HEADERS, { 'term' => response.term, 'vote_granted' => response.vote_granted }]
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  def append_entries_response(params)
         
     | 
| 
      
 50 
     | 
    
         
            +
                    #STDOUT.write("\nnode #{@node.id} received append_entries from #{params['leader_id']}, term #{params['term']}\n")
         
     | 
| 
      
 51 
     | 
    
         
            +
                    entries = params['entries'].map {|entry| Pontoon::LogEntry.new(entry['term'], entry['index'], entry['command'])}
         
     | 
| 
      
 52 
     | 
    
         
            +
                    request = Pontoon::AppendEntriesRequest.new(
         
     | 
| 
      
 53 
     | 
    
         
            +
                        params['term'],
         
     | 
| 
      
 54 
     | 
    
         
            +
                        params['leader_id'],
         
     | 
| 
      
 55 
     | 
    
         
            +
                        params['prev_log_index'],
         
     | 
| 
      
 56 
     | 
    
         
            +
                        params['prev_log_term'],
         
     | 
| 
      
 57 
     | 
    
         
            +
                        entries,
         
     | 
| 
      
 58 
     | 
    
         
            +
                        params['commit_index'])
         
     | 
| 
      
 59 
     | 
    
         
            +
                    #STDOUT.write("\nnode #{@node.id} received entries: #{request.entries.pretty_inspect}\n")
         
     | 
| 
      
 60 
     | 
    
         
            +
                    response = @node.handle_append_entries(request)
         
     | 
| 
      
 61 
     | 
    
         
            +
                    #STDOUT.write("\nnode #{@node.id} completed append_entries from #{params['leader_id']}, term #{params['term']} (#{response})\n")
         
     | 
| 
      
 62 
     | 
    
         
            +
                    [200, HEADERS, { 'term' => response.term, 'success' => response.success }]
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  def command_response(params)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    request = Pontoon::CommandRequest.new(params['command'])
         
     | 
| 
      
 67 
     | 
    
         
            +
                    response = @node.handle_command(request)
         
     | 
| 
      
 68 
     | 
    
         
            +
                    [response.success ? 200 : 409, HEADERS, { 'success' => response.success }]
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  def handle_errors
         
     | 
| 
      
 72 
     | 
    
         
            +
                    yield
         
     | 
| 
      
 73 
     | 
    
         
            +
                  rescue StandardError => se
         
     | 
| 
      
 74 
     | 
    
         
            +
                    error_response(422, se)
         
     | 
| 
      
 75 
     | 
    
         
            +
                  rescue Exception => e
         
     | 
| 
      
 76 
     | 
    
         
            +
                    error_response(500, e)
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                  def error_message(exception)
         
     | 
| 
      
 80 
     | 
    
         
            +
                    "#{exception.message}\n\t#{exception.backtrace.join("\n\t")}".tap {|m| STDOUT.write("\n\n\t#{m}\n\n")}
         
     | 
| 
      
 81 
     | 
    
         
            +
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                  def error_response(code, exception)
         
     | 
| 
      
 84 
     | 
    
         
            +
                    [code, HEADERS, { 'error' => error_message(exception) }]
         
     | 
| 
      
 85 
     | 
    
         
            +
                  end
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                module HashMarshalling
         
     | 
| 
      
 89 
     | 
    
         
            +
                  def self.hash_to_object(hash, klass)
         
     | 
| 
      
 90 
     | 
    
         
            +
                    object = klass.new
         
     | 
| 
      
 91 
     | 
    
         
            +
                    hash.each_pair do |k, v|
         
     | 
| 
      
 92 
     | 
    
         
            +
                      object.send("#{k}=", v)
         
     | 
| 
      
 93 
     | 
    
         
            +
                    end
         
     | 
| 
      
 94 
     | 
    
         
            +
                    object
         
     | 
| 
      
 95 
     | 
    
         
            +
                  end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                  def self.object_to_hash(object, attrs)
         
     | 
| 
      
 98 
     | 
    
         
            +
                    attrs.reduce({}) { |hash, attr|
         
     | 
| 
      
 99 
     | 
    
         
            +
                      hash[attr] = object.send(attr); hash
         
     | 
| 
      
 100 
     | 
    
         
            +
                    }
         
     | 
| 
      
 101 
     | 
    
         
            +
                  end
         
     | 
| 
      
 102 
     | 
    
         
            +
                end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                class HttpJsonRpcProvider < Pontoon::RpcProvider
         
     | 
| 
      
 105 
     | 
    
         
            +
                  attr_reader :uri_generator
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                  def initialize(uri_generator)
         
     | 
| 
      
 108 
     | 
    
         
            +
                    @uri_generator = uri_generator
         
     | 
| 
      
 109 
     | 
    
         
            +
                  end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                  def request_votes(request, cluster, &block)
         
     | 
| 
      
 112 
     | 
    
         
            +
                    sent_hash = HashMarshalling.object_to_hash(request, %w(term candidate_id last_log_index last_log_term))
         
     | 
| 
      
 113 
     | 
    
         
            +
                    sent_json = MultiJson.dump(sent_hash)
         
     | 
| 
      
 114 
     | 
    
         
            +
                    deferred_calls = []
         
     | 
| 
      
 115 
     | 
    
         
            +
                    EM.synchrony do
         
     | 
| 
      
 116 
     | 
    
         
            +
                      cluster.node_ids.each do |node_id|
         
     | 
| 
      
 117 
     | 
    
         
            +
                        next if node_id == request.candidate_id
         
     | 
| 
      
 118 
     | 
    
         
            +
                        http = EventMachine::HttpRequest.new(uri_generator.call(node_id, 'request_vote')).apost(
         
     | 
| 
      
 119 
     | 
    
         
            +
                            :body => sent_json,
         
     | 
| 
      
 120 
     | 
    
         
            +
                            :head => { 'Content-Type' => 'application/json' })
         
     | 
| 
      
 121 
     | 
    
         
            +
                        http.callback do
         
     | 
| 
      
 122 
     | 
    
         
            +
                          if http.response_header.status == 200
         
     | 
| 
      
 123 
     | 
    
         
            +
                            received_hash = MultiJson.load(http.response)
         
     | 
| 
      
 124 
     | 
    
         
            +
                            response = HashMarshalling.hash_to_object(received_hash, Pontoon::RequestVoteResponse)
         
     | 
| 
      
 125 
     | 
    
         
            +
                            #STDOUT.write("\n\t#{node_id} responded #{response.vote_granted} to #{request.candidate_id}\n\n")
         
     | 
| 
      
 126 
     | 
    
         
            +
                            yield node_id, request, response
         
     | 
| 
      
 127 
     | 
    
         
            +
                          else
         
     | 
| 
      
 128 
     | 
    
         
            +
                            Pontoon::Goliath.log("request_vote failed for node '#{node_id}' with code #{http.response_header.status}")
         
     | 
| 
      
 129 
     | 
    
         
            +
                          end
         
     | 
| 
      
 130 
     | 
    
         
            +
                        end
         
     | 
| 
      
 131 
     | 
    
         
            +
                        deferred_calls << http
         
     | 
| 
      
 132 
     | 
    
         
            +
                      end
         
     | 
| 
      
 133 
     | 
    
         
            +
                    end
         
     | 
| 
      
 134 
     | 
    
         
            +
                    deferred_calls.each do |http|
         
     | 
| 
      
 135 
     | 
    
         
            +
                      EM::Synchrony.sync http
         
     | 
| 
      
 136 
     | 
    
         
            +
                    end
         
     | 
| 
      
 137 
     | 
    
         
            +
                  end
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                  def append_entries(request, cluster, &block)
         
     | 
| 
      
 140 
     | 
    
         
            +
                    deferred_calls = []
         
     | 
| 
      
 141 
     | 
    
         
            +
                    EM.synchrony do
         
     | 
| 
      
 142 
     | 
    
         
            +
                      cluster.node_ids.each do |node_id|
         
     | 
| 
      
 143 
     | 
    
         
            +
                        next if node_id == request.leader_id
         
     | 
| 
      
 144 
     | 
    
         
            +
                        deferred_calls << create_append_entries_to_follower_request(request, node_id, &block)
         
     | 
| 
      
 145 
     | 
    
         
            +
                      end
         
     | 
| 
      
 146 
     | 
    
         
            +
                    end
         
     | 
| 
      
 147 
     | 
    
         
            +
                    deferred_calls.each do |http|
         
     | 
| 
      
 148 
     | 
    
         
            +
                      EM::Synchrony.sync http
         
     | 
| 
      
 149 
     | 
    
         
            +
                    end
         
     | 
| 
      
 150 
     | 
    
         
            +
                  end
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                  def append_entries_to_follower(request, node_id, &block)
         
     | 
| 
      
 153 
     | 
    
         
            +
            #        EM.synchrony do
         
     | 
| 
      
 154 
     | 
    
         
            +
                      create_append_entries_to_follower_request(request, node_id, &block)
         
     | 
| 
      
 155 
     | 
    
         
            +
            #        end
         
     | 
| 
      
 156 
     | 
    
         
            +
                  end
         
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
                  def create_append_entries_to_follower_request(request, node_id, &block)
         
     | 
| 
      
 159 
     | 
    
         
            +
                    sent_hash = HashMarshalling.object_to_hash(request, %w(term leader_id prev_log_index prev_log_term entries commit_index))
         
     | 
| 
      
 160 
     | 
    
         
            +
                    sent_hash['entries'] = sent_hash['entries'].map {|obj| HashMarshalling.object_to_hash(obj, %w(term index command))}
         
     | 
| 
      
 161 
     | 
    
         
            +
                    sent_json = MultiJson.dump(sent_hash)
         
     | 
| 
      
 162 
     | 
    
         
            +
                    raise "replicating to self!" if request.leader_id == node_id
         
     | 
| 
      
 163 
     | 
    
         
            +
                    #STDOUT.write("\nleader #{request.leader_id} replicating entries to #{node_id}: #{sent_hash.pretty_inspect}\n")#"\t#{caller[0..4].join("\n\t")}")
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
                    http = EventMachine::HttpRequest.new(uri_generator.call(node_id, 'append_entries')).apost(
         
     | 
| 
      
 166 
     | 
    
         
            +
                        :body => sent_json,
         
     | 
| 
      
 167 
     | 
    
         
            +
                        :head => { 'Content-Type' => 'application/json' })
         
     | 
| 
      
 168 
     | 
    
         
            +
                    http.callback do
         
     | 
| 
      
 169 
     | 
    
         
            +
                      #STDOUT.write("\nleader #{request.leader_id} calling back to #{node_id} to append entries\n")
         
     | 
| 
      
 170 
     | 
    
         
            +
                      if http.response_header.status == 200
         
     | 
| 
      
 171 
     | 
    
         
            +
                        received_hash = MultiJson.load(http.response)
         
     | 
| 
      
 172 
     | 
    
         
            +
                        response = HashMarshalling.hash_to_object(received_hash, Pontoon::AppendEntriesResponse)
         
     | 
| 
      
 173 
     | 
    
         
            +
                        yield node_id, response
         
     | 
| 
      
 174 
     | 
    
         
            +
                      else
         
     | 
| 
      
 175 
     | 
    
         
            +
                        Pontoon::Goliath.log("append_entries failed for node '#{node_id}' with code #{http.response_header.status}")
         
     | 
| 
      
 176 
     | 
    
         
            +
                      end
         
     | 
| 
      
 177 
     | 
    
         
            +
                    end
         
     | 
| 
      
 178 
     | 
    
         
            +
                    http
         
     | 
| 
      
 179 
     | 
    
         
            +
                  end
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
                  def command(request, node_id)
         
     | 
| 
      
 182 
     | 
    
         
            +
                    sent_hash = HashMarshalling.object_to_hash(request, %w(command))
         
     | 
| 
      
 183 
     | 
    
         
            +
                    sent_json = MultiJson.dump(sent_hash)
         
     | 
| 
      
 184 
     | 
    
         
            +
                    http = EventMachine::HttpRequest.new(uri_generator.call(node_id, 'command')).apost(
         
     | 
| 
      
 185 
     | 
    
         
            +
                        :body => sent_json,
         
     | 
| 
      
 186 
     | 
    
         
            +
                        :head => { 'Content-Type' => 'application/json' })
         
     | 
| 
      
 187 
     | 
    
         
            +
                    http = EM::Synchrony.sync(http)
         
     | 
| 
      
 188 
     | 
    
         
            +
                    if http.response_header.status == 200
         
     | 
| 
      
 189 
     | 
    
         
            +
                      received_hash = MultiJson.load(http.response)
         
     | 
| 
      
 190 
     | 
    
         
            +
                      HashMarshalling.hash_to_object(received_hash, Pontoon::CommandResponse)
         
     | 
| 
      
 191 
     | 
    
         
            +
                    else
         
     | 
| 
      
 192 
     | 
    
         
            +
                      Pontoon::Goliath.log("command failed for node '#{node_id}' with code #{http.response_header.status}")
         
     | 
| 
      
 193 
     | 
    
         
            +
                      CommandResponse.new(false)
         
     | 
| 
      
 194 
     | 
    
         
            +
                    end
         
     | 
| 
      
 195 
     | 
    
         
            +
                  end
         
     | 
| 
      
 196 
     | 
    
         
            +
                end
         
     | 
| 
      
 197 
     | 
    
         
            +
             
     | 
| 
      
 198 
     | 
    
         
            +
                class EventMachineAsyncProvider < Pontoon::AsyncProvider
         
     | 
| 
      
 199 
     | 
    
         
            +
                  def await
         
     | 
| 
      
 200 
     | 
    
         
            +
                    f = Fiber.current
         
     | 
| 
      
 201 
     | 
    
         
            +
                    until yield
         
     | 
| 
      
 202 
     | 
    
         
            +
                      EM.next_tick {f.resume}
         
     | 
| 
      
 203 
     | 
    
         
            +
                      Fiber.yield
         
     | 
| 
      
 204 
     | 
    
         
            +
                    end
         
     | 
| 
      
 205 
     | 
    
         
            +
                  end
         
     | 
| 
      
 206 
     | 
    
         
            +
                end
         
     | 
| 
      
 207 
     | 
    
         
            +
             
     | 
| 
      
 208 
     | 
    
         
            +
                def self.rpc_provider(uri_generator)
         
     | 
| 
      
 209 
     | 
    
         
            +
                  HttpJsonRpcProvider.new(uri_generator)
         
     | 
| 
      
 210 
     | 
    
         
            +
                end
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
                def self.async_provider
         
     | 
| 
      
 213 
     | 
    
         
            +
                  EventMachineAsyncProvider.new
         
     | 
| 
      
 214 
     | 
    
         
            +
                end
         
     | 
| 
      
 215 
     | 
    
         
            +
             
     | 
| 
      
 216 
     | 
    
         
            +
                def initialize(node)
         
     | 
| 
      
 217 
     | 
    
         
            +
                  @node = node
         
     | 
| 
      
 218 
     | 
    
         
            +
                end
         
     | 
| 
      
 219 
     | 
    
         
            +
             
     | 
| 
      
 220 
     | 
    
         
            +
                attr_reader :node
         
     | 
| 
      
 221 
     | 
    
         
            +
                attr_reader :update_fiber
         
     | 
| 
      
 222 
     | 
    
         
            +
                attr_reader :running
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
                def start(options = {})
         
     | 
| 
      
 225 
     | 
    
         
            +
                  @runner = ::Goliath::Runner.new(ARGV, nil)
         
     | 
| 
      
 226 
     | 
    
         
            +
                  @runner.api = HttpJsonRpcResponder.new(node)
         
     | 
| 
      
 227 
     | 
    
         
            +
                  @runner.app = ::Goliath::Rack::Builder.build(HttpJsonRpcResponder, @runner.api)
         
     | 
| 
      
 228 
     | 
    
         
            +
                  @runner.address = options[:address] if options[:address]
         
     | 
| 
      
 229 
     | 
    
         
            +
                  @runner.port = options[:port] if options[:port]
         
     | 
| 
      
 230 
     | 
    
         
            +
                  @runner.run
         
     | 
| 
      
 231 
     | 
    
         
            +
                  @running = true
         
     | 
| 
      
 232 
     | 
    
         
            +
             
     | 
| 
      
 233 
     | 
    
         
            +
                  update_proc = Proc.new do
         
     | 
| 
      
 234 
     | 
    
         
            +
                    EM.synchrony do
         
     | 
| 
      
 235 
     | 
    
         
            +
                      @node.update
         
     | 
| 
      
 236 
     | 
    
         
            +
                    end
         
     | 
| 
      
 237 
     | 
    
         
            +
                  end
         
     | 
| 
      
 238 
     | 
    
         
            +
                  @update_timer = EventMachine.add_periodic_timer(node.config.update_interval, update_proc)
         
     | 
| 
      
 239 
     | 
    
         
            +
            #      @node.update
         
     | 
| 
      
 240 
     | 
    
         
            +
                end
         
     | 
| 
      
 241 
     | 
    
         
            +
             
     | 
| 
      
 242 
     | 
    
         
            +
                def stop
         
     | 
| 
      
 243 
     | 
    
         
            +
                  @update_timer.cancel
         
     | 
| 
      
 244 
     | 
    
         
            +
                end
         
     | 
| 
      
 245 
     | 
    
         
            +
              end
         
     | 
| 
      
 246 
     | 
    
         
            +
            end
         
     | 
| 
      
 247 
     | 
    
         
            +
             
     | 
| 
      
 248 
     | 
    
         
            +
             
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,115 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: pontoon
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version
         
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.1.0
         
     | 
| 
      
 5 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 6 
     | 
    
         
            +
            authors:
         
     | 
| 
      
 7 
     | 
    
         
            +
            - Anthony Corletti
         
     | 
| 
      
 8 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 9 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 10 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2016-04-20 00:00:00.000000000 Z
         
     | 
| 
      
 12 
     | 
    
         
            +
            dependencies:
         
     | 
| 
      
 13 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 14 
     | 
    
         
            +
              name: goliath
         
     | 
| 
      
 15 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 16 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 17 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 18 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 19 
     | 
    
         
            +
                    version: '1.0'
         
     | 
| 
      
 20 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 21 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 22 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 23 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 24 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 25 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 26 
     | 
    
         
            +
                    version: '1.0'
         
     | 
| 
      
 27 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 28 
     | 
    
         
            +
              name: multi_json
         
     | 
| 
      
 29 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 30 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 31 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 32 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 33 
     | 
    
         
            +
                    version: '1.3'
         
     | 
| 
      
 34 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 35 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 36 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 37 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 38 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 39 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 40 
     | 
    
         
            +
                    version: '1.3'
         
     | 
| 
      
 41 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 42 
     | 
    
         
            +
              name: cucumber
         
     | 
| 
      
 43 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 44 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 45 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 46 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 47 
     | 
    
         
            +
                    version: '1.0'
         
     | 
| 
      
 48 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 49 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 50 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 51 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 52 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 53 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 54 
     | 
    
         
            +
                    version: '1.0'
         
     | 
| 
      
 55 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 56 
     | 
    
         
            +
              name: em-http-request
         
     | 
| 
      
 57 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 58 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 59 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 60 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 61 
     | 
    
         
            +
                    version: '1.0'
         
     | 
| 
      
 62 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 63 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 64 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 65 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 66 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 67 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 68 
     | 
    
         
            +
                    version: '1.0'
         
     | 
| 
      
 69 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 70 
     | 
    
         
            +
              name: rspec
         
     | 
| 
      
 71 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 72 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 73 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 74 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 75 
     | 
    
         
            +
                    version: '2.0'
         
     | 
| 
      
 76 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 77 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 78 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 79 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 80 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 81 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 82 
     | 
    
         
            +
                    version: '2.0'
         
     | 
| 
      
 83 
     | 
    
         
            +
            description: A simple Raft distributed consensus implementation
         
     | 
| 
      
 84 
     | 
    
         
            +
            email: anthcor@gmail.com
         
     | 
| 
      
 85 
     | 
    
         
            +
            executables: []
         
     | 
| 
      
 86 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 87 
     | 
    
         
            +
            extra_rdoc_files: []
         
     | 
| 
      
 88 
     | 
    
         
            +
            files:
         
     | 
| 
      
 89 
     | 
    
         
            +
            - lib/pontoon.rb
         
     | 
| 
      
 90 
     | 
    
         
            +
            - lib/pontoon/goliath.rb
         
     | 
| 
      
 91 
     | 
    
         
            +
            homepage: http://github.com/anthcor/pontoon
         
     | 
| 
      
 92 
     | 
    
         
            +
            licenses:
         
     | 
| 
      
 93 
     | 
    
         
            +
            - MIT
         
     | 
| 
      
 94 
     | 
    
         
            +
            metadata: {}
         
     | 
| 
      
 95 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 96 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 97 
     | 
    
         
            +
            require_paths:
         
     | 
| 
      
 98 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 99 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 100 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 101 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 102 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 103 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 104 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 105 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 106 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 107 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 108 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 109 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 110 
     | 
    
         
            +
            rubyforge_project: 
         
     | 
| 
      
 111 
     | 
    
         
            +
            rubygems_version: 2.4.8
         
     | 
| 
      
 112 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 113 
     | 
    
         
            +
            specification_version: 4
         
     | 
| 
      
 114 
     | 
    
         
            +
            summary: A simple Raft distributed consensus implementation
         
     | 
| 
      
 115 
     | 
    
         
            +
            test_files: []
         
     |