em-synchrony 0.1.5 → 0.2.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/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
|