em-synchrony 1.0.0 → 1.0.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/Gemfile +3 -1
- data/README.md +173 -170
- data/Rakefile +3 -5
- data/em-synchrony.gemspec +1 -1
- data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +47 -0
- data/lib/em-synchrony.rb +33 -10
- data/lib/em-synchrony/activerecord.rb +103 -5
- data/lib/em-synchrony/amqp.rb +180 -0
- data/lib/em-synchrony/connection_pool.rb +3 -2
- data/lib/em-synchrony/core_ext.rb +10 -0
- data/lib/em-synchrony/em-hiredis.rb +103 -24
- data/lib/em-synchrony/em-mongo.rb +19 -0
- data/lib/em-synchrony/em-multi.rb +4 -2
- data/lib/em-synchrony/mongo.rb +6 -2
- data/lib/em-synchrony/mysql2.rb +16 -0
- data/lib/em-synchrony/tcpsocket.rb +5 -4
- data/lib/em-synchrony/thread.rb +108 -7
- data/spec/activerecord_spec.rb +108 -49
- data/spec/amqp_spec.rb +146 -0
- data/spec/connection_pool_spec.rb +4 -4
- data/spec/em-mongo_spec.rb +26 -0
- data/spec/helper/all.rb +1 -7
- data/spec/hiredis_spec.rb +24 -1
- data/spec/http_spec.rb +8 -8
- data/spec/multi_spec.rb +13 -0
- data/spec/mysql2_spec.rb +13 -0
- data/spec/synchrony_spec.rb +29 -10
- data/spec/tcpsocket_spec.rb +10 -0
- data/spec/thread_spec.rb +191 -0
- metadata +15 -10
- data/lib/em-synchrony/active_record/connection_adapters/em_mysql2_adapter.rb +0 -18
- data/lib/em-synchrony/active_record/patches.rb +0 -132
@@ -7,6 +7,25 @@ end
|
|
7
7
|
module EM
|
8
8
|
module Mongo
|
9
9
|
|
10
|
+
class Database
|
11
|
+
def authenticate(username, password)
|
12
|
+
auth_result = self.collection(SYSTEM_COMMAND_COLLECTION).first({'getnonce' => 1})
|
13
|
+
|
14
|
+
auth = BSON::OrderedHash.new
|
15
|
+
auth['authenticate'] = 1
|
16
|
+
auth['user'] = username
|
17
|
+
auth['nonce'] = auth_result['nonce']
|
18
|
+
auth['key'] = EM::Mongo::Support.auth_key(username, password, auth_result['nonce'])
|
19
|
+
|
20
|
+
auth_result2 = self.collection(SYSTEM_COMMAND_COLLECTION).first(auth)
|
21
|
+
if EM::Mongo::Support.ok?(auth_result2)
|
22
|
+
true
|
23
|
+
else
|
24
|
+
raise AuthenticationError, auth_result2["errmsg"]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
10
29
|
class Connection
|
11
30
|
def initialize(host = DEFAULT_IP, port = DEFAULT_PORT, timeout = nil, opts = {})
|
12
31
|
f = Fiber.current
|
@@ -6,16 +6,18 @@ module EventMachine
|
|
6
6
|
attr_reader :requests, :responses
|
7
7
|
|
8
8
|
def initialize
|
9
|
-
@requests =
|
9
|
+
@requests = {}
|
10
10
|
@responses = {:callback => {}, :errback => {}}
|
11
11
|
end
|
12
12
|
|
13
13
|
def add(name, conn)
|
14
|
+
raise 'Duplicate Multi key' if @requests.key? name
|
15
|
+
|
14
16
|
fiber = Fiber.current
|
15
17
|
conn.callback { @responses[:callback][name] = conn; check_progress(fiber) }
|
16
18
|
conn.errback { @responses[:errback][name] = conn; check_progress(fiber) }
|
17
19
|
|
18
|
-
@requests
|
20
|
+
@requests[name] = conn
|
19
21
|
end
|
20
22
|
|
21
23
|
def finished?
|
data/lib/em-synchrony/mongo.rb
CHANGED
@@ -5,7 +5,9 @@ rescue LoadError => error
|
|
5
5
|
end
|
6
6
|
|
7
7
|
# monkey-patch Mongo to use em-synchrony's socket and thread classs
|
8
|
-
|
8
|
+
old_verbose = $VERBOSE
|
9
|
+
begin
|
10
|
+
$VERBOSE = nil
|
9
11
|
class Mongo::Connection
|
10
12
|
TCPSocket = ::EventMachine::Synchrony::TCPSocket
|
11
13
|
Mutex = ::EventMachine::Synchrony::Thread::Mutex
|
@@ -29,4 +31,6 @@ silence_warnings do
|
|
29
31
|
end
|
30
32
|
|
31
33
|
Mongo::TimeoutHandler = EventMachine::Synchrony::MongoTimeoutHandler
|
32
|
-
|
34
|
+
ensure
|
35
|
+
$VERBOSE = old_verbose
|
36
|
+
end
|
data/lib/em-synchrony/mysql2.rb
CHANGED
@@ -7,11 +7,27 @@ end
|
|
7
7
|
module Mysql2
|
8
8
|
module EM
|
9
9
|
class Client
|
10
|
+
module Watcher
|
11
|
+
def notify_readable
|
12
|
+
detach
|
13
|
+
begin
|
14
|
+
result = @client.async_result
|
15
|
+
rescue Exception => e
|
16
|
+
@deferable.fail(e)
|
17
|
+
else
|
18
|
+
@deferable.succeed(result)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
10
22
|
|
11
23
|
alias :aquery :query
|
12
24
|
def query(sql, opts={})
|
13
25
|
deferable = aquery(sql, opts)
|
14
26
|
|
27
|
+
# if EM is not running, we just get the sql result directly
|
28
|
+
# if we get a deferable, then let's do the deferable thing.
|
29
|
+
return deferable unless deferable.kind_of? ::EM::DefaultDeferrable
|
30
|
+
|
15
31
|
f = Fiber.current
|
16
32
|
deferable.callback { |res| f.resume(res) }
|
17
33
|
deferable.errback { |err| f.resume(err) }
|
@@ -5,9 +5,9 @@ module EventMachine
|
|
5
5
|
alias_method :_old_new, :new
|
6
6
|
def new(*args)
|
7
7
|
if args.size == 1
|
8
|
-
_old_new
|
8
|
+
_old_new(*args)
|
9
9
|
else
|
10
|
-
socket = EventMachine::connect(
|
10
|
+
socket = EventMachine::connect(*args[0..1], self)
|
11
11
|
raise SocketError unless socket.sync(:in) # wait for connection
|
12
12
|
socket
|
13
13
|
end
|
@@ -34,7 +34,7 @@ module EventMachine
|
|
34
34
|
def setsockopt(level, name, value); end
|
35
35
|
|
36
36
|
def send(msg, flags = 0)
|
37
|
-
raise "Unknown flags in send(): #{flags}"
|
37
|
+
raise "Unknown flags in send(): #{flags}" if flags.nonzero?
|
38
38
|
len = msg.bytesize
|
39
39
|
write_data(msg) or sync(:out) or raise(IOError)
|
40
40
|
len
|
@@ -60,6 +60,7 @@ module EventMachine
|
|
60
60
|
def unbind
|
61
61
|
@in_req.fail nil if @in_req
|
62
62
|
@out_req.fail nil if @out_req
|
63
|
+
close
|
63
64
|
end
|
64
65
|
|
65
66
|
def receive_data(data)
|
@@ -107,4 +108,4 @@ module EventMachine
|
|
107
108
|
end
|
108
109
|
end
|
109
110
|
end
|
110
|
-
end
|
111
|
+
end
|
data/lib/em-synchrony/thread.rb
CHANGED
@@ -4,20 +4,121 @@ module EventMachine
|
|
4
4
|
|
5
5
|
# Fiber-aware drop-in replacements for thread objects
|
6
6
|
class Mutex
|
7
|
-
def
|
8
|
-
|
7
|
+
def initialize
|
8
|
+
@waiters = []
|
9
|
+
@slept = {}
|
9
10
|
end
|
11
|
+
|
12
|
+
def lock
|
13
|
+
current = Fiber.current
|
14
|
+
raise FiberError if @waiters.include?(current)
|
15
|
+
@waiters << current
|
16
|
+
Fiber.yield unless @waiters.first == current
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def locked?
|
21
|
+
!@waiters.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def _wakeup(fiber)
|
25
|
+
fiber.resume if @slept.delete(fiber)
|
26
|
+
end
|
27
|
+
|
28
|
+
def sleep(timeout = nil)
|
29
|
+
unlock
|
30
|
+
beg = Time.now
|
31
|
+
current = Fiber.current
|
32
|
+
@slept[current] = true
|
33
|
+
if timeout
|
34
|
+
timer = EM.add_timer(timeout) do
|
35
|
+
_wakeup(current)
|
36
|
+
end
|
37
|
+
Fiber.yield
|
38
|
+
EM.cancel_timer timer # if we resumes not via timer
|
39
|
+
else
|
40
|
+
Fiber.yield
|
41
|
+
end
|
42
|
+
@slept.delete current
|
43
|
+
yield if block_given?
|
44
|
+
lock
|
45
|
+
Time.now - beg
|
46
|
+
end
|
47
|
+
|
48
|
+
def try_lock
|
49
|
+
lock unless locked?
|
50
|
+
end
|
51
|
+
|
52
|
+
def unlock
|
53
|
+
raise FiberError unless @waiters.first == Fiber.current
|
54
|
+
@waiters.shift
|
55
|
+
unless @waiters.empty?
|
56
|
+
EM.next_tick{ @waiters.first.resume }
|
57
|
+
end
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def synchronize
|
62
|
+
lock
|
63
|
+
yield
|
64
|
+
ensure
|
65
|
+
unlock
|
66
|
+
end
|
67
|
+
|
10
68
|
end
|
11
69
|
|
12
70
|
class ConditionVariable
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
71
|
+
#
|
72
|
+
# Creates a new ConditionVariable
|
73
|
+
#
|
74
|
+
def initialize
|
75
|
+
@waiters = []
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Releases the lock held in +mutex+ and waits; reacquires the lock on wakeup.
|
80
|
+
#
|
81
|
+
# If +timeout+ is given, this method returns after +timeout+ seconds passed,
|
82
|
+
# even if no other thread doesn't signal.
|
83
|
+
#
|
84
|
+
def wait(mutex, timeout=nil)
|
85
|
+
current = Fiber.current
|
86
|
+
pair = [mutex, current]
|
87
|
+
@waiters << pair
|
88
|
+
mutex.sleep timeout do
|
89
|
+
@waiters.delete pair
|
90
|
+
end
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
def _wakeup(mutex, fiber)
|
95
|
+
if alive = fiber.alive?
|
96
|
+
EM.next_tick {
|
97
|
+
mutex._wakeup(fiber)
|
98
|
+
}
|
99
|
+
end
|
100
|
+
alive
|
17
101
|
end
|
18
102
|
|
103
|
+
#
|
104
|
+
# Wakes up the first thread in line waiting for this lock.
|
105
|
+
#
|
19
106
|
def signal
|
20
|
-
|
107
|
+
while (pair = @waiters.shift)
|
108
|
+
break if _wakeup(*pair)
|
109
|
+
end
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Wakes up all threads waiting for this lock.
|
115
|
+
#
|
116
|
+
def broadcast
|
117
|
+
@waiters.each do |mutex, fiber|
|
118
|
+
_wakeup(mutex, fiber)
|
119
|
+
end
|
120
|
+
@waiters.clear
|
121
|
+
self
|
21
122
|
end
|
22
123
|
end
|
23
124
|
|
data/spec/activerecord_spec.rb
CHANGED
@@ -1,50 +1,109 @@
|
|
1
|
-
require "spec/helper/all"
|
2
|
-
require "em-synchrony/activerecord"
|
3
|
-
|
4
|
-
|
5
|
-
#
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
it "should
|
30
|
-
EventMachine.synchrony do
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
res
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
1
|
+
require "spec/helper/all"
|
2
|
+
require "em-synchrony/activerecord"
|
3
|
+
require "em-synchrony/fiber_iterator"
|
4
|
+
|
5
|
+
# create database widgets;
|
6
|
+
# use widgets;
|
7
|
+
# create table widgets (
|
8
|
+
# id INT NOT NULL AUTO_INCREMENT,
|
9
|
+
# title varchar(255),
|
10
|
+
# PRIMARY KEY (`id`)
|
11
|
+
# );
|
12
|
+
|
13
|
+
class Widget < ActiveRecord::Base; end;
|
14
|
+
|
15
|
+
describe "Fiberized ActiveRecord driver for mysql2" do
|
16
|
+
DELAY = 0.25
|
17
|
+
QUERY = "SELECT sleep(#{DELAY})"
|
18
|
+
|
19
|
+
def establish_connection
|
20
|
+
ActiveRecord::Base.establish_connection(
|
21
|
+
:adapter => 'em_mysql2',
|
22
|
+
:database => 'widgets',
|
23
|
+
:username => 'root',
|
24
|
+
:pool => 10
|
25
|
+
)
|
26
|
+
Widget.delete_all
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should establish AR connection" do
|
30
|
+
EventMachine.synchrony do
|
31
|
+
establish_connection
|
32
|
+
|
33
|
+
result = Widget.find_by_sql(QUERY)
|
34
|
+
result.size.should eql(1)
|
35
|
+
EventMachine.stop
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should fire sequential, synchronous requests within single fiber" do
|
40
|
+
EventMachine.synchrony do
|
41
|
+
establish_connection
|
42
|
+
|
43
|
+
start = now
|
44
|
+
res = []
|
45
|
+
|
46
|
+
res.push Widget.find_by_sql(QUERY)
|
47
|
+
res.push Widget.find_by_sql(QUERY)
|
48
|
+
|
49
|
+
(now - start.to_f).should be_within(DELAY * res.size * 0.15).of(DELAY * res.size)
|
50
|
+
res.size.should eql(2)
|
51
|
+
|
52
|
+
EventMachine.stop
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should fire 100 requests in fibers" do
|
57
|
+
EM.synchrony do
|
58
|
+
establish_connection
|
59
|
+
EM::Synchrony::FiberIterator.new(1..100, 40).each do |i|
|
60
|
+
widget = Widget.create title: 'hi'
|
61
|
+
widget.update_attributes title: 'hello'
|
62
|
+
end
|
63
|
+
EM.stop
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should create widget" do
|
68
|
+
EM.synchrony do
|
69
|
+
establish_connection
|
70
|
+
Widget.create
|
71
|
+
Widget.create
|
72
|
+
Widget.count.should eql(2)
|
73
|
+
EM.stop
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should update widget" do
|
78
|
+
EM.synchrony do
|
79
|
+
establish_connection
|
80
|
+
ActiveRecord::Base.connection.execute("TRUNCATE TABLE widgets;")
|
81
|
+
widget = Widget.create title: 'hi'
|
82
|
+
widget.update_attributes title: 'hello'
|
83
|
+
Widget.find(widget.id).title.should eql('hello')
|
84
|
+
EM.stop
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "transactions" do
|
89
|
+
it "should work properly" do
|
90
|
+
EM.synchrony do
|
91
|
+
establish_connection
|
92
|
+
EM::Synchrony::FiberIterator.new(1..50, 30).each do |i|
|
93
|
+
widget = Widget.create title: "hi#{i}"
|
94
|
+
ActiveRecord::Base.transaction do
|
95
|
+
widget.update_attributes title: "hello"
|
96
|
+
end
|
97
|
+
ActiveRecord::Base.transaction do
|
98
|
+
raise ActiveRecord::Rollback
|
99
|
+
end
|
100
|
+
end
|
101
|
+
Widget.all.each do |widget|
|
102
|
+
widget.title.should eq('hello')
|
103
|
+
end
|
104
|
+
EM.stop
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
50
109
|
end
|
data/spec/amqp_spec.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
require "spec/helper/all"
|
2
|
+
|
3
|
+
describe EM::Synchrony::AMQP do
|
4
|
+
|
5
|
+
it "should yield until connection is ready" do
|
6
|
+
EM.synchrony do
|
7
|
+
connection = EM::Synchrony::AMQP.connect
|
8
|
+
connection.connected?.should be_true
|
9
|
+
EM.stop
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should yield until disconnection is complete" do
|
14
|
+
EM.synchrony do
|
15
|
+
connection = EM::Synchrony::AMQP.connect
|
16
|
+
connection.disconnect
|
17
|
+
connection.connected?.should be_false
|
18
|
+
EM.stop
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should yield until the channel is created" do
|
23
|
+
EM.synchrony do
|
24
|
+
connection = EM::Synchrony::AMQP.connect
|
25
|
+
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
26
|
+
channel.should be_kind_of(EM::Synchrony::AMQP::Channel)
|
27
|
+
EM.stop
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should yield until the queue is created" do
|
32
|
+
EM.synchrony do
|
33
|
+
connection = EM::Synchrony::AMQP.connect
|
34
|
+
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
35
|
+
queue = EM::Synchrony::AMQP::Queue.new(channel, "test.em-synchrony.queue1", :auto_delete => true)
|
36
|
+
EM.stop
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should yield when a queue is created from a channel" do
|
41
|
+
EM.synchrony do
|
42
|
+
connection = EM::Synchrony::AMQP.connect
|
43
|
+
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
44
|
+
queue = channel.queue("test.em-synchrony.queue1", :auto_delete => true)
|
45
|
+
EM.stop
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should yield until the exchange is created" do
|
50
|
+
EM.synchrony do
|
51
|
+
connection = EM::Synchrony::AMQP.connect
|
52
|
+
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
53
|
+
|
54
|
+
exchange = EM::Synchrony::AMQP::Exchange.new(channel, :fanout, "test.em-synchrony.exchange")
|
55
|
+
exchange.should be_kind_of(EventMachine::Synchrony::AMQP::Exchange)
|
56
|
+
|
57
|
+
direct = channel.direct("test.em-synchrony.direct")
|
58
|
+
fanout = channel.fanout("test.em-synchrony.fanout")
|
59
|
+
topic = channel.topic("test.em-synchrony.topic")
|
60
|
+
headers = channel.headers("test.em-synchrony.headers")
|
61
|
+
|
62
|
+
direct.should be_kind_of(EventMachine::Synchrony::AMQP::Exchange)
|
63
|
+
fanout.should be_kind_of(EventMachine::Synchrony::AMQP::Exchange)
|
64
|
+
topic.should be_kind_of(EventMachine::Synchrony::AMQP::Exchange)
|
65
|
+
headers.should be_kind_of(EventMachine::Synchrony::AMQP::Exchange)
|
66
|
+
EM.stop
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should publish and receive messages" do
|
71
|
+
nb_msg = 10
|
72
|
+
EM.synchrony do
|
73
|
+
connection = EM::Synchrony::AMQP.connect
|
74
|
+
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
75
|
+
ex = EM::Synchrony::AMQP::Exchange.new(channel, :fanout, "test.em-synchrony.fanout")
|
76
|
+
|
77
|
+
q1 = channel.queue("test.em-synchrony.queues.1", :auto_delete => true)
|
78
|
+
q2 = channel.queue("test.em-synchrony.queues.2", :auto_delete => true)
|
79
|
+
|
80
|
+
q1.bind(ex)
|
81
|
+
q2.bind(ex)
|
82
|
+
|
83
|
+
nb_q1, nb_q2 = 0, 0
|
84
|
+
stop_cb = proc { EM.stop if nb_q1 + nb_q2 == 2 * nb_msg }
|
85
|
+
|
86
|
+
q1.subscribe do |meta, msg|
|
87
|
+
msg.should match(/^Bonjour [0-9]+/)
|
88
|
+
nb_q1 += 1
|
89
|
+
stop_cb.call
|
90
|
+
end
|
91
|
+
|
92
|
+
q2.subscribe do |meta, msg|
|
93
|
+
msg.should match(/^Bonjour [0-9]+/)
|
94
|
+
nb_q2 += 1
|
95
|
+
stop_cb.call
|
96
|
+
end
|
97
|
+
|
98
|
+
Fiber.new do
|
99
|
+
nb_msg.times do |n|
|
100
|
+
ex.publish("Bonjour #{n}")
|
101
|
+
EM::Synchrony.sleep(0.1)
|
102
|
+
end
|
103
|
+
end.resume
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should handle several consumers" do
|
108
|
+
nb_msg = 10
|
109
|
+
EM.synchrony do
|
110
|
+
connection = EM::Synchrony::AMQP.connect
|
111
|
+
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
112
|
+
exchange = EM::Synchrony::AMQP::Exchange.new(channel, :fanout, "test.em-synchrony.consumers.fanout")
|
113
|
+
|
114
|
+
queue = channel.queue("test.em-synchrony.consumers.queue", :auto_delete => true)
|
115
|
+
queue.bind(exchange)
|
116
|
+
|
117
|
+
cons1 = EM::Synchrony::AMQP::Consumer.new(channel, queue)
|
118
|
+
cons2 = EM::Synchrony::AMQP::Consumer.new(channel, queue)
|
119
|
+
|
120
|
+
nb_cons1, nb_cons2 = 0, 0
|
121
|
+
stop_cb = Proc.new do
|
122
|
+
if nb_cons1 + nb_cons2 == nb_msg
|
123
|
+
nb_cons1.should eq(nb_cons2)
|
124
|
+
EM.stop
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
cons1.on_delivery do |meta, msg|
|
129
|
+
msg.should match(/^Bonjour [0-9]+/)
|
130
|
+
nb_cons1 += 1
|
131
|
+
stop_cb.call
|
132
|
+
end.consume
|
133
|
+
|
134
|
+
cons2.on_delivery do |meta, msg|
|
135
|
+
msg.should match(/^Bonjour [0-9]+/)
|
136
|
+
nb_cons2 += 1
|
137
|
+
stop_cb.call
|
138
|
+
end.consume
|
139
|
+
|
140
|
+
10.times do |n|
|
141
|
+
exchange.publish("Bonjour #{n}")
|
142
|
+
EM::Synchrony.sleep(0.1)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|