promiscuous 0.90.0 → 0.91.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/promiscuous/amqp/bunny.rb +63 -36
- data/lib/promiscuous/amqp/fake.rb +3 -1
- data/lib/promiscuous/amqp/hot_bunnies.rb +26 -16
- data/lib/promiscuous/amqp/null.rb +1 -0
- data/lib/promiscuous/amqp.rb +12 -12
- data/lib/promiscuous/cli.rb +70 -29
- data/lib/promiscuous/config.rb +54 -29
- data/lib/promiscuous/convenience.rb +1 -1
- data/lib/promiscuous/dependency.rb +25 -6
- data/lib/promiscuous/error/connection.rb +11 -9
- data/lib/promiscuous/error/dependency.rb +8 -1
- data/lib/promiscuous/loader.rb +4 -2
- data/lib/promiscuous/publisher/bootstrap/connection.rb +25 -0
- data/lib/promiscuous/publisher/bootstrap/data.rb +127 -0
- data/lib/promiscuous/publisher/bootstrap/mode.rb +19 -0
- data/lib/promiscuous/publisher/bootstrap/status.rb +40 -0
- data/lib/promiscuous/publisher/bootstrap/version.rb +46 -0
- data/lib/promiscuous/publisher/bootstrap.rb +27 -0
- data/lib/promiscuous/publisher/context/base.rb +67 -0
- data/lib/promiscuous/{middleware.rb → publisher/context/middleware.rb} +16 -13
- data/lib/promiscuous/publisher/context/transaction.rb +36 -0
- data/lib/promiscuous/publisher/context.rb +4 -88
- data/lib/promiscuous/publisher/mock_generator.rb +9 -9
- data/lib/promiscuous/publisher/model/active_record.rb +7 -7
- data/lib/promiscuous/publisher/model/base.rb +29 -29
- data/lib/promiscuous/publisher/model/ephemeral.rb +5 -3
- data/lib/promiscuous/publisher/model/mock.rb +9 -5
- data/lib/promiscuous/publisher/model/mongoid.rb +5 -22
- data/lib/promiscuous/publisher/operation/active_record.rb +360 -0
- data/lib/promiscuous/publisher/operation/atomic.rb +167 -0
- data/lib/promiscuous/publisher/operation/base.rb +279 -474
- data/lib/promiscuous/publisher/operation/mongoid.rb +153 -145
- data/lib/promiscuous/publisher/operation/non_persistent.rb +28 -0
- data/lib/promiscuous/publisher/operation/proxy_for_query.rb +42 -0
- data/lib/promiscuous/publisher/operation/transaction.rb +85 -0
- data/lib/promiscuous/publisher/operation.rb +1 -1
- data/lib/promiscuous/publisher/worker.rb +7 -7
- data/lib/promiscuous/publisher.rb +1 -1
- data/lib/promiscuous/railtie.rb +20 -5
- data/lib/promiscuous/redis.rb +104 -56
- data/lib/promiscuous/subscriber/message_processor/base.rb +38 -0
- data/lib/promiscuous/subscriber/message_processor/bootstrap.rb +17 -0
- data/lib/promiscuous/subscriber/message_processor/regular.rb +192 -0
- data/lib/promiscuous/subscriber/message_processor.rb +4 -0
- data/lib/promiscuous/subscriber/model/base.rb +20 -15
- data/lib/promiscuous/subscriber/model/mongoid.rb +4 -4
- data/lib/promiscuous/subscriber/model/observer.rb +16 -2
- data/lib/promiscuous/subscriber/operation/base.rb +68 -0
- data/lib/promiscuous/subscriber/operation/bootstrap.rb +54 -0
- data/lib/promiscuous/subscriber/operation/regular.rb +13 -0
- data/lib/promiscuous/subscriber/operation.rb +3 -166
- data/lib/promiscuous/subscriber/worker/message.rb +61 -35
- data/lib/promiscuous/subscriber/worker/message_synchronizer.rb +90 -59
- data/lib/promiscuous/subscriber/worker/pump.rb +17 -5
- data/lib/promiscuous/subscriber/worker/recorder.rb +4 -1
- data/lib/promiscuous/subscriber/worker/runner.rb +49 -9
- data/lib/promiscuous/subscriber/worker/stats.rb +2 -2
- data/lib/promiscuous/subscriber/worker.rb +6 -0
- data/lib/promiscuous/subscriber.rb +1 -1
- data/lib/promiscuous/timer.rb +31 -18
- data/lib/promiscuous/version.rb +1 -1
- data/lib/promiscuous.rb +23 -3
- metadata +104 -89
- data/lib/promiscuous/subscriber/payload.rb +0 -34
| @@ -12,7 +12,7 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer | |
| 12 12 | 
             
                @root = root
         | 
| 13 13 | 
             
                @node_synchronizers = {}
         | 
| 14 14 | 
             
                @lock = Mutex.new
         | 
| 15 | 
            -
                @reconnect_timer = Promiscuous::Timer.new
         | 
| 15 | 
            +
                @reconnect_timer = Promiscuous::Timer.new("redis", RECONNECT_INTERVAL) { reconnect }
         | 
| 16 16 | 
             
              end
         | 
| 17 17 |  | 
| 18 18 | 
             
              def connected?
         | 
| @@ -29,7 +29,11 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer | |
| 29 29 | 
             
                  redis.nodes.each { |node| @node_synchronizers[node] = NodeSynchronizer.new(self, node) }
         | 
| 30 30 | 
             
                  @redis = redis
         | 
| 31 31 | 
             
                end
         | 
| 32 | 
            -
                 | 
| 32 | 
            +
                # Do not recover messages while bootstrapping as there are a very large
         | 
| 33 | 
            +
                # number of messages that remain un-acked. If bootstrap messages are missed
         | 
| 34 | 
            +
                # these will be caught in the final phase of bootstrapping (see
         | 
| 35 | 
            +
                # Promiscuous::Subscriber::Operation).
         | 
| 36 | 
            +
                @root.pump.recover unless Promiscuous::Config.bootstrap
         | 
| 33 37 | 
             
              end
         | 
| 34 38 |  | 
| 35 39 | 
             
              def disconnect
         | 
| @@ -51,14 +55,13 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer | |
| 51 55 | 
             
                Promiscuous.warn "[redis] Reconnected"
         | 
| 52 56 | 
             
              end
         | 
| 53 57 |  | 
| 54 | 
            -
              def rescue_connection
         | 
| 55 | 
            -
                 | 
| 58 | 
            +
              def rescue_connection(node, exception)
         | 
| 59 | 
            +
                # TODO stop the pump to unack all messages
         | 
| 60 | 
            +
                @reconnect_timer.start
         | 
| 56 61 |  | 
| 62 | 
            +
                e = Promiscuous::Redis.lost_connection_exception(node, :inner => exception)
         | 
| 57 63 | 
             
                Promiscuous.warn "[redis] #{e}. Reconnecting..."
         | 
| 58 | 
            -
                Promiscuous::Config.error_notifier. | 
| 59 | 
            -
             | 
| 60 | 
            -
                # TODO stop the pump to unack all messages
         | 
| 61 | 
            -
                @reconnect_timer.run_every(RECONNECT_INTERVAL) { reconnect }
         | 
| 64 | 
            +
                Promiscuous::Config.error_notifier.call(e)
         | 
| 62 65 | 
             
              end
         | 
| 63 66 |  | 
| 64 67 | 
             
              # process_when_ready() is called by the AMQP pump. This is what happens:
         | 
| @@ -67,32 +70,33 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer | |
| 67 70 | 
             
              #    If not we bail out and rely on the subscription to kick the processing.
         | 
| 68 71 | 
             
              # Because we subscribed in advanced, we will not miss the notification.
         | 
| 69 72 | 
             
              def process_when_ready(msg)
         | 
| 73 | 
            +
                unless msg.has_dependencies?
         | 
| 74 | 
            +
                  process_message!(msg)
         | 
| 75 | 
            +
                  return
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 70 78 | 
             
                # Dropped messages will be redelivered as we (re)connect
         | 
| 71 79 | 
             
                return unless self.redis
         | 
| 72 80 |  | 
| 73 | 
            -
                @lock.synchronize  | 
| 74 | 
            -
                  @num_queued_messages += 1
         | 
| 75 | 
            -
                end
         | 
| 81 | 
            +
                @lock.synchronize { @num_queued_messages += 1 }
         | 
| 76 82 |  | 
| 77 | 
            -
                 | 
| 78 | 
            -
             | 
| 79 | 
            -
                   | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
                  end.call
         | 
| 88 | 
            -
                else
         | 
| 89 | 
            -
                  process_message!(msg)
         | 
| 90 | 
            -
                end
         | 
| 83 | 
            +
                process_message_proc = proc { process_message!(msg) }
         | 
| 84 | 
            +
                msg.happens_before_dependencies.reduce(process_message_proc) do |chain, dep|
         | 
| 85 | 
            +
                  get_redis = dep.redis_node
         | 
| 86 | 
            +
                  subscriber_redis = dep.redis_node(@redis)
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  key = dep.key(:sub).join('rw').to_s
         | 
| 89 | 
            +
                  version = dep.version
         | 
| 90 | 
            +
                  node_synchronizer = @node_synchronizers[subscriber_redis]
         | 
| 91 | 
            +
                  proc { node_synchronizer.on_version(subscriber_redis, get_redis, key, version, msg) { chain.call } }
         | 
| 92 | 
            +
                end.call
         | 
| 91 93 | 
             
              end
         | 
| 92 94 |  | 
| 93 95 | 
             
              def process_message!(msg)
         | 
| 94 96 | 
             
                @root.runner.messages_to_process << msg
         | 
| 95 97 |  | 
| 98 | 
            +
                return unless msg.has_dependencies?
         | 
| 99 | 
            +
             | 
| 96 100 | 
             
                cleanup = false
         | 
| 97 101 | 
             
                @lock.synchronize do
         | 
| 98 102 | 
             
                  @num_queued_messages -= 1
         | 
| @@ -108,38 +112,57 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer | |
| 108 112 | 
             
                  # We also know that we are not processing messages (@num_queued_messages is
         | 
| 109 113 | 
             
                  # decremented before we send the message to the runners), and we are called
         | 
| 110 114 | 
             
                  # after adding a pending callback.
         | 
| 111 | 
            -
                   | 
| 115 | 
            +
                  recover_dependencies_for(blocked_messages.first)
         | 
| 112 116 | 
             
                end
         | 
| 113 117 | 
             
              end
         | 
| 114 118 |  | 
| 115 | 
            -
              def  | 
| 119 | 
            +
              def recover_dependencies_for(msg)
         | 
| 116 120 | 
             
                # XXX This recovery mechanism only works with one worker.
         | 
| 117 121 | 
             
                # We are taking the earliest message to unblock, but in reality we should
         | 
| 118 122 | 
             
                # do the DAG of the happens before dependencies, take root nodes
         | 
| 119 123 | 
             
                # of the disconnected graphs, and sort by timestamps if needed.
         | 
| 120 | 
            -
                msg = blocked_messages.first
         | 
| 121 124 |  | 
| 122 | 
            -
                 | 
| 123 | 
            -
             | 
| 124 | 
            -
             | 
| 125 | 
            -
                   | 
| 126 | 
            -
             | 
| 125 | 
            +
                incremented_deps = {}
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                msg.happens_before_dependencies.each do |dep|
         | 
| 128 | 
            +
                  key = dep.key(:sub).join('rw')
         | 
| 129 | 
            +
                  guard_key = key.join('guard') if dep.write?
         | 
| 130 | 
            +
                  version = dep.version
         | 
| 127 131 |  | 
| 128 | 
            -
             | 
| 132 | 
            +
                  @@version_recovery_script ||= Promiscuous::Redis::Script.new <<-SCRIPT
         | 
| 133 | 
            +
                    local key = ARGV[1]
         | 
| 134 | 
            +
                    local wanted_version = tonumber(ARGV[2])
         | 
| 135 | 
            +
                    local guard_key = ARGV[3]
         | 
| 129 136 |  | 
| 130 | 
            -
             | 
| 131 | 
            -
             | 
| 132 | 
            -
             | 
| 133 | 
            -
             | 
| 137 | 
            +
                    if redis.call('exists', guard_key) == 1 then
         | 
| 138 | 
            +
                      return
         | 
| 139 | 
            +
                    end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                    local current_version = tonumber(redis.call('get', key)) or 0
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                    if wanted_version > current_version then
         | 
| 144 | 
            +
                      redis.call('set', guard_key, 1)
         | 
| 145 | 
            +
                      redis.call('expire', guard_key, 10)
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                      redis.call('set', key, wanted_version)
         | 
| 148 | 
            +
                      redis.call('publish', key, wanted_version)
         | 
| 149 | 
            +
                      return wanted_version - current_version
         | 
| 150 | 
            +
                    end
         | 
| 151 | 
            +
                  SCRIPT
         | 
| 152 | 
            +
                  increment = @@version_recovery_script.eval(dep.redis_node, :argv => [key, version, guard_key].compact)
         | 
| 153 | 
            +
                  incremented_deps[dep] = increment if increment
         | 
| 154 | 
            +
                end
         | 
| 134 155 |  | 
| 135 | 
            -
             | 
| 136 | 
            -
                   | 
| 137 | 
            -
             | 
| 156 | 
            +
                if incremented_deps.present?
         | 
| 157 | 
            +
                  recovery_msg = "Incrementing "
         | 
| 158 | 
            +
                  recovery_msg += incremented_deps.map { |dep, increment| "#{dep} by #{increment}" }.join(", ")
         | 
| 138 159 |  | 
| 139 | 
            -
             | 
| 140 | 
            -
             | 
| 141 | 
            -
             | 
| 142 | 
            -
             | 
| 160 | 
            +
                  e = Promiscuous::Error::Recovery.new(recovery_msg)
         | 
| 161 | 
            +
                  Promiscuous.error "[synchronization recovery] #{e}"
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                  # TODO Should we report the error to the notifier, or the log file is enough?
         | 
| 164 | 
            +
                  # Promiscuous::Config.error_notifier.call(e)
         | 
| 165 | 
            +
                end
         | 
| 143 166 | 
             
              end
         | 
| 144 167 |  | 
| 145 168 | 
             
              def blocked_messages
         | 
| @@ -150,10 +173,6 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer | |
| 150 173 | 
             
                  .sort_by { |msg| msg.timestamp }
         | 
| 151 174 | 
             
              end
         | 
| 152 175 |  | 
| 153 | 
            -
              def not_recovering
         | 
| 154 | 
            -
                Promiscuous.warn "[synchronization recovery] Nothing to recover from"
         | 
| 155 | 
            -
              end
         | 
| 156 | 
            -
             | 
| 157 176 | 
             
              class NodeSynchronizer
         | 
| 158 177 | 
             
                attr_accessor :node, :subscriptions, :root_synchronizer
         | 
| 159 178 |  | 
| @@ -181,16 +200,16 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer | |
| 181 200 | 
             
                      notify_key_change(subscription, arg)
         | 
| 182 201 | 
             
                    end
         | 
| 183 202 | 
             
                  end
         | 
| 184 | 
            -
                rescue EOFError, Errno::ECONNRESET
         | 
| 203 | 
            +
                rescue EOFError, Errno::ECONNRESET => e
         | 
| 185 204 | 
             
                  # Unwanted disconnection
         | 
| 186 | 
            -
                  @root_synchronizer.rescue_connection unless @stop
         | 
| 187 | 
            -
                rescue IOError => e
         | 
| 188 | 
            -
                  raise e unless @stop
         | 
| 205 | 
            +
                  @root_synchronizer.rescue_connection(redis_client, e) unless @stop
         | 
| 189 206 | 
             
                rescue Exception => e
         | 
| 190 | 
            -
                   | 
| 191 | 
            -
             | 
| 207 | 
            +
                  unless @stop
         | 
| 208 | 
            +
                    Promiscuous.warn "[redis] #{e.class} #{e.message}"
         | 
| 209 | 
            +
                    Promiscuous.warn "[redis] #{e}\n#{e.backtrace.join("\n")}"
         | 
| 192 210 |  | 
| 193 | 
            -
             | 
| 211 | 
            +
                    Promiscuous::Config.error_notifier.call(e)
         | 
| 212 | 
            +
                  end
         | 
| 194 213 | 
             
                end
         | 
| 195 214 |  | 
| 196 215 | 
             
                def stop_main_loop
         | 
| @@ -270,6 +289,17 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer | |
| 270 289 | 
             
                    refresh_activity
         | 
| 271 290 | 
             
                  end
         | 
| 272 291 |  | 
| 292 | 
            +
                  def with_rescue_connection(node, &block)
         | 
| 293 | 
            +
                    block.call
         | 
| 294 | 
            +
                  rescue Exception => e
         | 
| 295 | 
            +
                    # TODO only catch exceptions related to network issues
         | 
| 296 | 
            +
                    node_synchronizer.root_synchronizer.rescue_connection(node, e)
         | 
| 297 | 
            +
                  end
         | 
| 298 | 
            +
             | 
| 299 | 
            +
                  def redis_exec_raw(node, *commands)
         | 
| 300 | 
            +
                    with_rescue_connection(node) { node.client.process([commands]) }
         | 
| 301 | 
            +
                  end
         | 
| 302 | 
            +
             | 
| 273 303 | 
             
                  def total_num_processed_messages
         | 
| 274 304 | 
             
                    node_synchronizer.root_synchronizer.num_processed_messages
         | 
| 275 305 | 
             
                  end
         | 
| @@ -285,7 +315,7 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer | |
| 285 315 |  | 
| 286 316 | 
             
                  def cleanup_if_old
         | 
| 287 317 | 
             
                    if is_old?
         | 
| 288 | 
            -
                      subscriber_redis | 
| 318 | 
            +
                      redis_exec_raw(subscriber_redis, :unsubscribe, key)
         | 
| 289 319 | 
             
                      node_synchronizer.subscriptions.delete(key) # lock is already held
         | 
| 290 320 | 
             
                    end
         | 
| 291 321 | 
             
                  end
         | 
| @@ -296,11 +326,12 @@ class Promiscuous::Subscriber::Worker::MessageSynchronizer | |
| 296 326 | 
             
                      @subscription_requested = true
         | 
| 297 327 | 
             
                    end
         | 
| 298 328 |  | 
| 299 | 
            -
                    subscriber_redis | 
| 329 | 
            +
                    redis_exec_raw(subscriber_redis, :subscribe, key)
         | 
| 300 330 | 
             
                  end
         | 
| 301 331 |  | 
| 302 332 | 
             
                  def finalize_subscription
         | 
| 303 | 
            -
                     | 
| 333 | 
            +
                    v = with_rescue_connection(get_redis) { get_redis.get(key) }
         | 
| 334 | 
            +
                    signal_version(v)
         | 
| 304 335 | 
             
                  end
         | 
| 305 336 |  | 
| 306 337 | 
             
                  def signal_version(current_version)
         | 
| @@ -8,17 +8,29 @@ class Promiscuous::Subscriber::Worker::Pump | |
| 8 8 |  | 
| 9 9 | 
             
              def connect
         | 
| 10 10 | 
             
                options = {}
         | 
| 11 | 
            -
                options[: | 
| 11 | 
            +
                options[:bindings] = {}
         | 
| 12 12 | 
             
                # We need to subscribe to everything to keep up with the version tracking
         | 
| 13 | 
            -
                 | 
| 13 | 
            +
                Promiscuous::Config.subscriber_exchanges.each do |exchange|
         | 
| 14 | 
            +
                  options[:bindings][exchange] = ['*']
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                if Promiscuous::Config.bootstrap
         | 
| 18 | 
            +
                  options[:bindings][Promiscuous::AMQP::BOOTSTRAP_EXCHANGE] = ['*']
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 14 21 | 
             
                subscribe(options, &method(:on_message))
         | 
| 15 22 | 
             
              end
         | 
| 16 23 |  | 
| 17 24 | 
             
              def on_message(metadata, payload)
         | 
| 18 25 | 
             
                msg = Promiscuous::Subscriber::Worker::Message.new(payload, :metadata => metadata, :root_worker => @root)
         | 
| 19 | 
            -
                 | 
| 26 | 
            +
                if Promiscuous::Config.bootstrap
         | 
| 27 | 
            +
                  # Bootstrapping doesn't require synchronzation
         | 
| 28 | 
            +
                  @root.runner.messages_to_process << msg
         | 
| 29 | 
            +
                else
         | 
| 30 | 
            +
                  @root.message_synchronizer.process_when_ready(msg)
         | 
| 31 | 
            +
                end
         | 
| 20 32 | 
             
              rescue Exception => e
         | 
| 21 | 
            -
                Promiscuous.warn "[receive] cannot process message: #{e} | 
| 22 | 
            -
                Promiscuous::Config.error_notifier. | 
| 33 | 
            +
                Promiscuous.warn "[receive] cannot process message: #{e}\n#{e.backtrace.join("\n")}"
         | 
| 34 | 
            +
                Promiscuous::Config.error_notifier.call(e)
         | 
| 23 35 | 
             
              end
         | 
| 24 36 | 
             
            end
         | 
| @@ -8,8 +8,11 @@ class Promiscuous::Subscriber::Worker::Recorder | |
| 8 8 | 
             
                @file = File.open(@log_file, 'a')
         | 
| 9 9 | 
             
                options = {}
         | 
| 10 10 | 
             
                options[:queue_name] = "#{Promiscuous::Config.app}.promiscuous"
         | 
| 11 | 
            +
                options[:bindings] = {}
         | 
| 11 12 | 
             
                # We need to subscribe to everything to keep up with the version tracking
         | 
| 12 | 
            -
                 | 
| 13 | 
            +
                Promiscuous::Config.subscriber_exchanges.each do |exchange|
         | 
| 14 | 
            +
                  options[:bindings][exchange] = ['*']
         | 
| 15 | 
            +
                end
         | 
| 13 16 |  | 
| 14 17 | 
             
                subscribe(options) do |metadata, payload|
         | 
| 15 18 | 
             
                  @file.puts payload
         | 
| @@ -8,21 +8,61 @@ class Promiscuous::Subscriber::Worker::Runner | |
| 8 8 |  | 
| 9 9 | 
             
              def start
         | 
| 10 10 | 
             
                num_threads = Promiscuous::Config.subscriber_threads
         | 
| 11 | 
            -
                @ | 
| 12 | 
            -
                @threads ||= num_threads.times.map { |i| Thread.new { main_loop(@locks[i]) } }
         | 
| 11 | 
            +
                @runner_threads ||= num_threads.times.map { RunnerThread.new(@messages_to_process) }
         | 
| 13 12 | 
             
              end
         | 
| 14 13 |  | 
| 15 14 | 
             
              def stop
         | 
| 16 | 
            -
                return unless @ | 
| 17 | 
            -
             | 
| 18 | 
            -
                @ | 
| 15 | 
            +
                return unless @runner_threads
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                @runner_threads.each { |runner_thread| runner_thread.stop }
         | 
| 18 | 
            +
                @runner_threads = nil
         | 
| 19 | 
            +
             | 
| 19 20 | 
             
                @messages_to_process.clear
         | 
| 20 21 | 
             
              end
         | 
| 21 22 |  | 
| 22 | 
            -
              def  | 
| 23 | 
            -
                 | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 23 | 
            +
              def show_stop_status(num_requests)
         | 
| 24 | 
            +
                @runner_threads.each { |runner_thread| runner_thread.show_stop_status(num_requests) }
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              class RunnerThread
         | 
| 28 | 
            +
                def initialize(message_queue)
         | 
| 29 | 
            +
                  @message_queue = message_queue
         | 
| 30 | 
            +
                  @kill_lock = Mutex.new
         | 
| 31 | 
            +
                  @thread = Thread.new { main_loop }
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def main_loop
         | 
| 35 | 
            +
                  loop do
         | 
| 36 | 
            +
                    msg = @message_queue.pop
         | 
| 37 | 
            +
                    @kill_lock.synchronize do
         | 
| 38 | 
            +
                      @current_message = msg
         | 
| 39 | 
            +
                      msg.process # msg.process does not throw
         | 
| 40 | 
            +
                      @current_message = nil
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def stop
         | 
| 46 | 
            +
                  @kill_lock.synchronize { @thread.kill }
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                def show_stop_status(num_requests)
         | 
| 50 | 
            +
                  msg = @current_message
         | 
| 51 | 
            +
                  backtrace = @thread.backtrace
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  if msg
         | 
| 54 | 
            +
                    STDERR.puts "Still processing #{msg.payload}"
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                    if num_requests > 1 && backtrace
         | 
| 57 | 
            +
                      STDERR.puts
         | 
| 58 | 
            +
                      STDERR.puts backtrace.map { |line| "  \e[1;30m#{line}\e[0m\n" }
         | 
| 59 | 
            +
                      STDERR.puts
         | 
| 60 | 
            +
                      STDERR.puts "I'm a little busy, check out my stack trace."
         | 
| 61 | 
            +
                      STDERR.puts "Be patient (or kill me with -9, but that wouldn't be very nice of you)."
         | 
| 62 | 
            +
                    else
         | 
| 63 | 
            +
                      STDERR.puts "Just a second..."
         | 
| 64 | 
            +
                    end
         | 
| 65 | 
            +
                  end
         | 
| 26 66 | 
             
                end
         | 
| 27 67 | 
             
              end
         | 
| 28 68 | 
             
            end
         | 
| @@ -15,8 +15,8 @@ class Promiscuous::Subscriber::Worker::Stats | |
| 15 15 |  | 
| 16 16 | 
             
                STDERR.puts ""
         | 
| 17 17 |  | 
| 18 | 
            -
                @timer ||= Promiscuous::Timer.new
         | 
| 19 | 
            -
                @timer. | 
| 18 | 
            +
                @timer ||= Promiscuous::Timer.new("stats", @interval) { aggregate_stats }
         | 
| 19 | 
            +
                @timer.start
         | 
| 20 20 | 
             
              end
         | 
| 21 21 |  | 
| 22 22 | 
             
              def disconnect
         | 
| @@ -24,4 +24,10 @@ class Promiscuous::Subscriber::Worker | |
| 24 24 | 
             
                @pump.disconnect
         | 
| 25 25 | 
             
                @message_synchronizer.disconnect
         | 
| 26 26 | 
             
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              def show_stop_status
         | 
| 29 | 
            +
                @num_show_stop_requests ||= 0
         | 
| 30 | 
            +
                @num_show_stop_requests += 1
         | 
| 31 | 
            +
                @runner.show_stop_status(@num_show_stop_requests)
         | 
| 32 | 
            +
              end
         | 
| 27 33 | 
             
            end
         | 
    
        data/lib/promiscuous/timer.rb
    CHANGED
    
    | @@ -1,32 +1,44 @@ | |
| 1 1 | 
             
            class Promiscuous::Timer
         | 
| 2 | 
            -
              def initialize
         | 
| 2 | 
            +
              def initialize(name, duration, &block)
         | 
| 3 | 
            +
                @name = name
         | 
| 4 | 
            +
                @duration = duration.to_f
         | 
| 5 | 
            +
                @block = block
         | 
| 3 6 | 
             
                @lock = Mutex.new
         | 
| 4 7 | 
             
              end
         | 
| 5 8 |  | 
| 6 | 
            -
              def  | 
| 9 | 
            +
              def run_callback
         | 
| 10 | 
            +
                @block.call
         | 
| 11 | 
            +
              rescue Exception => e
         | 
| 12 | 
            +
                # Report the exception only once
         | 
| 13 | 
            +
                unless @last_exception == e.to_s
         | 
| 14 | 
            +
                  @last_exception = e.to_s
         | 
| 15 | 
            +
                  Promiscuous.warn "[#{@name}] #{e}"
         | 
| 16 | 
            +
                  Promiscuous::Config.error_notifier.call(e)
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              def main_loop(options={})
         | 
| 21 | 
            +
                Thread.current[:promiscuous_timer_instance] = self
         | 
| 7 22 | 
             
                options = options.dup
         | 
| 8 | 
            -
                duration = duration.to_f unless duration.is_a?(Integer)
         | 
| 9 | 
            -
                reset
         | 
| 10 23 |  | 
| 11 | 
            -
                 | 
| 12 | 
            -
                  @ | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
                        if @thread == Thread.current
         | 
| 17 | 
            -
                          begin
         | 
| 18 | 
            -
                            block.call
         | 
| 19 | 
            -
                          rescue Exception
         | 
| 20 | 
            -
                          end
         | 
| 21 | 
            -
                        end
         | 
| 22 | 
            -
                      end
         | 
| 23 | 
            -
                    end
         | 
| 24 | 
            +
                loop do
         | 
| 25 | 
            +
                  sleep @duration unless options.delete(:run_immediately)
         | 
| 26 | 
            +
                  @lock.synchronize do
         | 
| 27 | 
            +
                    return unless @thread
         | 
| 28 | 
            +
                    run_callback
         | 
| 24 29 | 
             
                  end
         | 
| 25 30 | 
             
                end
         | 
| 26 31 | 
             
              end
         | 
| 27 32 |  | 
| 33 | 
            +
              def start(options={})
         | 
| 34 | 
            +
                @lock.synchronize do
         | 
| 35 | 
            +
                  @thread ||= Thread.new { main_loop(options) }
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 28 39 | 
             
              def reset
         | 
| 29 | 
            -
                if  | 
| 40 | 
            +
                if Thread.current[:promiscuous_timer_instance] == self
         | 
| 41 | 
            +
                  # We already hold the lock (the callback called reset)
         | 
| 30 42 | 
             
                  @thread = nil
         | 
| 31 43 | 
             
                else
         | 
| 32 44 | 
             
                  @lock.synchronize do
         | 
| @@ -34,5 +46,6 @@ class Promiscuous::Timer | |
| 34 46 | 
             
                    @thread = nil
         | 
| 35 47 | 
             
                  end
         | 
| 36 48 | 
             
                end
         | 
| 49 | 
            +
                @last_exception = nil
         | 
| 37 50 | 
             
              end
         | 
| 38 51 | 
             
            end
         | 
    
        data/lib/promiscuous/version.rb
    CHANGED
    
    
    
        data/lib/promiscuous.rb
    CHANGED
    
    | @@ -18,7 +18,10 @@ module Promiscuous | |
| 18 18 | 
             
              extend Promiscuous::Autoload
         | 
| 19 19 | 
             
              autoload :Common, :Publisher, :Subscriber, :Observer, :Worker, :Ephemeral,
         | 
| 20 20 | 
             
                       :CLI, :Error, :Loader, :AMQP, :Redis, :ZK, :Config, :DSL, :Key,
         | 
| 21 | 
            -
                       :Convenience, :Dependency, : | 
| 21 | 
            +
                       :Convenience, :Dependency, :Timer
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              # Shortcut for the middleware, TODO make load on demand
         | 
| 24 | 
            +
              Middleware = Publisher::Context::Middleware
         | 
| 22 25 |  | 
| 23 26 | 
             
              extend Promiscuous::DSL
         | 
| 24 27 |  | 
| @@ -38,11 +41,17 @@ module Promiscuous | |
| 38 41 | 
             
                def connect
         | 
| 39 42 | 
             
                  AMQP.connect
         | 
| 40 43 | 
             
                  Redis.connect
         | 
| 44 | 
            +
                  @should_be_connected = true
         | 
| 41 45 | 
             
                end
         | 
| 42 46 |  | 
| 43 47 | 
             
                def disconnect
         | 
| 44 48 | 
             
                  AMQP.disconnect
         | 
| 45 49 | 
             
                  Redis.disconnect
         | 
| 50 | 
            +
                  @should_be_connected = false
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def should_be_connected?
         | 
| 54 | 
            +
                  !!@should_be_connected
         | 
| 46 55 | 
             
                end
         | 
| 47 56 |  | 
| 48 57 | 
             
                def healthy?
         | 
| @@ -54,16 +63,27 @@ module Promiscuous | |
| 54 63 | 
             
                  true
         | 
| 55 64 | 
             
                end
         | 
| 56 65 |  | 
| 66 | 
            +
                def ensure_connected
         | 
| 67 | 
            +
                  unless should_be_connected?
         | 
| 68 | 
            +
                    connect
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 57 72 | 
             
                def disabled
         | 
| 58 | 
            -
                  Thread.current[:promiscuous_disabled] | 
| 73 | 
            +
                  return $promiscuous_disabled if Thread.current[:promiscuous_disabled].nil?
         | 
| 74 | 
            +
                  Thread.current[:promiscuous_disabled]
         | 
| 59 75 | 
             
                end
         | 
| 60 76 |  | 
| 61 77 | 
             
                def disabled=(value)
         | 
| 62 78 | 
             
                  Thread.current[:promiscuous_disabled] = value
         | 
| 63 79 | 
             
                end
         | 
| 64 80 |  | 
| 81 | 
            +
                def disabled?
         | 
| 82 | 
            +
                  !!Thread.current[:promiscuous_disabled]
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 65 85 | 
             
                def context(*args, &block)
         | 
| 66 | 
            -
                  Publisher::Context. | 
| 86 | 
            +
                  Publisher::Context::Base.with_context(*args, &block)
         | 
| 67 87 | 
             
                end
         | 
| 68 88 | 
             
              end
         | 
| 69 89 |  |