qs 0.6.1 → 0.7.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 +4 -4
- data/bench/config.qs +1 -0
- data/bench/dispatcher.qs +1 -0
- data/bench/report.rb +2 -0
- data/bench/setup.rb +1 -1
- data/lib/qs.rb +95 -41
- data/lib/qs/client.rb +5 -5
- data/lib/qs/config_file.rb +1 -1
- data/lib/qs/daemon.rb +133 -99
- data/lib/qs/daemon_data.rb +22 -21
- data/lib/qs/message_handler.rb +1 -0
- data/lib/qs/process.rb +20 -10
- data/lib/qs/qs_runner.rb +13 -21
- data/lib/qs/runner.rb +4 -0
- data/lib/qs/test_runner.rb +12 -2
- data/lib/qs/version.rb +1 -1
- data/qs.gemspec +6 -7
- data/test/helper.rb +11 -0
- data/test/support/app_queue.rb +104 -0
- data/test/support/client_spy.rb +2 -2
- data/test/support/config.qs +9 -2
- data/test/support/factory.rb +1 -1
- data/test/system/daemon_tests.rb +117 -36
- data/test/system/queue_tests.rb +1 -1
- data/test/unit/cli_tests.rb +2 -2
- data/test/unit/client_tests.rb +11 -11
- data/test/unit/config_file_tests.rb +2 -2
- data/test/unit/daemon_data_tests.rb +53 -48
- data/test/unit/daemon_tests.rb +221 -214
- data/test/unit/message_handler_tests.rb +14 -1
- data/test/unit/process_tests.rb +50 -25
- data/test/unit/qs_runner_tests.rb +120 -34
- data/test/unit/qs_tests.rb +180 -75
- data/test/unit/queue_tests.rb +5 -5
- data/test/unit/runner_tests.rb +9 -1
- data/test/unit/test_runner_tests.rb +12 -1
- metadata +13 -23
- data/test/support/app_daemon.rb +0 -143
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- 
         | 
| 2 2 | 
             
            SHA1: 
         | 
| 3 | 
            -
              data.tar.gz:  | 
| 4 | 
            -
              metadata.gz:  | 
| 3 | 
            +
              data.tar.gz: 110c7cbf5c19120501d4dfae9f332e428363aff3
         | 
| 4 | 
            +
              metadata.gz: 053fda8d651bb4509a99537ef8b8edb906a80acd
         | 
| 5 5 | 
             
            SHA512: 
         | 
| 6 | 
            -
              data.tar.gz:  | 
| 7 | 
            -
              metadata.gz:  | 
| 6 | 
            +
              data.tar.gz: 6c145c40e72aecad5c87b33709a3a3aa097a07a4bd08a17819d23965efd36fdbe46312a01fd638f346e4e34a2c7cc4c2ab85af7a9951083e97aae68f93527624
         | 
| 7 | 
            +
              metadata.gz: 56bff595f01f50dba796e2b436b842376188d3c031ab6cb88395bb7cc41e04250217c04ef5eedc78ba221586795045ba8e6b739816cbe65d89cb01dacd9607ab
         | 
    
        data/bench/config.qs
    CHANGED
    
    
    
        data/bench/dispatcher.qs
    CHANGED
    
    
    
        data/bench/report.rb
    CHANGED
    
    
    
        data/bench/setup.rb
    CHANGED
    
    
    
        data/lib/qs.rb
    CHANGED
    
    | @@ -1,4 +1,3 @@ | |
| 1 | 
            -
            require 'ns-options'
         | 
| 2 1 | 
             
            require 'qs/version'
         | 
| 3 2 | 
             
            require 'qs/client'
         | 
| 4 3 | 
             
            require 'qs/daemon'
         | 
| @@ -15,28 +14,24 @@ module Qs | |
| 15 14 | 
             
              end
         | 
| 16 15 |  | 
| 17 16 | 
             
              def self.init
         | 
| 18 | 
            -
                self.config. | 
| 19 | 
            -
                  self.config.redis.ip,
         | 
| 20 | 
            -
                  self.config.redis.port,
         | 
| 21 | 
            -
                  self.config.redis.db
         | 
| 22 | 
            -
                )
         | 
| 17 | 
            +
                self.config.validate!
         | 
| 23 18 |  | 
| 24 19 | 
             
                @dispatcher_queue ||= DispatcherQueue.new({
         | 
| 25 20 | 
             
                  :queue_class            => self.config.dispatcher_queue_class,
         | 
| 26 | 
            -
                  :queue_name             => self.config. | 
| 27 | 
            -
                  :job_name               => self.config. | 
| 28 | 
            -
                  :job_handler_class_name => self.config. | 
| 21 | 
            +
                  :queue_name             => self.config.dispatcher_queue_name,
         | 
| 22 | 
            +
                  :job_name               => self.config.dispatcher_job_name,
         | 
| 23 | 
            +
                  :job_handler_class_name => self.config.dispatcher_job_handler_class_name
         | 
| 29 24 | 
             
                })
         | 
| 30 25 |  | 
| 31 26 | 
             
                @encoder ||= self.config.encoder
         | 
| 32 27 | 
             
                @decoder ||= self.config.decoder
         | 
| 33 | 
            -
                @client  ||= Client.new(self. | 
| 28 | 
            +
                @client  ||= Client.new(self.redis_connect_hash)
         | 
| 34 29 | 
             
                @redis   ||= @client.redis
         | 
| 35 30 | 
             
                true
         | 
| 36 31 | 
             
              end
         | 
| 37 32 |  | 
| 38 33 | 
             
              def self.reset!
         | 
| 39 | 
            -
                 | 
| 34 | 
            +
                @config           = nil
         | 
| 40 35 | 
             
                @dispatcher_queue = nil
         | 
| 41 36 | 
             
                @encoder          = nil
         | 
| 42 37 | 
             
                @decoder          = nil
         | 
| @@ -46,19 +41,19 @@ module Qs | |
| 46 41 | 
             
              end
         | 
| 47 42 |  | 
| 48 43 | 
             
              def self.enqueue(queue, job_name, params = nil)
         | 
| 49 | 
            -
                 | 
| 44 | 
            +
                self.client.enqueue(queue, job_name, params)
         | 
| 50 45 | 
             
              end
         | 
| 51 46 |  | 
| 52 47 | 
             
              def self.publish(channel, name, params = nil)
         | 
| 53 | 
            -
                 | 
| 48 | 
            +
                self.client.publish(channel, name, params)
         | 
| 54 49 | 
             
              end
         | 
| 55 50 |  | 
| 56 51 | 
             
              def self.publish_as(publisher, channel, name, params = nil)
         | 
| 57 | 
            -
                 | 
| 52 | 
            +
                self.client.publish_as(publisher, channel, name, params)
         | 
| 58 53 | 
             
              end
         | 
| 59 54 |  | 
| 60 55 | 
             
              def self.push(queue_name, payload)
         | 
| 61 | 
            -
                 | 
| 56 | 
            +
                self.client.push(queue_name, payload)
         | 
| 62 57 | 
             
              end
         | 
| 63 58 |  | 
| 64 59 | 
             
              def self.encode(payload)
         | 
| @@ -82,15 +77,15 @@ module Qs | |
| 82 77 | 
             
              end
         | 
| 83 78 |  | 
| 84 79 | 
             
              def self.client
         | 
| 85 | 
            -
                @client
         | 
| 80 | 
            +
                @client || NullClient.new
         | 
| 86 81 | 
             
              end
         | 
| 87 82 |  | 
| 88 83 | 
             
              def self.redis
         | 
| 89 84 | 
             
                @redis
         | 
| 90 85 | 
             
              end
         | 
| 91 86 |  | 
| 92 | 
            -
              def self. | 
| 93 | 
            -
                self.config. | 
| 87 | 
            +
              def self.redis_connect_hash
         | 
| 88 | 
            +
                self.config.redis_connect_hash
         | 
| 94 89 | 
             
              end
         | 
| 95 90 |  | 
| 96 91 | 
             
              def self.dispatcher_queue
         | 
| @@ -98,7 +93,7 @@ module Qs | |
| 98 93 | 
             
              end
         | 
| 99 94 |  | 
| 100 95 | 
             
              def self.dispatcher_job_name
         | 
| 101 | 
            -
                self.config. | 
| 96 | 
            +
                self.config.dispatcher_job_name
         | 
| 102 97 | 
             
              end
         | 
| 103 98 |  | 
| 104 99 | 
             
              def self.event_publisher
         | 
| @@ -110,46 +105,105 @@ module Qs | |
| 110 105 | 
             
              end
         | 
| 111 106 |  | 
| 112 107 | 
             
              class Config
         | 
| 113 | 
            -
                include NsOptions::Proxy
         | 
| 114 108 |  | 
| 115 | 
            -
                 | 
| 116 | 
            -
                 | 
| 109 | 
            +
                DEFAULT_ENCODER = proc{ |payload|         ::JSON.dump(payload)         }
         | 
| 110 | 
            +
                DEFAULT_DECODER = proc{ |encoded_payload| ::JSON.load(encoded_payload) }
         | 
| 117 111 |  | 
| 118 | 
            -
                 | 
| 112 | 
            +
                DEFAULT_DISPATCHER_QUEUE_CLASS            = Queue
         | 
| 113 | 
            +
                DEFAULT_DISPATCHER_QUEUE_NAME             = 'dispatcher'.freeze
         | 
| 114 | 
            +
                DEFAULT_DISPATCHER_JOB_NAME               = 'run_dispatch_job'.freeze
         | 
| 115 | 
            +
                DEFAULT_DISPATCHER_JOB_HANDLER_CLASS_NAME = DispatcherQueue::RunDispatchJob.to_s.freeze
         | 
| 119 116 |  | 
| 120 | 
            -
                 | 
| 117 | 
            +
                DEFAULT_REDIS_IP      = '127.0.0.1'.freeze
         | 
| 118 | 
            +
                DEFAULT_REDIS_PORT    = 6379.freeze
         | 
| 119 | 
            +
                DEFAULT_REDIS_DB      = 0.freeze
         | 
| 120 | 
            +
                DEFAULT_REDIS_NS      = 'qs'.freeze
         | 
| 121 | 
            +
                DEFAULT_REDIS_DRIVER  = 'ruby'.freeze
         | 
| 122 | 
            +
                DEFAULT_REDIS_TIMEOUT = 1.freeze
         | 
| 123 | 
            +
                DEFAULT_REDIS_SIZE    = 4.freeze
         | 
| 121 124 |  | 
| 122 | 
            -
                 | 
| 123 | 
            -
             | 
| 124 | 
            -
             | 
| 125 | 
            -
             | 
| 126 | 
            -
                 | 
| 125 | 
            +
                attr_accessor :encoder, :decoder, :timeout, :event_publisher
         | 
| 126 | 
            +
                attr_accessor :dispatcher_queue_class, :dispatcher_queue_name
         | 
| 127 | 
            +
                attr_accessor :dispatcher_job_name, :dispatcher_job_handler_class_name
         | 
| 128 | 
            +
                attr_accessor :redis_ip, :redis_port, :redis_db, :redis_ns
         | 
| 129 | 
            +
                attr_accessor :redis_driver, :redis_timeout, :redis_size, :redis_url
         | 
| 127 130 |  | 
| 128 | 
            -
                 | 
| 129 | 
            -
                   | 
| 130 | 
            -
                   | 
| 131 | 
            -
                   | 
| 131 | 
            +
                def initialize
         | 
| 132 | 
            +
                  @encoder         = DEFAULT_ENCODER
         | 
| 133 | 
            +
                  @decoder         = DEFAULT_DECODER
         | 
| 134 | 
            +
                  @timeout         = nil
         | 
| 135 | 
            +
                  @event_publisher = nil
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                  @dispatcher_queue_class            = DEFAULT_DISPATCHER_QUEUE_CLASS
         | 
| 138 | 
            +
                  @dispatcher_queue_name             = DEFAULT_DISPATCHER_QUEUE_NAME
         | 
| 139 | 
            +
                  @dispatcher_job_name               = DEFAULT_DISPATCHER_JOB_NAME
         | 
| 140 | 
            +
                  @dispatcher_job_handler_class_name = DEFAULT_DISPATCHER_JOB_HANDLER_CLASS_NAME
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                  @redis_ip      = DEFAULT_REDIS_IP
         | 
| 143 | 
            +
                  @redis_port    = DEFAULT_REDIS_PORT
         | 
| 144 | 
            +
                  @redis_db      = DEFAULT_REDIS_DB
         | 
| 145 | 
            +
                  @redis_ns      = DEFAULT_REDIS_NS
         | 
| 146 | 
            +
                  @redis_driver  = DEFAULT_REDIS_DRIVER
         | 
| 147 | 
            +
                  @redis_timeout = DEFAULT_REDIS_TIMEOUT
         | 
| 148 | 
            +
                  @redis_size    = DEFAULT_REDIS_SIZE
         | 
| 149 | 
            +
                  @redis_url     = nil
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                  @valid = nil
         | 
| 152 | 
            +
                end
         | 
| 132 153 |  | 
| 133 | 
            -
             | 
| 154 | 
            +
                # the keys here should be compatible with HellaRedis connection configs
         | 
| 155 | 
            +
                # https://github.com/redding/hella-redis#connection
         | 
| 156 | 
            +
                def redis_connect_hash
         | 
| 157 | 
            +
                  { :ip       => self.redis_ip,
         | 
| 158 | 
            +
                    :port     => self.redis_port,
         | 
| 159 | 
            +
                    :db       => self.redis_db,
         | 
| 160 | 
            +
                    :redis_ns => self.redis_ns,
         | 
| 161 | 
            +
                    :driver   => self.redis_driver,
         | 
| 162 | 
            +
                    :timeout  => self.redis_timeout,
         | 
| 163 | 
            +
                    :size     => self.redis_size,
         | 
| 164 | 
            +
                    :url      => self.redis_url
         | 
| 165 | 
            +
                  }
         | 
| 166 | 
            +
                end
         | 
| 134 167 |  | 
| 135 | 
            -
             | 
| 136 | 
            -
                   | 
| 137 | 
            -
                  option :timeout,  Integer, :default => 1
         | 
| 138 | 
            -
                  option :size,     Integer, :default => 4
         | 
| 168 | 
            +
                def valid?
         | 
| 169 | 
            +
                  !!@valid
         | 
| 139 170 | 
             
                end
         | 
| 140 171 |  | 
| 141 | 
            -
                 | 
| 172 | 
            +
                def validate!
         | 
| 173 | 
            +
                  return @valid if !@valid.nil? # only need to run this once per config
         | 
| 142 174 |  | 
| 143 | 
            -
             | 
| 144 | 
            -
                  self. | 
| 175 | 
            +
                  # set the `redis_url`
         | 
| 176 | 
            +
                  self.redis_url ||= RedisUrl.new(self.redis_ip, self.redis_port, self.redis_db)
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                  @valid = true
         | 
| 145 179 | 
             
                end
         | 
| 180 | 
            +
             | 
| 146 181 | 
             
              end
         | 
| 147 182 |  | 
| 148 183 | 
             
              module RedisUrl
         | 
| 184 | 
            +
             | 
| 149 185 | 
             
                def self.new(ip, port, db)
         | 
| 150 186 | 
             
                  return if ip.to_s.empty? || port.to_s.empty? || db.to_s.empty?
         | 
| 151 187 | 
             
                  "redis://#{ip}:#{port}/#{db}"
         | 
| 152 188 | 
             
                end
         | 
| 189 | 
            +
             | 
| 153 190 | 
             
              end
         | 
| 154 191 |  | 
| 192 | 
            +
              class NullClient
         | 
| 193 | 
            +
                def initialize
         | 
| 194 | 
            +
                  @error = UninitializedError.new("Qs hasn't been initialized")
         | 
| 195 | 
            +
                end
         | 
| 196 | 
            +
             | 
| 197 | 
            +
                def enqueue(*args);             raise @error; end
         | 
| 198 | 
            +
                def publish(*args);             raise @error; end
         | 
| 199 | 
            +
                def publish_as(*args);          raise @error; end
         | 
| 200 | 
            +
                def push(*args);                raise @error; end
         | 
| 201 | 
            +
                def sync_subscriptions(*args);  raise @error; end
         | 
| 202 | 
            +
                def clear_subscriptions(*args); raise @error; end
         | 
| 203 | 
            +
                def event_subscribers(*args);   raise @error; end
         | 
| 204 | 
            +
              end
         | 
| 205 | 
            +
             | 
| 206 | 
            +
              UninitializedError = Class.new(RuntimeError)
         | 
| 207 | 
            +
              TimeoutError       = Class.new(RuntimeError)
         | 
| 208 | 
            +
             | 
| 155 209 | 
             
            end
         | 
    
        data/lib/qs/client.rb
    CHANGED
    
    | @@ -25,10 +25,10 @@ module Qs | |
| 25 25 |  | 
| 26 26 | 
             
                module InstanceMethods
         | 
| 27 27 |  | 
| 28 | 
            -
                  attr_reader : | 
| 28 | 
            +
                  attr_reader :redis_connect_hash, :redis
         | 
| 29 29 |  | 
| 30 | 
            -
                  def initialize( | 
| 31 | 
            -
                    @ | 
| 30 | 
            +
                  def initialize(redis_connect_hash)
         | 
| 31 | 
            +
                    @redis_connect_hash = redis_connect_hash
         | 
| 32 32 | 
             
                  end
         | 
| 33 33 |  | 
| 34 34 | 
             
                  def enqueue(queue, job_name, job_params = nil)
         | 
| @@ -119,7 +119,7 @@ module Qs | |
| 119 119 |  | 
| 120 120 | 
             
                def initialize(*args)
         | 
| 121 121 | 
             
                  super
         | 
| 122 | 
            -
                  @redis = HellaRedis::Connection.new(self. | 
| 122 | 
            +
                  @redis = HellaRedis::Connection.new(self.redis_connect_hash)
         | 
| 123 123 | 
             
                end
         | 
| 124 124 |  | 
| 125 125 | 
             
                def push(queue_name, payload_hash)
         | 
| @@ -145,7 +145,7 @@ module Qs | |
| 145 145 | 
             
                def initialize(*args)
         | 
| 146 146 | 
             
                  super
         | 
| 147 147 | 
             
                  require 'hella-redis/connection_spy'
         | 
| 148 | 
            -
                  @redis = HellaRedis::ConnectionSpy.new(self. | 
| 148 | 
            +
                  @redis = HellaRedis::ConnectionSpy.new(self.redis_connect_hash)
         | 
| 149 149 | 
             
                  @pushed_items = []
         | 
| 150 150 | 
             
                end
         | 
| 151 151 |  | 
    
        data/lib/qs/config_file.rb
    CHANGED
    
    | @@ -35,7 +35,7 @@ module Qs | |
| 35 35 | 
             
                  full_path_with_sanford
         | 
| 36 36 | 
             
                end
         | 
| 37 37 |  | 
| 38 | 
            -
                # This evaluates the file and creates a proc using  | 
| 38 | 
            +
                # This evaluates the file and creates a proc using its contents. This is
         | 
| 39 39 | 
             
                # a trick borrowed from Rack. It is essentially converting a file into a
         | 
| 40 40 | 
             
                # proc and then instance eval'ing it. This has a couple benefits:
         | 
| 41 41 | 
             
                # * The obvious benefit is the file is evaluated in the context of this
         | 
    
        data/lib/qs/daemon.rb
    CHANGED
    
    | @@ -1,8 +1,5 @@ | |
| 1 1 | 
             
            require 'dat-worker-pool'
         | 
| 2 2 | 
             
            require 'much-plugin'
         | 
| 3 | 
            -
            require 'ns-options'
         | 
| 4 | 
            -
            require 'pathname'
         | 
| 5 | 
            -
            require 'system_timer'
         | 
| 6 3 | 
             
            require 'thread'
         | 
| 7 4 | 
             
            require 'qs'
         | 
| 8 5 | 
             
            require 'qs/client'
         | 
| @@ -16,9 +13,8 @@ module Qs | |
| 16 13 | 
             
              module Daemon
         | 
| 17 14 | 
             
                include MuchPlugin
         | 
| 18 15 |  | 
| 19 | 
            -
                 | 
| 20 | 
            -
             | 
| 21 | 
            -
                SIGNAL = '.'.freeze
         | 
| 16 | 
            +
                SIGNAL               = '.'.freeze
         | 
| 17 | 
            +
                FETCH_ERR_SLEEP_TIME = 1.0.freeze
         | 
| 22 18 |  | 
| 23 19 | 
             
                plugin_included do
         | 
| 24 20 | 
             
                  extend ClassMethods
         | 
| @@ -27,28 +23,46 @@ module Qs | |
| 27 23 |  | 
| 28 24 | 
             
                module InstanceMethods
         | 
| 29 25 |  | 
| 30 | 
            -
                  attr_reader :daemon_data, : | 
| 31 | 
            -
                  attr_reader :signals_redis_key, :queue_redis_keys
         | 
| 26 | 
            +
                  attr_reader :daemon_data, :signals_redis_key
         | 
| 32 27 |  | 
| 33 | 
            -
                  # set the size of the client to the num workers + 1, this ensures we have
         | 
| 34 | 
            -
                  # 1 connection for fetching work from redis and at least 1 connection for
         | 
| 35 | 
            -
                  # each worker to requeue its message when hard-shutdown
         | 
| 36 28 | 
             
                  def initialize
         | 
| 37 | 
            -
                    self.class. | 
| 29 | 
            +
                    config = self.class.config
         | 
| 30 | 
            +
                    begin
         | 
| 31 | 
            +
                      config.validate!
         | 
| 32 | 
            +
                    rescue InvalidError => exception
         | 
| 33 | 
            +
                      exception.set_backtrace(caller)
         | 
| 34 | 
            +
                      raise exception
         | 
| 35 | 
            +
                    end
         | 
| 38 36 | 
             
                    Qs.init
         | 
| 39 | 
            -
                    @daemon_data = DaemonData.new(self.class.configuration.to_hash)
         | 
| 40 | 
            -
                    @logger      = @daemon_data.logger
         | 
| 41 37 |  | 
| 42 | 
            -
                    @ | 
| 43 | 
            -
                      : | 
| 44 | 
            -
                      : | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 38 | 
            +
                    @daemon_data = DaemonData.new({
         | 
| 39 | 
            +
                      :name             => config.name,
         | 
| 40 | 
            +
                      :pid_file         => config.pid_file,
         | 
| 41 | 
            +
                      :shutdown_timeout => config.shutdown_timeout,
         | 
| 42 | 
            +
                      :worker_class     => config.worker_class,
         | 
| 43 | 
            +
                      :worker_params    => config.worker_params,
         | 
| 44 | 
            +
                      :num_workers      => config.num_workers,
         | 
| 45 | 
            +
                      :error_procs      => config.error_procs,
         | 
| 46 | 
            +
                      :logger           => config.logger,
         | 
| 47 | 
            +
                      :queues           => config.queues,
         | 
| 48 | 
            +
                      :verbose_logging  => config.verbose_logging,
         | 
| 49 | 
            +
                      :routes           => config.routes
         | 
| 50 | 
            +
                    })
         | 
| 47 51 |  | 
| 48 | 
            -
                    @signals_redis_key = "signals:#{ | 
| 52 | 
            +
                    @signals_redis_key = "signals:#{self.daemon_data.name}-" \
         | 
| 49 53 | 
             
                                         "#{Socket.gethostname}-#{::Process.pid}"
         | 
| 50 54 |  | 
| 55 | 
            +
                    @thread           = nil
         | 
| 51 56 | 
             
                    @worker_available = WorkerAvailable.new
         | 
| 57 | 
            +
                    @state            = State.new(:stop)
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    # set the size of the client to the num workers + 1, this ensures we
         | 
| 60 | 
            +
                    # have 1 connection for fetching work from redis and at least 1
         | 
| 61 | 
            +
                    # connection for each worker to requeue its message when hard-shutdown
         | 
| 62 | 
            +
                    @client = QsClient.new(Qs.redis_connect_hash.merge({
         | 
| 63 | 
            +
                      :timeout => 1,
         | 
| 64 | 
            +
                      :size    => self.daemon_data.num_workers + 1
         | 
| 65 | 
            +
                    }))
         | 
| 52 66 |  | 
| 53 67 | 
             
                    @worker_pool = DatWorkerPool.new(self.daemon_data.worker_class, {
         | 
| 54 68 | 
             
                      :num_workers   => self.daemon_data.num_workers,
         | 
| @@ -57,15 +71,9 @@ module Qs | |
| 57 71 | 
             
                        :qs_daemon_data      => self.daemon_data,
         | 
| 58 72 | 
             
                        :qs_client           => @client,
         | 
| 59 73 | 
             
                        :qs_worker_available => @worker_available,
         | 
| 60 | 
            -
                        :qs_logger           =>  | 
| 74 | 
            +
                        :qs_logger           => self.logger
         | 
| 61 75 | 
             
                      })
         | 
| 62 76 | 
             
                    })
         | 
| 63 | 
            -
             | 
| 64 | 
            -
                    @thread = nil
         | 
| 65 | 
            -
                    @state = State.new(:stop)
         | 
| 66 | 
            -
                  rescue InvalidError => exception
         | 
| 67 | 
            -
                    exception.set_backtrace(caller)
         | 
| 68 | 
            -
                    raise exception
         | 
| 69 77 | 
             
                  end
         | 
| 70 78 |  | 
| 71 79 | 
             
                  def name
         | 
| @@ -80,14 +88,22 @@ module Qs | |
| 80 88 | 
             
                    @daemon_data.pid_file
         | 
| 81 89 | 
             
                  end
         | 
| 82 90 |  | 
| 91 | 
            +
                  def logger
         | 
| 92 | 
            +
                    @daemon_data.logger
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                  def queue_redis_keys
         | 
| 96 | 
            +
                    @daemon_data.queue_redis_keys
         | 
| 97 | 
            +
                  end
         | 
| 98 | 
            +
             | 
| 83 99 | 
             
                  def running?
         | 
| 84 100 | 
             
                    !!(@thread && @thread.alive?)
         | 
| 85 101 | 
             
                  end
         | 
| 86 102 |  | 
| 87 | 
            -
                  # ping to check that it can communicate with redis before running, this is
         | 
| 88 | 
            -
                  # friendlier than starting and continously erroring because it can't
         | 
| 89 | 
            -
                  # dequeue
         | 
| 90 103 | 
             
                  def start
         | 
| 104 | 
            +
                    # ping to check that it can communicate with redis before running,
         | 
| 105 | 
            +
                    # this is friendlier than starting and continously erroring because
         | 
| 106 | 
            +
                    # it can't dequeue
         | 
| 91 107 | 
             
                    @client.ping
         | 
| 92 108 | 
             
                    @state.set :run
         | 
| 93 109 | 
             
                    @thread ||= Thread.new{ work_loop }
         | 
| @@ -121,25 +137,25 @@ module Qs | |
| 121 137 | 
             
                    teardown
         | 
| 122 138 | 
             
                  end
         | 
| 123 139 |  | 
| 124 | 
            -
                  # clear any signals that are already on the signals list in redis
         | 
| 125 140 | 
             
                  def setup
         | 
| 126 | 
            -
             | 
| 141 | 
            +
                   # clear any signals that are already on the signals list in redis
         | 
| 142 | 
            +
                   @client.clear(self.signals_redis_key)
         | 
| 127 143 | 
             
                    @worker_pool.start
         | 
| 128 144 | 
             
                  end
         | 
| 129 145 |  | 
| 130 | 
            -
                  # shuffle the queue redis keys to avoid queue starvation, redis will pull
         | 
| 131 | 
            -
                  # messages off queues in the order they are passed to the command, by
         | 
| 132 | 
            -
                  # shuffling we ensure they are randomly ordered so every queue should get
         | 
| 133 | 
            -
                  # a chance; use 0 for the brpop timeout which means block indefinitely;
         | 
| 134 | 
            -
                  # rescue runtime errors so the daemon thread doesn't fail if redis is
         | 
| 135 | 
            -
                  # temporarily down, sleep for a second to keep the thread from thrashing
         | 
| 136 | 
            -
                  # by repeatedly erroring if redis is down
         | 
| 137 146 | 
             
                  def fetch_messages
         | 
| 138 147 | 
             
                    if !@worker_pool.worker_available? && @state.run?
         | 
| 139 148 | 
             
                      @worker_available.wait
         | 
| 140 149 | 
             
                    end
         | 
| 141 150 | 
             
                    return unless @worker_pool.worker_available? && @state.run?
         | 
| 142 151 |  | 
| 152 | 
            +
                    # shuffle the queue redis keys to avoid queue starvation, redis will
         | 
| 153 | 
            +
                    # pull messages off queues in the order they are passed to the command,
         | 
| 154 | 
            +
                    # by shuffling we ensure they are randomly ordered so every queue
         | 
| 155 | 
            +
                    # should  get a chance; use 0 for the brpop timeout which means block
         | 
| 156 | 
            +
                    # indefinitely; rescue runtime errors so the daemon thread doesn't fail
         | 
| 157 | 
            +
                    # if redis is temporarily down, sleep for a second to keep the thread
         | 
| 158 | 
            +
                    # from thrashing by repeatedly erroring if redis is down
         | 
| 143 159 | 
             
                    begin
         | 
| 144 160 | 
             
                      args = [self.signals_redis_key, self.queue_redis_keys.shuffle, 0].flatten
         | 
| 145 161 | 
             
                      redis_key, encoded_payload = @client.block_dequeue(*args)
         | 
| @@ -150,7 +166,7 @@ module Qs | |
| 150 166 | 
             
                      log "Error occurred while dequeueing", :error
         | 
| 151 167 | 
             
                      log "#{exception.class}: #{exception.message}", :error
         | 
| 152 168 | 
             
                      (exception.backtrace || []).each{ |l| log(l, :error) }
         | 
| 153 | 
            -
                      sleep  | 
| 169 | 
            +
                      sleep FETCH_ERR_SLEEP_TIME
         | 
| 154 170 | 
             
                    end
         | 
| 155 171 | 
             
                  end
         | 
| 156 172 |  | 
| @@ -183,86 +199,102 @@ module Qs | |
| 183 199 |  | 
| 184 200 | 
             
                module ClassMethods
         | 
| 185 201 |  | 
| 186 | 
            -
                  def  | 
| 187 | 
            -
                    @ | 
| 202 | 
            +
                  def config
         | 
| 203 | 
            +
                    @config ||= Config.new
         | 
| 204 | 
            +
                  end
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                  def name(value = nil)
         | 
| 207 | 
            +
                    self.config.name = value if !value.nil?
         | 
| 208 | 
            +
                    self.config.name
         | 
| 188 209 | 
             
                  end
         | 
| 189 210 |  | 
| 190 | 
            -
                  def  | 
| 191 | 
            -
                    self. | 
| 211 | 
            +
                  def pid_file(value = nil)
         | 
| 212 | 
            +
                    self.config.pid_file = value if !value.nil?
         | 
| 213 | 
            +
                    self.config.pid_file
         | 
| 192 214 | 
             
                  end
         | 
| 193 215 |  | 
| 194 | 
            -
                  def  | 
| 195 | 
            -
                    self. | 
| 216 | 
            +
                  def shutdown_timeout(value = nil)
         | 
| 217 | 
            +
                    self.config.shutdown_timeout = value if !value.nil?
         | 
| 218 | 
            +
                    self.config.shutdown_timeout
         | 
| 196 219 | 
             
                  end
         | 
| 197 220 |  | 
| 198 | 
            -
                  def worker_class( | 
| 199 | 
            -
                    self. | 
| 200 | 
            -
                    self. | 
| 221 | 
            +
                  def worker_class(value = nil)
         | 
| 222 | 
            +
                    self.config.worker_class = value if !value.nil?
         | 
| 223 | 
            +
                    self.config.worker_class
         | 
| 201 224 | 
             
                  end
         | 
| 202 225 |  | 
| 203 | 
            -
                  def worker_params( | 
| 204 | 
            -
                    self. | 
| 205 | 
            -
                    self. | 
| 226 | 
            +
                  def worker_params(value = nil)
         | 
| 227 | 
            +
                    self.config.worker_params = value if !value.nil?
         | 
| 228 | 
            +
                    self.config.worker_params
         | 
| 206 229 | 
             
                  end
         | 
| 207 230 |  | 
| 208 | 
            -
                  def num_workers( | 
| 209 | 
            -
                    self. | 
| 231 | 
            +
                  def num_workers(new_num_workers = nil)
         | 
| 232 | 
            +
                    self.config.num_workers = new_num_workers if new_num_workers
         | 
| 233 | 
            +
                    self.config.num_workers
         | 
| 210 234 | 
             
                  end
         | 
| 211 235 | 
             
                  alias :workers :num_workers
         | 
| 212 236 |  | 
| 213 | 
            -
                  def  | 
| 214 | 
            -
                    self. | 
| 237 | 
            +
                  def init(&block)
         | 
| 238 | 
            +
                    self.config.init_procs << block
         | 
| 215 239 | 
             
                  end
         | 
| 216 240 |  | 
| 217 | 
            -
                  def  | 
| 218 | 
            -
                    self. | 
| 241 | 
            +
                  def init_procs
         | 
| 242 | 
            +
                    self.config.init_procs
         | 
| 219 243 | 
             
                  end
         | 
| 220 244 |  | 
| 221 | 
            -
                  def  | 
| 222 | 
            -
                    self. | 
| 245 | 
            +
                  def error(&block)
         | 
| 246 | 
            +
                    self.config.error_procs << block
         | 
| 223 247 | 
             
                  end
         | 
| 224 248 |  | 
| 225 | 
            -
                  def  | 
| 226 | 
            -
                    self. | 
| 249 | 
            +
                  def error_procs
         | 
| 250 | 
            +
                    self.config.error_procs
         | 
| 227 251 | 
             
                  end
         | 
| 228 252 |  | 
| 229 | 
            -
                  def  | 
| 230 | 
            -
                    self. | 
| 253 | 
            +
                  def logger(value = nil)
         | 
| 254 | 
            +
                    self.config.logger = value if !value.nil?
         | 
| 255 | 
            +
                    self.config.logger
         | 
| 231 256 | 
             
                  end
         | 
| 232 257 |  | 
| 233 | 
            -
                  def queue( | 
| 234 | 
            -
                    self. | 
| 258 | 
            +
                  def queue(value)
         | 
| 259 | 
            +
                    self.config.queues << value
         | 
| 235 260 | 
             
                  end
         | 
| 236 261 |  | 
| 237 262 | 
             
                  def queues
         | 
| 238 | 
            -
                    self. | 
| 263 | 
            +
                    self.config.queues
         | 
| 239 264 | 
             
                  end
         | 
| 240 265 |  | 
| 241 | 
            -
             | 
| 266 | 
            +
                  # flags
         | 
| 242 267 |  | 
| 243 | 
            -
             | 
| 244 | 
            -
             | 
| 268 | 
            +
                  def verbose_logging(value = nil)
         | 
| 269 | 
            +
                    self.config.verbose_logging = value if !value.nil?
         | 
| 270 | 
            +
                    self.config.verbose_logging
         | 
| 271 | 
            +
                  end
         | 
| 245 272 |  | 
| 246 | 
            -
             | 
| 247 | 
            -
                  option :pid_file, Pathname
         | 
| 273 | 
            +
                end
         | 
| 248 274 |  | 
| 249 | 
            -
             | 
| 275 | 
            +
                class Config
         | 
| 250 276 |  | 
| 251 | 
            -
                   | 
| 252 | 
            -
                  option :logger,          :default => proc{ Qs::NullLogger.new }
         | 
| 277 | 
            +
                  DEFAULT_NUM_WORKERS = 4.freeze
         | 
| 253 278 |  | 
| 254 | 
            -
                   | 
| 279 | 
            +
                  attr_accessor :name, :pid_file, :shutdown_timeout
         | 
| 280 | 
            +
                  attr_accessor :worker_class, :worker_params, :num_workers
         | 
| 281 | 
            +
                  attr_accessor :init_procs, :error_procs, :logger, :queues
         | 
| 282 | 
            +
                  attr_accessor :verbose_logging
         | 
| 255 283 |  | 
| 256 | 
            -
                   | 
| 257 | 
            -
             | 
| 258 | 
            -
             | 
| 284 | 
            +
                  def initialize
         | 
| 285 | 
            +
                    @name             = nil
         | 
| 286 | 
            +
                    @pid_file         = nil
         | 
| 287 | 
            +
                    @shutdown_timeout = nil
         | 
| 288 | 
            +
                    @worker_class     = DefaultWorker
         | 
| 289 | 
            +
                    @worker_params    = nil
         | 
| 290 | 
            +
                    @num_workers      = DEFAULT_NUM_WORKERS
         | 
| 291 | 
            +
                    @init_procs       = []
         | 
| 292 | 
            +
                    @error_procs      = []
         | 
| 293 | 
            +
                    @logger           = Qs::NullLogger.new
         | 
| 294 | 
            +
                    @queues           = []
         | 
| 295 | 
            +
             | 
| 296 | 
            +
                    @verbose_logging = true
         | 
| 259 297 |  | 
| 260 | 
            -
                  def initialize(values = nil)
         | 
| 261 | 
            -
                    super(values)
         | 
| 262 | 
            -
                    @init_procs, @error_procs = [], []
         | 
| 263 | 
            -
                    @worker_class  = DefaultWorker
         | 
| 264 | 
            -
                    @worker_params = nil
         | 
| 265 | 
            -
                    @queues = []
         | 
| 266 298 | 
             
                    @valid = nil
         | 
| 267 299 | 
             
                  end
         | 
| 268 300 |  | 
| @@ -270,35 +302,37 @@ module Qs | |
| 270 302 | 
             
                    @queues.map(&:routes).flatten
         | 
| 271 303 | 
             
                  end
         | 
| 272 304 |  | 
| 273 | 
            -
                  def to_hash
         | 
| 274 | 
            -
                    super.merge({
         | 
| 275 | 
            -
                      :error_procs      => self.error_procs,
         | 
| 276 | 
            -
                      :worker_class     => self.worker_class,
         | 
| 277 | 
            -
                      :worker_params    => self.worker_params,
         | 
| 278 | 
            -
                      :routes           => self.routes,
         | 
| 279 | 
            -
                      :queue_redis_keys => self.queues.map(&:redis_key)
         | 
| 280 | 
            -
                    })
         | 
| 281 | 
            -
                  end
         | 
| 282 | 
            -
             | 
| 283 305 | 
             
                  def valid?
         | 
| 284 306 | 
             
                    !!@valid
         | 
| 285 307 | 
             
                  end
         | 
| 286 308 |  | 
| 309 | 
            +
                  # for the config to be considered "valid", a few things need to happen.
         | 
| 310 | 
            +
                  # The key here is that this only needs to be done _once_ for each config.
         | 
| 311 | 
            +
             | 
| 287 312 | 
             
                  def validate!
         | 
| 288 | 
            -
                    return @valid if !@valid.nil?
         | 
| 313 | 
            +
                    return @valid if !@valid.nil? # only need to run this once per config
         | 
| 314 | 
            +
             | 
| 315 | 
            +
                    # ensure all user and plugin configs/settings are applied
         | 
| 289 316 | 
             
                    self.init_procs.each(&:call)
         | 
| 290 | 
            -
                    if self.queues.empty? ||  | 
| 291 | 
            -
                      raise InvalidError, "a name and queue must be configured"
         | 
| 317 | 
            +
                    if self.queues.empty? || self.name.nil?
         | 
| 318 | 
            +
                      raise InvalidError, "a name and at least 1 queue must be configured"
         | 
| 292 319 | 
             
                    end
         | 
| 320 | 
            +
             | 
| 321 | 
            +
                    # validate the worker class
         | 
| 293 322 | 
             
                    if !self.worker_class.kind_of?(Class) || !self.worker_class.include?(Qs::Worker)
         | 
| 294 323 | 
             
                      raise InvalidError, "worker class must include `#{Qs::Worker}`"
         | 
| 295 324 | 
             
                    end
         | 
| 325 | 
            +
             | 
| 326 | 
            +
                    # validate the routes
         | 
| 296 327 | 
             
                    self.routes.each(&:validate!)
         | 
| 297 | 
            -
             | 
| 328 | 
            +
             | 
| 329 | 
            +
                    @valid = true # if it made it this far, it's valid!
         | 
| 298 330 | 
             
                  end
         | 
| 331 | 
            +
             | 
| 299 332 | 
             
                end
         | 
| 300 333 |  | 
| 301 334 | 
             
                DefaultWorker = Class.new{ include Qs::Worker }
         | 
| 335 | 
            +
                InvalidError  = Class.new(ArgumentError)
         | 
| 302 336 |  | 
| 303 337 | 
             
                class WorkerAvailable
         | 
| 304 338 | 
             
                  def initialize
         |