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.
@@ -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.push(conn)
20
+ @requests[name] = conn
19
21
  end
20
22
 
21
23
  def finished?
@@ -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
- silence_warnings do
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
- end
34
+ ensure
35
+ $VERBOSE = old_verbose
36
+ end
@@ -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 *args
8
+ _old_new(*args)
9
9
  else
10
- socket = EventMachine::connect( *args[0..1], self )
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}" if flags.nonzero?
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
@@ -4,20 +4,121 @@ module EventMachine
4
4
 
5
5
  # Fiber-aware drop-in replacements for thread objects
6
6
  class Mutex
7
- def synchronize( &blk )
8
- blk.call
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
- def wait( mutex )
14
- @deferrable = EventMachine::DefaultDeferrable.new
15
- EventMachine::Synchrony.sync @deferrable
16
- @deferrable = nil
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
- @deferrable and @deferrable.succeed
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
 
@@ -1,50 +1,109 @@
1
- require "spec/helper/all"
2
- require "em-synchrony/activerecord"
3
-
4
- # create database widgets;
5
- # use widgets;
6
- # create table widgets (idx INT);
7
-
8
- class Widget < ActiveRecord::Base; end;
9
-
10
- describe "Fiberized ActiveRecord driver for mysql2" do
11
- DELAY = 0.25
12
- QUERY = "SELECT sleep(#{DELAY})"
13
-
14
- it "should establish AR connection" do
15
- EventMachine.synchrony do
16
- ActiveRecord::Base.establish_connection(
17
- :adapter => 'em_mysql2',
18
- :database => 'widgets',
19
- :username => 'root'
20
- )
21
-
22
- result = Widget.find_by_sql(QUERY)
23
- result.size.should == 1
24
-
25
- EventMachine.stop
26
- end
27
- end
28
-
29
- it "should fire sequential, synchronous requests within single fiber" do
30
- EventMachine.synchrony do
31
- ActiveRecord::Base.establish_connection(
32
- :adapter => 'em_mysql2',
33
- :database => 'widgets',
34
- :username => 'root'
35
- )
36
-
37
- start = now
38
- res = []
39
-
40
- res.push Widget.find_by_sql(QUERY)
41
- res.push Widget.find_by_sql(QUERY)
42
-
43
- (now - start.to_f).should be_within(DELAY * res.size * 0.15).of(DELAY * res.size)
44
- res.size.should == 2
45
-
46
- EventMachine.stop
47
- end
48
- end
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