pebbles-river 0.0.7 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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: