em-synchrony 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +15 -0
- data/README.md +54 -30
- data/Rakefile +3 -19
- data/em-synchrony.gemspec +23 -0
- data/examples/bitly.rb +25 -0
- data/examples/go/README.md +50 -0
- data/examples/go/channel.go +17 -0
- data/examples/go/channel.rb +14 -0
- data/examples/go/consumer-publisher.go +34 -0
- data/examples/go/consumer-publisher.rb +62 -0
- data/examples/go/go.rb +49 -0
- data/examples/nethttp.rb +13 -0
- data/lib/em-synchrony.rb +35 -1
- data/lib/em-synchrony/connection_pool.rb +2 -2
- data/lib/em-synchrony/em-bitly.rb +33 -1
- data/lib/em-synchrony/em-memcache.rb +19 -0
- data/lib/em-synchrony/em-mongo.rb +46 -0
- data/lib/em-synchrony/em-multi.rb +8 -4
- data/lib/em-synchrony/em-mysqlplus.rb +5 -2
- data/lib/em-synchrony/em-redis.rb +45 -0
- data/lib/em-synchrony/mongo.rb +14 -0
- data/lib/em-synchrony/mongoid.rb +12 -0
- data/lib/em-synchrony/tcpsocket.rb +110 -0
- data/lib/em-synchrony/thread.rb +26 -0
- data/spec/em-mongo_spec.rb +69 -0
- data/spec/helper/all.rb +13 -3
- data/spec/inlinesync_spec.rb +37 -0
- data/spec/memcache_spec.rb +33 -0
- data/spec/mongo_spec.rb +12 -0
- data/spec/mysqlplus_spec.rb +12 -1
- data/spec/redis_spec.rb +66 -0
- data/spec/tcpsocket_spec.rb +20 -0
- metadata +45 -13
- data/VERSION +0 -1
data/examples/nethttp.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "lib/em-synchrony"
|
2
|
+
require "net/http"
|
3
|
+
|
4
|
+
$VERBOSE = nil
|
5
|
+
|
6
|
+
EM.synchrony do
|
7
|
+
# monkey patch default Socket code to use EventMachine Sockets instead
|
8
|
+
TCPSocket = EventMachine::Synchrony::TCPSocket
|
9
|
+
|
10
|
+
Net::HTTP.get_print 'www.google.com', '/index.html'
|
11
|
+
|
12
|
+
EM.stop
|
13
|
+
end
|
data/lib/em-synchrony.rb
CHANGED
@@ -8,9 +8,11 @@ rescue LoadError => error
|
|
8
8
|
raise error unless defined? Fiber
|
9
9
|
end
|
10
10
|
|
11
|
+
require "em-synchrony/thread"
|
11
12
|
require "em-synchrony/em-multi"
|
13
|
+
require "em-synchrony/tcpsocket"
|
12
14
|
require "em-synchrony/connection_pool"
|
13
|
-
|
15
|
+
require "em-synchrony/iterator" unless EventMachine::VERSION >= '0.12.10'
|
14
16
|
|
15
17
|
module EventMachine
|
16
18
|
|
@@ -24,4 +26,36 @@ module EventMachine
|
|
24
26
|
self.run(context, tail)
|
25
27
|
end
|
26
28
|
|
29
|
+
module Synchrony
|
30
|
+
|
31
|
+
# sync is a close relative to inclineCallbacks from Twisted (Python)
|
32
|
+
#
|
33
|
+
# Synchrony.sync allows you to write sequential code while using asynchronous
|
34
|
+
# or callback-based methods under the hood. Example:
|
35
|
+
#
|
36
|
+
# result = EM::Synchrony.sync EventMachine::HttpRequest.new(URL).get
|
37
|
+
# p result.response
|
38
|
+
#
|
39
|
+
# As long as the asynchronous function returns a Deferrable object, which
|
40
|
+
# has a "callback" and an "errback", the sync methond will automatically
|
41
|
+
# yield and automatically resume your code (via Fibers) when the call
|
42
|
+
# either succeeds or fails. You do not need to patch or modify the
|
43
|
+
# Deferrable object, simply pass it to EM::Synchrony.sync
|
44
|
+
#
|
45
|
+
def self.sync(df)
|
46
|
+
f = Fiber.current
|
47
|
+
df.callback { |r| f.resume(r) }
|
48
|
+
df.errback { |r| f.resume(r) }
|
49
|
+
|
50
|
+
Fiber.yield
|
51
|
+
end
|
52
|
+
|
53
|
+
# a Fiber-aware sleep function using an EM timer
|
54
|
+
def self.sleep( secs )
|
55
|
+
fiber = Fiber.current
|
56
|
+
EM::Timer.new(secs) { fiber.resume }
|
57
|
+
Fiber.yield
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
27
61
|
end
|
@@ -43,7 +43,7 @@ module EventMachine
|
|
43
43
|
end
|
44
44
|
|
45
45
|
# Release connection assigned to the supplied fiber and
|
46
|
-
# resume any other pending connections (which will
|
46
|
+
# resume any other pending connections (which will
|
47
47
|
# immediately try to run acquire on the pool)
|
48
48
|
def release(fiber)
|
49
49
|
@available.push(@reserved.delete(fiber.object_id))
|
@@ -73,7 +73,7 @@ module EventMachine
|
|
73
73
|
df.callback { release(fiber) }
|
74
74
|
df.errback { release(fiber) }
|
75
75
|
end
|
76
|
-
|
76
|
+
|
77
77
|
df
|
78
78
|
end
|
79
79
|
end
|
@@ -1,8 +1,10 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
1
3
|
begin
|
2
4
|
require "em-synchrony/em-http"
|
3
5
|
require "bitly"
|
4
6
|
rescue LoadError => error
|
5
|
-
raise "Missing EM-Synchrony dependencies: gem install em-http-request; gem install bitly -v=0.
|
7
|
+
raise "Missing EM-Synchrony dependencies: gem install em-http-request; gem install bitly -v=0.5.0"
|
6
8
|
end
|
7
9
|
|
8
10
|
module Bitly
|
@@ -23,4 +25,34 @@ module Bitly
|
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
28
|
+
|
29
|
+
module V3
|
30
|
+
class Client
|
31
|
+
class << self
|
32
|
+
def get(method, query)
|
33
|
+
query_values=[]
|
34
|
+
query[:query].each do |key, value|
|
35
|
+
query_values << "#{key}=#{CGI::escape(value.to_s)}"
|
36
|
+
end
|
37
|
+
query_values=query_values.join('&')
|
38
|
+
request=(method[0]=='/' ? "#{base_uri}#{method}" : method)
|
39
|
+
request=(request.include?('?') ? "#{request}&#{query_values}" : "#{request}?#{query_values}")
|
40
|
+
|
41
|
+
http = EventMachine::HttpRequest.new(request).get(:timeout => 100)
|
42
|
+
response = if (http.response_header.status == 200)
|
43
|
+
Crack::JSON.parse(http.response)
|
44
|
+
else
|
45
|
+
{'errorMessage' => 'JSON Parse Error(Bit.ly messed up)', 'errorCode' => 69, 'statusCode' => 'ERROR'}
|
46
|
+
end
|
47
|
+
|
48
|
+
if response['status_code'] == 200
|
49
|
+
return response
|
50
|
+
else
|
51
|
+
raise BitlyError.new(response['status_txt'], response['status_code'])
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
26
58
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module EventMachine::Protocols::Memcache
|
2
|
+
%w[delete get set].each do |type|
|
3
|
+
module_eval %[
|
4
|
+
alias :a#{type} :#{type}
|
5
|
+
def #{type}(*params, &blk)
|
6
|
+
f = Fiber.current
|
7
|
+
self.a#{type}(*params) { |*cb_params| f.resume(*cb_params) }
|
8
|
+
|
9
|
+
Fiber.yield
|
10
|
+
end
|
11
|
+
]
|
12
|
+
end
|
13
|
+
|
14
|
+
alias :aget_hash :get_hash
|
15
|
+
def get_hash(*keys)
|
16
|
+
index = 0
|
17
|
+
get(*keys).inject({}) { |h,v| h[keys[index]] = v; index += 1; h }
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
begin
|
2
|
+
require "em-mongo"
|
3
|
+
rescue LoadError => error
|
4
|
+
raise "Missing EM-Synchrony dependency: gem install em-mongo"
|
5
|
+
end
|
6
|
+
|
7
|
+
module EM
|
8
|
+
module Mongo
|
9
|
+
|
10
|
+
class Connection
|
11
|
+
def initialize(host = DEFAULT_IP, port = DEFAULT_PORT, timeout = nil, opts = {})
|
12
|
+
f = Fiber.current
|
13
|
+
|
14
|
+
@em_connection = EMConnection.connect(host, port, timeout, opts)
|
15
|
+
@db = {}
|
16
|
+
|
17
|
+
# establish connection before returning
|
18
|
+
EM.next_tick { f.resume }
|
19
|
+
Fiber.yield
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Collection
|
24
|
+
|
25
|
+
alias :afind :find
|
26
|
+
def find(selector={}, opts={})
|
27
|
+
|
28
|
+
f = Fiber.current
|
29
|
+
cb = proc { |res| f.resume(res) }
|
30
|
+
|
31
|
+
skip = opts.delete(:skip) || 0
|
32
|
+
limit = opts.delete(:limit) || 0
|
33
|
+
|
34
|
+
@connection.find(@name, skip, limit, selector, nil, &cb)
|
35
|
+
Fiber.yield
|
36
|
+
end
|
37
|
+
|
38
|
+
alias :afirst :first
|
39
|
+
def first(selector={}, opts={})
|
40
|
+
opts[:limit] = 1
|
41
|
+
find(selector, opts).first
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -13,23 +13,27 @@ module EventMachine
|
|
13
13
|
def add(name, conn)
|
14
14
|
fiber = Fiber.current
|
15
15
|
conn.callback { @responses[:callback][name] = conn; check_progress(fiber) }
|
16
|
-
conn.errback { @responses[:errback][name]
|
16
|
+
conn.errback { @responses[:errback][name] = conn; check_progress(fiber) }
|
17
17
|
|
18
18
|
@requests.push(conn)
|
19
19
|
end
|
20
20
|
|
21
|
+
def finished?
|
22
|
+
(@responses[:callback].size + @responses[:errback].size) == @requests.size
|
23
|
+
end
|
24
|
+
|
21
25
|
def perform
|
22
|
-
Fiber.yield
|
26
|
+
Fiber.yield unless finished?
|
23
27
|
end
|
24
28
|
|
25
29
|
protected
|
26
30
|
|
27
31
|
def check_progress(fiber)
|
28
|
-
if
|
32
|
+
if finished?
|
29
33
|
succeed
|
30
34
|
|
31
35
|
# continue processing
|
32
|
-
fiber.resume(self)
|
36
|
+
fiber.resume(self) if fiber.alive? && fiber != Fiber.current
|
33
37
|
end
|
34
38
|
end
|
35
39
|
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
begin
|
2
|
+
require "mysqlplus"
|
2
3
|
require "em-mysqlplus"
|
3
4
|
rescue LoadError => error
|
4
|
-
raise "Missing EM-Synchrony dependency: gem install em-mysqlplus"
|
5
|
+
raise "Missing EM-Synchrony dependency: gem install mysqlplus, gem install em-mysqlplus"
|
5
6
|
end
|
6
7
|
|
7
8
|
module EventMachine
|
@@ -17,7 +18,9 @@ module EventMachine
|
|
17
18
|
|
18
19
|
@connection.execute(sql, cb, eb)
|
19
20
|
|
20
|
-
Fiber.yield
|
21
|
+
result = Fiber.yield
|
22
|
+
raise result if Mysql::Error == result.class
|
23
|
+
result
|
21
24
|
end
|
22
25
|
|
23
26
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
begin
|
2
|
+
require 'em-redis'
|
3
|
+
rescue LoadError => error
|
4
|
+
raise 'Missing EM-Synchrony dependency: gem install em-redis'
|
5
|
+
end
|
6
|
+
|
7
|
+
module EventMachine
|
8
|
+
module Protocols
|
9
|
+
module Redis
|
10
|
+
attr_reader :connected
|
11
|
+
|
12
|
+
class << self
|
13
|
+
alias :aconnect :connect
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.connect(*args)
|
17
|
+
f = Fiber.current
|
18
|
+
|
19
|
+
conn = self.aconnect(*args)
|
20
|
+
conn.callback { f.resume(conn) }
|
21
|
+
|
22
|
+
Fiber.yield
|
23
|
+
end
|
24
|
+
|
25
|
+
def call_command(argv, &blk)
|
26
|
+
# async commands are 'a' prefixed, but do check
|
27
|
+
# for the 'add' command corner case (ugh)
|
28
|
+
if argv.first.size > 3 && argv.first[0] == 'a'
|
29
|
+
argv[0] = argv[0].to_s.slice(1,argv[0].size)
|
30
|
+
callback { raw_call_command(argv, &blk) }
|
31
|
+
|
32
|
+
else
|
33
|
+
# wrap response blocks into fiber callbacks
|
34
|
+
# to emulate the sync api
|
35
|
+
f = Fiber.current
|
36
|
+
blk = proc { |v| v } if !block_given?
|
37
|
+
clb = proc { |v| f.resume(blk.call(v)) }
|
38
|
+
|
39
|
+
callback { raw_call_command(argv, &clb) }
|
40
|
+
Fiber.yield
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
begin
|
2
|
+
require "mongo/connection"
|
3
|
+
rescue LoadError => error
|
4
|
+
raise "Missing EM-Synchrony dependency: gem install mongo"
|
5
|
+
end
|
6
|
+
|
7
|
+
# monkey-patch Mongo to use em-synchrony's socket and thread classs
|
8
|
+
silence_warnings do
|
9
|
+
class Mongo::Connection
|
10
|
+
TCPSocket = ::EventMachine::Synchrony::TCPSocket
|
11
|
+
Mutex = ::EventMachine::Synchrony::Thread::Mutex
|
12
|
+
ConditionVariable = ::EventMachine::Synchrony::Thread::ConditionVariable
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "em-synchrony/mongo"
|
2
|
+
|
3
|
+
# disable mongoid connection initializer
|
4
|
+
if defined? Rails
|
5
|
+
module Rails
|
6
|
+
module Mongoid
|
7
|
+
class Railtie < Rails::Railtie
|
8
|
+
initializers.delete_if { |i| i.name == 'verify that mongoid is configured' }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module Synchrony
|
3
|
+
class TCPSocket < Connection
|
4
|
+
class << self
|
5
|
+
alias_method :_old_new, :new
|
6
|
+
def new(*args)
|
7
|
+
if args.size == 1
|
8
|
+
_old_new *args
|
9
|
+
else
|
10
|
+
socket = EventMachine::connect( *args[0..1], self )
|
11
|
+
raise SocketError unless socket.sync(:in) # wait for connection
|
12
|
+
socket
|
13
|
+
end
|
14
|
+
end
|
15
|
+
alias :open :new
|
16
|
+
end
|
17
|
+
|
18
|
+
def post_init
|
19
|
+
@in_buff, @out_buff = '', ''
|
20
|
+
@in_req = @out_req = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def closed?
|
24
|
+
@in_req.nil? && @out_req.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
# direction must be one of :in or :out
|
28
|
+
def sync(direction)
|
29
|
+
req = self.instance_variable_set "@#{direction.to_s}_req", EventMachine::DefaultDeferrable.new
|
30
|
+
EventMachine::Synchrony.sync req
|
31
|
+
end
|
32
|
+
|
33
|
+
# TCPSocket interface
|
34
|
+
def setsockopt(level, name, value); end
|
35
|
+
|
36
|
+
def send(msg, flags = 0)
|
37
|
+
raise "Unknown flags in send(): #{flags}" if flags.nonzero?
|
38
|
+
len = msg.bytesize
|
39
|
+
write_data(msg) or sync(:out) or raise(IOError)
|
40
|
+
len
|
41
|
+
end
|
42
|
+
alias_method :write, :send
|
43
|
+
|
44
|
+
def read(num_bytes = 16*1024, dest = nil)
|
45
|
+
read_data(num_bytes, dest) or sync(:in) or raise(IOError)
|
46
|
+
end
|
47
|
+
alias_method :read_nonblock, :read
|
48
|
+
alias_method :recv, :read
|
49
|
+
|
50
|
+
def close
|
51
|
+
close_connection true
|
52
|
+
@in_req = @out_req = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
# EventMachine interface
|
56
|
+
def connection_completed
|
57
|
+
@in_req.succeed self
|
58
|
+
end
|
59
|
+
|
60
|
+
def unbind
|
61
|
+
@in_req.fail nil if @in_req
|
62
|
+
@out_req.fail nil if @out_req
|
63
|
+
end
|
64
|
+
|
65
|
+
def receive_data(data)
|
66
|
+
@in_buff << data
|
67
|
+
if @in_req && (data = read_data)
|
68
|
+
@in_req.succeed data
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
protected
|
73
|
+
def read_data(num_bytes = nil, dest = nil)
|
74
|
+
@read_bytes = num_bytes if num_bytes
|
75
|
+
@read_dest = dest if dest
|
76
|
+
if @in_buff.size > 0
|
77
|
+
data = @in_buff.slice!(0, @read_bytes)
|
78
|
+
@read_bytes = 0
|
79
|
+
|
80
|
+
if @read_dest
|
81
|
+
@read_dest.replace data
|
82
|
+
@read_dest = nil
|
83
|
+
end
|
84
|
+
data
|
85
|
+
else
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def write_data(data = nil)
|
91
|
+
@out_buff += data if data
|
92
|
+
|
93
|
+
loop do
|
94
|
+
if @out_buff.empty?
|
95
|
+
@out_req.succeed true if @out_req
|
96
|
+
return true
|
97
|
+
end
|
98
|
+
|
99
|
+
if self.get_outbound_data_size > EventMachine::FileStreamer::BackpressureLevel
|
100
|
+
EventMachine::next_tick { write_data }
|
101
|
+
return false
|
102
|
+
else
|
103
|
+
len = [@out_buff.bytesize, EventMachine::FileStreamer::ChunkSize].min
|
104
|
+
self.send_data @out_buff.slice!( 0, len )
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module EventMachine
|
2
|
+
module Synchrony
|
3
|
+
module Thread
|
4
|
+
|
5
|
+
# Fiber-aware drop-in replacements for thread objects
|
6
|
+
class Mutex
|
7
|
+
def synchronize( &blk )
|
8
|
+
blk.call
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ConditionVariable
|
13
|
+
def wait( mutex )
|
14
|
+
@deferrable = EventMachine::DefaultDeferrable.new
|
15
|
+
EventMachine::Synchrony.sync @deferrable
|
16
|
+
@deferrable = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def signal
|
20
|
+
@deferrable and @deferrable.succeed
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|