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.
@@ -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
- socket = EventMachine::connect(*args[0..1], self)
11
- raise SocketError unless socket.sync(:in) # wait for connection
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
- @in_req.nil? && @out_req.nil?
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 = 0)
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
- write_data(msg) or sync(:out) or raise(IOError)
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
- alias_method :write, :send
80
+
81
+ def write(msg)
82
+ send(msg,0)
83
+ end
43
84
 
44
- def read(num_bytes = 16*1024, dest = nil)
45
- read_data(num_bytes, dest) or sync(:in) or raise(IOError)
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
- def unbind
61
- @in_req.fail nil if @in_req
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
- close
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 read_data(num_bytes = nil, dest = nil)
75
- @read_bytes = num_bytes if num_bytes
76
- @read_dest = dest if dest
77
- if @in_buff.size > 0
78
- data = @in_buff.slice!(0, @read_bytes)
79
- @read_bytes = 0
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
- if @read_dest
82
- @read_dest.replace data
83
- @read_dest = nil
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