ione-rpc 1.0.0.pre4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2219fec79fa845ec467d80462b2b25b521649afe
4
- data.tar.gz: 0a505a71f3c16bba88a160925c9f4d6d511b51cf
3
+ metadata.gz: 518c9857895955f9c26c4be3d3be5d21773c7525
4
+ data.tar.gz: 6b6c39ffa664e4b290bb5c0ebaeab462dd8fbb23
5
5
  SHA512:
6
- metadata.gz: 2e7f795882099bbc4753f0295085962ddb6c05af8cfd4ae67dc4cdcabb854e5a16816acaf147a7ba43860ea37249e61f06270de638cd47bb2c2cc64bc4930709
7
- data.tar.gz: 89b9886afc1ff2b7fda147ed7fda063489a60052204b5a2ab9a5e334571aefc51520f432ece8aa6e1d2266814f9c944d2cf1403add28bf7623a32442227ed526
6
+ metadata.gz: d3e6c4d47dda87d60ac7034243d0c946af0117bd8fed3b043d23ecbbbc6cb6817253e3b5199bb5967d38b077d51ed40a4991d04de29e93cd7adbb0e12098427e
7
+ data.tar.gz: 9f60335dcdb60a2353fcf07684fdd9e9f17715c679253badb1f6415e4e276de67759bfe427392e48266db2216d7a7be3a816dbb2543e6eeb9152e53c702926bb
@@ -179,31 +179,34 @@ module Ione
179
179
  # error response – that is protocol specific and up to the implementation
180
180
  # to handle), or when there was no connection open.
181
181
  def send_request(request, connection=nil, timeout=nil)
182
- unless connection
182
+ if connection
183
+ chosen_connection = connection
184
+ else
183
185
  @lock.lock
184
186
  begin
185
- connection = choose_connection(@connections, request)
187
+ chosen_connection = choose_connection(@connections, request)
186
188
  ensure
187
189
  @lock.unlock
188
190
  end
189
191
  end
190
- if connection
191
- f = connection.send_message(request, timeout)
192
+ if chosen_connection && !chosen_connection.closed?
193
+ f = chosen_connection.send_message(request, timeout)
192
194
  f = f.fallback do |error|
193
- if error.is_a?(Io::ConnectionClosedError)
195
+ if error.is_a?(Rpc::ConnectionClosedError)
194
196
  @logger.warn('Request failed because the connection closed, retrying') if @logger
195
- send_request(request)
197
+ send_request(request, connection, timeout)
196
198
  else
197
- raise error
199
+ Ione::Future.failed(error)
198
200
  end
199
201
  end
200
202
  f.on_failure do |error|
201
203
  @logger.warn('Request failed: %s' % error.message) if @logger
202
204
  end
203
205
  f
206
+ elsif chosen_connection
207
+ Future.failed(Rpc::RequestNotSentError.new('Not connected'))
204
208
  else
205
- @logger.warn('Could not send request: not connected') if @logger
206
- Future.failed(Io::ConnectionError.new('Not connected'))
209
+ Future.failed(Rpc::NoConnectionError.new('No connection'))
207
210
  end
208
211
  rescue => e
209
212
  Future.failed(e)
@@ -252,6 +255,19 @@ module Ione
252
255
  connections.sample
253
256
  end
254
257
 
258
+ # Override this method to control if, and how many times, the client should
259
+ # attempt to reconnect on connection failures.
260
+ #
261
+ # You can, for example, stop reconnecting after a certain number of attempts.
262
+ #
263
+ # @param [String] host the host to connect to
264
+ # @param [Integer] port the port to connect to
265
+ # @param [Integer] attempts the number of attempts that have been made so
266
+ # far – when 1 or above a connection attempt has just failed, when 0
267
+ # an open connection was abruptly closed and the question is whether or
268
+ # not to attempt to connect again.
269
+ # @return [Boolean] `true` if a connection attempt should be made, `false`
270
+ # otherwise.
255
271
  def reconnect?(host, port, attempts)
256
272
  true
257
273
  end
@@ -288,7 +304,7 @@ module Ione
288
304
  else
289
305
  @logger.info('Not reconnecting to %s:%d' % [host, port]) if @logger
290
306
  remove_host(host, port)
291
- raise e
307
+ Ione::Future.failed(e)
292
308
  end
293
309
  end
294
310
  f.flat_map do |connection|
@@ -326,7 +342,11 @@ module Ione
326
342
  @logger.info(message) if @logger
327
343
  end
328
344
  @lock.synchronize { @connections.delete(connection) }
329
- connect(connection.host, connection.port) if error
345
+ if error && reconnect?(connection.host, connection.port, 0)
346
+ connect(connection.host, connection.port)
347
+ else
348
+ remove_host(connection.host, connection.port)
349
+ end
330
350
  end
331
351
 
332
352
  def normalize_address(host, port)
@@ -36,6 +36,9 @@ module Ione
36
36
  end
37
37
 
38
38
  def send_message(request, timeout=nil)
39
+ if closed?
40
+ return Ione::Future.failed(Rpc::RequestNotSentError.new('Connection closed'))
41
+ end
39
42
  promise = Ione::Promise.new
40
43
  channel = nil
41
44
  @lock.lock
@@ -117,9 +120,23 @@ module Ione
117
120
  end
118
121
 
119
122
  def handle_closed(cause=nil)
120
- error = Io::ConnectionClosedError.new('Connection closed')
121
- promises_to_fail = @lock.synchronize { @channels.reject(&:nil?) }
122
- promises_to_fail.each { |p| p.fail(error) }
123
+ in_flight_promises = nil
124
+ queued_promises = nil
125
+ @lock.lock
126
+ begin
127
+ in_flight_promises = @channels.reject(&:nil?)
128
+ @channels = [nil] * @channels.size
129
+ queued_promises = @queue.map(&:last)
130
+ @queue = []
131
+ ensure
132
+ @lock.unlock
133
+ end
134
+ message = 'Connection closed'
135
+ message << ": #{cause.message}" if cause
136
+ error = Io::ConnectionClosedError.new(message)
137
+ in_flight_promises.each { |p| p.fail(error) }
138
+ error = RequestNotSentError.new(message)
139
+ queued_promises.each { |p| p.fail(error) }
123
140
  super
124
141
  end
125
142
  end
data/lib/ione/rpc/peer.rb CHANGED
@@ -19,6 +19,10 @@ module Ione
19
19
  @current_message = nil
20
20
  end
21
21
 
22
+ def closed?
23
+ @connection.closed?
24
+ end
25
+
22
26
  def on_closed(&listener)
23
27
  @closed_promise.future.on_value(&listener)
24
28
  end
@@ -107,8 +107,9 @@ module Ione
107
107
 
108
108
  def setup_server
109
109
  @io_reactor.bind(@bind_address, @port, @queue_length) do |acceptor|
110
+ @acceptor = acceptor
110
111
  @logger.info('Server listening for connections on %s:%d' % [@bind_address, @port]) if @logger
111
- acceptor.on_accept do |connection|
112
+ @acceptor.on_accept do |connection|
112
113
  @logger.info('Connection from %s:%d accepted' % [connection.host, connection.port]) if @logger
113
114
  peer = ServerPeer.new(connection, @codec, self, @logger)
114
115
  peer.on_closed do
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ione
4
4
  module Rpc
5
- VERSION = '1.0.0.pre4'.freeze
5
+ VERSION = '1.0.0'.freeze
6
6
  end
7
7
  end
data/lib/ione/rpc.rb CHANGED
@@ -6,6 +6,9 @@ require 'ione'
6
6
  module Ione
7
7
  module Rpc
8
8
  TimeoutError = Class.new(StandardError)
9
+ NoConnectionError = Class.new(StandardError)
10
+ ConnectionClosedError = Io::ConnectionClosedError
11
+ RequestNotSentError = Class.new(ConnectionClosedError)
9
12
  end
10
13
  end
11
14
 
@@ -59,12 +59,31 @@ module Ione
59
59
  include_examples 'peers'
60
60
 
61
61
  context 'when the connection closes' do
62
- it 'fails all outstanding requests when closing' do
62
+ it 'fails all in-flight requests' do
63
63
  f1 = peer.send_message('hello')
64
64
  f2 = peer.send_message('world')
65
65
  connection.closed_listener.call
66
- expect { f1.value }.to raise_error(Io::ConnectionClosedError)
67
- expect { f2.value }.to raise_error(Io::ConnectionClosedError)
66
+ expect { f1.value }.to raise_error(Io::ConnectionClosedError, /connection closed/i)
67
+ expect { f2.value }.to raise_error(Io::ConnectionClosedError, /connection closed/i)
68
+ end
69
+
70
+ it 'includes the error message from the cause when there is one' do
71
+ f = peer.send_message('hello')
72
+ connection.closed_listener.call(StandardError.new('foo'))
73
+ expect { f.value }.to raise_error(Io::ConnectionClosedError, /connection closed: foo/i)
74
+ end
75
+
76
+ it 'fails queued requests' do
77
+ fs = Array.new(max_channels + 2) { peer.send_message('foo') }
78
+ connection.closed_listener.call
79
+ expect { fs[0].value }.to raise_error(Io::ConnectionClosedError)
80
+ expect { fs[max_channels].value }.to raise_error(Io::ConnectionClosedError)
81
+ end
82
+
83
+ it 'fails queued messages with an error that shows that the request was never sent' do
84
+ fs = Array.new(max_channels + 2) { peer.send_message('foo') }
85
+ connection.closed_listener.call
86
+ expect { fs[max_channels].value }.to raise_error(Rpc::RequestNotSentError)
68
87
  end
69
88
  end
70
89
 
@@ -127,6 +146,12 @@ module Ione
127
146
  expect { f.value }.to_not raise_error
128
147
  end
129
148
 
149
+ it 'fails the request when the connection is closed' do
150
+ connection.stub(:closed?).and_return(true)
151
+ f = peer.send_message('foo', 2)
152
+ expect { f.value }.to raise_error(Rpc::RequestNotSentError)
153
+ end
154
+
130
155
  context 'with a non-recoding codec' do
131
156
  let :codec do
132
157
  double(:codec, recoding?: false)
@@ -53,7 +53,8 @@ module Ione
53
53
  connection.stub(:on_data)
54
54
  connection.stub(:on_closed)
55
55
  connection.stub(:write)
56
- connection.stub(:close)
56
+ connection.stub(:close) { connection.stub(:closed?).and_return(true) }
57
+ connection.stub(:closed?).and_return(false)
57
58
  connection
58
59
  end
59
60
 
@@ -214,7 +215,7 @@ module Ione
214
215
 
215
216
  it 'returns a failed future when called when not connected' do
216
217
  client.stop.value
217
- expect { client.send_request('PING').value }.to raise_error(Io::ConnectionError)
218
+ expect { client.send_request('PING').value }.to raise_error(Rpc::NoConnectionError)
218
219
  end
219
220
  end
220
221
 
@@ -274,13 +275,24 @@ module Ione
274
275
  expect { f.value }.to raise_error('Bork')
275
276
  end
276
277
 
278
+ it 'fails the request when #choose_connection returns a closed connection' do
279
+ client.override_choose_connection do |connections, request|
280
+ c = connections.first
281
+ c.close
282
+ c
283
+ end
284
+ client.start.value
285
+ f = client.send_request('PING')
286
+ expect { f.value }.to raise_error(Rpc::RequestNotSentError)
287
+ end
288
+
277
289
  it 'fails the request when #choose_connection returns nil' do
278
290
  client.override_choose_connection do |connections, request|
279
291
  nil
280
292
  end
281
293
  client.start.value
282
294
  f = client.send_request('PING')
283
- expect { f.value }.to raise_error(Io::ConnectionError)
295
+ expect { f.value }.to raise_error(Rpc::NoConnectionError)
284
296
  end
285
297
  end
286
298
  end
@@ -510,6 +522,14 @@ module Ione
510
522
  connection_attempts_by_host['node1.example.com'].should == 10
511
523
  connection_attempts_by_host['node2.example.com'].should == 1
512
524
  end
525
+
526
+ it 'allows the connection to be manually reconnected' do
527
+ client.start.value
528
+ c1 = client.created_connections.find { |c| c.host == 'node1.example.com' }
529
+ c1.closed_listener.call
530
+ client.add_host(c1.host, c1.port).value
531
+ io_reactor.should have_received(:connect).with('node1.example.com', anything, anything).twice
532
+ end
513
533
  end
514
534
 
515
535
  context 'with multiple connections' do
@@ -120,6 +120,15 @@ shared_examples 'peers' do
120
120
  connection.should have_received(:close)
121
121
  end
122
122
  end
123
+
124
+ describe '#closed?' do
125
+ it 'reflects the underlying connection\'s state' do
126
+ connection.stub(:closed?).and_return(true)
127
+ peer.should be_closed
128
+ connection.stub(:closed?).and_return(false)
129
+ peer.should_not be_closed
130
+ end
131
+ end
123
132
  end
124
133
 
125
134
  module RpcSpec
@@ -130,10 +139,16 @@ module RpcSpec
130
139
  @host = 'example.com'
131
140
  @port = 9999
132
141
  @written_bytes = ''
142
+ @closed = false
143
+ end
144
+
145
+ def closed?
146
+ @closed
133
147
  end
134
148
 
135
149
  def close(cause=nil)
136
150
  @closed_listener.call(cause) if @closed_listener
151
+ @closed = true
137
152
  end
138
153
 
139
154
  def write(bytes)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ione-rpc
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre4
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Theo Hultberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-26 00:00:00.000000000 Z
11
+ date: 2014-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ione
@@ -61,9 +61,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
61
61
  version: 1.9.3
62
62
  required_rubygems_version: !ruby/object:Gem::Requirement
63
63
  requirements:
64
- - - '>'
64
+ - - '>='
65
65
  - !ruby/object:Gem::Version
66
- version: 1.3.1
66
+ version: '0'
67
67
  requirements: []
68
68
  rubyforge_project:
69
69
  rubygems_version: 2.2.1