who_can 0.3.5 → 0.4.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,2 +1,11 @@
1
1
  require 'bundler'
2
2
  Bundler::GemHelper.install_tasks
3
+
4
+ task :environment do
5
+ Bundler.require(:default, :development)
6
+ end
7
+
8
+ task :draw do
9
+ ruby %Q[-r 'bundler/setup' -r who_can -e 'WhoCan::ConnectionManager.state_machines[:state].draw']
10
+ end
11
+
data/lib/who_can/base.rb CHANGED
@@ -25,7 +25,10 @@ module WhoCan
25
25
  alias :connect! :connection
26
26
 
27
27
  def on_open(&block)
28
- connection.on_open(&block)
28
+ Deferred::Default.new.tap do |my_dfr|
29
+ my_dfr.callback(&block) if block
30
+ connection.on_open { |*a| my_dfr.succeed(*a) }
31
+ end
29
32
  end
30
33
 
31
34
  def start_monitoring!(opts = {})
@@ -37,6 +40,10 @@ module WhoCan
37
40
  @ekg.start!
38
41
  end
39
42
 
43
+ def monitoring?
44
+ false|@ekg.running?
45
+ end
46
+
40
47
  def new_pinger
41
48
  WhoCan::Pinger.new(connection)
42
49
  end
@@ -67,6 +74,14 @@ module WhoCan
67
74
  end
68
75
  false
69
76
  end
77
+
78
+ def to_async
79
+ self
80
+ end
81
+
82
+ def to_synchrony
83
+ WhoCan::Synchrony::Base.new(self)
84
+ end
70
85
  end
71
86
  end
72
87
 
@@ -95,23 +95,23 @@ module WhoCan
95
95
  # the primary has connected within the grace period, enter the connected state
96
96
  after_transition :awaiting_primary => :connected, :do => :become_usable!
97
97
 
98
- #make sure we are up as soon as possible - don't wait for the secondary
98
+ # make sure we are up as soon as possible - don't wait for the secondary
99
99
  after_transition :start_up => :degraded, :do => :become_usable!
100
100
 
101
- #primary has returned
101
+ # primary has returned
102
102
  after_transition :failed_over => :connected, :do => [:fire_failure_deferred, :become_usable!]
103
103
 
104
104
  # the primary fails, secondary is available
105
105
  after_transition :connected => :failed_over, :do => [:fire_failure_deferred, :enter_failed_over_state, :do_connect_primary]
106
106
 
107
- #the two states we can be in when the secondary is going down
107
+ # the two states we can be in when the secondary is going down
108
108
  after_transition :failed_over => :down, :do => [:fire_failure_deferred, :do_connect_secondary]
109
109
  after_transition :connected => :degraded, :do => :do_connect_secondary
110
110
 
111
- #primary went down, and secondary was already down
111
+ # primary went down, and secondary was already down
112
112
  after_transition :degraded => :down, :do => [:fire_failure_deferred, :do_connect_primary, :do_connect_secondary]
113
113
 
114
- #we've been down, now we're coming back up
114
+ # we've been down, now we're coming back up
115
115
  after_transition :down => :failed_over, :do => :enter_failed_over_state
116
116
  after_transition :down => :degraded, :do => :become_usable!
117
117
 
@@ -208,6 +208,14 @@ module WhoCan
208
208
 
209
209
  alias :useable? :usable?
210
210
 
211
+ def to_async
212
+ self
213
+ end
214
+
215
+ def to_synchrony
216
+ Synchrony::ConnectionManager.new(self)
217
+ end
218
+
211
219
  protected
212
220
  def do_connect_primary
213
221
  @primary.on_failure { primary_failed }
@@ -11,6 +11,8 @@ module WhoCan
11
11
  include Logging
12
12
  include Deferred
13
13
 
14
+ CHANNEL_SHUTDOWN_TIMEOUT = 0.1
15
+
14
16
  attr_reader :connection, :queue, :interval, :max_failures, :failure_count
15
17
 
16
18
  # fired when the amqp channel has opened and we've started sending beats
@@ -46,6 +48,10 @@ module WhoCan
46
48
  @outstanding_beats = {}
47
49
  end
48
50
 
51
+ def running?
52
+ false|@running
53
+ end
54
+
49
55
  def channel
50
56
  @channel ||= AMQP::Channel.new(connection)
51
57
  end
@@ -84,22 +90,20 @@ module WhoCan
84
90
  return on_shutdown unless @running
85
91
  @running = false
86
92
 
87
- logger.debug { "performing shutdown!" }
88
-
89
93
  @queue.unsubscribe if @queue
90
94
 
91
95
  @timer.cancel if @timer
92
96
 
93
- close_failsafe_timer = EM::Timer.new(1.0) do
94
- logger.warn { "channel.close never called its block, probably hung. calling on_shutdown.succeed" }
97
+ timeout = EM::Timer.new(CHANNEL_SHUTDOWN_TIMEOUT) do
98
+ logger.debug { "channel shutdown has timed out" }
95
99
  on_shutdown.succeed
96
100
  end
97
101
 
98
- @channel.close {
99
- close_failsafe_timer.cancel
100
- logger.debug { "channel has closed!" }
102
+ @channel.close do
103
+ logger.debug { "channel has closed, cancelling timeout" }
104
+ timeout.cancel
101
105
  on_shutdown.succeed
102
- }
106
+ end
103
107
 
104
108
  on_shutdown
105
109
  rescue AMQ::Client::ConnectionClosedError
@@ -15,8 +15,12 @@ module WhoCan
15
15
  @connection = connection
16
16
  @needs_reply = {}
17
17
  @queue = nil
18
+ @started = false
18
19
  end
19
20
 
21
+ def started?
22
+ false|@started
23
+ end
20
24
 
21
25
  def start!(&blk)
22
26
  EM.schedule do
@@ -34,6 +38,7 @@ module WhoCan
34
38
  @queue = q
35
39
  block_callback = lambda do |*args|
36
40
  logger.debug {"calling back to with_queue"}
41
+ @started = true
37
42
  on_start.succeed
38
43
  end
39
44
 
@@ -51,18 +56,23 @@ module WhoCan
51
56
  def ping!(exchange, timeout=5, &callback)
52
57
  logger.debug { "sending a ping to #{exchange}" }
53
58
  deferred = Deferred::Default.new
59
+
54
60
  deferred.callback(&callback)
55
- deferred.timeout(timeout)
61
+ deferred.timeout(timeout, WhoCan::TimeoutError)
62
+
56
63
  raise "Channel not opened" unless channel.open?
57
- msg_id = self.class.create_msg_id
58
- @needs_reply[msg_id] = deferred
59
-
60
- deferred.errback_on_exception do
61
- logger.debug {"sending the ping to #{exchange} with a reply to of #{@queue.name}"}
62
- # XXX: the block given to publish here appears to be a bug w/ amqp gem, without it an exception is raised
63
- ping_exchange = channel.fanout(exchange)
64
- ping_exchange.publish('PING', :reply_to => @queue.name, :message_id => msg_id) { logger.debug { "actually sent the ping to #{exchange}" } }
65
- end
64
+
65
+ msg_id = self.class.create_msg_id
66
+ @needs_reply[msg_id] = deferred
67
+
68
+ deferred.errback_on_exception do
69
+ logger.debug {"sending the ping to #{exchange} with a reply to of #{@queue.name}"}
70
+ # XXX: the block given to publish here appears to be a bug w/ amqp gem, without it an exception is raised
71
+
72
+ ping_exchange = channel.fanout(exchange)
73
+ ping_exchange.publish('PING', :reply_to => @queue.name, :message_id => msg_id) { logger.debug { "actually sent the ping to #{exchange}" } }
74
+ end
75
+
66
76
  deferred
67
77
  rescue Exception => e
68
78
  logger.error {"received an exception on ping!: #{e.to_std_format}"}
@@ -70,13 +80,22 @@ module WhoCan
70
80
  deferred
71
81
  end
72
82
 
73
- def handle_response(header, payload)
74
- logger.debug {"handling a response (#{header.message_id}) with a payload of: #{payload}"}
75
- if deferred = @needs_reply.delete(header.message_id)
76
- EM.schedule do
77
- deferred.succeed(header, payload)
83
+ def to_synchrony
84
+ WhoCan::Synchrony::Pinger.new(self)
85
+ end
86
+
87
+ def to_async
88
+ self
89
+ end
90
+
91
+ private
92
+ def handle_response(header, payload)
93
+ logger.debug {"handling a response (#{header.message_id}) with a payload of: #{payload}"}
94
+ if deferred = @needs_reply.delete(header.message_id)
95
+ EM.schedule do
96
+ deferred.succeed(header, payload)
97
+ end
78
98
  end
79
99
  end
80
- end
81
100
  end
82
101
  end
@@ -41,6 +41,7 @@ module WhoCan
41
41
  @ping_exchange_name = ping_exchange_name
42
42
  @on_ping_cb = nil
43
43
  @set_up = false
44
+ @started = false
44
45
  end
45
46
 
46
47
  # wc = WhoCan.new(connection_opts)
@@ -77,10 +78,11 @@ module WhoCan
77
78
 
78
79
  unless @close_requested
79
80
  @queue.bind(@ping_exchange) do
80
- #logger.debug {"subscribing to the #{@ping_exchange_name}"}
81
+ logger.debug {"subscribing to the #{@ping_exchange_name}"}
81
82
 
82
83
  confirm_queue = lambda do |*a|
83
- #logger.debug { "queue subscribed to #{@ping_exchange_name}" }
84
+ logger.debug { "queue subscribed to #{@ping_exchange_name}" }
85
+ @started = true
84
86
  on_start.succeed
85
87
  end
86
88
 
@@ -91,6 +93,10 @@ module WhoCan
91
93
  on_start
92
94
  end
93
95
 
96
+ def started?
97
+ false|@started
98
+ end
99
+
94
100
  def handle_message(header, payload)
95
101
  if @on_ping_cb
96
102
  resp = Response.new(header, payload)
@@ -116,6 +122,7 @@ module WhoCan
116
122
 
117
123
  @queue.unsubscribe if @queue
118
124
  @queue = nil
125
+ @started = false
119
126
  @set_up = false
120
127
  end
121
128
 
@@ -124,6 +131,14 @@ module WhoCan
124
131
  def on_ping(&block)
125
132
  @on_ping_cb = block
126
133
  end
134
+
135
+ def to_synchrony
136
+ WhoCan::Synchrony::Responder.new(self)
137
+ end
138
+
139
+ def to_async
140
+ self
141
+ end
127
142
  end
128
143
  end
129
144
 
@@ -0,0 +1,57 @@
1
+ module WhoCan
2
+ module Synchrony
3
+ class Base
4
+ include Logging
5
+
6
+ def initialize(connection_opts=nil)
7
+ case connection_opts
8
+ when WhoCan::Base
9
+ @base = connection_opts
10
+ else
11
+ @base = WhoCan::Base.new(connection_opts)
12
+ end
13
+ end
14
+
15
+ def connect!
16
+ @base.connect!
17
+ wait_for_open
18
+ end
19
+
20
+ # this can be used instead of on_open to wait for the connection
21
+ # to be in an opened state before proceeding
22
+ def wait_for_open
23
+ Synchrony.sync!(@base.on_open)
24
+ end
25
+
26
+ def start_monitoring!(opts={})
27
+ Synchrony.sync!(@base.start_monitoring!(opts))
28
+ end
29
+
30
+ def close!
31
+ Synchrony.sync!(@base.close!)
32
+ end
33
+
34
+ def new_pinger
35
+ WhoCan::Synchrony::Pinger.new(@base.connection)
36
+ end
37
+
38
+ def new_responder(exchange_name)
39
+ WhoCan::Synchrony::Responder.new(@base.connection, exchange_name)
40
+ end
41
+
42
+ def to_async
43
+ @base
44
+ end
45
+
46
+ def to_synchrony
47
+ self
48
+ end
49
+
50
+ private
51
+ def method_missing(m, *a, &b)
52
+ @base.__send__(m, *a, &b)
53
+ end
54
+ end
55
+ end
56
+ end
57
+
@@ -0,0 +1,52 @@
1
+ module WhoCan
2
+ module Synchrony
3
+ class ConnectionManager
4
+ include Logging
5
+
6
+ def initialize(*args)
7
+ if (2..3).include?(args.length)
8
+ @cm = WhoCan::ConnectionManager.new(*args)
9
+ elsif args.length == 1
10
+ @cm = args.first
11
+ else
12
+ raise ArgumentError, "Don't know how to deal with args: #{args.inspect}"
13
+ end
14
+ end
15
+
16
+ def start(*a)
17
+ @cm.start(*a)
18
+ Synchrony.sync!(@cm.on_open)
19
+ end
20
+
21
+ def start!(*a)
22
+ @cm.start!(*a)
23
+ Synchony.sync!(@cm.on_open)
24
+ end
25
+
26
+ def close(*a)
27
+ @cm.close(*a)
28
+ Synchrony.sync!(@cm.on_close)
29
+ end
30
+
31
+ def close!(*a)
32
+ @cm.close!(*a)
33
+ Synchrony.sync!(@cm.on_close)
34
+ end
35
+
36
+ def to_synchrony
37
+ self
38
+ end
39
+
40
+ # returns async ConnectionManager instance
41
+ def to_async
42
+ @cm
43
+ end
44
+
45
+ private
46
+ def method_missing(m, *a, &b)
47
+ @cm.__send__(m, *a, &b)
48
+ end
49
+ end
50
+ end
51
+ end
52
+
@@ -0,0 +1,37 @@
1
+ module WhoCan
2
+ module Synchrony
3
+ class Pinger
4
+ def initialize(connection)
5
+ case connection
6
+ when WhoCan::Pinger
7
+ @pinger = connection
8
+ else
9
+ @pinger = WhoCan::Pinger.new(connection)
10
+ end
11
+ end
12
+
13
+ def start!
14
+ Synchrony.sync!(@pinger.start!)
15
+ end
16
+
17
+ # returns the header, paylod
18
+ def ping!(*a, &b)
19
+ Synchrony.sync!(@pinger.ping!(*a, &b))
20
+ end
21
+
22
+ def to_async
23
+ @pinger
24
+ end
25
+
26
+ def to_synchrony
27
+ self
28
+ end
29
+
30
+ private
31
+ def method_missing(m, *a, &b)
32
+ @pinger.__send__(m, *a, &b)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
@@ -0,0 +1,36 @@
1
+ module WhoCan
2
+ module Synchrony
3
+ class Responder
4
+ include Logging
5
+
6
+ def initialize(*args)
7
+ case args.length
8
+ when 2
9
+ @responder = WhoCan::Responder.new(*args)
10
+ when 1
11
+ @responder = args.first
12
+ else
13
+ raise ArgumentError, "bad args: #{args.inspect}"
14
+ end
15
+ end
16
+
17
+ def setup!
18
+ Synchrony.sync!(@responder.setup!)
19
+ end
20
+
21
+ def to_synchrony
22
+ self
23
+ end
24
+
25
+ def to_async
26
+ @responder
27
+ end
28
+
29
+ private
30
+ def method_missing(m, *a, &b)
31
+ @responder.__send__(m, *a, &b)
32
+ end
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,54 @@
1
+ module WhoCan
2
+ module Synchrony
3
+ extend Logging
4
+
5
+ def self.new(*a, &b)
6
+ WhoCan::Synchrony::Base.new(*a, &b)
7
+ end
8
+
9
+ module SyncMethods
10
+ # @private
11
+ # a modification of EM::Synchrony.sync to handle multiple callback arguments properly
12
+ def sync(df)
13
+ f = Fiber.current
14
+
15
+ xback = proc do |*args|
16
+ if f == Fiber.current
17
+ return *args
18
+ else
19
+ f.resume(*args)
20
+ end
21
+ end
22
+
23
+ df.callback(&xback)
24
+ df.errback(&xback)
25
+
26
+ Fiber.yield
27
+ end
28
+
29
+ # @private
30
+ # like sync, but if the deferred returns an exception instance, re-raises
31
+ def sync!(df)
32
+ rval = sync(df)
33
+
34
+ # XXX: arrgh, this is really gross...
35
+
36
+ if rval.kind_of?(Exception)
37
+ raise rval
38
+ elsif rval.respond_to?(:first) and rval.first.kind_of?(Exception)
39
+ raise rval.first
40
+ else
41
+ return rval
42
+ end
43
+ end
44
+ end
45
+
46
+ extend SyncMethods
47
+ end
48
+ end
49
+
50
+ require 'who_can/synchrony/connection_manager'
51
+ require 'who_can/synchrony/pinger'
52
+ require 'who_can/synchrony/responder'
53
+ require 'who_can/synchrony/base'
54
+
@@ -1,3 +1,3 @@
1
1
  module WhoCan
2
- VERSION = "0.3.5"
2
+ VERSION = "0.4.0.beta.1"
3
3
  end
data/lib/who_can.rb CHANGED
@@ -2,6 +2,7 @@ require 'rubygems'
2
2
  require 'amqp'
3
3
  require 'state_machine'
4
4
  require 'uuidtools'
5
+ require 'em-synchrony'
5
6
  require 'thread'
6
7
  require 'monitor'
7
8
  require 'logger'
@@ -60,5 +61,6 @@ require 'who_can/responder'
60
61
  require 'who_can/heartbeater'
61
62
  require 'who_can/connection_wrapper'
62
63
  require 'who_can/connection_manager'
64
+ require 'who_can/synchrony'
63
65
 
64
66
 
@@ -0,0 +1,11 @@
1
+
2
+ class ::Fiber
3
+ if method_defined?(:wrap)
4
+ raise "WTF?! Fiber.wrap exists?"
5
+ else
6
+ def self.wrap(&blk)
7
+ new { blk.call }.resume
8
+ end
9
+ end
10
+ end
11
+
@@ -2,7 +2,6 @@ require 'spec_helper'
2
2
 
3
3
 
4
4
  module WhoCan
5
-
6
5
  describe "Base" do
7
6
  describe "using an existing connection" do
8
7
  include EventedSpec::AMQPSpec
@@ -55,12 +54,6 @@ module WhoCan
55
54
  heartbeat_failure_block.call
56
55
  end
57
56
  end
58
-
59
-
60
57
  end
61
-
62
-
63
-
64
58
  end
65
-
66
59
  end
@@ -4,11 +4,11 @@ module WhoCan::Heartbeater
4
4
 
5
5
  describe "EKG" do
6
6
  include EventedSpec::AMQPSpec
7
- include WhoCan::Logging
8
-
9
7
  default_timeout 3.0
10
8
  default_options :host => 'localhost'
11
9
 
10
+ let(:logger) { WhoCan.logger }
11
+
12
12
  amqp_before do
13
13
  @ekg = EKG.new(AMQP.connection, :interval => 0.2, :max_failures => 1, :timeout => 0.5)
14
14
  end
@@ -26,10 +26,10 @@ module WhoCan::Heartbeater
26
26
 
27
27
  it "should fire failure when there is a tcp connection loss" do
28
28
  @ekg.on_heartbeat_failure do
29
- logger.debug {"heartbeat failure received"}
29
+ # logger.debug {"heartbeat failure received"}
30
30
 
31
31
  @ekg.shutdown! do
32
- logger.debug { "on shutdown fired" }
32
+ # logger.debug { "on shutdown fired" }
33
33
  done
34
34
  end
35
35
  end
@@ -1,8 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
-
4
3
  module WhoCan
5
-
6
4
  describe "Responder" do
7
5
  include EventedSpec::AMQPSpec
8
6
  default_timeout 3.0
@@ -14,7 +12,6 @@ module WhoCan
14
12
  @responder = @who_can.new_responder(@exchange_name)
15
13
  end
16
14
 
17
-
18
15
  it "should respond to the pings" do
19
16
  @responder.on_ping do |response|
20
17
  response.delay = 0.1
@@ -37,12 +34,6 @@ module WhoCan
37
34
  end
38
35
  end
39
36
  end
40
-
41
-
42
- end
43
-
44
-
37
+ end # it should respond to the pings
45
38
  end
46
-
47
-
48
39
  end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ module WhoCan
4
+ describe 'Base' do
5
+ describe 'using an existing connection' do
6
+ include EventedSpec::AMQPSpec
7
+ include Logging
8
+
9
+ default_timeout 3.0
10
+ default_options :host => 'localhost'
11
+
12
+ amqp_before do
13
+ @who_can = WhoCan::Synchrony.new(AMQP.connection)
14
+ @who_can.on_disconnection do
15
+ logger.debug { "on_disconnection called, calling done" }
16
+ done
17
+ end
18
+ end
19
+
20
+ describe 'start_monitoring!' do
21
+ it %[should be monitoring after start_monitoring! returns] do
22
+ Fiber.wrap do
23
+ @who_can.start_monitoring!
24
+ logger.debug { "start_monitoring! returned" }
25
+
26
+ @who_can.should be_monitoring
27
+ logger.debug { "we are monitoring, now closing" }
28
+
29
+ @who_can.close!
30
+ logger.debug { "close returned" }
31
+ end
32
+ end
33
+ end
34
+
35
+ it %[should fire the on_disconnection when a tcp failure occurs] do
36
+ Fiber.wrap do
37
+ @who_can.start_monitoring!
38
+ AMQP.connection.tcp_connection_lost
39
+ end
40
+ end
41
+
42
+ it %[should should fire disconnection on close!] do
43
+ Fiber.wrap do
44
+ @who_can.wait_for_open
45
+ @who_can.close!
46
+ end
47
+ end
48
+
49
+ it %[should fire disconnection on heartbeat_failure] do
50
+ bogus_start_dfr = Deferred::Default.new.tap { |d| d.succeed }
51
+
52
+ heartbeat_failure_block = nil
53
+ fake_ekg = flexmock('ekg') do |m|
54
+ m.should_receive(:on_heartbeat_failure).with(Proc).and_return do |blk|
55
+ heartbeat_failure_block = blk
56
+ end
57
+ m.should_receive(:start!).and_return(bogus_start_dfr)
58
+ end
59
+
60
+ flexmock(Heartbeater::EKG).should_receive(:new).and_return(fake_ekg)
61
+
62
+ Fiber.wrap do
63
+ @who_can.start_monitoring!
64
+ @who_can.wait_for_open
65
+
66
+ heartbeat_failure_block.should_not be_nil
67
+ heartbeat_failure_block.call
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ module WhoCan
4
+ describe 'ConnectionManager' do
5
+ include EventedSpec::EMSpec
6
+ include Logging
7
+
8
+ default_timeout 3.0
9
+
10
+ em_before do
11
+ @connection1 = ConnectionWrapper.new("amqp://localhost")
12
+ @connection2 = ConnectionWrapper.new("amqp://localhost")
13
+ @connection_manager = WhoCan::Synchrony::ConnectionManager.new(@connection1, @connection2)
14
+ end
15
+
16
+ def clean_and_done
17
+ # logger.debug {"calling test done on next tick"}
18
+ EM.next_tick do
19
+ Fiber.wrap do
20
+ # logger.debug {"test done, closing"}
21
+ @connection_manager.close
22
+ done
23
+ end
24
+ end
25
+ end
26
+
27
+ def neuter_connection(connection)
28
+ flexmock(connection).should_receive(:connect).and_return true
29
+ end
30
+
31
+ describe "under totally normal circumstances" do
32
+ it "should enter a usable state after calling start" do
33
+ Fiber.wrap do
34
+ @connection_manager.start
35
+ @connection_manager.should be_usable
36
+ clean_and_done
37
+ end
38
+ end
39
+
40
+ it "should enter the degraded state if #2 never starts" do
41
+ neuter_connection(@connection2)
42
+
43
+ Fiber.wrap do
44
+ @connection_manager.start
45
+ @connection_manager.should be_degraded
46
+ clean_and_done
47
+ end
48
+ end
49
+
50
+ it "should be useable after grace period if only secondary starts up" do
51
+ neuter_connection(@connection1)
52
+
53
+ @connection_manager.primary_grace_time = 0.1
54
+
55
+ Fiber.wrap do
56
+ @connection_manager.start
57
+ @connection_manager.should be_failed_over
58
+ @connection_manager.should be_usable
59
+ clean_and_done
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ module WhoCan
4
+ class TestingError < StandardError; end
5
+
6
+ describe "Pinger" do
7
+ include EventedSpec::AMQPSpec
8
+ include Logging
9
+
10
+ default_timeout 3.0
11
+ default_options :host => 'localhost'
12
+
13
+ amqp_before do
14
+ @who_can = WhoCan::Synchrony.new(AMQP.connection) #default opts
15
+ @pinger = @who_can.new_pinger
16
+ end
17
+
18
+ it %[should call back with the responders info] do
19
+ exchange_name = "test_exchange"
20
+
21
+ call_pinger = lambda do
22
+ Fiber.wrap do
23
+ @pinger.start!
24
+ @pinger.should be_started
25
+
26
+ header, payload = @pinger.ping!(exchange_name)
27
+
28
+ logger.debug { "received response, payload: #{payload.inspect}" }
29
+ payload.should == "HAI"
30
+ done
31
+ end
32
+ end
33
+
34
+ AMQP::Channel.new do |channel|
35
+ channel.fanout(exchange_name) do |exchange|
36
+ channel.queue('', :exclusive => true, :auto_delete => true) do |queue|
37
+ queue.bind(exchange) do
38
+ queue.subscribe do |header, payload|
39
+ channel.default_exchange.publish("HAI", :routing_key => header.reply_to, :message_id => header.message_id) {}
40
+ end
41
+ call_pinger.call
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ it "should call the errback when there is a timeout" do
49
+ Fiber.wrap do
50
+ @pinger.start!
51
+ @pinger.should be_started
52
+
53
+ lambda { @pinger.ping!("exchangename", 0.01) }.should raise_error(WhoCan::TimeoutError)
54
+
55
+ done
56
+ end
57
+ end
58
+
59
+ it "should call the errback when there is an exception in queueing" do
60
+ exchange_name = 'exchangename'
61
+
62
+ Fiber.wrap do
63
+ @pinger.start!
64
+ @pinger.should be_started
65
+
66
+ flexmock(@pinger.channel).should_receive(:fanout).with(exchange_name).at_least.once.and_raise(TestingError)
67
+
68
+ lambda do
69
+ rval = @pinger.ping!(exchange_name, 0.1)
70
+
71
+ raise "Should not have returned #{rval.inspect}"
72
+
73
+ end.should raise_error(TestingError)
74
+
75
+ done
76
+ end
77
+ end
78
+ end
79
+ end
80
+
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ module WhoCan
4
+ describe "Responder" do
5
+ include EventedSpec::AMQPSpec
6
+ include Logging
7
+ default_timeout 3.0
8
+ default_options :host => 'localhost'
9
+
10
+ amqp_before do
11
+ @who_can = WhoCan::Synchrony.new(AMQP.connection) #default opts
12
+ @exchange_name = "test_exchange"
13
+ @responder = @who_can.new_responder(@exchange_name)
14
+ end
15
+
16
+ it %[should respond to the pings] do
17
+ @responder.on_ping do |response|
18
+ response.delay = 0.1
19
+ response.response = "OHHAI"
20
+ end
21
+
22
+ Fiber.wrap do
23
+ @responder.setup!
24
+ logger.debug { "@responder.setup! returned" }
25
+ @responder.should be_started
26
+
27
+ AMQP::Channel.new do |channel|
28
+ channel.queue('', :exclusive => true, :auto_delete => true) do |queue|
29
+ queue.subscribe do |header, payload|
30
+ payload.should == "OHHAI"
31
+ done
32
+ end
33
+
34
+ channel.fanout(@exchange_name) do |exchange|
35
+ exchange.publish("PING", :reply_to => queue.name, :message_id => "MYMESSAGEID") {}
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
data/who_can.gemspec CHANGED
@@ -13,16 +13,17 @@ Gem::Specification.new do |s|
13
13
  s.description = s.summary
14
14
 
15
15
  s.add_dependency('eventmachine', '~> 1.0.0.beta.3')
16
+ s.add_dependency('em-synchrony', '~> 1.0.0')
16
17
  s.add_dependency('amqp', '~> 0.8.0.rc13')
17
18
  s.add_dependency('uuidtools', '~> 2.1.2')
18
19
  s.add_dependency('deferred', '~> 0.5.0')
19
20
  s.add_dependency('state_machine', '~> 1.0.1')
20
- s.add_dependency('activesupport', '~> 3.0')
21
+ s.add_dependency('activesupport', '~> 3.0.7')
21
22
 
22
- s.add_development_dependency('ZenTest', '>= 4.5.0')
23
- s.add_development_dependency('rspec', '~> 2.5.0')
24
- s.add_development_dependency('evented-spec', '~> 0.4.1')
25
- s.add_development_dependency('flexmock', '~> 0.8.11')
23
+ s.add_development_dependency('ZenTest', '>= 4.5.0')
24
+ s.add_development_dependency('rspec', '~> 2.5.0')
25
+ s.add_development_dependency('evented-spec', '~> 0.4.1')
26
+ s.add_development_dependency('flexmock', '~> 0.8.11')
26
27
  s.add_development_dependency('ruby-graphviz', '~> 0.9.0')
27
28
 
28
29
  s.files = `git ls-files`.split("\n")
metadata CHANGED
@@ -1,13 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: who_can
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
5
- prerelease:
4
+ hash: 62196449
5
+ prerelease: 6
6
6
  segments:
7
7
  - 0
8
- - 3
9
- - 5
10
- version: 0.3.5
8
+ - 4
9
+ - 0
10
+ - beta
11
+ - 1
12
+ version: 0.4.0.beta.1
11
13
  platform: ruby
12
14
  authors:
13
15
  - Jonathan D. Simms
@@ -16,7 +18,7 @@ autorequire:
16
18
  bindir: bin
17
19
  cert_chain: []
18
20
 
19
- date: 2012-03-21 00:00:00 Z
21
+ date: 2012-01-24 00:00:00 Z
20
22
  dependencies:
21
23
  - !ruby/object:Gem::Dependency
22
24
  name: eventmachine
@@ -37,9 +39,25 @@ dependencies:
37
39
  type: :runtime
38
40
  version_requirements: *id001
39
41
  - !ruby/object:Gem::Dependency
40
- name: amqp
42
+ name: em-synchrony
41
43
  prerelease: false
42
44
  requirement: &id002 !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ~>
48
+ - !ruby/object:Gem::Version
49
+ hash: 23
50
+ segments:
51
+ - 1
52
+ - 0
53
+ - 0
54
+ version: 1.0.0
55
+ type: :runtime
56
+ version_requirements: *id002
57
+ - !ruby/object:Gem::Dependency
58
+ name: amqp
59
+ prerelease: false
60
+ requirement: &id003 !ruby/object:Gem::Requirement
43
61
  none: false
44
62
  requirements:
45
63
  - - ~>
@@ -53,11 +71,11 @@ dependencies:
53
71
  - 13
54
72
  version: 0.8.0.rc13
55
73
  type: :runtime
56
- version_requirements: *id002
74
+ version_requirements: *id003
57
75
  - !ruby/object:Gem::Dependency
58
76
  name: uuidtools
59
77
  prerelease: false
60
- requirement: &id003 !ruby/object:Gem::Requirement
78
+ requirement: &id004 !ruby/object:Gem::Requirement
61
79
  none: false
62
80
  requirements:
63
81
  - - ~>
@@ -69,11 +87,11 @@ dependencies:
69
87
  - 2
70
88
  version: 2.1.2
71
89
  type: :runtime
72
- version_requirements: *id003
90
+ version_requirements: *id004
73
91
  - !ruby/object:Gem::Dependency
74
92
  name: deferred
75
93
  prerelease: false
76
- requirement: &id004 !ruby/object:Gem::Requirement
94
+ requirement: &id005 !ruby/object:Gem::Requirement
77
95
  none: false
78
96
  requirements:
79
97
  - - ~>
@@ -85,11 +103,11 @@ dependencies:
85
103
  - 0
86
104
  version: 0.5.0
87
105
  type: :runtime
88
- version_requirements: *id004
106
+ version_requirements: *id005
89
107
  - !ruby/object:Gem::Dependency
90
108
  name: state_machine
91
109
  prerelease: false
92
- requirement: &id005 !ruby/object:Gem::Requirement
110
+ requirement: &id006 !ruby/object:Gem::Requirement
93
111
  none: false
94
112
  requirements:
95
113
  - - ~>
@@ -101,26 +119,27 @@ dependencies:
101
119
  - 1
102
120
  version: 1.0.1
103
121
  type: :runtime
104
- version_requirements: *id005
122
+ version_requirements: *id006
105
123
  - !ruby/object:Gem::Dependency
106
124
  name: activesupport
107
125
  prerelease: false
108
- requirement: &id006 !ruby/object:Gem::Requirement
126
+ requirement: &id007 !ruby/object:Gem::Requirement
109
127
  none: false
110
128
  requirements:
111
129
  - - ~>
112
130
  - !ruby/object:Gem::Version
113
- hash: 7
131
+ hash: 9
114
132
  segments:
115
133
  - 3
116
134
  - 0
117
- version: "3.0"
135
+ - 7
136
+ version: 3.0.7
118
137
  type: :runtime
119
- version_requirements: *id006
138
+ version_requirements: *id007
120
139
  - !ruby/object:Gem::Dependency
121
140
  name: ZenTest
122
141
  prerelease: false
123
- requirement: &id007 !ruby/object:Gem::Requirement
142
+ requirement: &id008 !ruby/object:Gem::Requirement
124
143
  none: false
125
144
  requirements:
126
145
  - - ">="
@@ -132,11 +151,11 @@ dependencies:
132
151
  - 0
133
152
  version: 4.5.0
134
153
  type: :development
135
- version_requirements: *id007
154
+ version_requirements: *id008
136
155
  - !ruby/object:Gem::Dependency
137
156
  name: rspec
138
157
  prerelease: false
139
- requirement: &id008 !ruby/object:Gem::Requirement
158
+ requirement: &id009 !ruby/object:Gem::Requirement
140
159
  none: false
141
160
  requirements:
142
161
  - - ~>
@@ -148,11 +167,11 @@ dependencies:
148
167
  - 0
149
168
  version: 2.5.0
150
169
  type: :development
151
- version_requirements: *id008
170
+ version_requirements: *id009
152
171
  - !ruby/object:Gem::Dependency
153
172
  name: evented-spec
154
173
  prerelease: false
155
- requirement: &id009 !ruby/object:Gem::Requirement
174
+ requirement: &id010 !ruby/object:Gem::Requirement
156
175
  none: false
157
176
  requirements:
158
177
  - - ~>
@@ -164,11 +183,11 @@ dependencies:
164
183
  - 1
165
184
  version: 0.4.1
166
185
  type: :development
167
- version_requirements: *id009
186
+ version_requirements: *id010
168
187
  - !ruby/object:Gem::Dependency
169
188
  name: flexmock
170
189
  prerelease: false
171
- requirement: &id010 !ruby/object:Gem::Requirement
190
+ requirement: &id011 !ruby/object:Gem::Requirement
172
191
  none: false
173
192
  requirements:
174
193
  - - ~>
@@ -180,11 +199,11 @@ dependencies:
180
199
  - 11
181
200
  version: 0.8.11
182
201
  type: :development
183
- version_requirements: *id010
202
+ version_requirements: *id011
184
203
  - !ruby/object:Gem::Dependency
185
204
  name: ruby-graphviz
186
205
  prerelease: false
187
- requirement: &id011 !ruby/object:Gem::Requirement
206
+ requirement: &id012 !ruby/object:Gem::Requirement
188
207
  none: false
189
208
  requirements:
190
209
  - - ~>
@@ -196,7 +215,7 @@ dependencies:
196
215
  - 0
197
216
  version: 0.9.0
198
217
  type: :development
199
- version_requirements: *id011
218
+ version_requirements: *id012
200
219
  description: An AMQP-based scatter-gather job queuing protocol library
201
220
  email:
202
221
  - simms@hp.com
@@ -223,12 +242,18 @@ files:
223
242
  - lib/who_can/logging.rb
224
243
  - lib/who_can/pinger.rb
225
244
  - lib/who_can/responder.rb
245
+ - lib/who_can/synchrony.rb
246
+ - lib/who_can/synchrony/base.rb
247
+ - lib/who_can/synchrony/connection_manager.rb
248
+ - lib/who_can/synchrony/pinger.rb
249
+ - lib/who_can/synchrony/responder.rb
226
250
  - lib/who_can/version.rb
227
251
  - poc/heartbeater.rb
228
252
  - poc/submitter.rb
229
253
  - poc/who_can_with_hearbeat.rb
230
254
  - scripts/cross_cluster_ping.rb
231
255
  - spec/spec_helper.rb
256
+ - spec/support/em_synchrony_spec.rb
232
257
  - spec/support/evented_spec_extensions.rb
233
258
  - spec/support/logging.rb
234
259
  - spec/support/logging_progress_bar_formatter.rb
@@ -240,6 +265,10 @@ files:
240
265
  - spec/who_can/pinger_responder_integration_spec.rb
241
266
  - spec/who_can/pinger_spec.rb
242
267
  - spec/who_can/responder_spec.rb
268
+ - spec/who_can/synchrony/base_spec.rb
269
+ - spec/who_can/synchrony/connection_manager_spec.rb
270
+ - spec/who_can/synchrony/pinger_spec.rb
271
+ - spec/who_can/synchrony/responder_spec.rb
243
272
  - who_can.gemspec
244
273
  homepage:
245
274
  licenses: []
@@ -261,12 +290,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
261
290
  required_rubygems_version: !ruby/object:Gem::Requirement
262
291
  none: false
263
292
  requirements:
264
- - - ">="
293
+ - - ">"
265
294
  - !ruby/object:Gem::Version
266
- hash: 3
295
+ hash: 25
267
296
  segments:
268
- - 0
269
- version: "0"
297
+ - 1
298
+ - 3
299
+ - 1
300
+ version: 1.3.1
270
301
  requirements: []
271
302
 
272
303
  rubyforge_project:
@@ -276,6 +307,7 @@ specification_version: 3
276
307
  summary: An AMQP-based scatter-gather job queuing protocol library
277
308
  test_files:
278
309
  - spec/spec_helper.rb
310
+ - spec/support/em_synchrony_spec.rb
279
311
  - spec/support/evented_spec_extensions.rb
280
312
  - spec/support/logging.rb
281
313
  - spec/support/logging_progress_bar_formatter.rb
@@ -287,3 +319,7 @@ test_files:
287
319
  - spec/who_can/pinger_responder_integration_spec.rb
288
320
  - spec/who_can/pinger_spec.rb
289
321
  - spec/who_can/responder_spec.rb
322
+ - spec/who_can/synchrony/base_spec.rb
323
+ - spec/who_can/synchrony/connection_manager_spec.rb
324
+ - spec/who_can/synchrony/pinger_spec.rb
325
+ - spec/who_can/synchrony/responder_spec.rb