em-synchrony 1.0.1 → 1.0.2
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/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
|