em-synchrony 0.3.0.beta.1 → 1.0.0
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/.gitignore +1 -0
- data/.rspec +1 -0
- data/Gemfile +7 -5
- data/README.md +121 -91
- data/Rakefile +7 -1
- data/em-synchrony.gemspec +2 -2
- data/lib/em-synchrony.rb +29 -5
- data/lib/em-synchrony/active_record/connection_adapters/em_mysql2_adapter.rb +18 -0
- data/lib/em-synchrony/active_record/patches.rb +132 -0
- data/lib/em-synchrony/activerecord.rb +5 -0
- data/lib/em-synchrony/connection_pool.rb +3 -3
- data/lib/em-synchrony/em-hiredis.rb +24 -0
- data/lib/em-synchrony/em-http.rb +10 -6
- data/lib/em-synchrony/em-mongo.rb +80 -13
- data/lib/em-synchrony/em-redis.rb +29 -6
- data/lib/em-synchrony/em-remcached.rb +43 -19
- data/lib/em-synchrony/fiber_iterator.rb +18 -0
- data/lib/em-synchrony/keyboard.rb +33 -0
- data/lib/em-synchrony/mongo.rb +19 -1
- data/lib/em-synchrony/mysql2.rb +25 -0
- data/spec/activerecord_spec.rb +50 -0
- data/spec/em-mongo_spec.rb +192 -28
- data/spec/fiber_iterator_spec.rb +39 -0
- data/spec/helper/all.rb +2 -1
- data/spec/hiredis_spec.rb +40 -0
- data/spec/http_spec.rb +23 -0
- data/spec/keyboard_spec.rb +59 -0
- data/spec/{mysqlplus_spec.rb → mysql2_spec.rb} +26 -14
- data/spec/redis_spec.rb +39 -0
- data/spec/synchrony_spec.rb +14 -0
- data/spec/timer_spec.rb +35 -0
- metadata +52 -60
- data/examples/bitly.rb +0 -25
- data/lib/em-synchrony/em-bitly.rb +0 -58
- data/lib/em-synchrony/em-mysqlplus.rb +0 -27
@@ -0,0 +1,50 @@
|
|
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
|
+
|
50
|
+
end
|
data/spec/em-mongo_spec.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require "spec/helper/all"
|
2
2
|
|
3
3
|
describe EM::Mongo do
|
4
|
-
|
5
4
|
it "should yield until connection is ready" do
|
6
5
|
EventMachine.synchrony do
|
7
6
|
connection = EM::Mongo::Connection.new
|
@@ -14,41 +13,207 @@ describe EM::Mongo do
|
|
14
13
|
end
|
15
14
|
end
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
16
|
+
describe 'Synchronously (find & first)' do
|
17
|
+
it "should insert a record into db" do
|
18
|
+
EventMachine.synchrony do
|
19
|
+
collection = EM::Mongo::Connection.new.db('db').collection('test')
|
20
|
+
collection.remove({}) # nuke all keys in collection
|
21
21
|
|
22
|
-
|
23
|
-
|
22
|
+
obj = collection.insert('hello' => 'world')
|
23
|
+
obj.should be_a(BSON::ObjectId)
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
obj = collection.find
|
26
|
+
obj.size.should == 1
|
27
|
+
obj.first['hello'].should == 'world'
|
28
28
|
|
29
|
-
|
29
|
+
EventMachine.stop
|
30
|
+
end
|
30
31
|
end
|
31
|
-
end
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
it "should insert a record into db and be able to find it" do
|
34
|
+
EventMachine.synchrony do
|
35
|
+
collection = EM::Mongo::Connection.new.db('db').collection('test')
|
36
|
+
collection.remove({}) # nuke all keys in collection
|
37
37
|
|
38
|
-
|
39
|
-
|
38
|
+
obj = collection.insert('hello' => 'world')
|
39
|
+
obj = collection.insert('hello2' => 'world2')
|
40
40
|
|
41
|
-
|
42
|
-
|
41
|
+
obj = collection.find({})
|
42
|
+
obj.size.should == 2
|
43
43
|
|
44
|
-
|
45
|
-
|
44
|
+
obj2 = collection.find({}, {:limit => 1})
|
45
|
+
obj2.size.should == 1
|
46
46
|
|
47
|
-
|
48
|
-
|
47
|
+
obj3 = collection.first
|
48
|
+
obj3.is_a?(Hash).should be_true
|
49
49
|
|
50
|
-
|
50
|
+
EventMachine.stop
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should be able to order results" do
|
55
|
+
EventMachine.synchrony do
|
56
|
+
collection = EM::Mongo::Connection.new.db('db').collection('test')
|
57
|
+
collection.remove({}) # nuke all keys in collection
|
58
|
+
|
59
|
+
collection.insert(:name => 'one', :position => 0)
|
60
|
+
collection.insert(:name => 'three', :position => 2)
|
61
|
+
collection.insert(:name => 'two', :position => 1)
|
62
|
+
|
63
|
+
res = collection.find({}, {:order => 'position'})
|
64
|
+
res[0]["name"].should == 'one'
|
65
|
+
res[1]["name"].should == 'two'
|
66
|
+
res[2]["name"].should == 'three'
|
67
|
+
|
68
|
+
res1 = collection.find({}, {:order => [:position, :desc]})
|
69
|
+
res1[0]["name"].should == 'three'
|
70
|
+
res1[1]["name"].should == 'two'
|
71
|
+
res1[2]["name"].should == 'one'
|
72
|
+
|
73
|
+
EventMachine.stop
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
#
|
80
|
+
# em-mongo version > 0.3.6
|
81
|
+
#
|
82
|
+
if defined?(EM::Mongo::Cursor)
|
83
|
+
describe '*A*synchronously (afind & afirst) [Mongo > 0.3.6, using cursor]' do
|
84
|
+
it "should insert a record into db" do
|
85
|
+
EventMachine.synchrony do
|
86
|
+
collection = EM::Mongo::Connection.new.db('db').collection('test')
|
87
|
+
collection.remove({}) # nuke all keys in collection
|
88
|
+
|
89
|
+
obj = collection.insert('hello' => 'world')
|
90
|
+
obj.should be_a(BSON::ObjectId)
|
91
|
+
|
92
|
+
cursor = collection.afind
|
93
|
+
cursor.should be_a(EM::Mongo::Cursor)
|
94
|
+
cursor.to_a.callback do |obj|
|
95
|
+
obj.size.should == 1
|
96
|
+
obj.first['hello'].should == 'world'
|
97
|
+
EM.next_tick{ EventMachine.stop }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should insert a record into db and be able to find it" do
|
103
|
+
EventMachine.synchrony do
|
104
|
+
collection = EM::Mongo::Connection.new.db('db').collection('test')
|
105
|
+
collection.remove({}) # nuke all keys in collection
|
106
|
+
|
107
|
+
obj = collection.insert('hello' => 'world')
|
108
|
+
obj = collection.insert('hello2' => 'world2')
|
109
|
+
|
110
|
+
collection.afind({}).to_a.callback do |obj|
|
111
|
+
obj.size.should == 2
|
112
|
+
end
|
113
|
+
collection.afind({}, {:limit => 1}).to_a.callback do |obj2|
|
114
|
+
obj2.size.should == 1
|
115
|
+
end
|
116
|
+
collection.afirst.callback do |obj3|
|
117
|
+
obj3.is_a?(Hash).should be_true
|
118
|
+
obj3['hello'].should == 'world'
|
119
|
+
EM.next_tick{ EventMachine.stop }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should be able to order results" do
|
125
|
+
EventMachine.synchrony do
|
126
|
+
collection = EM::Mongo::Connection.new.db('db').collection('test')
|
127
|
+
collection.remove({}) # nuke all keys in collection
|
128
|
+
|
129
|
+
collection.insert(:name => 'one', :position => 0)
|
130
|
+
collection.insert(:name => 'three', :position => 2)
|
131
|
+
collection.insert(:name => 'two', :position => 1)
|
132
|
+
|
133
|
+
collection.afind({}, {:order => 'position'}).to_a.callback do |res|
|
134
|
+
res[0]["name"].should == 'one'
|
135
|
+
res[1]["name"].should == 'two'
|
136
|
+
res[2]["name"].should == 'three'
|
137
|
+
end
|
138
|
+
|
139
|
+
collection.afind({}, {:order => [:position, :desc]}).to_a.callback do |res1|
|
140
|
+
res1[0]["name"].should == 'three'
|
141
|
+
res1[1]["name"].should == 'two'
|
142
|
+
res1[2]["name"].should == 'one'
|
143
|
+
EM.next_tick{ EventMachine.stop }
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
else
|
151
|
+
describe '*A*synchronously (afind & afirst) [Mongo <= 0.3.6, using blocks]' do
|
152
|
+
it "should insert a record into db" do
|
153
|
+
EventMachine.synchrony do
|
154
|
+
collection = EM::Mongo::Connection.new.db('db').collection('test')
|
155
|
+
collection.remove({}) # nuke all keys in collection
|
156
|
+
|
157
|
+
obj = collection.insert('hello' => 'world')
|
158
|
+
obj.should be_a(BSON::ObjectId)
|
159
|
+
|
160
|
+
ret_val = collection.afind do |obj|
|
161
|
+
obj.size.should == 1
|
162
|
+
obj.first['hello'].should == 'world'
|
163
|
+
EM.next_tick{ EventMachine.stop }
|
164
|
+
end
|
165
|
+
ret_val.should be_a(Integer)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should insert a record into db and be able to find it" do
|
170
|
+
EventMachine.synchrony do
|
171
|
+
collection = EM::Mongo::Connection.new.db('db').collection('test')
|
172
|
+
collection.remove({}) # nuke all keys in collection
|
173
|
+
|
174
|
+
obj = collection.insert('hello' => 'world')
|
175
|
+
obj = collection.insert('hello2' => 'world2')
|
176
|
+
|
177
|
+
collection.afind({}) do |obj|
|
178
|
+
obj.size.should == 2
|
179
|
+
end
|
180
|
+
collection.afind({}, {:limit => 1}) do |obj2|
|
181
|
+
obj2.size.should == 1
|
182
|
+
end
|
183
|
+
collection.afirst do |obj3|
|
184
|
+
obj3.is_a?(Hash).should be_true
|
185
|
+
obj3['hello'].should == 'world'
|
186
|
+
EM.next_tick{ EventMachine.stop }
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should be able to order results" do
|
192
|
+
EventMachine.synchrony do
|
193
|
+
collection = EM::Mongo::Connection.new.db('db').collection('test')
|
194
|
+
collection.remove({}) # nuke all keys in collection
|
195
|
+
|
196
|
+
collection.insert(:name => 'one', :position => 0)
|
197
|
+
collection.insert(:name => 'three', :position => 2)
|
198
|
+
collection.insert(:name => 'two', :position => 1)
|
199
|
+
|
200
|
+
collection.afind({}, {:order => 'position'}) do |res|
|
201
|
+
res[0]["name"].should == 'one'
|
202
|
+
res[1]["name"].should == 'two'
|
203
|
+
res[2]["name"].should == 'three'
|
204
|
+
end
|
205
|
+
|
206
|
+
collection.afind({}, {:order => [:position, :desc]}) do |res1|
|
207
|
+
res1[0]["name"].should == 'three'
|
208
|
+
res1[1]["name"].should == 'two'
|
209
|
+
res1[2]["name"].should == 'one'
|
210
|
+
EM.next_tick{ EventMachine.stop }
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
end
|
51
215
|
end
|
216
|
+
|
52
217
|
end
|
53
218
|
|
54
219
|
it "should update records in db" do
|
@@ -56,14 +221,13 @@ describe EM::Mongo do
|
|
56
221
|
collection = EM::Mongo::Connection.new.db('db').collection('test')
|
57
222
|
collection.remove({}) # nuke all keys in collection
|
58
223
|
|
59
|
-
|
224
|
+
obj_id = collection.insert('hello' => 'world')
|
60
225
|
collection.update({'hello' => 'world'}, {'hello' => 'newworld'})
|
61
226
|
|
62
|
-
new_obj = collection.first({'_id' =>
|
227
|
+
new_obj = collection.first({'_id' => obj_id})
|
63
228
|
new_obj['hello'].should == 'newworld'
|
64
229
|
|
65
230
|
EventMachine.stop
|
66
231
|
end
|
67
232
|
end
|
68
|
-
|
69
233
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "spec/helper/all"
|
2
|
+
require "em-synchrony/fiber_iterator"
|
3
|
+
|
4
|
+
describe EventMachine::Synchrony::FiberIterator do
|
5
|
+
|
6
|
+
it "should wait until the iterator is done and wrap internal block within a fiber" do
|
7
|
+
EM.synchrony do
|
8
|
+
|
9
|
+
results = []
|
10
|
+
i = EM::Synchrony::FiberIterator.new(1..5, 2).each do |num|
|
11
|
+
EM::Synchrony.sleep(0.1)
|
12
|
+
results.push num
|
13
|
+
end
|
14
|
+
|
15
|
+
results.should == (1..5).to_a
|
16
|
+
results.size.should == 5
|
17
|
+
EventMachine.stop
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# it "should sum values within the iterator" do
|
23
|
+
# EM.synchrony do
|
24
|
+
# data = (1..5).to_a
|
25
|
+
# res = EM::Synchrony::FiberIterator.new(data, 2).inject(0) do |total, num, iter|
|
26
|
+
# EM::Synchrony.sleep(0.1)
|
27
|
+
#
|
28
|
+
# p [:sync, total, num]
|
29
|
+
# iter.return(total += num)
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# res.should == data.inject(:+)
|
33
|
+
# EventMachine.stop
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
end
|
data/spec/helper/all.rb
CHANGED
@@ -4,11 +4,12 @@ require 'pp'
|
|
4
4
|
|
5
5
|
require 'lib/em-synchrony'
|
6
6
|
require 'lib/em-synchrony/em-http'
|
7
|
-
require 'lib/em-synchrony/
|
7
|
+
require 'lib/em-synchrony/mysql2'
|
8
8
|
require 'lib/em-synchrony/em-remcached'
|
9
9
|
require 'lib/em-synchrony/em-memcache'
|
10
10
|
require 'lib/em-synchrony/em-mongo'
|
11
11
|
require 'lib/em-synchrony/em-redis'
|
12
|
+
require 'lib/em-synchrony/em-hiredis'
|
12
13
|
|
13
14
|
require 'helper/tolerance_matcher'
|
14
15
|
require 'helper/stub-http-server'
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "spec/helper/all"
|
2
|
+
|
3
|
+
describe EM::Hiredis do
|
4
|
+
|
5
|
+
it "should yield until connection is ready" do
|
6
|
+
EventMachine.synchrony do
|
7
|
+
connection = EM::Hiredis::Client.connect
|
8
|
+
connection.connected.should be_true
|
9
|
+
|
10
|
+
EventMachine.stop
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should get/set records synchronously" do
|
15
|
+
EventMachine.synchrony do
|
16
|
+
redis = EM::Hiredis::Client.connect
|
17
|
+
|
18
|
+
redis.set('a', 'foo')
|
19
|
+
redis.get('a').should == 'foo'
|
20
|
+
redis.get('c').should == nil
|
21
|
+
|
22
|
+
EM.stop
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should incr/decr key synchronously" do
|
27
|
+
EventMachine.synchrony do
|
28
|
+
redis = EM::Hiredis::Client.connect
|
29
|
+
redis.delete('key')
|
30
|
+
|
31
|
+
redis.incr('key')
|
32
|
+
redis.get('key').to_i.should == 1
|
33
|
+
|
34
|
+
redis.decr('key')
|
35
|
+
redis.get('key').to_i.should == 0
|
36
|
+
|
37
|
+
EM.stop
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/spec/http_spec.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "spec/helper/all"
|
2
2
|
|
3
3
|
URL = "http://localhost:8081/"
|
4
|
+
CONNECTION_ERROR_URL = "http://random-domain-blah.com/"
|
4
5
|
DELAY = 0.25
|
5
6
|
|
6
7
|
describe EventMachine::HttpRequest do
|
@@ -58,4 +59,26 @@ describe EventMachine::HttpRequest do
|
|
58
59
|
EventMachine.stop
|
59
60
|
end
|
60
61
|
end
|
62
|
+
|
63
|
+
it "should terminate immediately in case of connection errors" do
|
64
|
+
EventMachine.synchrony do
|
65
|
+
response = EventMachine::HttpRequest.new(CONNECTION_ERROR_URL).get
|
66
|
+
response.error.should_not be_nil
|
67
|
+
|
68
|
+
EventMachine.stop
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should process inactivity timeout correctly" do
|
73
|
+
EventMachine.synchrony do
|
74
|
+
s = StubServer.new("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nFoo", 5)
|
75
|
+
|
76
|
+
start = now
|
77
|
+
r = EventMachine::HttpRequest.new(URL, :inactivity_timeout => 0.5).get
|
78
|
+
(now - start.to_f).should be_within(0.2).of(0.5)
|
79
|
+
|
80
|
+
s.stop
|
81
|
+
EventMachine.stop
|
82
|
+
end
|
83
|
+
end
|
61
84
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "spec/helper/all"
|
2
|
+
require "tempfile"
|
3
|
+
|
4
|
+
DELAY = 0.1
|
5
|
+
|
6
|
+
describe EventMachine::Synchrony do
|
7
|
+
before(:each) { @temp_file = Tempfile.new("stdout") }
|
8
|
+
after(:each) { @temp_file.unlink }
|
9
|
+
|
10
|
+
def with_input(string = "", &block)
|
11
|
+
string = "#{string}\n"
|
12
|
+
|
13
|
+
@temp_file.write string
|
14
|
+
@temp_file.flush
|
15
|
+
|
16
|
+
EM::Synchrony.add_timer(DELAY) do
|
17
|
+
original_stdin = STDIN
|
18
|
+
STDIN.reopen(@temp_file.path)
|
19
|
+
|
20
|
+
block.call if block_given?
|
21
|
+
|
22
|
+
STDIN.reopen(original_stdin)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "waits for input" do
|
27
|
+
EM.synchrony do
|
28
|
+
start = now
|
29
|
+
|
30
|
+
with_input do
|
31
|
+
EM::Synchrony.gets
|
32
|
+
|
33
|
+
(now - start.to_f).should be_within(DELAY * 0.15).of(DELAY)
|
34
|
+
end
|
35
|
+
|
36
|
+
EM.add_timer(DELAY * 2) { EM.stop }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "trails input with a newline to emulate gets" do
|
41
|
+
EM.synchrony do
|
42
|
+
with_input("Hello") do
|
43
|
+
EM::Synchrony.gets.should == "Hello\n"
|
44
|
+
end
|
45
|
+
|
46
|
+
EM.add_timer(DELAY * 2) { EM.stop }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should stop after the first line" do
|
51
|
+
EM.synchrony do
|
52
|
+
with_input("Hello\nWorld!") do
|
53
|
+
EM::Synchrony.gets.should == "Hello\n"
|
54
|
+
end
|
55
|
+
|
56
|
+
EM.add_timer(DELAY * 2) { EM.stop }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|