empipelines 0.2.4 → 0.2.5

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.
data/empipelines.gemspec CHANGED
@@ -4,8 +4,8 @@ Gem::Specification.new do |s|
4
4
  s.rubygems_version = '1.3.5'
5
5
  ## Leave these as is they will be modified for you by the rake gemspec task.
6
6
  s.name = 'empipelines'
7
- s.version = '0.2.4'
8
- s.date = '2012-01-10'
7
+ s.version = '0.2.5'
8
+ s.date = '2012-01-13'
9
9
  s.rubyforge_project = 'empipelines'
10
10
 
11
11
  s.summary = "Simple Event Handling Pipeline Architecture for EventMachine"
@@ -35,7 +35,6 @@ Gem::Specification.new do |s|
35
35
  Gemfile.lock
36
36
  README.md
37
37
  Rakefile
38
- empipelines-0.2.4.gem
39
38
  empipelines.gemspec
40
39
  functional/consuming_events_from_amqp_spec.rb
41
40
  functional/consuming_events_from_batch_spec.rb
@@ -4,32 +4,38 @@ require 'amqp'
4
4
  require 'json'
5
5
  require File.join(File.dirname(__FILE__), 'test_stages')
6
6
 
7
+ ExchangeName = 'empipelines.build'
8
+ QueueName = 'empipelines.build.queue'
9
+
10
+ def setup_queues
11
+ connection = AMQP.connect()
12
+ channel = AMQP::Channel.new(connection)
13
+ channel.prefetch(1)
14
+
15
+ exchange = channel.direct(ExchangeName, :durable => true)
16
+ queue = channel.queue(QueueName, :durable => true)
17
+ queue.bind(exchange)
18
+ queue.purge
19
+ [exchange, queue]
20
+ end
21
+
7
22
  module TestStages
8
- ExchangeName = 'empipelines.build'
9
- QueueName = 'empipelines.build.queue'
10
23
 
11
24
  describe 'Consumption of events from a in-memory batch' do
12
- let(:monitoring) { stub(:inform => nil, :debug => nil) }
25
+ let(:monitoring) { Monitoring.new }
13
26
  let (:processed) { [] }
14
27
 
15
28
  include EmRunner
16
29
 
17
30
  it 'consumes all events from a queue' do
18
31
  with_em_run do
19
- connection = AMQP.connect()
20
- channel = AMQP::Channel.new(connection)
21
- channel.prefetch(1)
22
-
23
- exchange = channel.direct(ExchangeName, :durable => true)
24
- queue = channel.queue(QueueName, :durable => true)
25
- queue.bind(exchange)
26
- queue.purge
32
+ exchange, queue = setup_queues
27
33
 
28
34
  messages = (1..1000).map { |i| {:data => i}.to_json }
29
35
  messages.each { |m| exchange.publish(m, :rounting_key => QueueName) }
30
36
 
31
37
  pipeline = EmPipelines::Pipeline.new(EM, {:processed => processed}, monitoring)
32
- source = EmPipelines::AmqpEventSource.new(EM, queue, 'msg')
38
+ source = EmPipelines::AmqpEventSource.new(EM, queue, 'msg', monitoring)
33
39
 
34
40
  stages = [PassthroughStage, PassthroughStage, PassthroughStage]
35
41
  event_pipeline = EmPipelines::EventPipeline.new(source, pipeline.for(stages), monitoring)
@@ -43,5 +49,52 @@ module TestStages
43
49
  event_pipeline.start!
44
50
  end
45
51
  end
52
+
53
+ it 'discards broken messages' do
54
+ with_em_run do
55
+ exchange, queue = setup_queues
56
+
57
+ messages = (1..1000).map { |i| {:data => i}.to_json }
58
+ messages.each { |m| exchange.publish(m, :rounting_key => QueueName) }
59
+
60
+ pipeline = EmPipelines::Pipeline.new(EM, {:processed => processed}, monitoring)
61
+ source = EmPipelines::AmqpEventSource.new(EM, queue, 'msg', monitoring)
62
+
63
+ stages = [BrokenMessageStage, ShouldNotBeReachedStage]
64
+ event_pipeline = EmPipelines::EventPipeline.new(source, pipeline.for(stages), monitoring)
65
+
66
+ EM.add_periodic_timer(0.1) do
67
+ if processed.size == messages.size then
68
+ EM.stop
69
+ end
70
+ end
71
+
72
+ event_pipeline.start!
73
+ end
74
+ end
75
+
76
+ it 'retries messages if told to do so' do
77
+ with_em_run do
78
+ exchange, queue = setup_queues
79
+
80
+ messages = [ {:data => "b"}.to_json ]
81
+ messages.each { |m| exchange.publish(m, :rounting_key => QueueName) }
82
+
83
+ pipeline = EmPipelines::Pipeline.new(EM, {:processed => processed}, monitoring)
84
+ source = EmPipelines::AmqpEventSource.new(EM, queue, 'msg', monitoring)
85
+
86
+ stages = [RetryOscillator]
87
+ event_pipeline = EmPipelines::EventPipeline.new(source, pipeline.for(stages), monitoring)
88
+
89
+ EM.add_periodic_timer(0.1) do
90
+ if processed.size == 2 then
91
+ EM.stop
92
+ end
93
+ end
94
+
95
+ event_pipeline.start!
96
+ end
97
+ end
98
+
46
99
  end
47
100
  end
@@ -1,4 +1,22 @@
1
1
  module TestStages
2
+ class Monitoring
3
+ def initialize(output = false)
4
+ @output = output
5
+ end
6
+
7
+ def inform(txt)
8
+ puts "#{Time.now.usec} INFO: #{txt}" if @output
9
+ end
10
+
11
+ def debug(txt)
12
+ puts "#{Time.now.usec} DEBUG: #{txt}" if @output
13
+ end
14
+
15
+ def inform_exception!(exc, origin, extra = nil)
16
+ puts "#{Time.now.usec} ERROR: #{exc} at #{origin} - #{extra}" if @output
17
+ end
18
+ end
19
+
2
20
  module EmRunner
3
21
  def with_em_run(&test_body)
4
22
  EM.run do
@@ -33,6 +51,15 @@ module TestStages
33
51
  end
34
52
  end
35
53
 
54
+ class RetryOscillator
55
+ include SomeStage
56
+
57
+ def process(message, callback)
58
+ @flip = !@flip || false
59
+ message.rejected! if @flip
60
+ end
61
+ end
62
+
36
63
  class ShouldNotBeReachedStage
37
64
  include SomeStage
38
65
 
@@ -7,22 +7,27 @@ module EmPipelines
7
7
  #this must have a on_finished!
8
8
  class AmqpEventSource < EventSource
9
9
 
10
- def initialize(em, queue, event_name)
11
- @em, @queue, @event_name = em, queue, event_name
10
+ def initialize(em, queue, event_name, monitoring)
11
+ @em, @queue, @event_name, @monitoring = em, queue, event_name, monitoring
12
12
  end
13
13
 
14
14
  def start!
15
15
  @queue.subscribe(:ack => true) do |header, json_payload|
16
- message = Message.new({
17
- :origin => @queue.name,
18
- :payload => JSON.parse(json_payload),
19
- :event => @event_name,
20
- :started_at => Time.now.to_i
21
- })
22
- message.on_consumed { |m| header.ack }
23
- message.on_broken { |m| header.reject(:requeue => true) }
24
- message.on_rejected { |m| header.reject(:requeue => true) }
25
- event!(message)
16
+ begin
17
+ message = Message.new({
18
+ :origin => @queue.name,
19
+ :payload => JSON.parse(json_payload),
20
+ :event => @event_name,
21
+ :started_at => Time.now.to_i
22
+ })
23
+ message.on_consumed { |m| header.ack }
24
+ message.on_broken { |m| header.reject(:requeue => false) }
25
+ message.on_rejected { |m| header.reject(:requeue => true) }
26
+ event!(message)
27
+ rescue => exc
28
+ @monitoring.inform_exception!(exc, self, 'removing message from queue')
29
+ header.reject(:requeue => false)
30
+ end
26
31
  end
27
32
  end
28
33
  end
@@ -5,35 +5,35 @@ module EmPipelines
5
5
  message.consumed!
6
6
  end
7
7
  end
8
-
8
+
9
9
  def initialize(em, context, monitoring)
10
10
  @em = em
11
11
  @context = context
12
12
  @monitoring = monitoring
13
13
  end
14
-
14
+
15
15
  def for(event_definition)
16
16
  stages = event_definition.map(&instantiate_with_dependencies)
17
17
 
18
18
  monitoring = @monitoring
19
-
19
+
20
20
  first_stage_process = stages.reverse.reduce(TerminatorStage) do |current_head, next_stage|
21
- @em.spawn do |input|
21
+ @em.spawn do |input_message|
22
22
  begin
23
- monitoring.debug "#{next_stage.class}#notify with #{input}}"
24
- next_stage.call(input) do |output|
23
+ monitoring.debug "#{next_stage.class}#notify with #{input_message}}"
24
+ next_stage.call(input_message) do |output|
25
25
  current_head.notify(output)
26
26
  end
27
27
  rescue => exception
28
- monitoring.inform_exception!(exception, next_stage)
28
+ monitoring.inform_exception!(exception, next_stage, "Message #{input_message} is broken")
29
+ input_message.broken!
29
30
  end
30
- end
31
+ end
31
32
  end
32
-
33
- @monitoring.inform "Pipeline for event_definition is: #{stages.map(&:class).join('->')}"
33
+ @monitoring.inform "Pipeline for event_definition is: #{stages.map(&:class).join('->')}"
34
34
  first_stage_process
35
35
  end
36
-
36
+
37
37
  private
38
38
  def instantiate_with_dependencies
39
39
  lambda do |stage_class|
data/lib/empipelines.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module EmPipelines
2
- VERSION = '0.2.4'
2
+ VERSION = '0.2.5'
3
3
  end
4
4
 
5
5
  require 'empipelines/message'
@@ -14,6 +14,7 @@ module EmPipelines
14
14
 
15
15
  describe AmqpEventSource do
16
16
  let (:em) { mock('eventmachine') }
17
+ let (:monitoring) { mock('monitoring') }
17
18
 
18
19
  it 'wraps each AMQP message and send to listeners' do
19
20
  json_payload = '{"key":"value"}'
@@ -24,7 +25,7 @@ module EmPipelines
24
25
 
25
26
  received_messages = []
26
27
 
27
- amqp_source = AmqpEventSource.new(em, queue, event_type)
28
+ amqp_source = AmqpEventSource.new(em, queue, event_type, monitoring)
28
29
  amqp_source.on_event { |e| received_messages << e }
29
30
  amqp_source.start!
30
31
 
@@ -43,20 +44,34 @@ module EmPipelines
43
44
 
44
45
  header.should_receive(:ack)
45
46
 
46
- amqp_source = AmqpEventSource.new(em, queue, 'event type')
47
+ amqp_source = AmqpEventSource.new(em, queue, 'event type', monitoring)
47
48
  amqp_source.on_event { |e| e.consumed! }
48
49
  amqp_source.start!
49
50
 
50
51
  queue.publish(header, '{"key":"value"}')
51
52
  end
52
53
 
53
- it 'rejects broken messages with no requeue' do
54
+ it 'marks message as broken if cannot be parsed' do
54
55
  queue = StubQueue.new
55
56
  header = mock('header')
56
57
 
57
- header.should_receive(:reject)
58
+ header.should_receive(:reject).with({:requeue => false})
59
+ monitoring.should_receive(:inform_exception!)
58
60
 
59
- amqp_source = AmqpEventSource.new(em, queue, 'event type')
61
+ amqp_source = AmqpEventSource.new(em, queue, 'event type', monitoring)
62
+ amqp_source.on_event { raise 'should never happen' }
63
+ amqp_source.start!
64
+
65
+ queue.publish(header, 'some junk')
66
+ end
67
+
68
+ it 'rejects broken messages with no requeue' do
69
+ queue = StubQueue.new
70
+ header = mock('header')
71
+
72
+ header.should_receive(:reject).with({:requeue => false})
73
+
74
+ amqp_source = AmqpEventSource.new(em, queue, 'event type', monitoring)
60
75
  amqp_source.on_event { |e| e.broken! }
61
76
  amqp_source.start!
62
77
 
@@ -69,7 +84,7 @@ module EmPipelines
69
84
 
70
85
  header.should_receive(:reject).with({:requeue => true})
71
86
 
72
- amqp_source = AmqpEventSource.new(em, queue, 'event type')
87
+ amqp_source = AmqpEventSource.new(em, queue, 'event type', monitoring)
73
88
  amqp_source.on_event { |e| e.rejected! }
74
89
  amqp_source.start!
75
90
 
@@ -10,7 +10,7 @@ module EmPipelines
10
10
  def consumed!
11
11
  raise 'unexpected call'
12
12
  end
13
-
13
+
14
14
  def rejected!
15
15
  raise 'unexpected call'
16
16
  end
@@ -61,7 +61,7 @@ module EmPipelines
61
61
  next_stage.call(input.merge!({:orange => orange}))
62
62
  end
63
63
  end
64
-
64
+
65
65
  class GlobalHolder
66
66
  @@value = nil
67
67
  def GlobalHolder.held
@@ -71,7 +71,7 @@ module EmPipelines
71
71
  def initialize
72
72
  @@value = nil
73
73
  end
74
-
74
+
75
75
  def call(input, &next_step)
76
76
  @@value = input
77
77
  next_step.call(input)
@@ -79,17 +79,17 @@ module EmPipelines
79
79
  end
80
80
 
81
81
  class StubSpawner
82
-
82
+
83
83
  class StubProcess
84
84
  def initialize(block)
85
85
  @block = block
86
86
  end
87
-
87
+
88
88
  def notify(input)
89
89
  @block.call(input)
90
90
  end
91
91
  end
92
-
92
+
93
93
  def spawn(&block)
94
94
  StubProcess.new(block)
95
95
  end
@@ -131,10 +131,14 @@ module EmPipelines
131
131
  GlobalHolder.held[:orange].should ==(:some_orange)
132
132
  end
133
133
 
134
- it 'sends exception to the proper handler' do
134
+ it 'marks message as broken if uncaught exception' do
135
+ a_msg = msg({})
135
136
  monitoring.should_receive(:inform_exception!)
137
+
138
+ a_msg.should_receive(:broken!)
139
+
136
140
  pipeline = Pipeline.new(StubSpawner.new, {}, monitoring)
137
- pipeline.for([BrokenStage]).notify(msg({}))
141
+ pipeline.for([BrokenStage]).notify(a_msg)
138
142
  end
139
143
 
140
144
  it 'flags the message as consumed if goest through all stages' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: empipelines
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-01-10 00:00:00.000000000Z
13
+ date: 2012-01-13 00:00:00.000000000Z
14
14
  dependencies: []
15
15
  description: Simple Event Handling Pipeline Architecture for EventMachine
16
16
  email: pcalcado+empipelines@gmail.com
@@ -23,7 +23,6 @@ files:
23
23
  - Gemfile.lock
24
24
  - README.md
25
25
  - Rakefile
26
- - empipelines-0.2.4.gem
27
26
  - empipelines.gemspec
28
27
  - functional/consuming_events_from_amqp_spec.rb
29
28
  - functional/consuming_events_from_batch_spec.rb
@@ -69,7 +68,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
69
68
  version: '0'
70
69
  segments:
71
70
  - 0
72
- hash: 4556679468386328917
71
+ hash: 3829933959180740476
73
72
  required_rubygems_version: !ruby/object:Gem::Requirement
74
73
  none: false
75
74
  requirements: