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.
@@ -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