ione-rpc 1.0.0.pre4 → 1.0.0

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