pebbles-river 0.0.7 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ODk5MzUyYzZiYTQzZGUzMGQyYTBmNDExZGE1YWRhNDA1N2RkYjVkZA==
5
+ data.tar.gz: !binary |-
6
+ MTRkODBlMTZjMDMwNGI3MmM2MmE5MjdmZGY0ZTY5NzQxMDBhMGExZQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ YzM5NzBhYTEzZTQ4ZjY0YTNmNzhmYTkxYTQ5MDNhMjEyMDk3ODRjNTM2M2Vk
10
+ MjcwMzA2ZWI4ZTM0YzgzNzA4NzNkNTk4ZjUwNzE4NDEwMjg2NGJiNDIwN2E0
11
+ YjlkYTdiOTQzODgxYjU0OTdjNzI3YTFjNjcxNDdlZTA4YjlhMWQ=
12
+ data.tar.gz: !binary |-
13
+ ZDhhOGZlZTIyODk3NWI5MmNhMDgyNjk2MjE4ZjNkNjNjMTQ4MDA0NWFmY2Qx
14
+ YmNmYmZkNTVjODk5NmUyYjljN2U1MjZmNDY5MDU4NzIyNDMzMGMyNTVmYWQy
15
+ ZjJlZTY3MmYxNTU4Yjc3NGVkMGQ5NTc0M2FmYjM0YzQwZjk2NzU=
@@ -0,0 +1,152 @@
1
+ require 'logger'
2
+ require 'mercenary'
3
+
4
+ module Pebbles
5
+ module River
6
+
7
+ # Simple helper class for easily writing daemons that run multiple queue
8
+ # workers. Handles command line parsing and daemonization.
9
+ #
10
+ # Adapter should support:
11
+ #
12
+ # * `name`: Name of the process. Optional, defaults to `$0`.
13
+ #
14
+ # * `configure_start_command(command)`. Implement to add more options to
15
+ # the start command, eg. configuration options. Optional.
16
+ #
17
+ # * `on_start(options)`. Implement to inject code before the program
18
+ # starts. Options are the start options, a hash. Optional.
19
+ #
20
+ # * `configure_supervisor(supervisor)`. This must call `add_listener` on the
21
+ # supervisor to add workers. Required.
22
+ #
23
+ class DaemonHelper
24
+
25
+ def initialize(adapter, options = {})
26
+ @adapter = adapter
27
+
28
+ @name = @adapter.name if @adapter.respond_to?(:name)
29
+ @name ||= File.basename($0).gsub(/\.rb$/, '')
30
+
31
+ @logger = options.fetch(:logger, Logger.new($stderr))
32
+ end
33
+
34
+ def run
35
+ Mercenary.program(@name) do |p|
36
+ p.syntax "#{@name} <subcommand> [OPTION ...]"
37
+ p.command(:start) do |c|
38
+ c.syntax 'start'
39
+ c.description 'Starts daemon'
40
+ c.option :daemonize, '-d', '--daemon', 'To daemonize; otherwise will run synchronously.'
41
+ c.option :pidfile, '-p', '--pidfile PIDFILE', 'Path to pid file.'
42
+ c.option :workers, Integer, '-w', '--workers N', 'Set number of workers per queue (defaults to 1).'
43
+ c.action do |_, options|
44
+ start(options)
45
+ end
46
+ if @adapter.respond_to?(:configure_start_command)
47
+ @adapter.configure_start_command(c)
48
+ end
49
+ end
50
+ p.command(:stop) do |c|
51
+ c.syntax 'stop'
52
+ c.description 'Stops daemon'
53
+ c.option :pidfile, '-p', '--pidfile PIDFILE', 'Path to pid file.'
54
+ c.action do |_, options|
55
+ stop(options)
56
+ end
57
+ end
58
+ p.command(:status) do |c|
59
+ c.syntax 'status'
60
+ c.description 'Prints daemon status'
61
+ c.option :pidfile, '-p', '--pidfile PIDFILE', 'Path to pid file.'
62
+ c.action do |_, options|
63
+ status(options)
64
+ end
65
+ end
66
+ end
67
+
68
+ if ARGV.any?
69
+ abort "Unknown command '#{ARGV.first}'."
70
+ else
71
+ abort "Run with -h for help."
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def start(options)
78
+ if @adapter.respond_to?(:on_start)
79
+ @adapter.on_start(options)
80
+ end
81
+
82
+ daemon = new_daemon(options)
83
+ if daemon.alive?
84
+ abort "#{daemon.name} is already running."
85
+ end
86
+ if options[:daemonize]
87
+ print "Starting #{daemon.name}"
88
+ daemon.startup
89
+ puts ", done."
90
+ else
91
+ daemon.server.startup
92
+ end
93
+ exit
94
+ end
95
+
96
+ def stop(options)
97
+ daemon = get_daemon(options)
98
+ unless daemon.alive?
99
+ abort "#{daemon.name} is not running."
100
+ end
101
+ print "Stopping #{daemon.name}"
102
+ daemon.shutdown
103
+ puts ", done."
104
+ exit
105
+ end
106
+
107
+ def status(options)
108
+ daemon = get_daemon(options)
109
+ if daemon.alive?
110
+ puts "#{daemon.name} is running."
111
+ else
112
+ puts "#{daemon.name} is not running."
113
+ end
114
+ exit(daemon.alive? ? 0 : 1)
115
+ end
116
+
117
+ def get_daemon(options)
118
+ unless options[:pidfile]
119
+ abort "Specify pidfile with --pidfile."
120
+ end
121
+
122
+ Servolux::Daemon.new(
123
+ name: @name,
124
+ pid_file: options[:pidfile],
125
+ logger: @logger,
126
+ startup_command: '/bin/true',
127
+ nochdir: true)
128
+ end
129
+
130
+ def new_daemon(options)
131
+ unless options[:pidfile]
132
+ abort "Specify pidfile with --pidfile."
133
+ end
134
+
135
+ supervisor = Pebbles::River::Supervisor.new(@name,
136
+ pid_file: options[:pidfile],
137
+ logger: @logger,
138
+ worker_count: options[:workers])
139
+
140
+ @adapter.configure_supervisor(supervisor)
141
+
142
+ supervisor.start_workers
143
+
144
+ Servolux::Daemon.new(
145
+ server: supervisor,
146
+ nochdir: true)
147
+ end
148
+
149
+ end
150
+
151
+ end
152
+ end
@@ -3,11 +3,11 @@ module Pebbles
3
3
 
4
4
  class InvalidPayloadError < StandardError
5
5
 
6
- attr_reader :payload
6
+ attr_reader :content
7
7
 
8
- def initalize(message, payload)
8
+ def initialize(message, content)
9
9
  super(message)
10
- @payload = payload
10
+ @content = content
11
11
  end
12
12
 
13
13
  end
@@ -16,22 +16,22 @@ module Pebbles
16
16
 
17
17
  attr_reader :payload
18
18
  attr_reader :queue
19
- attr_reader :raw_message
19
+ attr_reader :delivery_info
20
20
 
21
- def self.deserialize_payload(payload)
22
- if payload
21
+ def self.deserialize_payload(content)
22
+ if content
23
23
  begin
24
- return JSON.parse(payload)
24
+ return JSON.parse(content)
25
25
  rescue => e
26
- raise InvalidPayloadError.new(e.message, payload)
26
+ raise InvalidPayloadError.new(e.message, content)
27
27
  end
28
28
  end
29
29
  end
30
30
 
31
- def initialize(raw_message, queue = nil)
31
+ def initialize(content, delivery_info, queue = nil)
32
32
  @queue = queue
33
- @raw_message = raw_message
34
- @payload = self.class.deserialize_payload(raw_message[:payload])
33
+ @delivery_info = delivery_info
34
+ @payload = self.class.deserialize_payload(content)
35
35
  end
36
36
 
37
37
  def ==(other)
@@ -41,21 +41,15 @@ module Pebbles
41
41
  end
42
42
 
43
43
  def delivery_tag
44
- delivery_details[:delivery_tag]
45
- end
46
-
47
- def delivery_details
48
- @raw_message[:delivery_details] || {}
44
+ @delivery_info.delivery_tag if @delivery_info
49
45
  end
50
46
 
51
47
  def ack
52
- @queue.ack(delivery_tag: delivery_tag)
48
+ @queue.channel.ack(delivery_tag)
53
49
  end
54
50
 
55
51
  def nack
56
- # TODO: This requires Bunny 0.9+. We therefore don't nack at all, but
57
- # let messages simply expire, since pre-0.9 doesn't have a way to nack.
58
- #@channel.nack(delivery_tag, false)
52
+ @queue.channel.nack(delivery_tag, false, true)
59
53
  end
60
54
 
61
55
  end
@@ -0,0 +1,49 @@
1
+ module Pebbles
2
+ module River
3
+
4
+ # Overrides preforker to instantiate a round-robin set of modules. The
5
+ # desired worker count is multiplied by the number of modules; for example,
6
+ # if specifying modules [A, B] and the worker count is 2, then 4 actual
7
+ # processes will be forked.
8
+ class MultiPrefork < ::Servolux::Prefork
9
+
10
+ def initialize(options, &block)
11
+ raise ArgumentError, "Block invocation not supported" if block
12
+ raise ArgumentError, "Must pass :modules, not :module" if options[:module]
13
+
14
+ # Like Prefork we support passing procs
15
+ @modules = options[:modules].map { |m|
16
+ if m.is_a?(Proc)
17
+ Module.new { define_method :execute, &m }
18
+ else
19
+ m
20
+ end
21
+ }
22
+ @index = 0
23
+
24
+ options = {}.merge(options)
25
+ options[:module] = @modules.first
26
+ options.delete(:modules)
27
+ if (min_workers = options[:min_workers])
28
+ options[:min_workers] = min_workers * @modules.length
29
+ end
30
+ if (max_workers = options[:max_workers])
31
+ options[:max_workers] = max_workers * @modules.length
32
+ end
33
+
34
+ super(options)
35
+ end
36
+
37
+ # This cheats by overriding `Prefork#add_workers` and replacing the
38
+ # `@module instance` variable.
39
+ def add_workers(number = 1)
40
+ number.times do
41
+ @module = @modules[@index]
42
+ super(1)
43
+ @index = (@index + 1) % @modules.length
44
+ end
45
+ end
46
+ end
47
+
48
+ end
49
+ end
@@ -4,6 +4,9 @@ module Pebbles
4
4
  class River
5
5
 
6
6
  attr_reader :environment
7
+ attr_reader :exchange
8
+ attr_reader :session
9
+ attr_reader :channel
7
10
 
8
11
  def initialize(options = {})
9
12
  options = {environment: options} if options.is_a?(String) # Backwards compatibility
@@ -13,36 +16,41 @@ module Pebbles
13
16
  end
14
17
 
15
18
  def connected?
16
- bunny.connected?
19
+ @session && @session.connected?
17
20
  end
18
21
 
19
22
  def connect
20
- unless connected?
21
- handle_connection_error do
22
- bunny.start
23
- bunny.qos
23
+ unless @session
24
+ handle_session_error do
25
+ @session = Bunny::Session.new
26
+ @session.start
27
+
28
+ @channel = @session.create_channel
29
+
30
+ @exchange = @channel.exchange(exchange_name, EXCHANGE_OPTIONS.dup)
24
31
  end
25
32
  end
26
33
  end
27
34
 
28
35
  def disconnect
29
- bunny = @bunny
30
- if bunny and bunny.connected?
36
+ @exchange = nil
37
+ if @channel
38
+ @channel.close
39
+ @channel = nil
40
+ end
41
+ if @session
31
42
  begin
32
- bunny.stop
43
+ @session.stop
33
44
  rescue *CONNECTION_EXCEPTIONS
34
45
  # Ignore
35
46
  end
47
+ @session = nil
36
48
  end
37
-
38
- # This will force fresh connection the next time we need it, otherwise
39
- # Bunny won't create queues that no longer might exist
40
- @bunny = nil
41
49
  end
42
50
 
43
51
  def publish(options = {})
44
52
  connect
45
- handle_connection_error(SendFailure) do
53
+ handle_session_error(SendFailure) do
46
54
  exchange.publish(options.to_json,
47
55
  persistent: options.fetch(:persistent, true),
48
56
  key: Routing.routing_key_for(options.slice(:event, :uid)))
@@ -53,22 +61,17 @@ module Pebbles
53
61
  raise ArgumentError.new 'Queue must be named' unless options[:name]
54
62
 
55
63
  connect
56
-
57
- queue = bunny.queue(options[:name], QUEUE_OPTIONS.dup)
64
+ queue = @channel.queue(options[:name], QUEUE_OPTIONS.dup)
58
65
  Subscription.new(options).queries.each do |key|
59
66
  queue.bind(exchange.name, key: key)
60
67
  end
61
68
  queue
62
69
  end
63
70
 
64
- def exchange_name
65
- return @exchange_name ||= format_exchange_name
66
- end
67
-
68
71
  private
69
72
 
70
- def bunny
71
- @bunny ||= Bunny.new
73
+ def exchange_name
74
+ return @exchange_name ||= format_exchange_name
72
75
  end
73
76
 
74
77
  def format_exchange_name
@@ -77,12 +80,7 @@ module Pebbles
77
80
  name
78
81
  end
79
82
 
80
- def exchange
81
- connect
82
- @exchange ||= bunny.exchange(exchange_name, EXCHANGE_OPTIONS.dup)
83
- end
84
-
85
- def handle_connection_error(exception_klass = ConnectFailure, &block)
83
+ def handle_session_error(exception_klass = ConnectFailure, &block)
86
84
  last_exception = nil
87
85
  Timeout.timeout(MAX_RETRY_TIMEOUT) do
88
86
  retry_until, retry_count = nil, 0
@@ -1,6 +1,8 @@
1
1
  module Pebbles
2
2
  module River
3
3
 
4
+ class ConfigurationError < StandardError; end
5
+
4
6
  class Supervisor < Servolux::Server
5
7
 
6
8
  def initialize(name, options = {})
@@ -9,33 +11,62 @@ module Pebbles
9
11
  options.assert_valid_keys(:logger, :pid_file, :worker_count, :worker)
10
12
 
11
13
  @worker_count = options[:worker_count] || 1
12
- @worker = options[:worker]
13
14
 
14
- worker = @worker
15
- @pool = Servolux::Prefork.new(min_workers: @worker_count) do
16
- $0 = "#{name}: worker"
15
+ @queue_modules = []
16
+ end
17
+
18
+ def start_workers
19
+ if @queue_modules.empty?
20
+ raise ConfigurationError.new("No listeners configured")
21
+ end
22
+
23
+ @prefork = MultiPrefork.new(
24
+ min_workers: @worker_count,
25
+ modules: @queue_modules)
26
+ end
27
+
28
+ def add_listener(listener, queue_spec)
29
+ worker = Pebbles::River::Worker.new(listener,
30
+ queue: queue_spec,
31
+ on_exception: ->(e) {
32
+ if logger.respond_to?(:exception)
33
+ logger.exception(e)
34
+ else
35
+ logger.error("Exception #{e.class}: #{e} #{e.backtrace.join("\n")}")
36
+ end
37
+ })
38
+
39
+ process_name = "#{@name}: queue worker: #{queue_spec[:name]}"
40
+ logger = @logger
41
+
42
+ @queue_modules.push(-> {
43
+ $0 = process_name
17
44
  trap('TERM') { worker.stop }
18
45
  worker.run
19
- end
46
+ })
20
47
  end
21
48
 
49
+ # From Servolux::Server
22
50
  def before_starting
23
51
  $0 = "#{name}: master"
24
52
 
25
53
  logger.info "Starting workers"
26
- @pool.start(1)
54
+ @prefork.start(1)
27
55
  end
28
56
 
57
+ # From Servolux::Server
29
58
  def after_stopping
30
59
  shutdown_workers
31
60
  end
32
61
 
62
+ # From Servolux::Server
33
63
  def usr2
34
64
  shutdown_workers
35
65
  end
36
66
 
67
+ # From Servolux::Server
37
68
  def run
38
- @pool.ensure_worker_pool_size
69
+ @prefork.ensure_worker_pool_size
39
70
  rescue => e
40
71
  if logger.respond_to? :exception
41
72
  logger.exception(e)
@@ -49,9 +80,9 @@ module Pebbles
49
80
 
50
81
  def shutdown_workers
51
82
  logger.info "Shutting down all workers"
52
- @pool.stop
83
+ @prefork.stop
53
84
  loop do
54
- break if @pool.live_worker_count <= 0
85
+ break if @prefork.live_worker_count <= 0
55
86
  logger.info "Waiting for workers to quit"
56
87
  sleep 0.25
57
88
  end
@@ -1,5 +1,5 @@
1
1
  module Pebbles
2
2
  module River
3
- VERSION = '0.0.7'
3
+ VERSION = '0.1.0'
4
4
  end
5
5
  end
@@ -115,9 +115,9 @@ module Pebbles
115
115
 
116
116
  def process_next
117
117
  with_exceptions do
118
- queue.pop(auto_ack: false, ack: true) do |raw_message|
119
- if raw_message[:payload] != :queue_empty
120
- process_message(raw_message)
118
+ queue.pop(ack: true) do |delivery_info, properties, content|
119
+ if delivery_info
120
+ process_message(delivery_info, properties, content)
121
121
  return true
122
122
  else
123
123
  return false
@@ -126,12 +126,12 @@ module Pebbles
126
126
  end
127
127
  end
128
128
 
129
- def process_message(raw_message)
129
+ def process_message(delivery_info, properties, content)
130
130
  begin
131
- message = Message.new(raw_message, queue)
131
+ message = Message.new(content, delivery_info, queue)
132
132
  rescue => exception
133
133
  ignore_exceptions do
134
- queue.nack(delivery_tag: raw_message[:delivery_details][:delivery_tag])
134
+ queue.channel.nack(delivery_info, false, true)
135
135
  end
136
136
  raise exception
137
137
  else
data/lib/pebbles/river.rb CHANGED
@@ -8,6 +8,7 @@ require 'servolux'
8
8
  require_relative "river/version"
9
9
  require_relative "river/errors"
10
10
  require_relative "river/message"
11
+ require_relative "river/multi_prefork"
11
12
  require_relative "river/worker"
12
13
  require_relative "river/subscription"
13
14
  require_relative "river/supervisor"
@@ -15,3 +16,5 @@ require_relative "river/routing"
15
16
  require_relative "river/river"
16
17
  require_relative "river/compatibility"
17
18
  require_relative "river/rate_limiter"
19
+ require_relative "river/multi_prefork"
20
+ require_relative "river/daemon_helper"
@@ -20,12 +20,13 @@ Gem::Specification.new do |spec|
20
20
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
21
  spec.require_paths = ["lib"]
22
22
 
23
- spec.add_runtime_dependency 'pebblebed', '>= 0.1.3'
24
- spec.add_runtime_dependency 'bunny', '~> 0.8.0'
23
+ spec.add_runtime_dependency 'pebblebed', '>= 0.2'
24
+ spec.add_runtime_dependency 'bunny', '~> 1.2.2'
25
25
  spec.add_runtime_dependency 'activesupport', '>= 3.0'
26
26
  spec.add_runtime_dependency 'servolux', '~> 0.10'
27
+ spec.add_runtime_dependency 'mercenary', '~> 0.3.3'
27
28
 
28
- spec.add_development_dependency 'rspec'
29
+ spec.add_development_dependency 'rspec', "~> 2.99"
29
30
  spec.add_development_dependency "bundler", "~> 1.5"
30
31
  spec.add_development_dependency "rake"
31
32
  spec.add_development_dependency "simplecov"
@@ -21,11 +21,13 @@ describe Pebbles::River::River do
21
21
  ]
22
22
 
23
23
  after(:each) do
24
- subject.send(:bunny).queues.each do |name, queue|
25
- queue.purge
26
- # If you don't delete the queue, the subscription will not
27
- # change, even if you give it a new one.
28
- queue.delete
24
+ if (channel = subject.channel)
25
+ channel.queues.each do |name, queue|
26
+ queue.purge
27
+ # If you don't delete the queue, the subscription will not
28
+ # change, even if you give it a new one.
29
+ queue.delete
30
+ end
29
31
  end
30
32
  subject.disconnect
31
33
  end
@@ -33,14 +35,16 @@ describe Pebbles::River::River do
33
35
  it { subject.should_not be_connected }
34
36
 
35
37
  it "gets the name right" do
36
- subject.exchange_name.should eq('pebblebed.river.whatever')
38
+ subject.connect
39
+ subject.exchange.name.should eq('pebblebed.river.whatever')
37
40
  end
38
41
 
39
42
  context "in production" do
40
43
  subject { Pebbles::River::River.new('production') }
41
44
 
42
45
  it "doesn't append the thing" do
43
- subject.exchange_name.should eq('pebblebed.river')
46
+ subject.connect
47
+ subject.exchange.name.should eq('pebblebed.river')
44
48
  end
45
49
  end
46
50
 
@@ -62,12 +66,6 @@ describe Pebbles::River::River do
62
66
  subject.should be_connected
63
67
  end
64
68
 
65
- it "connects if you try to talk to the exchange" do
66
- subject.should_not be_connected
67
- subject.send(:exchange)
68
- subject.should be_connected
69
- end
70
-
71
69
  describe "publishing" do
72
70
 
73
71
  it "gets selected messages" do
@@ -96,7 +94,8 @@ describe Pebbles::River::River do
96
94
  queue = subject.queue(:name => 'eatseverything')
97
95
  subject.publish(:event => 'smile', :source => 'rspec', :uid => 'klass:path$1', :attributes => {:a => 'b'})
98
96
  sleep(0.1)
99
- JSON.parse(queue.pop[:payload])['uid'].should eq('klass:path$1')
97
+ _, _, payload = queue.pop
98
+ JSON.parse(payload)['uid'].should eq('klass:path$1')
100
99
  end
101
100
 
102
101
  CONNECTION_EXCEPTIONS.each do |exception_class|
@@ -108,7 +107,7 @@ describe Pebbles::River::River do
108
107
  exchange.stub(:publish) do
109
108
  count += 1
110
109
  if count < 3
111
- raise exception_class.new
110
+ raise create_exception(exception_class)
112
111
  end
113
112
  end
114
113
 
@@ -133,7 +132,7 @@ describe Pebbles::River::River do
133
132
  it "retries with exponential backoff until timeout and gives up with SendFailure" do
134
133
  exchange = double('exchange')
135
134
  exchange.stub(:publish) do
136
- raise exception_class.new
135
+ raise create_exception(exception_class)
137
136
  end
138
137
  subject.stub(:exchange) { exchange }
139
138
 
@@ -42,27 +42,37 @@ describe Worker do
42
42
  {'answer' => 42}
43
43
  end
44
44
 
45
+ let :delivery_info do
46
+ Class.new {
47
+ def delivery_tag
48
+ 'doink'
49
+ end
50
+ }.new
51
+ end
52
+
45
53
  let :raw_message do
46
- {
47
- header: 'someheader',
48
- payload: JSON.dump(payload),
49
- delivery_details: {delivery_tag: 'foo'}
50
- }
51
- end
54
+ [delivery_info, {}, JSON.dump(payload)]
55
+ end
56
+
57
+ let :channel do
58
+ channel = double('Bunny::Channel')
59
+ channel.stub(:ack) { }
60
+ channel.stub(:nack) { }
61
+ channel
62
+ end
52
63
 
53
64
  let :queue do
54
65
  queue = double('Bunny::Queue')
55
66
  queue.stub(:close) { nil }
56
67
  queue.stub(:pop) { |&block|
57
- block.call(raw_message)
68
+ block.call(*raw_message)
58
69
  }
59
- queue.stub(:ack) { }
60
- queue.stub(:nack) { }
70
+ queue.stub(:channel) { channel }
61
71
  queue
62
72
  end
63
73
 
64
74
  let :message do
65
- Message.new(raw_message, queue)
75
+ Message.new(raw_message[2], nil, queue)
66
76
  end
67
77
 
68
78
  let :river do
@@ -99,8 +109,9 @@ describe Worker do
99
109
 
100
110
  it 'creates a queue and runs worker with it' do
101
111
  expect(queue).to receive(:pop).at_least(1).times
102
- expect(queue).to receive(:ack).at_least(1).times
103
- expect(queue).to_not receive(:nack)
112
+
113
+ expect(channel).to receive(:ack).at_least(1).times
114
+ expect(channel).to_not receive(:nack)
104
115
 
105
116
  expect(null_handler).to receive(:call).with(message)
106
117
 
@@ -114,7 +125,7 @@ describe Worker do
114
125
  context 'when queue is empty' do
115
126
  it 'does nothing' do
116
127
  queue.stub(:pop) { |&block|
117
- block.call({payload: :queue_empty, delivery_details: {}})
128
+ block.call(nil, nil, nil)
118
129
  }
119
130
 
120
131
  expect(queue).to receive(:pop).at_least(1).times
@@ -128,7 +139,7 @@ describe Worker do
128
139
 
129
140
  it 'calls #on_idle if implemented' do
130
141
  queue.stub(:pop) { |&block|
131
- block.call({payload: :queue_empty, delivery_details: {}})
142
+ block.call(nil, nil, nil)
132
143
  }
133
144
 
134
145
  null_handler.stub(:on_idle) { }
@@ -140,10 +151,11 @@ describe Worker do
140
151
 
141
152
  context 'when handler is successful' do
142
153
  it 'acks the message' do
143
- expect(queue).to receive(:ack).at_least(1).times
144
- expect(queue).to_not receive(:nack)
145
154
  expect(queue).to_not receive(:close)
146
155
 
156
+ expect(channel).to receive(:ack).at_least(1).times
157
+ expect(channel).to_not receive(:nack)
158
+
147
159
  expect(river).to receive(:connected?).with(no_args).at_least(1).times
148
160
  expect(river).to_not receive(:connect)
149
161
 
@@ -154,10 +166,11 @@ describe Worker do
154
166
  context 'when handler returns false' do
155
167
  if false # Needs Bunny 0.9+
156
168
  it 'nacks the message' do
157
- expect(queue).to receive(:nack).at_least(1).times
158
- expect(queue).to_not receive(:ack)
159
169
  expect(queue).to_not receive(:close)
160
170
 
171
+ expect(channel).to receive(:nack).at_least(1).times
172
+ expect(channel).to_not receive(:ack)
173
+
161
174
  expect(river).to receive(:connected?).with(no_args).at_least(1).times
162
175
  expect(river).to_not receive(:connect)
163
176
 
@@ -210,7 +223,7 @@ describe Worker do
210
223
  ].each do |exception_class|
211
224
  context "connection exception #{exception_class}" do
212
225
  let :exception do
213
- exception_class.new("Dangit")
226
+ create_exception(exception_class)
214
227
  end
215
228
 
216
229
  let :handler do
@@ -257,8 +270,10 @@ describe Worker do
257
270
 
258
271
  logger = double('logger')
259
272
  logger.stub(:error) { }
273
+
274
+ # TODO: Test exception contents here
260
275
  expect(logger).to receive(:error).
261
- with(/#{Regexp.escape(exception.message)}/).at_least(1).times
276
+ with(/.*/).at_least(1).times
262
277
 
263
278
  river.stub(:disconnect)
264
279
 
@@ -319,7 +334,7 @@ describe Worker do
319
334
  context 'when queue is empty' do
320
335
  it 'calls #sleep to delay polling a bit' do
321
336
  queue.stub(:pop) { |&block|
322
- block.call({payload: :queue_empty, delivery_details: {}})
337
+ block.call(nil, nil, nil)
323
338
  }
324
339
 
325
340
  count = 0
data/spec/spec_helper.rb CHANGED
@@ -8,6 +8,21 @@ SimpleCov.start
8
8
 
9
9
  require_relative '../lib/pebbles/river'
10
10
 
11
- RSpec.configure do |c|
12
- c.mock_with :rspec
11
+ module SpecHelpers
12
+ def create_exception(exception_class)
13
+ # TODO: Using #allocate here because Bunny has a whole bunch
14
+ # of exceptions; the one comparison against ECONNRESET is sad
15
+ # and could be generalized
16
+ if exception_class == Errno::ECONNRESET
17
+ raise exception_class.new
18
+ else
19
+ raise exception_class.allocate
20
+ end
21
+ end
13
22
  end
23
+
24
+ RSpec.configure do |config|
25
+ config.mock_with :rspec
26
+ config.include SpecHelpers
27
+ end
28
+
metadata CHANGED
@@ -1,8 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pebbles-river
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
5
- prerelease:
4
+ version: 0.1.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Alexander Staubo
@@ -10,44 +9,39 @@ authors:
10
9
  autorequire:
11
10
  bindir: bin
12
11
  cert_chain: []
13
- date: 2014-06-11 00:00:00.000000000 Z
12
+ date: 2014-06-23 00:00:00.000000000 Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: pebblebed
17
16
  requirement: !ruby/object:Gem::Requirement
18
- none: false
19
17
  requirements:
20
18
  - - ! '>='
21
19
  - !ruby/object:Gem::Version
22
- version: 0.1.3
20
+ version: '0.2'
23
21
  type: :runtime
24
22
  prerelease: false
25
23
  version_requirements: !ruby/object:Gem::Requirement
26
- none: false
27
24
  requirements:
28
25
  - - ! '>='
29
26
  - !ruby/object:Gem::Version
30
- version: 0.1.3
27
+ version: '0.2'
31
28
  - !ruby/object:Gem::Dependency
32
29
  name: bunny
33
30
  requirement: !ruby/object:Gem::Requirement
34
- none: false
35
31
  requirements:
36
32
  - - ~>
37
33
  - !ruby/object:Gem::Version
38
- version: 0.8.0
34
+ version: 1.2.2
39
35
  type: :runtime
40
36
  prerelease: false
41
37
  version_requirements: !ruby/object:Gem::Requirement
42
- none: false
43
38
  requirements:
44
39
  - - ~>
45
40
  - !ruby/object:Gem::Version
46
- version: 0.8.0
41
+ version: 1.2.2
47
42
  - !ruby/object:Gem::Dependency
48
43
  name: activesupport
49
44
  requirement: !ruby/object:Gem::Requirement
50
- none: false
51
45
  requirements:
52
46
  - - ! '>='
53
47
  - !ruby/object:Gem::Version
@@ -55,7 +49,6 @@ dependencies:
55
49
  type: :runtime
56
50
  prerelease: false
57
51
  version_requirements: !ruby/object:Gem::Requirement
58
- none: false
59
52
  requirements:
60
53
  - - ! '>='
61
54
  - !ruby/object:Gem::Version
@@ -63,7 +56,6 @@ dependencies:
63
56
  - !ruby/object:Gem::Dependency
64
57
  name: servolux
65
58
  requirement: !ruby/object:Gem::Requirement
66
- none: false
67
59
  requirements:
68
60
  - - ~>
69
61
  - !ruby/object:Gem::Version
@@ -71,31 +63,41 @@ dependencies:
71
63
  type: :runtime
72
64
  prerelease: false
73
65
  version_requirements: !ruby/object:Gem::Requirement
74
- none: false
75
66
  requirements:
76
67
  - - ~>
77
68
  - !ruby/object:Gem::Version
78
69
  version: '0.10'
70
+ - !ruby/object:Gem::Dependency
71
+ name: mercenary
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: 0.3.3
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ~>
82
+ - !ruby/object:Gem::Version
83
+ version: 0.3.3
79
84
  - !ruby/object:Gem::Dependency
80
85
  name: rspec
81
86
  requirement: !ruby/object:Gem::Requirement
82
- none: false
83
87
  requirements:
84
- - - ! '>='
88
+ - - ~>
85
89
  - !ruby/object:Gem::Version
86
- version: '0'
90
+ version: '2.99'
87
91
  type: :development
88
92
  prerelease: false
89
93
  version_requirements: !ruby/object:Gem::Requirement
90
- none: false
91
94
  requirements:
92
- - - ! '>='
95
+ - - ~>
93
96
  - !ruby/object:Gem::Version
94
- version: '0'
97
+ version: '2.99'
95
98
  - !ruby/object:Gem::Dependency
96
99
  name: bundler
97
100
  requirement: !ruby/object:Gem::Requirement
98
- none: false
99
101
  requirements:
100
102
  - - ~>
101
103
  - !ruby/object:Gem::Version
@@ -103,7 +105,6 @@ dependencies:
103
105
  type: :development
104
106
  prerelease: false
105
107
  version_requirements: !ruby/object:Gem::Requirement
106
- none: false
107
108
  requirements:
108
109
  - - ~>
109
110
  - !ruby/object:Gem::Version
@@ -111,7 +112,6 @@ dependencies:
111
112
  - !ruby/object:Gem::Dependency
112
113
  name: rake
113
114
  requirement: !ruby/object:Gem::Requirement
114
- none: false
115
115
  requirements:
116
116
  - - ! '>='
117
117
  - !ruby/object:Gem::Version
@@ -119,7 +119,6 @@ dependencies:
119
119
  type: :development
120
120
  prerelease: false
121
121
  version_requirements: !ruby/object:Gem::Requirement
122
- none: false
123
122
  requirements:
124
123
  - - ! '>='
125
124
  - !ruby/object:Gem::Version
@@ -127,7 +126,6 @@ dependencies:
127
126
  - !ruby/object:Gem::Dependency
128
127
  name: simplecov
129
128
  requirement: !ruby/object:Gem::Requirement
130
- none: false
131
129
  requirements:
132
130
  - - ! '>='
133
131
  - !ruby/object:Gem::Version
@@ -135,7 +133,6 @@ dependencies:
135
133
  type: :development
136
134
  prerelease: false
137
135
  version_requirements: !ruby/object:Gem::Requirement
138
- none: false
139
136
  requirements:
140
137
  - - ! '>='
141
138
  - !ruby/object:Gem::Version
@@ -154,8 +151,10 @@ files:
154
151
  - Rakefile
155
152
  - lib/pebbles/river.rb
156
153
  - lib/pebbles/river/compatibility.rb
154
+ - lib/pebbles/river/daemon_helper.rb
157
155
  - lib/pebbles/river/errors.rb
158
156
  - lib/pebbles/river/message.rb
157
+ - lib/pebbles/river/multi_prefork.rb
159
158
  - lib/pebbles/river/rate_limiter.rb
160
159
  - lib/pebbles/river/river.rb
161
160
  - lib/pebbles/river/routing.rb
@@ -172,27 +171,26 @@ files:
172
171
  homepage: ''
173
172
  licenses:
174
173
  - MIT
174
+ metadata: {}
175
175
  post_install_message:
176
176
  rdoc_options: []
177
177
  require_paths:
178
178
  - lib
179
179
  required_ruby_version: !ruby/object:Gem::Requirement
180
- none: false
181
180
  requirements:
182
181
  - - ! '>='
183
182
  - !ruby/object:Gem::Version
184
183
  version: '0'
185
184
  required_rubygems_version: !ruby/object:Gem::Requirement
186
- none: false
187
185
  requirements:
188
186
  - - ! '>='
189
187
  - !ruby/object:Gem::Version
190
188
  version: '0'
191
189
  requirements: []
192
190
  rubyforge_project:
193
- rubygems_version: 1.8.23.2
191
+ rubygems_version: 2.2.2
194
192
  signing_key:
195
- specification_version: 3
193
+ specification_version: 4
196
194
  summary: Implements an event river mechanism for Pebblebed.
197
195
  test_files:
198
196
  - spec/lib/river_spec.rb
@@ -200,3 +198,4 @@ test_files:
200
198
  - spec/lib/subscription_spec.rb
201
199
  - spec/lib/worker_spec.rb
202
200
  - spec/spec_helper.rb
201
+ has_rdoc: