em-synchrony 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|