job_dispatch 0.0.1
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/.gitignore +19 -0
 - data/.rspec +1 -0
 - data/.travis.yml +13 -0
 - data/Gemfile +20 -0
 - data/Guardfile +13 -0
 - data/LICENSE.txt +22 -0
 - data/README.md +85 -0
 - data/Rakefile +10 -0
 - data/bin/job-dispatcher +34 -0
 - data/bin/job-status +69 -0
 - data/bin/job-worker +40 -0
 - data/examples/mongoid-job.rb +43 -0
 - data/job_dispatch.gemspec +33 -0
 - data/lib/job_dispatch/broker/command.rb +45 -0
 - data/lib/job_dispatch/broker/internal_job.rb +32 -0
 - data/lib/job_dispatch/broker/socket.rb +85 -0
 - data/lib/job_dispatch/broker.rb +523 -0
 - data/lib/job_dispatch/client/proxy.rb +34 -0
 - data/lib/job_dispatch/client/proxy_error.rb +18 -0
 - data/lib/job_dispatch/client/synchronous_proxy.rb +29 -0
 - data/lib/job_dispatch/client.rb +49 -0
 - data/lib/job_dispatch/configuration.rb +7 -0
 - data/lib/job_dispatch/identity.rb +54 -0
 - data/lib/job_dispatch/job.rb +44 -0
 - data/lib/job_dispatch/signaller.rb +30 -0
 - data/lib/job_dispatch/sockets/enqueue.rb +18 -0
 - data/lib/job_dispatch/status.rb +79 -0
 - data/lib/job_dispatch/version.rb +3 -0
 - data/lib/job_dispatch/worker/item.rb +43 -0
 - data/lib/job_dispatch/worker/socket.rb +96 -0
 - data/lib/job_dispatch/worker.rb +120 -0
 - data/lib/job_dispatch.rb +97 -0
 - data/spec/factories/jobs.rb +19 -0
 - data/spec/job_dispatch/broker/socket_spec.rb +53 -0
 - data/spec/job_dispatch/broker_spec.rb +737 -0
 - data/spec/job_dispatch/identity_spec.rb +88 -0
 - data/spec/job_dispatch/job_spec.rb +77 -0
 - data/spec/job_dispatch/worker/socket_spec.rb +32 -0
 - data/spec/job_dispatch/worker_spec.rb +24 -0
 - data/spec/job_dispatch_spec.rb +0 -0
 - data/spec/spec_helper.rb +23 -0
 - data/spec/support/test_job.rb +30 -0
 - metadata +255 -0
 
| 
         @@ -0,0 +1,54 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'active_support/core_ext'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'active_support/core_ext/object/json'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module JobDispatch
         
     | 
| 
      
 5 
     | 
    
         
            +
              # Identity encapsulates a ZeroMQ socket identity, which is a string of binary characters, typically
         
     | 
| 
      
 6 
     | 
    
         
            +
              # containing nulls or non-utf8 compatible characters in ASCII-8BIT encoding.
         
     | 
| 
      
 7 
     | 
    
         
            +
              class Identity
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                include Comparable
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                attr_reader :identity
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def initialize(identity)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @identity = identity.to_sym
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @identity.to_s
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def to_str
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @identity.to_str
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def to_hex
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @identity.to_s.bytes.map { |x| '%02x' % x }.join
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def as_json(options={})
         
     | 
| 
      
 30 
     | 
    
         
            +
                  to_hex.as_json(options)
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def to_sym
         
     | 
| 
      
 34 
     | 
    
         
            +
                  @identity
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def hash
         
     | 
| 
      
 38 
     | 
    
         
            +
                  @identity.hash
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def ==(other)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  @identity == other.identity
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def eql?(other)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  self.class == other.class && @identity == other.identity
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                def <=>(other)
         
     | 
| 
      
 50 
     | 
    
         
            +
                  @identity <=> other.identity
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
              end
         
     | 
| 
      
 54 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,44 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module JobDispatch
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Job
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
                DEFAULT_EXECUTION_TIMEOUT = 30
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                PENDING = 0
         
     | 
| 
      
 7 
     | 
    
         
            +
                IN_PROGRESS = 1
         
     | 
| 
      
 8 
     | 
    
         
            +
                COMPLETED = 2
         
     | 
| 
      
 9 
     | 
    
         
            +
                FAILED = 3
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                STATUS_STRINGS = {
         
     | 
| 
      
 12 
     | 
    
         
            +
                    PENDING => 'pending',
         
     | 
| 
      
 13 
     | 
    
         
            +
                    IN_PROGRESS => 'in progress',
         
     | 
| 
      
 14 
     | 
    
         
            +
                    COMPLETED => 'completed',
         
     | 
| 
      
 15 
     | 
    
         
            +
                    FAILED => 'failed'
         
     | 
| 
      
 16 
     | 
    
         
            +
                }
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def timed_out?
         
     | 
| 
      
 19 
     | 
    
         
            +
                  expire_execution_at < Time.now
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def failed!(results)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  # update database
         
     | 
| 
      
 24 
     | 
    
         
            +
                  self.completed_at = Time.now
         
     | 
| 
      
 25 
     | 
    
         
            +
                  self.result = results
         
     | 
| 
      
 26 
     | 
    
         
            +
                  if retry_count && retry_count > 0 && retry_delay && retry_delay > 0
         
     | 
| 
      
 27 
     | 
    
         
            +
                    self.retry_count -= 1
         
     | 
| 
      
 28 
     | 
    
         
            +
                    self.scheduled_at = Time.now + retry_delay.seconds
         
     | 
| 
      
 29 
     | 
    
         
            +
                    self.retry_delay *= 2
         
     | 
| 
      
 30 
     | 
    
         
            +
                    self.status = PENDING
         
     | 
| 
      
 31 
     | 
    
         
            +
                  else
         
     | 
| 
      
 32 
     | 
    
         
            +
                    self.status = FAILED
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                  save!
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def succeeded!(results)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  self.status = COMPLETED
         
     | 
| 
      
 39 
     | 
    
         
            +
                  self.result = results
         
     | 
| 
      
 40 
     | 
    
         
            +
                  self.completed_at = Time.now
         
     | 
| 
      
 41 
     | 
    
         
            +
                  save!
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
              end
         
     | 
| 
      
 44 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,30 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module JobDispatch
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              # This class represents a ZeroMQ socket for signalling to the broker that there are jobs immediately available.
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Signaller
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr :socket
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                def initialize(wakeup_connect_address)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @wakeup_connect_address = wakeup_connect_address
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def connect
         
     | 
| 
      
 12 
     | 
    
         
            +
                  if @socket.nil?
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @socket = JobDispatch.context.socket(ZMQ::PUB)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @socket.connect(@wakeup_connect_address)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def disconnect
         
     | 
| 
      
 19 
     | 
    
         
            +
                  if @socket
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @socket.close
         
     | 
| 
      
 21 
     | 
    
         
            +
                    @socket = nil
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                # signals are a straight
         
     | 
| 
      
 26 
     | 
    
         
            +
                def signal(queue='default')
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @socket.send(queue)
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
              end
         
     | 
| 
      
 30 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,18 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module JobDispatch::Sockets
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Enqueue
         
     | 
| 
      
 3 
     | 
    
         
            +
                def initialize(bind_address)
         
     | 
| 
      
 4 
     | 
    
         
            +
                  @socket = JobDispatch.context.socket(ZMQ::REQ)
         
     | 
| 
      
 5 
     | 
    
         
            +
                  @socket.bind(bind_address)
         
     | 
| 
      
 6 
     | 
    
         
            +
                end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                def poll_item
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @poll_item ||= ZMQ::Pollitem(@socket, ZMQ::POLLIN)
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                # Enqueue socket when it receives a message simply stores it in the database.
         
     | 
| 
      
 13 
     | 
    
         
            +
                # It will also send a message to wake a connected dispatcher
         
     | 
| 
      
 14 
     | 
    
         
            +
                def process
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,79 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'text-table'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module JobDispatch
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Status
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                attr :socket
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def initialize(connect_address)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @connect_address = connect_address
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def connect
         
     | 
| 
      
 14 
     | 
    
         
            +
                  if @socket.nil?
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @socket = JobDispatch.context.socket(ZMQ::REQ)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @socket.connect(@connect_address)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def disconnect
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @socket.close
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @socket = nil
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def fetch
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @socket.send(JSON.dump({command:'status'}))
         
     | 
| 
      
 27 
     | 
    
         
            +
                  json = @socket.recv
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @status = JSON.parse(json).with_indifferent_access
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @time = Time.now
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                def print
         
     | 
| 
      
 33 
     | 
    
         
            +
                  puts "Job Dispatcher status: #{@status[:status]} at #{@time}"
         
     | 
| 
      
 34 
     | 
    
         
            +
                  puts ""
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  table = Text::Table.new
         
     | 
| 
      
 37 
     | 
    
         
            +
                  table.head = ['Queue', 'Worker ID', 'Worker Name', 'Status', 'Job ID', 'Job Details']
         
     | 
| 
      
 38 
     | 
    
         
            +
                  table.rows = []
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  @status[:queues].each do |queue, workers|
         
     | 
| 
      
 41 
     | 
    
         
            +
                    if workers.empty?
         
     | 
| 
      
 42 
     | 
    
         
            +
                      table.rows << [
         
     | 
| 
      
 43 
     | 
    
         
            +
                          queue,
         
     | 
| 
      
 44 
     | 
    
         
            +
                          '- no workers -',
         
     | 
| 
      
 45 
     | 
    
         
            +
                          '',
         
     | 
| 
      
 46 
     | 
    
         
            +
                          '',
         
     | 
| 
      
 47 
     | 
    
         
            +
                          '',
         
     | 
| 
      
 48 
     | 
    
         
            +
                          '',
         
     | 
| 
      
 49 
     | 
    
         
            +
                      ]
         
     | 
| 
      
 50 
     | 
    
         
            +
                    else
         
     | 
| 
      
 51 
     | 
    
         
            +
                      workers.each_pair do |worker_id, worker_status|
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                        job = worker_status[:job]
         
     | 
| 
      
 54 
     | 
    
         
            +
                        if job
         
     | 
| 
      
 55 
     | 
    
         
            +
                          params_str = if job[:parameters]
         
     | 
| 
      
 56 
     | 
    
         
            +
                                         job[:parameters].map(&:inspect).join(',')[0..20]
         
     | 
| 
      
 57 
     | 
    
         
            +
                                       else
         
     | 
| 
      
 58 
     | 
    
         
            +
                                         ''
         
     | 
| 
      
 59 
     | 
    
         
            +
                                       end
         
     | 
| 
      
 60 
     | 
    
         
            +
                          job_details = "#{job[:target]}.#{job[:method]}(#{params_str})"
         
     | 
| 
      
 61 
     | 
    
         
            +
                        end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                        table.rows << [
         
     | 
| 
      
 64 
     | 
    
         
            +
                            queue,
         
     | 
| 
      
 65 
     | 
    
         
            +
                            worker_id,
         
     | 
| 
      
 66 
     | 
    
         
            +
                            worker_status[:name],
         
     | 
| 
      
 67 
     | 
    
         
            +
                            worker_status[:status],
         
     | 
| 
      
 68 
     | 
    
         
            +
                            worker_status[:job_id],
         
     | 
| 
      
 69 
     | 
    
         
            +
                            job_details,
         
     | 
| 
      
 70 
     | 
    
         
            +
                        ]
         
     | 
| 
      
 71 
     | 
    
         
            +
                      end
         
     | 
| 
      
 72 
     | 
    
         
            +
                    end
         
     | 
| 
      
 73 
     | 
    
         
            +
                  end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                  puts table.to_s
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
              end
         
     | 
| 
      
 79 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,43 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # encoding: UTF-8
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'active_support/dependencies'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module JobDispatch
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Worker
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                #
         
     | 
| 
      
 9 
     | 
    
         
            +
                # This represents a unit of work to be done. It will be serialised to Mongo database
         
     | 
| 
      
 10 
     | 
    
         
            +
                #
         
     | 
| 
      
 11 
     | 
    
         
            +
                class Item
         
     | 
| 
      
 12 
     | 
    
         
            +
                  attr_accessor :job_id
         
     | 
| 
      
 13 
     | 
    
         
            +
                  attr :target
         
     | 
| 
      
 14 
     | 
    
         
            +
                  attr :method
         
     | 
| 
      
 15 
     | 
    
         
            +
                  attr :params
         
     | 
| 
      
 16 
     | 
    
         
            +
                  attr :result
         
     | 
| 
      
 17 
     | 
    
         
            +
                  attr :status
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def initialize(target, method, *params)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @target, @method, @params = target, method, params
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  # execute the method on the target with the given parameters
         
     | 
| 
      
 24 
     | 
    
         
            +
                  # This will capture standard exceptions for return over network.
         
     | 
| 
      
 25 
     | 
    
         
            +
                  def execute
         
     | 
| 
      
 26 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 27 
     | 
    
         
            +
                      JobDispatch.logger.info "Worker executing job #{job_id}: #{target}.#{method}"
         
     | 
| 
      
 28 
     | 
    
         
            +
                      Thread.current["JobDispatch::Worker.job_id"] = job_id
         
     | 
| 
      
 29 
     | 
    
         
            +
                      @klass = target.constantize
         
     | 
| 
      
 30 
     | 
    
         
            +
                      @result = @klass.__send__(method.to_sym, *params)
         
     | 
| 
      
 31 
     | 
    
         
            +
                      @status = :success
         
     | 
| 
      
 32 
     | 
    
         
            +
                    rescue StandardError => ex
         
     | 
| 
      
 33 
     | 
    
         
            +
                      @result = ex
         
     | 
| 
      
 34 
     | 
    
         
            +
                      @status = :error
         
     | 
| 
      
 35 
     | 
    
         
            +
                    ensure
         
     | 
| 
      
 36 
     | 
    
         
            +
                      Thread.current["JobDispatch::Worker.job_id"] = nil
         
     | 
| 
      
 37 
     | 
    
         
            +
                      JobDispatch.logger.info "Worker completed job #{job_id}: #{target}.#{method}, status: #{@status}"
         
     | 
| 
      
 38 
     | 
    
         
            +
                    end
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
              end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,96 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # encoding: UTF-8
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'json'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module JobDispatch
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              class Worker
         
     | 
| 
      
 8 
     | 
    
         
            +
                class Socket
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  attr :socket
         
     | 
| 
      
 11 
     | 
    
         
            +
                  attr :item_class
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  def initialize(connect_address, item_klass)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @socket = JobDispatch.context.socket(ZMQ::REQ)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @socket.connect(connect_address)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @item_class = item_klass
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def poll_item
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @poll_item ||= ZMQ::Pollitem(@socket, ZMQ::POLLIN)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def ask_for_work(queue)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    @socket.send(JSON.dump({command: 'ready', queue: queue, worker_name: identity}))
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  def send_goodbye(queue)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    @socket.send(JSON.dump({command: 'goodbye', worker_name: identity}))
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  def close
         
     | 
| 
      
 32 
     | 
    
         
            +
                    @socket.close
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  def identity
         
     | 
| 
      
 36 
     | 
    
         
            +
                    @identity ||= begin
         
     | 
| 
      
 37 
     | 
    
         
            +
                      hostname = ::Socket.gethostname
         
     | 
| 
      
 38 
     | 
    
         
            +
                      process = Process.pid
         
     | 
| 
      
 39 
     | 
    
         
            +
                      thread = Thread.current.object_id.to_s(16)
         
     | 
| 
      
 40 
     | 
    
         
            +
                      ['ruby', hostname, process, thread].join(':')
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  # read an incoming message. The thread will block if there is no readable message.
         
     | 
| 
      
 45 
     | 
    
         
            +
                  #
         
     | 
| 
      
 46 
     | 
    
         
            +
                  # @return [JobDispatch::Item] the item to be processed (or nil if there isn't a valid job)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  def read_item
         
     | 
| 
      
 48 
     | 
    
         
            +
                    json = @socket.recv
         
     | 
| 
      
 49 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 50 
     | 
    
         
            +
                      params = JSON.parse(json)
         
     | 
| 
      
 51 
     | 
    
         
            +
                      case params["command"]
         
     | 
| 
      
 52 
     | 
    
         
            +
                        when "job"
         
     | 
| 
      
 53 
     | 
    
         
            +
                          item = item_class.new params["target"], params["method"], *params["parameters"]
         
     | 
| 
      
 54 
     | 
    
         
            +
                        when "idle"
         
     | 
| 
      
 55 
     | 
    
         
            +
                          item = item_class.new "JobDispatch", "idle"
         
     | 
| 
      
 56 
     | 
    
         
            +
                        when "quit"
         
     | 
| 
      
 57 
     | 
    
         
            +
                          puts "It's quittin' time!"
         
     | 
| 
      
 58 
     | 
    
         
            +
                          Process.exit(0)
         
     | 
| 
      
 59 
     | 
    
         
            +
                        else
         
     | 
| 
      
 60 
     | 
    
         
            +
                          item = item_class.new "JobDispatch", "unknown_command", params
         
     | 
| 
      
 61 
     | 
    
         
            +
                      end
         
     | 
| 
      
 62 
     | 
    
         
            +
                      item.job_id = params["job_id"]
         
     | 
| 
      
 63 
     | 
    
         
            +
                    rescue StandardError => e
         
     | 
| 
      
 64 
     | 
    
         
            +
                      JobDispatch.logger.error "Failed to read message from worker socket: #{e}"
         
     | 
| 
      
 65 
     | 
    
         
            +
                      nil
         
     | 
| 
      
 66 
     | 
    
         
            +
                    end
         
     | 
| 
      
 67 
     | 
    
         
            +
                    item
         
     | 
| 
      
 68 
     | 
    
         
            +
                  end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                  # after execution, send the response.
         
     | 
| 
      
 71 
     | 
    
         
            +
                  def send_response(job_id, status, result)
         
     | 
| 
      
 72 
     | 
    
         
            +
                    JobDispatch.logger.info "Worker #{Process.pid} completed job_id: #{job_id}: #{status}, result: #{result}"
         
     | 
| 
      
 73 
     | 
    
         
            +
                    response = {
         
     | 
| 
      
 74 
     | 
    
         
            +
                        command: 'completed',
         
     | 
| 
      
 75 
     | 
    
         
            +
                        ready: true,
         
     | 
| 
      
 76 
     | 
    
         
            +
                        job_id: job_id,
         
     | 
| 
      
 77 
     | 
    
         
            +
                        result: result,
         
     | 
| 
      
 78 
     | 
    
         
            +
                        status: status
         
     | 
| 
      
 79 
     | 
    
         
            +
                    }
         
     | 
| 
      
 80 
     | 
    
         
            +
                    @socket.send(JSON.dump(response))
         
     | 
| 
      
 81 
     | 
    
         
            +
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                  def send_touch(job_id, timeout=nil)
         
     | 
| 
      
 84 
     | 
    
         
            +
                    hash = {
         
     | 
| 
      
 85 
     | 
    
         
            +
                        command: 'touch',
         
     | 
| 
      
 86 
     | 
    
         
            +
                        job_id: job_id
         
     | 
| 
      
 87 
     | 
    
         
            +
                    }
         
     | 
| 
      
 88 
     | 
    
         
            +
                    hash[:timeout] = timeout if timeout
         
     | 
| 
      
 89 
     | 
    
         
            +
                    @socket.send(JSON.dump(hash))
         
     | 
| 
      
 90 
     | 
    
         
            +
                    json = @socket.recv # wait for acknowledgement... this could be done via pub/sub to be asynchronous.
         
     | 
| 
      
 91 
     | 
    
         
            +
                    JSON.parse(json) rescue {:error => "Failed to decode JSON from dispatcher: #{json}"}
         
     | 
| 
      
 92 
     | 
    
         
            +
                  end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                end
         
     | 
| 
      
 95 
     | 
    
         
            +
              end
         
     | 
| 
      
 96 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,120 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # encoding: UTF-8
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module JobDispatch
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              #
         
     | 
| 
      
 6 
     | 
    
         
            +
              # This class is the main worker loop. Run it as a whole process or just as a thread in a multi-threaded worker
         
     | 
| 
      
 7 
     | 
    
         
            +
              # process.
         
     | 
| 
      
 8 
     | 
    
         
            +
              #
         
     | 
| 
      
 9 
     | 
    
         
            +
              class Worker
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                IDLE_TIME = 3
         
     | 
| 
      
 12 
     | 
    
         
            +
                IDLE_COUNT = 10
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                attr :socket
         
     | 
| 
      
 15 
     | 
    
         
            +
                attr :queue
         
     | 
| 
      
 16 
     | 
    
         
            +
                attr :item_class
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def initialize(connect_address, options={})
         
     | 
| 
      
 19 
     | 
    
         
            +
                  options ||= {}
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @connect_address = connect_address
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @queue = options[:queue] || 'default'
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @running = false
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @item_class = options[:item_class] || Worker::Item
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def connect
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @socket ||= Worker::Socket.new(@connect_address, item_class)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  Thread.current["JobDispatch::Worker.socket"] = @socket
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                def disconnect
         
     | 
| 
      
 32 
     | 
    
         
            +
                  if @socket
         
     | 
| 
      
 33 
     | 
    
         
            +
                    @socket.close
         
     | 
| 
      
 34 
     | 
    
         
            +
                    @socket = nil
         
     | 
| 
      
 35 
     | 
    
         
            +
                    Thread.current["JobDispatch::Worker.socket"] = nil
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def run
         
     | 
| 
      
 40 
     | 
    
         
            +
                  @running = true
         
     | 
| 
      
 41 
     | 
    
         
            +
                  while running?
         
     | 
| 
      
 42 
     | 
    
         
            +
                    puts "connecting"
         
     | 
| 
      
 43 
     | 
    
         
            +
                    connect
         
     | 
| 
      
 44 
     | 
    
         
            +
                    puts "asking for work"
         
     | 
| 
      
 45 
     | 
    
         
            +
                    ask_for_work
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                    # if we are idle for too many times, the broker has restarted or gone away, and we will be stuck in receive
         
     | 
| 
      
 48 
     | 
    
         
            +
                    # state, so we need to close the socket and make a new one to ask for work again.
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                    idle_count = 0
         
     | 
| 
      
 51 
     | 
    
         
            +
                    poller = ZMQ::Poller.new
         
     | 
| 
      
 52 
     | 
    
         
            +
                    poller.register(socket.poll_item)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    while running? and idle_count < IDLE_COUNT
         
     | 
| 
      
 54 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 55 
     | 
    
         
            +
                        poller.poll(IDLE_TIME)
         
     | 
| 
      
 56 
     | 
    
         
            +
                        if poller.readables.include?(socket.socket)
         
     | 
| 
      
 57 
     | 
    
         
            +
                          process
         
     | 
| 
      
 58 
     | 
    
         
            +
                          idle_count = 0
         
     | 
| 
      
 59 
     | 
    
         
            +
                        else
         
     | 
| 
      
 60 
     | 
    
         
            +
                          idle
         
     | 
| 
      
 61 
     | 
    
         
            +
                          idle_count += 1
         
     | 
| 
      
 62 
     | 
    
         
            +
                        end
         
     | 
| 
      
 63 
     | 
    
         
            +
                      rescue Interrupt
         
     | 
| 
      
 64 
     | 
    
         
            +
                        puts "Worker stopping."
         
     | 
| 
      
 65 
     | 
    
         
            +
                        JobDispatch.logger.info("Worker #{}")
         
     | 
| 
      
 66 
     | 
    
         
            +
                        stop
         
     | 
| 
      
 67 
     | 
    
         
            +
                        disconnect
         
     | 
| 
      
 68 
     | 
    
         
            +
                        connect
         
     | 
| 
      
 69 
     | 
    
         
            +
                        send_goodbye
         
     | 
| 
      
 70 
     | 
    
         
            +
                      end
         
     | 
| 
      
 71 
     | 
    
         
            +
                    end
         
     | 
| 
      
 72 
     | 
    
         
            +
                    disconnect
         
     | 
| 
      
 73 
     | 
    
         
            +
                  end
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                def ask_for_work
         
     | 
| 
      
 77 
     | 
    
         
            +
                  socket.ask_for_work(queue)
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                def send_goodbye
         
     | 
| 
      
 81 
     | 
    
         
            +
                  socket.send_goodbye(queue)
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                def running?
         
     | 
| 
      
 85 
     | 
    
         
            +
                  @running
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                def stop
         
     | 
| 
      
 89 
     | 
    
         
            +
                  @running = false
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                def self.touch(timeout=nil)
         
     | 
| 
      
 93 
     | 
    
         
            +
                  sock = Thread.current["JobDispatch::Worker.socket"]
         
     | 
| 
      
 94 
     | 
    
         
            +
                  job_id = Thread.current["JobDispatch::Worker.job_id"]
         
     | 
| 
      
 95 
     | 
    
         
            +
                  if sock && job_id
         
     | 
| 
      
 96 
     | 
    
         
            +
                    sock.send_touch(job_id, timeout)
         
     | 
| 
      
 97 
     | 
    
         
            +
                  end
         
     | 
| 
      
 98 
     | 
    
         
            +
                end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                private
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                # called when the socket is readable. do some work.
         
     | 
| 
      
 103 
     | 
    
         
            +
                def process
         
     | 
| 
      
 104 
     | 
    
         
            +
                  item = @socket.read_item
         
     | 
| 
      
 105 
     | 
    
         
            +
                  if item
         
     | 
| 
      
 106 
     | 
    
         
            +
                    item.execute
         
     | 
| 
      
 107 
     | 
    
         
            +
                    @socket.send_response(item.job_id, item.status, item.result)
         
     | 
| 
      
 108 
     | 
    
         
            +
                  else
         
     | 
| 
      
 109 
     | 
    
         
            +
                    @socket.send_response("unknown", :error, "failed to decode command")
         
     | 
| 
      
 110 
     | 
    
         
            +
                  end
         
     | 
| 
      
 111 
     | 
    
         
            +
                end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
                def idle
         
     | 
| 
      
 114 
     | 
    
         
            +
                  puts "waiting for job to do…"
         
     | 
| 
      
 115 
     | 
    
         
            +
                end
         
     | 
| 
      
 116 
     | 
    
         
            +
              end
         
     | 
| 
      
 117 
     | 
    
         
            +
            end
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
            require 'job_dispatch/worker/socket'
         
     | 
| 
      
 120 
     | 
    
         
            +
            require 'job_dispatch/worker/item'
         
     | 
    
        data/lib/job_dispatch.rb
    ADDED
    
    | 
         @@ -0,0 +1,97 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # encoding: UTF-8
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "job_dispatch/version"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            require 'active_support/dependencies/autoload'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'active_support/core_ext/hash/indifferent_access'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require 'active_support/core_ext/module/attribute_accessors'
         
     | 
| 
      
 8 
     | 
    
         
            +
            require 'nullobject'
         
     | 
| 
      
 9 
     | 
    
         
            +
            require 'rbczmq'
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            module JobDispatch
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              extend ActiveSupport::Autoload
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              autoload :Broker
         
     | 
| 
      
 16 
     | 
    
         
            +
              autoload :Client
         
     | 
| 
      
 17 
     | 
    
         
            +
              autoload :Configuration
         
     | 
| 
      
 18 
     | 
    
         
            +
              autoload :Identity
         
     | 
| 
      
 19 
     | 
    
         
            +
              autoload :Job
         
     | 
| 
      
 20 
     | 
    
         
            +
              autoload :Signaller
         
     | 
| 
      
 21 
     | 
    
         
            +
              autoload :Status
         
     | 
| 
      
 22 
     | 
    
         
            +
              autoload :Worker
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              def configure(&block)
         
     | 
| 
      
 25 
     | 
    
         
            +
                Configuration.configure(&block)
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              def config
         
     | 
| 
      
 29 
     | 
    
         
            +
                Configuration.config
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
              def load_config_from_yml(filename='config/job_dispatch.yml', environment="default")
         
     | 
| 
      
 33 
     | 
    
         
            +
                require 'yaml'
         
     | 
| 
      
 34 
     | 
    
         
            +
                _config = YAML.load_file(filename).with_indifferent_access
         
     | 
| 
      
 35 
     | 
    
         
            +
                _config = _config[environment] || _config[:default]
         
     | 
| 
      
 36 
     | 
    
         
            +
                load_config(_config)
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
              def load_config(hash)
         
     | 
| 
      
 40 
     | 
    
         
            +
                configure do |c|
         
     | 
| 
      
 41 
     | 
    
         
            +
                  hash.each_pair do |key, value|
         
     | 
| 
      
 42 
     | 
    
         
            +
                    c[key] = value
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
              end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
              # @return [ZMQ::Context] return or create a ZeroMQ context.
         
     | 
| 
      
 48 
     | 
    
         
            +
              def context
         
     | 
| 
      
 49 
     | 
    
         
            +
                ZMQ.context || ZMQ::Context.new
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
              def idle
         
     | 
| 
      
 53 
     | 
    
         
            +
                "idle, doing nothing"
         
     | 
| 
      
 54 
     | 
    
         
            +
              end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
              def unknown_command(params)
         
     | 
| 
      
 57 
     | 
    
         
            +
                puts "Unknown command: #{params.inspect}"
         
     | 
| 
      
 58 
     | 
    
         
            +
              end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
              # This signals to the job broker(s) that there are jobs immediately available on the given queue.
         
     | 
| 
      
 61 
     | 
    
         
            +
              def signal(queue='default')
         
     | 
| 
      
 62 
     | 
    
         
            +
                self.signaller ||= if config.signaller && config.signaller[:connect]
         
     | 
| 
      
 63 
     | 
    
         
            +
                                     signaller = JobDispatch::Signaller.new(config.signaller[:connect])
         
     | 
| 
      
 64 
     | 
    
         
            +
                                     signaller.connect
         
     | 
| 
      
 65 
     | 
    
         
            +
                                     signaller
         
     | 
| 
      
 66 
     | 
    
         
            +
                                   else
         
     | 
| 
      
 67 
     | 
    
         
            +
                                     Null::Object.instance
         
     | 
| 
      
 68 
     | 
    
         
            +
                                   end
         
     | 
| 
      
 69 
     | 
    
         
            +
                self.signaller.signal(queue)
         
     | 
| 
      
 70 
     | 
    
         
            +
              end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
              def enqueue(job_attrs)
         
     | 
| 
      
 74 
     | 
    
         
            +
                address = JobDispatch.config.broker[:connect]
         
     | 
| 
      
 75 
     | 
    
         
            +
                socket = JobDispatch.context.socket(ZMQ::REQ)
         
     | 
| 
      
 76 
     | 
    
         
            +
                socket.connect(address)
         
     | 
| 
      
 77 
     | 
    
         
            +
                socket.send(JSON.dump({command:'enqueue',job:job_attrs}))
         
     | 
| 
      
 78 
     | 
    
         
            +
                result = JSON.parse(socket.recv)
         
     | 
| 
      
 79 
     | 
    
         
            +
                socket.close
         
     | 
| 
      
 80 
     | 
    
         
            +
                result
         
     | 
| 
      
 81 
     | 
    
         
            +
              end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
              module_function :context
         
     | 
| 
      
 84 
     | 
    
         
            +
              module_function :idle
         
     | 
| 
      
 85 
     | 
    
         
            +
              module_function :unknown_command
         
     | 
| 
      
 86 
     | 
    
         
            +
              module_function :signal
         
     | 
| 
      
 87 
     | 
    
         
            +
              module_function :configure
         
     | 
| 
      
 88 
     | 
    
         
            +
              module_function :config
         
     | 
| 
      
 89 
     | 
    
         
            +
              module_function :enqueue
         
     | 
| 
      
 90 
     | 
    
         
            +
              module_function :load_config
         
     | 
| 
      
 91 
     | 
    
         
            +
              module_function :load_config_from_yml
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
              mattr_accessor :logger
         
     | 
| 
      
 94 
     | 
    
         
            +
              mattr_accessor :signaller
         
     | 
| 
      
 95 
     | 
    
         
            +
            end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
            JobDispatch.logger = Null::Object.instance
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            FactoryGirl.define do
         
     | 
| 
      
 2 
     | 
    
         
            +
              factory :job do
         
     | 
| 
      
 3 
     | 
    
         
            +
                id { SecureRandom.uuid }
         
     | 
| 
      
 4 
     | 
    
         
            +
                queue :default
         
     | 
| 
      
 5 
     | 
    
         
            +
                status JobDispatch::Job::PENDING
         
     | 
| 
      
 6 
     | 
    
         
            +
                parameters []
         
     | 
| 
      
 7 
     | 
    
         
            +
                target "SecureRandom"
         
     | 
| 
      
 8 
     | 
    
         
            +
                add_attribute(:method) { "uuid" }
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                enqueued_at { Time.now }
         
     | 
| 
      
 11 
     | 
    
         
            +
                scheduled_at { Time.at(0) }
         
     | 
| 
      
 12 
     | 
    
         
            +
                expire_execution_at { nil}
         
     | 
| 
      
 13 
     | 
    
         
            +
                timeout 10
         
     | 
| 
      
 14 
     | 
    
         
            +
                retry_count 0
         
     | 
| 
      
 15 
     | 
    
         
            +
                retry_delay 20
         
     | 
| 
      
 16 
     | 
    
         
            +
                completed_at nil
         
     | 
| 
      
 17 
     | 
    
         
            +
                result nil
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,53 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'spec_helper'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe JobDispatch::Broker::Socket do
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              subject { JobDispatch::Broker::Socket.new('tcp://localhost:1999') }
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              context "Reading messages from a worker" do
         
     | 
| 
      
 8 
     | 
    
         
            +
                before :each do
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @socket = double('Socket')
         
     | 
| 
      
 10 
     | 
    
         
            +
                  subject.stub(:socket => @socket)
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                context "with a valid message" do
         
     | 
| 
      
 14 
     | 
    
         
            +
                  before :each do
         
     | 
| 
      
 15 
     | 
    
         
            +
                    message = ZMQ::Message.new
         
     | 
| 
      
 16 
     | 
    
         
            +
                    message.addstr(JSON.dump({command: 'ready', queue: 'my_queue'}))
         
     | 
| 
      
 17 
     | 
    
         
            +
                    message.wrap(ZMQ::Frame('my_worker_id'))
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @socket.stub(:recv_message => message)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    @command = subject.read_command
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  it "returns a Command" do
         
     | 
| 
      
 23 
     | 
    
         
            +
                    expect(@command).to be_a(JobDispatch::Broker::Command)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  it "reads the command" do
         
     | 
| 
      
 27 
     | 
    
         
            +
                    expect(@command.parameters[:command]).to eq('ready')
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  it "reads the worker id" do
         
     | 
| 
      
 31 
     | 
    
         
            +
                    expect(@command.worker_id.to_sym).to eq(:my_worker_id)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                context "with an invalid message" do
         
     | 
| 
      
 36 
     | 
    
         
            +
                  before :each do
         
     | 
| 
      
 37 
     | 
    
         
            +
                    message = ZMQ::Message.new
         
     | 
| 
      
 38 
     | 
    
         
            +
                    message.addstr("Hello")
         
     | 
| 
      
 39 
     | 
    
         
            +
                    message.wrap(ZMQ::Frame('my_worker_id'))
         
     | 
| 
      
 40 
     | 
    
         
            +
                    @socket.stub(:recv_message => message)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    @command = subject.read_command
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  it "returns a Command" do
         
     | 
| 
      
 45 
     | 
    
         
            +
                    expect(@command).to be_a(JobDispatch::Broker::Command)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  it "reads the worker id" do
         
     | 
| 
      
 49 
     | 
    
         
            +
                    expect(@command.worker_id.to_sym).to eq(:my_worker_id)
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
              end
         
     | 
| 
      
 53 
     | 
    
         
            +
            end
         
     |