em-synchrony 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +173 -173
- data/em-synchrony.gemspec +1 -1
- data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +47 -47
- data/lib/em-synchrony.rb +1 -1
- data/lib/em-synchrony/activerecord.rb +102 -102
- data/lib/em-synchrony/amqp.rb +180 -180
- data/lib/em-synchrony/em-hiredis.rb +103 -103
- data/lib/em-synchrony/tcpsocket.rb +157 -28
- data/spec/activerecord_spec.rb +108 -108
- data/spec/amqp_spec.rb +146 -146
- data/spec/tcpsocket_spec.rb +401 -18
- data/spec/timer_spec.rb +7 -0
- metadata +10 -5
@@ -1,103 +1,103 @@
|
|
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
|
-
|
10
|
-
def self.connect(uri = nil)
|
11
|
-
client = setup(uri)
|
12
|
-
EM::Synchrony.sync client.connect
|
13
|
-
client
|
14
|
-
end
|
15
|
-
|
16
|
-
class Client
|
17
|
-
def self.connect(host = 'localhost', port = 6379)
|
18
|
-
conn = new(host, port)
|
19
|
-
EM::Synchrony.sync conn.connect
|
20
|
-
conn
|
21
|
-
end
|
22
|
-
|
23
|
-
def connect
|
24
|
-
@connection = EM.connect(@host, @port, Connection, @host, @port)
|
25
|
-
|
26
|
-
@connection.on(:closed) do
|
27
|
-
if @connected
|
28
|
-
@defs.each { |d| d.fail("Redis disconnected") }
|
29
|
-
@defs = []
|
30
|
-
@deferred_status = nil
|
31
|
-
@connected = false
|
32
|
-
unless @closing_connection
|
33
|
-
@reconnecting = true
|
34
|
-
reconnect
|
35
|
-
end
|
36
|
-
else
|
37
|
-
unless @closing_connection
|
38
|
-
EM.add_timer(1) { reconnect }
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
@connection.on(:connected) do
|
44
|
-
Fiber.new do
|
45
|
-
@connected = true
|
46
|
-
|
47
|
-
auth(@password) if @password
|
48
|
-
select(@db) if @db
|
49
|
-
|
50
|
-
@subs.each { |s| method_missing(:subscribe, s) }
|
51
|
-
@psubs.each { |s| method_missing(:psubscribe, s) }
|
52
|
-
succeed
|
53
|
-
|
54
|
-
if @reconnecting
|
55
|
-
@reconnecting = false
|
56
|
-
emit(:reconnected)
|
57
|
-
end
|
58
|
-
end.resume
|
59
|
-
end
|
60
|
-
|
61
|
-
@connection.on(:message) do |reply|
|
62
|
-
if RuntimeError === reply
|
63
|
-
raise "Replies out of sync: #{reply.inspect}" if @defs.empty?
|
64
|
-
deferred = @defs.shift
|
65
|
-
deferred.fail(reply) if deferred
|
66
|
-
else
|
67
|
-
if reply && PUBSUB_MESSAGES.include?(reply[0]) # reply can be nil
|
68
|
-
kind, subscription, d1, d2 = *reply
|
69
|
-
|
70
|
-
case kind.to_sym
|
71
|
-
when :message
|
72
|
-
emit(:message, subscription, d1)
|
73
|
-
when :pmessage
|
74
|
-
emit(:pmessage, subscription, d1, d2)
|
75
|
-
end
|
76
|
-
else
|
77
|
-
if @defs.empty?
|
78
|
-
if @monitoring
|
79
|
-
emit(:monitor, reply)
|
80
|
-
else
|
81
|
-
raise "Replies out of sync: #{reply.inspect}"
|
82
|
-
end
|
83
|
-
else
|
84
|
-
deferred = @defs.shift
|
85
|
-
deferred.succeed(reply) if deferred
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
@connected = false
|
92
|
-
@reconnecting = false
|
93
|
-
|
94
|
-
return self
|
95
|
-
end
|
96
|
-
|
97
|
-
alias :old_method_missing :method_missing
|
98
|
-
def method_missing(sym, *args)
|
99
|
-
EM::Synchrony.sync old_method_missing(sym, *args)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
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
|
+
|
10
|
+
def self.connect(uri = nil)
|
11
|
+
client = setup(uri)
|
12
|
+
EM::Synchrony.sync client.connect
|
13
|
+
client
|
14
|
+
end
|
15
|
+
|
16
|
+
class Client
|
17
|
+
def self.connect(host = 'localhost', port = 6379)
|
18
|
+
conn = new(host, port)
|
19
|
+
EM::Synchrony.sync conn.connect
|
20
|
+
conn
|
21
|
+
end
|
22
|
+
|
23
|
+
def connect
|
24
|
+
@connection = EM.connect(@host, @port, Connection, @host, @port)
|
25
|
+
|
26
|
+
@connection.on(:closed) do
|
27
|
+
if @connected
|
28
|
+
@defs.each { |d| d.fail("Redis disconnected") }
|
29
|
+
@defs = []
|
30
|
+
@deferred_status = nil
|
31
|
+
@connected = false
|
32
|
+
unless @closing_connection
|
33
|
+
@reconnecting = true
|
34
|
+
reconnect
|
35
|
+
end
|
36
|
+
else
|
37
|
+
unless @closing_connection
|
38
|
+
EM.add_timer(1) { reconnect }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
@connection.on(:connected) do
|
44
|
+
Fiber.new do
|
45
|
+
@connected = true
|
46
|
+
|
47
|
+
auth(@password) if @password
|
48
|
+
select(@db) if @db
|
49
|
+
|
50
|
+
@subs.each { |s| method_missing(:subscribe, s) }
|
51
|
+
@psubs.each { |s| method_missing(:psubscribe, s) }
|
52
|
+
succeed
|
53
|
+
|
54
|
+
if @reconnecting
|
55
|
+
@reconnecting = false
|
56
|
+
emit(:reconnected)
|
57
|
+
end
|
58
|
+
end.resume
|
59
|
+
end
|
60
|
+
|
61
|
+
@connection.on(:message) do |reply|
|
62
|
+
if RuntimeError === reply
|
63
|
+
raise "Replies out of sync: #{reply.inspect}" if @defs.empty?
|
64
|
+
deferred = @defs.shift
|
65
|
+
deferred.fail(reply) if deferred
|
66
|
+
else
|
67
|
+
if reply && PUBSUB_MESSAGES.include?(reply[0]) # reply can be nil
|
68
|
+
kind, subscription, d1, d2 = *reply
|
69
|
+
|
70
|
+
case kind.to_sym
|
71
|
+
when :message
|
72
|
+
emit(:message, subscription, d1)
|
73
|
+
when :pmessage
|
74
|
+
emit(:pmessage, subscription, d1, d2)
|
75
|
+
end
|
76
|
+
else
|
77
|
+
if @defs.empty?
|
78
|
+
if @monitoring
|
79
|
+
emit(:monitor, reply)
|
80
|
+
else
|
81
|
+
raise "Replies out of sync: #{reply.inspect}"
|
82
|
+
end
|
83
|
+
else
|
84
|
+
deferred = @defs.shift
|
85
|
+
deferred.succeed(reply) if deferred
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
@connected = false
|
92
|
+
@reconnecting = false
|
93
|
+
|
94
|
+
return self
|
95
|
+
end
|
96
|
+
|
97
|
+
alias :old_method_missing :method_missing
|
98
|
+
def method_missing(sym, *args)
|
99
|
+
EM::Synchrony.sync old_method_missing(sym, *args)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -7,8 +7,22 @@ module EventMachine
|
|
7
7
|
if args.size == 1
|
8
8
|
_old_new(*args)
|
9
9
|
else
|
10
|
-
|
11
|
-
|
10
|
+
# In TCPSocket, new against an unknown hostname raises SocketError with
|
11
|
+
# a message "getaddrinfo: nodename nor servname provided, or not known".
|
12
|
+
# In EM, connect against an unknown hostname raises EM::ConnectionError
|
13
|
+
# with a message of "unable to resolve server address"
|
14
|
+
begin
|
15
|
+
socket = EventMachine::connect(*args[0..1], self)
|
16
|
+
rescue EventMachine::ConnectionError => e
|
17
|
+
raise SocketError, e.message
|
18
|
+
end
|
19
|
+
# In TCPSocket, new against a closed port raises Errno::ECONNREFUSED.
|
20
|
+
# In EM, connect against a closed port result in a call to unbind with
|
21
|
+
# a reason param of Errno::ECONNREFUSED as a class, not an instance.
|
22
|
+
unless socket.sync(:in) # wait for connection
|
23
|
+
raise socket.unbind_reason.new if socket.unbind_reason.is_a? Class
|
24
|
+
raise SocketError
|
25
|
+
end
|
12
26
|
socket
|
13
27
|
end
|
14
28
|
end
|
@@ -17,77 +31,186 @@ module EventMachine
|
|
17
31
|
|
18
32
|
def post_init
|
19
33
|
@in_buff, @out_buff = '', ''
|
20
|
-
@in_req = @out_req = nil
|
34
|
+
@in_req = @out_req = @unbind_reason = @read_type = nil
|
35
|
+
@opening = true
|
36
|
+
@closed = @remote_closed = false
|
21
37
|
end
|
22
38
|
|
23
39
|
def closed?
|
24
|
-
|
40
|
+
# In TCPSocket,
|
41
|
+
# closed? on a remotely closed socket, when we've not yet read EOF, returns false
|
42
|
+
# closed? on a remotely closed socket, when we've read EOF, returns false
|
43
|
+
# closed? on a socket after #close, returns true
|
44
|
+
# Therefore, we set @close to true when #close is called, but not when unbind is.
|
45
|
+
@closed
|
25
46
|
end
|
26
47
|
|
27
48
|
# direction must be one of :in or :out
|
28
49
|
def sync(direction)
|
29
50
|
req = self.instance_variable_set "@#{direction.to_s}_req", EventMachine::DefaultDeferrable.new
|
30
51
|
EventMachine::Synchrony.sync req
|
52
|
+
ensure
|
53
|
+
self.instance_variable_set "@#{direction.to_s}_req", nil
|
31
54
|
end
|
32
55
|
|
33
56
|
# TCPSocket interface
|
34
57
|
def setsockopt(level, name, value); end
|
35
58
|
|
36
|
-
def send(msg, flags
|
59
|
+
def send(msg, flags)
|
37
60
|
raise "Unknown flags in send(): #{flags}" if flags.nonzero?
|
61
|
+
# write(X) on a socket after #close, raises IOError with message "closed stream"
|
62
|
+
# send(X,0) on a socket after #close, raises IOError with message "closed stream"
|
63
|
+
raise IOError, "closed stream" if @closed
|
64
|
+
# the first write(X) on a remotely closed socket, <= than some buffer size, generates no error
|
65
|
+
# the first write(X) on a remotely closed socket, > than some buffer size, generates no error
|
66
|
+
# (on my box this buffer appears to be 80KB)
|
67
|
+
# further write(X) on a remotely closed socket, raises Errno::EPIPE
|
68
|
+
# the first send(X,0) on a remotely closed socket, <= than some buffer size, generates no error
|
69
|
+
# the first send(X,0) on a remotely closed socket, > than some buffer size, generates no error
|
70
|
+
# (on my box this buffer appears to be 80KB)
|
71
|
+
# further send(X,0) on a remotely closed socket, raises Errno::EPIPE
|
72
|
+
raise Errno::EPIPE if @remote_closed
|
73
|
+
|
38
74
|
len = msg.bytesize
|
39
|
-
|
75
|
+
# write(X) on an open socket, where the remote end closes during the write, raises Errno::EPIPE
|
76
|
+
# send(X,0) on an open socket, where the remote end closes during the write, raises Errno::EPIPE
|
77
|
+
write_data(msg) or sync(:out) or raise(Errno::EPIPE)
|
40
78
|
len
|
41
79
|
end
|
42
|
-
|
80
|
+
|
81
|
+
def write(msg)
|
82
|
+
send(msg,0)
|
83
|
+
end
|
43
84
|
|
44
|
-
def read(num_bytes =
|
45
|
-
|
85
|
+
def read(num_bytes = nil, dest = nil)
|
86
|
+
handle_read(:read, num_bytes, dest)
|
46
87
|
end
|
47
88
|
alias_method :read_nonblock, :read
|
48
|
-
alias_method :recv, :read
|
49
89
|
|
90
|
+
def recv(num_bytes, flags = 0)
|
91
|
+
raise "Unknown flags in recv(): #{flags}" if flags.nonzero?
|
92
|
+
handle_read(:recv, num_bytes)
|
93
|
+
end
|
94
|
+
|
50
95
|
def close
|
96
|
+
# close on a closed socket raises IOError with a message of "closed stream"
|
97
|
+
raise IOError, "closed stream" if @closed
|
98
|
+
@closed = true
|
51
99
|
close_connection true
|
52
100
|
@in_req = @out_req = nil
|
101
|
+
# close on an open socket returns nil
|
102
|
+
nil
|
53
103
|
end
|
54
104
|
|
55
105
|
# EventMachine interface
|
56
106
|
def connection_completed
|
107
|
+
@opening = false
|
57
108
|
@in_req.succeed self
|
58
109
|
end
|
59
|
-
|
60
|
-
|
61
|
-
|
110
|
+
|
111
|
+
attr_reader :unbind_reason
|
112
|
+
|
113
|
+
# Can't set a default value for reason (e.g. reason=nil), as in that case
|
114
|
+
# EM fails to pass in the reason argument and you'll always get the default
|
115
|
+
# value.
|
116
|
+
def unbind(reason)
|
117
|
+
@unbind_reason = reason
|
118
|
+
@remote_closed = true unless @closed
|
119
|
+
if @opening
|
120
|
+
@in_req.fail nil if @in_req
|
121
|
+
else
|
122
|
+
@in_req.succeed read_data if @in_req
|
123
|
+
end
|
62
124
|
@out_req.fail nil if @out_req
|
63
|
-
|
125
|
+
@in_req = @out_req = nil
|
64
126
|
end
|
65
127
|
|
66
128
|
def receive_data(data)
|
67
129
|
@in_buff << data
|
68
130
|
if @in_req && (data = read_data)
|
69
|
-
@in_req.succeed data
|
131
|
+
@in_req.succeed data unless data == :block
|
70
132
|
end
|
71
133
|
end
|
72
134
|
|
73
135
|
protected
|
74
|
-
def
|
75
|
-
|
76
|
-
|
77
|
-
if
|
78
|
-
|
79
|
-
|
136
|
+
def handle_read(type, num_bytes, dest=nil)
|
137
|
+
# read(-n) always raises ArgumentError
|
138
|
+
# recv(-n) always raises ArgumentError
|
139
|
+
raise ArgumentError, "negative length #{num_bytes} given" if num_bytes != nil and num_bytes < 0
|
140
|
+
# read(n) on a socket after #close, raises IOError with message "closed stream"
|
141
|
+
# read(0) on a socket after #close, raises IOError with message "closed stream"
|
142
|
+
# read() on a socket after #close, raises IOError with message "closed stream"
|
143
|
+
# recv(n) on a socket after #close, raises IOError with message "closed stream"
|
144
|
+
# recv(0) on a socket after #close, raises IOError with message "closed stream"
|
145
|
+
raise IOError, "closed stream" if @closed
|
146
|
+
# read(0) on an open socket, return ""
|
147
|
+
# read(0) on a remotely closed socket, with buffered data, returns ""
|
148
|
+
# read(0) on a remotely closed socket, with no buffered data, returns ""
|
149
|
+
# recv(0) on an open socket, return ""
|
150
|
+
# recv(0) on a remotely closed socket, with buffered data, returns ""
|
151
|
+
# recv(0) on a remotely closed socket, with no buffered data, returns ""
|
152
|
+
return "" if num_bytes == 0
|
80
153
|
|
81
|
-
|
82
|
-
|
83
|
-
|
154
|
+
@read_type = type
|
155
|
+
@read_bytes = num_bytes
|
156
|
+
@read_dest = dest if dest
|
157
|
+
|
158
|
+
(data = read_data) != :block ? data : sync(:in)
|
159
|
+
end
|
160
|
+
|
161
|
+
def try_read_data
|
162
|
+
if @read_type == :read
|
163
|
+
unless @remote_closed
|
164
|
+
if @read_bytes
|
165
|
+
# read(n) on an open socket, with >= than n buffered data, returns n data
|
166
|
+
if @in_buff.size >= @read_bytes then @in_buff.slice!(0, @read_bytes)
|
167
|
+
# read(n) on an open socket, with < than n buffered data, blocks
|
168
|
+
else :block end
|
169
|
+
else
|
170
|
+
# read() on an open socket, blocks until a remote close and returns all the data sent
|
171
|
+
:block
|
172
|
+
end
|
173
|
+
else
|
174
|
+
if @read_bytes
|
175
|
+
# read(n) on a remotely closed socket, with no buffered data, returns nil
|
176
|
+
if @in_buff.empty? then nil
|
177
|
+
# read(n) on a remotely closed socket, with buffered data, returns the buffered data up to n
|
178
|
+
else @in_buff.slice!(0, @read_bytes) end
|
179
|
+
else
|
180
|
+
# read() on a remotely closed socket, with no buffered data, returns ""
|
181
|
+
if @in_buff.empty? then ""
|
182
|
+
# read() on a remotely closed socket, with buffered data, returns the buffered data
|
183
|
+
else @in_buff.slice!(0, @in_buff.size) end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
else #recv
|
187
|
+
unless @remote_closed
|
188
|
+
# recv(n) on an open socket, with no buffered data, blocks
|
189
|
+
if @in_buff.empty? then :block
|
190
|
+
# recv(n) on an open socket, with < than n buffered data, return the buffered data
|
191
|
+
# recv(n) on an open socket, with >= than n buffered data, returns n data
|
192
|
+
else @in_buff.slice!(0, @read_bytes) end
|
193
|
+
else
|
194
|
+
# recv(n) on a remotely closed socket, with no buffered data, returns ""
|
195
|
+
if @in_buff.empty? then ""
|
196
|
+
# recv(n) on a remotely closed socket, with < than n buffered data, return the buffered data
|
197
|
+
# recv(n) on a remotely closed socket, with >= than n buffered data, returns n data
|
198
|
+
else @in_buff.slice!(0, @read_bytes) end
|
84
199
|
end
|
85
|
-
data
|
86
|
-
else
|
87
|
-
nil
|
88
200
|
end
|
89
201
|
end
|
90
|
-
|
202
|
+
|
203
|
+
def read_data
|
204
|
+
data = try_read_data
|
205
|
+
unless data == :block
|
206
|
+
@read_bytes = 0
|
207
|
+
# read(n,buffer) returns the buffer when it does not return nil or raise an exception
|
208
|
+
data = @read_dest.replace(data) if @read_dest and not data.nil?
|
209
|
+
@read_dest = nil
|
210
|
+
end
|
211
|
+
data
|
212
|
+
end
|
213
|
+
|
91
214
|
def write_data(data = nil)
|
92
215
|
@out_buff += data if data
|
93
216
|
|
@@ -98,9 +221,15 @@ module EventMachine
|
|
98
221
|
end
|
99
222
|
|
100
223
|
if self.get_outbound_data_size > EventMachine::FileStreamer::BackpressureLevel
|
224
|
+
# write(X) on an open socket, where the remote end is not reading, > than some buffer size, blocks
|
225
|
+
# send(X,0) on an open socket, where the remote end is not reading, > than some buffer size, blocks
|
226
|
+
# where that buffer size is EventMachine::FileStreamer::BackpressureLevel, returning false will
|
227
|
+
# cause write/send to block
|
101
228
|
EventMachine::next_tick { write_data }
|
102
229
|
return false
|
103
230
|
else
|
231
|
+
# write(X) on an open socket, where the remote end is not reading, <= than some buffer size, sends and returns
|
232
|
+
# send(X,0) on an open socket, where the remote end is not reading, <= than some buffer size, sends returns
|
104
233
|
len = [@out_buff.bytesize, EventMachine::FileStreamer::ChunkSize].min
|
105
234
|
self.send_data @out_buff.slice!( 0, len )
|
106
235
|
end
|