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.
@@ -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