proletariat 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,7 +7,7 @@ namespace :proletariat do
7
7
  task run: :environment do
8
8
  STDOUT.sync = true
9
9
 
10
- Proletariat.run!
10
+ Proletariat.run
11
11
 
12
12
  at_exit { Proletariat.stop }
13
13
 
@@ -22,17 +22,15 @@ module Proletariat
22
22
  @counters = []
23
23
  @subscribers = []
24
24
 
25
- expectations.each do |expectation|
26
- queue_config = generate_queue_config_for_topic(expectation.topics)
27
- counter = MessageCounter.new(expectation.quantity)
25
+ expectations.each_with_index do |expectation, i|
26
+ config = generate_queue_config_for_topic(expectation.topics)
27
+ suffix = "_#{i}_#{object_id}"
28
+ counter = MessageCounter.spawn!("c#{suffix}", expectation.quantity)
28
29
  counters << counter
29
- subscribers << Subscriber.new(counter, queue_config)
30
+ subscribers << Subscriber.spawn!("s#{suffix}", counter, config, Drop)
30
31
  end
31
32
 
32
33
  @block = block
33
- @noop = true if expectations.length == 0
34
-
35
- run_subscribers
36
34
  end
37
35
 
38
36
  # Public: Execute the blocks and waits for the expectations to be met.
@@ -44,13 +42,7 @@ module Proletariat
44
42
 
45
43
  return nil if noop
46
44
 
47
- timer = 0.0
48
-
49
- until passed?
50
- fail MessageTimeoutError if timer > MESSAGE_TIMEOUT
51
- sleep MESSAGE_CHECK_INTERVAL
52
- timer += MESSAGE_CHECK_INTERVAL
53
- end
45
+ wait!
54
46
 
55
47
  nil
56
48
  ensure
@@ -66,9 +58,6 @@ module Proletariat
66
58
  # Internal: Returns an array of MessageCounter instances.
67
59
  attr_reader :counters
68
60
 
69
- # Internal: Returns true if there aren't any expectations.
70
- attr_reader :noop
71
-
72
61
  # Internal: Returns an array of Subscriber instances.
73
62
  attr_reader :subscribers
74
63
 
@@ -82,30 +71,43 @@ module Proletariat
82
71
  # Returns false if one or more counters are not satisfied.
83
72
  def passed?
84
73
  counters
85
- .map(&:expected_messages_received?)
74
+ .map { |c| c.ask!(:expected_messages_received?) }
86
75
  .reduce { |a, e| a && e }
87
76
  end
88
77
 
89
- # Internal: Starts each subscriber.
78
+ # Internal: Returns true if there aren't any expectations.
79
+ def noop
80
+ subscribers.length == 0
81
+ end
82
+
83
+ # Internal: Stops each subscriber.
90
84
  #
91
85
  # Returns nil.
92
- def run_subscribers
93
- subscribers.each { |subscriber| subscriber.run! }
86
+ def stop_subscribers
87
+ subscribers.each { |subscriber| subscriber << :terminate! }
94
88
 
95
89
  nil
96
90
  end
97
91
 
98
- # Internal: Stops each subscriber.
92
+ # Internal: Sleeps the thread at regular intervals until the expectation
93
+ # is passed.
99
94
  #
100
- # Returns nil.
101
- def stop_subscribers
102
- subscribers.each { |subscriber| subscriber.stop }
95
+ # Returns nil if expectation passes within timeout.
96
+ # Raises MessageTimeoutError if expectation does not pass within timeout.
97
+ def wait!
98
+ timer = 0.0
99
+
100
+ until passed?
101
+ fail MessageTimeoutError if timer > MESSAGE_TIMEOUT
102
+ sleep MESSAGE_CHECK_INTERVAL
103
+ timer += MESSAGE_CHECK_INTERVAL
104
+ end
103
105
 
104
106
  nil
105
107
  end
106
108
 
107
109
  # Internal: Counts incoming messages to test expection satisfaction.
108
- class MessageCounter
110
+ class MessageCounter < Actor
109
111
  # Public: Creates a new MessageCounter instance.
110
112
  #
111
113
  # expected - The number of messages expected.
@@ -130,10 +132,14 @@ module Proletariat
130
132
  # headers - Hash of message headers.
131
133
  #
132
134
  # Returns a future-like object holding an :ok Symbol.
133
- def post?(message, routing_key, headers)
134
- self.count = count + 1
135
-
136
- Concurrent::Future.new { :ok }
135
+ def work(message)
136
+ if message.is_a?(Message)
137
+ self.count = count + 1
138
+
139
+ Concurrent::IVar.new(:ok)
140
+ else
141
+ expected_messages_received?
142
+ end
137
143
  end
138
144
 
139
145
  private
@@ -1,4 +1,4 @@
1
1
  # Public: Adds a constant for the current version number.
2
2
  module Proletariat
3
- VERSION = '0.0.6'
3
+ VERSION = '0.1.0'
4
4
  end
@@ -3,43 +3,14 @@ require 'proletariat/concerns/logging'
3
3
  module Proletariat
4
4
  # Public: Handles messages for Background processing. Subclasses should
5
5
  # overwrite the #work method.
6
- class Worker
6
+ class Worker < PoolableActor
7
7
  include Concerns::Logging
8
8
 
9
- # Public: Logs the 'online' status of the worker.
10
- #
11
- # Returns nil.
12
- def started
13
- log_info 'Now online'
14
-
15
- nil
16
- end
17
-
18
- # Public: Logs the 'offline' status of the worker.
19
- #
20
- # Returns nil.
21
- def stopped
22
- log_info 'Now offline'
23
-
24
- nil
25
- end
26
-
27
- # Public: Logs the 'shutting down' status of the worker.
9
+ # Internal: Handles the Actor mailbox. Delegates work to #work.
28
10
  #
29
- # Returns nil.
30
- def stopping
31
- log_info 'Attempting graceful shutdown.'
32
-
33
- nil
34
- end
35
-
36
- # Public: Handles an incoming message to perform background work.
37
- #
38
- # message - The incoming message.
39
- #
40
- # Raises NotImplementedError unless implemented in subclass.
41
- def work(message, routing_key, headers)
42
- fail NotImplementedError
11
+ # message - A Message to send.
12
+ def actor_work(message)
13
+ work message.body, message.to, message.headers if message.is_a?(Message)
43
14
  end
44
15
 
45
16
  # Public: Helper method to ease accessing the logger from within #work.
@@ -81,8 +52,50 @@ module Proletariat
81
52
  nil
82
53
  end
83
54
 
55
+ # Public: Logs the 'online' status of the worker.
56
+ #
57
+ # Returns nil.
58
+ def started
59
+ log_info 'Now online'
60
+
61
+ nil
62
+ end
63
+
64
+ # Public: Logs the 'offline' status of the worker.
65
+ #
66
+ # Returns nil.
67
+ def stopped
68
+ log_info 'Now offline'
69
+
70
+ nil
71
+ end
72
+
73
+ # Public: Handles an incoming message to perform background work.
74
+ #
75
+ # message - The incoming message.
76
+ # routing_key - The incoming message's routing key.
77
+ # headers - The incoming message's headers.
78
+ #
79
+ # Raises NotImplementedError unless implemented in subclass.
80
+ def work(message, routing_key, headers)
81
+ fail NotImplementedError
82
+ end
83
+
84
+ # Public: Use #actor_work to handle the actor mailbox.
85
+ def work_method
86
+ :actor_work
87
+ end
88
+
84
89
  # Internal: Class methods on Worker to provide configuration DSL.
85
90
  module ConfigurationMethods
91
+ def exception_handler(value = nil)
92
+ if value
93
+ @exception_handler = value
94
+ else
95
+ @exception_handler || :exponential_backoff
96
+ end
97
+ end
98
+
86
99
  # Public: A configuration method for adding a routing key to be used when
87
100
  # binding this worker type's queue to an exchange.
88
101
  #
data/proletariat.gemspec CHANGED
@@ -16,9 +16,9 @@ Gem::Specification.new do |s|
16
16
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
17
  s.require_paths = ['lib']
18
18
 
19
- s.add_development_dependency 'rspec', '3.0.0.beta1'
19
+ s.add_development_dependency 'rspec', '3.1.0'
20
20
  s.add_development_dependency 'rubocop'
21
21
 
22
- s.add_runtime_dependency 'concurrent-ruby', '~> 0.6.1'
23
- s.add_runtime_dependency 'bunny', '~> 1.3.1'
22
+ s.add_runtime_dependency 'concurrent-ruby', '~> 0.7'
23
+ s.add_runtime_dependency 'bunny', '~> 1.6.3'
24
24
  end
@@ -30,7 +30,7 @@ class PongWorker < Proletariat::Worker
30
30
 
31
31
  def work(message, routing_key, headers)
32
32
  if self.class.fail_mode == true
33
- fail 'Error' unless headers['failures'] == 2
33
+ fail 'Error' unless headers['x-death'] && headers['x-death'].length == 2
34
34
  end
35
35
 
36
36
  self.class.ponged = true
@@ -46,36 +46,47 @@ end
46
46
  describe Proletariat do
47
47
  before do
48
48
  Proletariat.configure do
49
+ config.exchange_name = 'proletariat-test-suite'
49
50
  config.logger = Logger.new('/dev/null')
51
+ config.test_mode!
50
52
  config.worker_classes = [PingWorker, PongWorker]
51
53
  end
52
54
 
53
55
  PongWorker.fail_mode = false
54
56
 
55
- Proletariat.purge
56
- Proletariat.run!
57
- sleep 2
57
+ Proletariat.run
58
+
59
+ sleep 1
58
60
  end
59
61
 
60
62
  after do
61
63
  Proletariat.stop
62
- Proletariat.purge
64
+
63
65
  PingWorker.pinged = false
64
66
  PongWorker.ponged = false
67
+
68
+ sleep 0.5
65
69
  end
66
70
 
67
71
  it 'should roughly work' do
68
72
  Proletariat.publish 'ping', ''
69
- sleep 5
73
+ sleep 1
70
74
 
71
75
  expect(PingWorker.pinged).to be_truthy
72
76
  expect(PongWorker.ponged).to be_truthy
73
77
  end
74
78
 
79
+ it 'should purge between tests' do
80
+ sleep 1
81
+
82
+ expect(PingWorker.pinged).to be_falsey
83
+ expect(PongWorker.ponged).to be_falsey
84
+ end
85
+
75
86
  it 'should work in error conditions' do
76
87
  PongWorker.fail_mode = true
77
88
  Proletariat.publish 'ping', ''
78
- sleep 10
89
+ sleep 15
79
90
 
80
91
  expect(PingWorker.pinged).to be_truthy
81
92
  expect(PongWorker.ponged).to be_truthy
@@ -18,38 +18,6 @@ module Proletariat
18
18
  end
19
19
  end
20
20
  end
21
-
22
- describe '#post?' do
23
- class FakeBlockTaker
24
- attr_reader :block
25
-
26
- def initialize(&block)
27
- @block = block
28
- end
29
- end
30
-
31
- before do
32
- stub_const 'Concurrent::Future', FakeBlockTaker
33
- end
34
-
35
- it 'should increment the count' do
36
- counter = ExpectationGuarantor::MessageCounter.new(1)
37
- counter.post?('message', 'key', {})
38
- expect(counter.expected_messages_received?).to be_truthy
39
- end
40
-
41
- it 'should return a future containing :ok' do
42
- counter = ExpectationGuarantor::MessageCounter.new(1)
43
- expect(Concurrent::Future).to receive(:new)
44
- counter.post?('message', 'key', {})
45
- end
46
-
47
- it 'should ensure the returned future contains :ok' do
48
- counter = ExpectationGuarantor::MessageCounter.new(1)
49
- future = counter.post?('message', 'key', {})
50
- expect(future.block.call).to eq :ok
51
- end
52
- end
53
21
  end
54
22
  end
55
23
  end
@@ -1,8 +1,13 @@
1
+ require 'concurrent'
2
+ require 'proletariat/concurrency/actor_common'
3
+ require 'proletariat/concurrency/poolable_actor'
1
4
  require 'proletariat/worker'
2
5
 
3
6
  module Proletariat
4
7
  describe Worker do
5
- let(:logger) { double.as_null_object }
8
+ let(:balancer) { double.as_null_object }
9
+ let(:logger) { double.as_null_object }
10
+ let(:worker) { Worker.new(balancer) }
6
11
 
7
12
  before do
8
13
  allow(Proletariat).to receive(:logger).and_return(logger)
@@ -11,27 +16,20 @@ module Proletariat
11
16
  describe '#started' do
12
17
  it 'should log status' do
13
18
  expect(logger).to receive(:info).with /online/
14
- Worker.new.started
19
+ worker.started
15
20
  end
16
21
  end
17
22
 
18
23
  describe '#stopped' do
19
24
  it 'should log status' do
20
25
  expect(logger).to receive(:info).with /offline/
21
- Worker.new.stopped
22
- end
23
- end
24
-
25
- describe '#stopping' do
26
- it 'should log status' do
27
- expect(logger).to receive(:info).with /graceful shutdown/
28
- Worker.new.stopping
26
+ worker.stopped
29
27
  end
30
28
  end
31
29
 
32
30
  describe '#work' do
33
31
  it 'should raise NotImplementedError' do
34
- expect { Worker.new.work('message', 'key', {}) }.to \
32
+ expect { worker.work('message', 'key', {}) }.to \
35
33
  raise_exception NotImplementedError
36
34
  end
37
35
  end
@@ -40,13 +38,13 @@ module Proletariat
40
38
  context 'when message is provided' do
41
39
  it 'should log the message directly' do
42
40
  expect(logger).to receive(:info).with 'message to log'
43
- Worker.new.log 'message to log'
41
+ worker.log 'message to log'
44
42
  end
45
43
  end
46
44
 
47
45
  context 'when no message is provided' do
48
46
  it 'should return the logger instance' do
49
- expect(Worker.new.log).to eq logger
47
+ expect(worker.log).to eq logger
50
48
  end
51
49
  end
52
50
  end
@@ -54,12 +52,12 @@ module Proletariat
54
52
  describe '#publish' do
55
53
  it 'should forward the message to the publisher' do
56
54
  expect(Proletariat).to receive(:publish).with('topic', 'message', {})
57
- Worker.new.publish 'topic', 'message', {}
55
+ worker.publish 'topic', 'message', {}
58
56
  end
59
57
 
60
58
  it 'should have a blank default message' do
61
59
  expect(Proletariat).to receive(:publish).with('topic', '', {})
62
- Worker.new.publish 'topic'
60
+ worker.publish 'topic'
63
61
  end
64
62
  end
65
63
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: proletariat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Edwards
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-14 00:00:00.000000000 Z
11
+ date: 2014-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 3.0.0.beta1
19
+ version: 3.1.0
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 3.0.0.beta1
26
+ version: 3.1.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rubocop
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,28 +44,28 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.6.1
47
+ version: '0.7'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.6.1
54
+ version: '0.7'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bunny
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 1.3.1
61
+ version: 1.6.3
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 1.3.1
68
+ version: 1.6.3
69
69
  description: Lightweight background processing powered by RabbitMQ
70
70
  email:
71
71
  - me@sebastianedwards.co.nz
@@ -84,10 +84,15 @@ files:
84
84
  - lib/proletariat.rb
85
85
  - lib/proletariat/concerns/logging.rb
86
86
  - lib/proletariat/concurrency/actor.rb
87
- - lib/proletariat/concurrency/supervisor.rb
87
+ - lib/proletariat/concurrency/actor_common.rb
88
+ - lib/proletariat/concurrency/poolable_actor.rb
88
89
  - lib/proletariat/configuration.rb
89
90
  - lib/proletariat/cucumber.rb
91
+ - lib/proletariat/exception_handler.rb
92
+ - lib/proletariat/exception_handler/drop.rb
93
+ - lib/proletariat/exception_handler/exponential_backoff.rb
90
94
  - lib/proletariat/manager.rb
95
+ - lib/proletariat/message.rb
91
96
  - lib/proletariat/publisher.rb
92
97
  - lib/proletariat/queue_config.rb
93
98
  - lib/proletariat/runner.rb
@@ -103,7 +108,6 @@ files:
103
108
  - proletariat.gemspec
104
109
  - spec/lib/configuration_spec.rb
105
110
  - spec/lib/proletariat_spec.rb
106
- - spec/lib/publisher_spec.rb
107
111
  - spec/lib/queue_config_spec.rb
108
112
  - spec/lib/testing/expectation_guarantor_spec.rb
109
113
  - spec/lib/testing/expectation_spec.rb
@@ -128,14 +132,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
128
132
  version: '0'
129
133
  requirements: []
130
134
  rubyforge_project:
131
- rubygems_version: 2.2.0
135
+ rubygems_version: 2.4.2
132
136
  signing_key:
133
137
  specification_version: 4
134
138
  summary: Lightweight background processing powered by RabbitMQ
135
139
  test_files:
136
140
  - spec/lib/configuration_spec.rb
137
141
  - spec/lib/proletariat_spec.rb
138
- - spec/lib/publisher_spec.rb
139
142
  - spec/lib/queue_config_spec.rb
140
143
  - spec/lib/testing/expectation_guarantor_spec.rb
141
144
  - spec/lib/testing/expectation_spec.rb