who_can 0.3.5 → 0.4.0.beta.1

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/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