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