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 +9 -0
- data/lib/who_can/base.rb +16 -1
- data/lib/who_can/connection_manager.rb +13 -5
- data/lib/who_can/heartbeater/ekg.rb +12 -8
- data/lib/who_can/pinger.rb +35 -16
- data/lib/who_can/responder.rb +17 -2
- data/lib/who_can/synchrony/base.rb +57 -0
- data/lib/who_can/synchrony/connection_manager.rb +52 -0
- data/lib/who_can/synchrony/pinger.rb +37 -0
- data/lib/who_can/synchrony/responder.rb +36 -0
- data/lib/who_can/synchrony.rb +54 -0
- data/lib/who_can/version.rb +1 -1
- data/lib/who_can.rb +2 -0
- data/spec/support/em_synchrony_spec.rb +11 -0
- data/spec/who_can/base_spec.rb +0 -7
- data/spec/who_can/heartbeater/ekg_spec.rb +4 -4
- data/spec/who_can/responder_spec.rb +1 -10
- data/spec/who_can/synchrony/base_spec.rb +73 -0
- data/spec/who_can/synchrony/connection_manager_spec.rb +65 -0
- data/spec/who_can/synchrony/pinger_spec.rb +80 -0
- data/spec/who_can/synchrony/responder_spec.rb +43 -0
- data/who_can.gemspec +6 -5
- metadata +68 -32
data/Rakefile
CHANGED
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
|
-
|
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
|
-
|
94
|
-
logger.
|
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
|
-
|
100
|
-
|
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
|
data/lib/who_can/pinger.rb
CHANGED
@@ -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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
data/lib/who_can/responder.rb
CHANGED
@@ -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
|
-
|
81
|
+
logger.debug {"subscribing to the #{@ping_exchange_name}"}
|
81
82
|
|
82
83
|
confirm_queue = lambda do |*a|
|
83
|
-
|
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
|
+
|
data/lib/who_can/version.rb
CHANGED
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
|
|
data/spec/who_can/base_spec.rb
CHANGED
@@ -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
|
-
|
29
|
+
# logger.debug {"heartbeat failure received"}
|
30
30
|
|
31
31
|
@ekg.shutdown! do
|
32
|
-
|
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',
|
23
|
-
s.add_development_dependency('rspec',
|
24
|
-
s.add_development_dependency('evented-spec',
|
25
|
-
s.add_development_dependency('flexmock',
|
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:
|
5
|
-
prerelease:
|
4
|
+
hash: 62196449
|
5
|
+
prerelease: 6
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
|
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-
|
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:
|
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: *
|
74
|
+
version_requirements: *id003
|
57
75
|
- !ruby/object:Gem::Dependency
|
58
76
|
name: uuidtools
|
59
77
|
prerelease: false
|
60
|
-
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: *
|
90
|
+
version_requirements: *id004
|
73
91
|
- !ruby/object:Gem::Dependency
|
74
92
|
name: deferred
|
75
93
|
prerelease: false
|
76
|
-
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: *
|
106
|
+
version_requirements: *id005
|
89
107
|
- !ruby/object:Gem::Dependency
|
90
108
|
name: state_machine
|
91
109
|
prerelease: false
|
92
|
-
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: *
|
122
|
+
version_requirements: *id006
|
105
123
|
- !ruby/object:Gem::Dependency
|
106
124
|
name: activesupport
|
107
125
|
prerelease: false
|
108
|
-
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:
|
131
|
+
hash: 9
|
114
132
|
segments:
|
115
133
|
- 3
|
116
134
|
- 0
|
117
|
-
|
135
|
+
- 7
|
136
|
+
version: 3.0.7
|
118
137
|
type: :runtime
|
119
|
-
version_requirements: *
|
138
|
+
version_requirements: *id007
|
120
139
|
- !ruby/object:Gem::Dependency
|
121
140
|
name: ZenTest
|
122
141
|
prerelease: false
|
123
|
-
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: *
|
154
|
+
version_requirements: *id008
|
136
155
|
- !ruby/object:Gem::Dependency
|
137
156
|
name: rspec
|
138
157
|
prerelease: false
|
139
|
-
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: *
|
170
|
+
version_requirements: *id009
|
152
171
|
- !ruby/object:Gem::Dependency
|
153
172
|
name: evented-spec
|
154
173
|
prerelease: false
|
155
|
-
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: *
|
186
|
+
version_requirements: *id010
|
168
187
|
- !ruby/object:Gem::Dependency
|
169
188
|
name: flexmock
|
170
189
|
prerelease: false
|
171
|
-
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: *
|
202
|
+
version_requirements: *id011
|
184
203
|
- !ruby/object:Gem::Dependency
|
185
204
|
name: ruby-graphviz
|
186
205
|
prerelease: false
|
187
|
-
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: *
|
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:
|
295
|
+
hash: 25
|
267
296
|
segments:
|
268
|
-
-
|
269
|
-
|
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
|