em-synchrony 0.3.0.beta.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -48,7 +48,7 @@ module EventMachine
|
|
48
48
|
def release(fiber)
|
49
49
|
@available.push(@reserved.delete(fiber.object_id))
|
50
50
|
|
51
|
-
if pending = @pending.
|
51
|
+
if pending = @pending.shift
|
52
52
|
pending.resume
|
53
53
|
end
|
54
54
|
end
|
@@ -62,11 +62,11 @@ module EventMachine
|
|
62
62
|
# once it is complete (assumption: fiber will yield until
|
63
63
|
# data is available, or request is complete)
|
64
64
|
#
|
65
|
-
def method_missing(method, *args)
|
65
|
+
def method_missing(method, *args, &blk)
|
66
66
|
async = (method[0,1] == "a")
|
67
67
|
|
68
68
|
execute(async) do |conn|
|
69
|
-
df = conn.send(method, *args)
|
69
|
+
df = conn.send(method, *args, &blk)
|
70
70
|
|
71
71
|
if async
|
72
72
|
fiber = Fiber.current
|
@@ -0,0 +1,24 @@
|
|
1
|
+
begin
|
2
|
+
require 'em-hiredis'
|
3
|
+
rescue LoadError => error
|
4
|
+
raise 'Missing EM-Synchrony dependency: gem install em-hiredis'
|
5
|
+
end
|
6
|
+
|
7
|
+
module EventMachine
|
8
|
+
module Hiredis
|
9
|
+
class Connection
|
10
|
+
attr_reader :connected
|
11
|
+
|
12
|
+
def self.connect(host = 'localhost', port = 6379)
|
13
|
+
conn = new(host, port)
|
14
|
+
EM::Synchrony.sync conn
|
15
|
+
conn
|
16
|
+
end
|
17
|
+
|
18
|
+
alias :old_method_missing :method_missing
|
19
|
+
def method_missing(sym, *args)
|
20
|
+
EM::Synchrony.sync old_method_missing(sym, *args)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/em-synchrony/em-http.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
begin
|
2
|
-
require "em-http"
|
2
|
+
require "em-http-request"
|
3
3
|
rescue LoadError => error
|
4
4
|
raise "Missing EM-Synchrony dependency: gem install em-http-request"
|
5
5
|
end
|
@@ -12,11 +12,15 @@ module EventMachine
|
|
12
12
|
def #{type}(options = {}, &blk)
|
13
13
|
f = Fiber.current
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
conn = setup_request(:#{type}, options, &blk)
|
16
|
+
if conn.error.nil?
|
17
|
+
conn.callback { f.resume(conn) }
|
18
|
+
conn.errback { f.resume(conn) }
|
19
|
+
|
20
|
+
Fiber.yield
|
21
|
+
else
|
22
|
+
conn
|
23
|
+
end
|
20
24
|
end
|
21
25
|
]
|
22
26
|
end
|
@@ -22,24 +22,91 @@ module EM
|
|
22
22
|
|
23
23
|
class Collection
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
#
|
26
|
+
# The upcoming versions of EM-Mongo change Collection#find's interface: it
|
27
|
+
# now returns a deferrable cursor YAY. This breaks compatibility with past
|
28
|
+
# versions BOO. We'll just choose based on the presence/absence of
|
29
|
+
# EM::Mongo::Cursor YAY
|
30
|
+
#
|
27
31
|
|
28
|
-
|
29
|
-
|
32
|
+
#
|
33
|
+
# em-mongo version > 0.3.6
|
34
|
+
#
|
35
|
+
if defined?(EM::Mongo::Cursor)
|
30
36
|
|
31
|
-
|
32
|
-
|
37
|
+
# afind is the old (async) find
|
38
|
+
# afind_one is rewritten to call afind
|
39
|
+
# find is sync, using a callback on the cursor
|
40
|
+
# find_one is sync, by calling find and taking the first element.
|
41
|
+
# first is sync, an alias for find_one
|
33
42
|
|
34
|
-
|
35
|
-
|
36
|
-
|
43
|
+
alias :afind :find
|
44
|
+
def find(*args)
|
45
|
+
f = Fiber.current
|
46
|
+
cursor = afind(*args)
|
47
|
+
cursor.to_a.callback{ |res| f.resume(res) }
|
48
|
+
Fiber.yield
|
49
|
+
end
|
50
|
+
|
51
|
+
# need to rewrite afind_one manually, as it calls 'find' (reasonably
|
52
|
+
# expecting it to be what is now known as 'afind')
|
53
|
+
|
54
|
+
def afind_one(spec_or_object_id=nil, opts={})
|
55
|
+
spec = case spec_or_object_id
|
56
|
+
when nil
|
57
|
+
{}
|
58
|
+
when BSON::ObjectId
|
59
|
+
{:_id => spec_or_object_id}
|
60
|
+
when Hash
|
61
|
+
spec_or_object_id
|
62
|
+
else
|
63
|
+
raise TypeError, "spec_or_object_id must be an instance of ObjectId or Hash, or nil"
|
64
|
+
end
|
65
|
+
afind(spec, opts.merge(:limit => -1)).next_document
|
66
|
+
end
|
67
|
+
alias :afirst :afind_one
|
68
|
+
|
69
|
+
def find_one(selector={}, opts={})
|
70
|
+
opts[:limit] = 1
|
71
|
+
find(selector, opts).first
|
72
|
+
end
|
73
|
+
alias :first :find_one
|
74
|
+
|
75
|
+
#
|
76
|
+
# em-mongo version <= 0.3.6
|
77
|
+
#
|
78
|
+
else
|
37
79
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
80
|
+
alias :afind :find
|
81
|
+
def find(selector={}, opts={})
|
82
|
+
|
83
|
+
f = Fiber.current
|
84
|
+
cb = proc { |res| f.resume(res) }
|
85
|
+
|
86
|
+
skip = opts.delete(:skip) || 0
|
87
|
+
limit = opts.delete(:limit) || 0
|
88
|
+
order = opts.delete(:order)
|
89
|
+
|
90
|
+
@connection.find(@name, skip, limit, order, selector, nil, &cb)
|
91
|
+
Fiber.yield
|
92
|
+
end
|
93
|
+
|
94
|
+
# need to rewrite afirst manually, as it calls 'find' (reasonably
|
95
|
+
# expecting it to be what is now known as 'afind')
|
96
|
+
|
97
|
+
def afirst(selector={}, opts={}, &blk)
|
98
|
+
opts[:limit] = 1
|
99
|
+
afind(selector, opts) do |res|
|
100
|
+
yield res.first
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def first(selector={}, opts={})
|
105
|
+
opts[:limit] = 1
|
106
|
+
find(selector, opts).first
|
107
|
+
end
|
42
108
|
end
|
109
|
+
|
43
110
|
end
|
44
111
|
|
45
112
|
end
|
@@ -21,13 +21,13 @@ module EventMachine
|
|
21
21
|
|
22
22
|
Fiber.yield
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
alias :old_call_command :call_command
|
26
|
-
|
26
|
+
|
27
|
+
SYNC = ['add', 'auth']
|
27
28
|
def call_command(argv, &blk)
|
28
|
-
# async commands are 'a' prefixed
|
29
|
-
|
30
|
-
if argv.first.size > 3 && argv.first[0] == 'a'
|
29
|
+
# async commands are 'a' prefixed
|
30
|
+
if (argv.first[0] == 'a') && !SYNC.include?(argv.first.to_s)
|
31
31
|
argv[0] = argv[0].to_s.slice(1,argv[0].size)
|
32
32
|
old_call_command(argv, &blk)
|
33
33
|
|
@@ -42,6 +42,29 @@ module EventMachine
|
|
42
42
|
Fiber.yield
|
43
43
|
end
|
44
44
|
end
|
45
|
+
|
46
|
+
# adapted from em-redis' implementation to use
|
47
|
+
# the asynchronous version of mget
|
48
|
+
def amapped_mget(*keys)
|
49
|
+
self.amget(*keys) do |response|
|
50
|
+
result = {}
|
51
|
+
response.each do |value|
|
52
|
+
key = keys.shift
|
53
|
+
result.merge!(key => value) unless value.nil?
|
54
|
+
end
|
55
|
+
yield result if block_given?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def mapped_mget(*keys)
|
60
|
+
f = Fiber.current
|
61
|
+
|
62
|
+
self.amapped_mget(*keys) do |values|
|
63
|
+
f.resume(values)
|
64
|
+
end
|
65
|
+
|
66
|
+
Fiber.yield
|
67
|
+
end
|
45
68
|
end
|
46
69
|
end
|
47
|
-
end
|
70
|
+
end
|
@@ -11,14 +11,18 @@ module Memcached
|
|
11
11
|
Memcached.servers = servers
|
12
12
|
|
13
13
|
f = Fiber.current
|
14
|
+
@w = EM::Timer.new(10.0) { f.resume :error }
|
14
15
|
@t = EM::PeriodicTimer.new(0.01) do
|
15
16
|
if Memcached.usable?
|
17
|
+
@w.cancel
|
16
18
|
@t.cancel
|
17
19
|
f.resume(self)
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
21
|
-
Fiber.yield
|
23
|
+
r = Fiber.yield
|
24
|
+
|
25
|
+
(r == :error) ? (raise Exception.new('Cannot connect to memcached server')) : r
|
22
26
|
end
|
23
27
|
|
24
28
|
%w[add get set delete].each do |type|
|
@@ -30,42 +34,62 @@ module Memcached
|
|
30
34
|
cb = Proc.new { |res| df.succeed(res) }
|
31
35
|
operation Request::#{type.capitalize}, contents, &cb
|
32
36
|
|
33
|
-
|
37
|
+
df
|
34
38
|
end
|
35
39
|
|
36
40
|
def #{type}(contents, &callback)
|
37
41
|
fiber = Fiber.current
|
42
|
+
paused = false
|
43
|
+
|
44
|
+
cb = Proc.new do |res|
|
45
|
+
if paused
|
46
|
+
fiber.resume(res)
|
47
|
+
else
|
48
|
+
return res
|
49
|
+
end
|
50
|
+
end
|
38
51
|
|
39
|
-
df = a#{type}(contents, &
|
52
|
+
df = a#{type}(contents, &cb)
|
40
53
|
df.callback &callback
|
41
54
|
|
55
|
+
paused = true
|
42
56
|
Fiber.yield
|
43
57
|
end
|
44
58
|
]
|
45
59
|
end
|
46
60
|
|
47
61
|
%w[add get set delete].each do |type|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
62
|
+
class_eval %[
|
63
|
+
def amulti_#{type}(contents, &callback)
|
64
|
+
df = EventMachine::DefaultDeferrable.new
|
65
|
+
df.callback &callback
|
66
|
+
|
67
|
+
cb = Proc.new { |res| df.succeed(res) }
|
68
|
+
multi_operation Request::#{type.capitalize}, contents, &cb
|
52
69
|
|
53
|
-
|
54
|
-
|
70
|
+
df
|
71
|
+
end
|
55
72
|
|
56
|
-
|
57
|
-
|
73
|
+
def multi_#{type}(contents, &callback)
|
74
|
+
fiber = Fiber.current
|
75
|
+
paused = false
|
58
76
|
|
59
|
-
|
60
|
-
|
77
|
+
cb = Proc.new do |res|
|
78
|
+
if paused
|
79
|
+
fiber.resume(res)
|
80
|
+
else
|
81
|
+
return res
|
82
|
+
end
|
83
|
+
end
|
61
84
|
|
62
|
-
|
63
|
-
|
85
|
+
df = amulti_#{type}(contents, &cb)
|
86
|
+
df.callback &callback
|
64
87
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
88
|
+
paused = true
|
89
|
+
Fiber.yield
|
90
|
+
end
|
91
|
+
]
|
92
|
+
end
|
69
93
|
|
70
94
|
end
|
71
95
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module Synchrony
|
3
|
+
|
4
|
+
class FiberIterator < EM::Synchrony::Iterator
|
5
|
+
|
6
|
+
# execute each iterator block within its own fiber
|
7
|
+
# and auto-advance the iterator after each call
|
8
|
+
def each(foreach=nil, after=nil, &blk)
|
9
|
+
fe = Proc.new do |obj, iter|
|
10
|
+
Fiber.new { (foreach || blk).call(obj); iter.next }.resume
|
11
|
+
end
|
12
|
+
|
13
|
+
super(fe, after)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module Synchrony
|
3
|
+
class Keyboard
|
4
|
+
attr_reader :current_fiber, :separator
|
5
|
+
|
6
|
+
def gets
|
7
|
+
@current_fiber = Fiber.current
|
8
|
+
EM.open_keyboard(EventMachine::Synchrony::KeyboardHandler, self)
|
9
|
+
|
10
|
+
Fiber.yield
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class KeyboardHandler < EM::Connection
|
15
|
+
include EM::Protocols::LineText2
|
16
|
+
|
17
|
+
def initialize(keyboard)
|
18
|
+
@keyboard = keyboard
|
19
|
+
end
|
20
|
+
|
21
|
+
def receive_line(line)
|
22
|
+
# Simulate gets by adding a trailing line feed
|
23
|
+
@input = "#{line}#{$/}"
|
24
|
+
|
25
|
+
close_connection
|
26
|
+
end
|
27
|
+
|
28
|
+
def unbind
|
29
|
+
@keyboard.current_fiber.resume @input
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/em-synchrony/mongo.rb
CHANGED
@@ -11,4 +11,22 @@ silence_warnings do
|
|
11
11
|
Mutex = ::EventMachine::Synchrony::Thread::Mutex
|
12
12
|
ConditionVariable = ::EventMachine::Synchrony::Thread::ConditionVariable
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
|
+
class Mongo::Pool
|
16
|
+
TCPSocket = ::EventMachine::Synchrony::TCPSocket
|
17
|
+
Mutex = ::EventMachine::Synchrony::Thread::Mutex
|
18
|
+
ConditionVariable = ::EventMachine::Synchrony::Thread::ConditionVariable
|
19
|
+
end
|
20
|
+
|
21
|
+
class EventMachine::Synchrony::MongoTimeoutHandler
|
22
|
+
def self.timeout(op_timeout, ex_class, &block)
|
23
|
+
f = Fiber.current
|
24
|
+
timer = EM::Timer.new(op_timeout) { f.resume(nil) }
|
25
|
+
res = block.call
|
26
|
+
timer.cancel
|
27
|
+
res
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Mongo::TimeoutHandler = EventMachine::Synchrony::MongoTimeoutHandler
|
32
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
begin
|
2
|
+
require 'mysql2/em'
|
3
|
+
rescue LoadError => error
|
4
|
+
raise 'Missing EM-Synchrony dependency: gem install mysql2'
|
5
|
+
end
|
6
|
+
|
7
|
+
module Mysql2
|
8
|
+
module EM
|
9
|
+
class Client
|
10
|
+
|
11
|
+
alias :aquery :query
|
12
|
+
def query(sql, opts={})
|
13
|
+
deferable = aquery(sql, opts)
|
14
|
+
|
15
|
+
f = Fiber.current
|
16
|
+
deferable.callback { |res| f.resume(res) }
|
17
|
+
deferable.errback { |err| f.resume(err) }
|
18
|
+
|
19
|
+
Fiber.yield.tap do |result|
|
20
|
+
raise result if result.is_a?(::Exception)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|