empipelines 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
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: