ione-rpc 1.0.0.pre0 → 1.0.0.pre1

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: 80f4a0fbab22cefd0de19d54c779637b15129ea8
4
- data.tar.gz: 35d701df7cba437f67fb5fd505b457d49d43ad7c
3
+ metadata.gz: 51144d6e83a35056bbefda51115605b74141c2a1
4
+ data.tar.gz: 0d983eee0b3c1810a6c8a3a1a311bf4d7e4b8941
5
5
  SHA512:
6
- metadata.gz: ea81e63e8b59f31ac42b60be6e432480a365efa8f7de7da8475eb99acf8877445045e93c77634d14d9eb717cf9e4301b7c0bee2b82dc0366a3bd381b2b61e47e
7
- data.tar.gz: a2384cb4793ed2dbbe7a344ee535d74844d386b94ff531aa0abad4a12ed1b5cc30461a90ca90cbdd787b761e1ee31b3936665f8c9ef164928387d02354571bc0
6
+ metadata.gz: 652a513b927f5c485e960e2211e4a687ba64034285fa631dbcfee72a91ab19f4bcbbaa09b95875b8deeba3efe2b3b3f77bf8674de1536225f9d0d042b7e06f61
7
+ data.tar.gz: 500e33e208a693abf1ce8495d4c8e6250672eb16832bd17ca8e9195def804a41338d2484d431bd13c8bbf0263164bd1c3dca4f3a1f95a3df6a8dc1a41419cc90
@@ -141,15 +141,18 @@ module Ione
141
141
  # @param [Object] request the request to send.
142
142
  # @param [Object] connection the connection to send the request on. This
143
143
  # parameter is internal and should only be used from {#initialize_connection}.
144
+ # @param [Object] timeout the maximum time in seconds to wait for a response
145
+ # before failing the returned future with a {Ione::Rpc::TimeoutError}.
146
+ # There is no timeout by default.
144
147
  # @return [Ione::Future<Object>] a future that resolves to the response
145
148
  # from the server, or fails because there was an error while processing
146
149
  # the request (this is not the same thing as the server sending an
147
150
  # error response – that is protocol specific and up to the implementation
148
151
  # to handle), or when there was no connection open.
149
- def send_request(request, connection=nil)
152
+ def send_request(request, connection=nil, timeout=nil)
150
153
  connection = connection || @lock.synchronize { choose_connection(@connections, request) }
151
154
  if connection
152
- f = connection.send_message(request)
155
+ f = connection.send_message(request, timeout)
153
156
  f = f.fallback do |error|
154
157
  if error.is_a?(Io::ConnectionClosedError)
155
158
  @logger.warn('Request failed because the connection closed, retrying') if @logger
@@ -261,7 +264,7 @@ module Ione
261
264
  end
262
265
 
263
266
  def create_connection(raw_connection)
264
- Ione::Rpc::ClientPeer.new(raw_connection, @codec, @max_channels)
267
+ Ione::Rpc::ClientPeer.new(raw_connection, @codec, @io_reactor, @max_channels)
265
268
  end
266
269
 
267
270
  def handle_connected(connection)
@@ -7,14 +7,15 @@ module Ione
7
7
  module Rpc
8
8
  # @private
9
9
  class ClientPeer < Peer
10
- def initialize(connection, codec, max_channels)
11
- super(connection, codec)
10
+ def initialize(connection, codec, scheduler, max_channels)
11
+ raise ArgumentError, 'More than 2**15 channels is not supported' if max_channels > 2**15
12
+ super(connection, codec, scheduler)
12
13
  @lock = Mutex.new
13
14
  @channels = [nil] * max_channels
14
15
  @queue = []
15
16
  end
16
17
 
17
- def send_message(request)
18
+ def send_message(request, timeout=nil)
18
19
  promise = Ione::Promise.new
19
20
  channel = @lock.synchronize do
20
21
  take_channel(promise)
@@ -26,6 +27,14 @@ module Ione
26
27
  @queue << [request, promise]
27
28
  end
28
29
  end
30
+ if timeout
31
+ @scheduler.schedule_timer(timeout).on_value do
32
+ unless promise.future.completed?
33
+ error = Rpc::TimeoutError.new('No response received within %ss' % timeout.to_s)
34
+ promise.fail(error)
35
+ end
36
+ end
37
+ end
29
38
  promise.future
30
39
  end
31
40
 
@@ -37,7 +46,7 @@ module Ione
37
46
  @channels[channel] = nil
38
47
  promise
39
48
  end
40
- if promise
49
+ if promise && !promise.future.completed?
41
50
  promise.fulfill(response)
42
51
  end
43
52
  flush_queue
@@ -28,7 +28,7 @@ module Ione
28
28
  # @return [String] an encoded frame with the message and channel
29
29
  def encode(message, channel)
30
30
  data = encode_message(message)
31
- [1, channel, data.bytesize, data.to_s].pack('ccNa*')
31
+ [2, 0, channel, data.bytesize, data.to_s].pack(FRAME_V2_FORMAT)
32
32
  end
33
33
 
34
34
  # Decodes a frame, piece by piece if necessary.
@@ -106,7 +106,11 @@ module Ione
106
106
  def read_header
107
107
  n = @buffer.read_short
108
108
  @version = n >> 8
109
- @channel = n & 0xff
109
+ if @version == 1
110
+ @channel = n & 0xff
111
+ else
112
+ @channel = @buffer.read_short
113
+ end
110
114
  @length = @buffer.read_int
111
115
  end
112
116
 
@@ -115,13 +119,18 @@ module Ione
115
119
  end
116
120
 
117
121
  def header_ready?
118
- @length.nil? && @buffer.size >= 6
122
+ @length.nil? && @buffer.size >= 8
119
123
  end
120
124
 
121
125
  def body_ready?
122
126
  @length && @buffer.size >= @length
123
127
  end
124
128
  end
129
+
130
+ private
131
+
132
+ FRAME_V1_FORMAT = 'ccNa*'.freeze
133
+ FRAME_V2_FORMAT = 'ccnNa*'.freeze
125
134
  end
126
135
 
127
136
  # A codec that works with encoders like JSON, MessagePack, YAML and others
data/lib/ione/rpc/peer.rb CHANGED
@@ -6,13 +6,14 @@ module Ione
6
6
  class Peer
7
7
  attr_reader :host, :port
8
8
 
9
- def initialize(connection, codec)
9
+ def initialize(connection, codec, scheduler=nil)
10
10
  @connection = connection
11
11
  @connection.on_data(&method(:handle_data))
12
12
  @connection.on_closed(&method(:handle_closed))
13
13
  @host = @connection.host
14
14
  @port = @connection.port
15
15
  @codec = codec
16
+ @scheduler = scheduler
16
17
  @buffer = Ione::ByteBuffer.new
17
18
  @closed_promise = Promise.new
18
19
  @current_message = nil
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ione
4
4
  module Rpc
5
- VERSION = '1.0.0.pre0'.freeze
5
+ VERSION = '1.0.0.pre1'.freeze
6
6
  end
7
7
  end
data/lib/ione/rpc.rb CHANGED
@@ -5,6 +5,7 @@ require 'ione'
5
5
 
6
6
  module Ione
7
7
  module Rpc
8
+ TimeoutError = Class.new(StandardError)
8
9
  end
9
10
  end
10
11
 
@@ -8,7 +8,7 @@ module Ione
8
8
  module Rpc
9
9
  describe ClientPeer do
10
10
  let! :peer do
11
- RpcSpec::TestClientPeer.new(connection, codec, max_channels)
11
+ RpcSpec::TestClientPeer.new(connection, codec, scheduler, max_channels)
12
12
  end
13
13
 
14
14
  let :connection do
@@ -19,6 +19,10 @@ module Ione
19
19
  double(:codec)
20
20
  end
21
21
 
22
+ let :scheduler do
23
+ double(:scheduler)
24
+ end
25
+
22
26
  let :max_channels do
23
27
  16
24
28
  end
@@ -39,6 +43,15 @@ module Ione
39
43
  end
40
44
  end
41
45
 
46
+ before do
47
+ timer_promises = []
48
+ scheduler.stub(:schedule_timer) do |timeout|
49
+ timer_promises << Promise.new
50
+ timer_promises.last.future
51
+ end
52
+ scheduler.stub(:timer_promises).and_return(timer_promises)
53
+ end
54
+
42
55
  include_examples 'peers'
43
56
 
44
57
  context 'when the connection closes' do
@@ -51,6 +64,12 @@ module Ione
51
64
  end
52
65
  end
53
66
 
67
+ describe '#initialize' do
68
+ it 'raises ArgumentError when the specified max channels is more than the protocol handles' do
69
+ expect { RpcSpec::TestClientPeer.new(connection, codec, scheduler, 2**24) }.to raise_error(ArgumentError)
70
+ end
71
+ end
72
+
54
73
  describe '#send_message' do
55
74
  it 'encodes and sends a request frame' do
56
75
  peer.send_message('hello')
@@ -85,6 +104,19 @@ module Ione
85
104
  connection.data_listener.call('bar@000')
86
105
  f.value.payload.should == 'bar'
87
106
  end
107
+
108
+ it 'fails the request when the timeout passes before the response is received' do
109
+ f = peer.send_message('foo', 2)
110
+ scheduler.timer_promises.first.fulfill
111
+ expect { f.value }.to raise_error(Rpc::TimeoutError)
112
+ end
113
+
114
+ it 'does not fail the request when the response is received before the timeout passes' do
115
+ f = peer.send_message('foo', 2)
116
+ connection.data_listener.call('bar@000')
117
+ scheduler.timer_promises.first.fulfill
118
+ expect { f.value }.to_not raise_error
119
+ end
88
120
  end
89
121
  end
90
122
  end
@@ -16,8 +16,14 @@ module Ione
16
16
 
17
17
  let :io_reactor do
18
18
  running = [false]
19
+ timer_promises = []
19
20
  r = double(:io_reactor)
20
21
  r.stub(:running?) { running[0] }
22
+ r.stub(:schedule_timer) do |timeout|
23
+ timer_promises << Promise.new
24
+ timer_promises.last.future
25
+ end
26
+ r.stub(:timer_promises).and_return(timer_promises)
21
27
  r.stub(:start) do
22
28
  running[0] = true
23
29
  Future.resolved(r)
@@ -197,7 +203,7 @@ module Ione
197
203
  before do
198
204
  client.start.value
199
205
  client.created_connections.each do |connection|
200
- connection.stub(:send_message).with('PING').and_return(Future.resolved('PONG'))
206
+ connection.stub(:send_message).with('PING', nil).and_return(Future.resolved('PONG'))
201
207
  end
202
208
  end
203
209
 
@@ -225,6 +231,20 @@ module Ione
225
231
  end
226
232
  end
227
233
 
234
+ context 'with a timeout' do
235
+ it 'passes the timeout to the connection' do
236
+ client.start.value
237
+ timeouts = []
238
+ client.created_connections.each do |connection|
239
+ connection.stub(:send_message) do |rq, timeout|
240
+ timeouts << timeout
241
+ end
242
+ end
243
+ client.send_request('PING', nil, 2)
244
+ timeouts.should include(2)
245
+ end
246
+ end
247
+
228
248
  context 'when the client chooses the connection per request' do
229
249
  it 'asks the client to choose a connection to send the request on' do
230
250
  client.override_choose_connection do |connections, request|
@@ -415,7 +435,7 @@ module Ione
415
435
 
416
436
  it 'logs when requests fail' do
417
437
  client.start.value
418
- client.created_connections.each { |connection| connection.stub(:send_message).with('PING').and_return(Future.failed(StandardError.new('BORK'))) }
438
+ client.created_connections.each { |connection| connection.stub(:send_message).with('PING', nil).and_return(Future.failed(StandardError.new('BORK'))) }
419
439
  client.send_request('PING')
420
440
  logger.should have_received(:warn).with(/request failed: BORK/i)
421
441
  end
@@ -605,9 +625,9 @@ module ClientSpec
605
625
  @raw_connection.port
606
626
  end
607
627
 
608
- def send_message(request)
628
+ def send_message(request, timeout=nil)
609
629
  @requests << request
610
- @peer_connection.send_message(request)
630
+ @peer_connection.send_message(request, timeout)
611
631
  Ione::Future.resolved
612
632
  end
613
633
  end
@@ -20,19 +20,19 @@ module Ione
20
20
  end
21
21
 
22
22
  it 'encodes the version in the first byte' do
23
- encoded_message[0, 1].unpack('c').should == [1]
23
+ encoded_message[0, 1].unpack('c').should == [2]
24
24
  end
25
25
 
26
- it 'encodes the channel in the second byte' do
27
- encoded_message[1, 1].unpack('c').should == [42]
26
+ it 'encodes the channel in the third and fourth byte' do
27
+ encoded_message[2, 2].unpack('n').should == [42]
28
28
  end
29
29
 
30
30
  it 'encodes the length of the frame in the following four bytes' do
31
- encoded_message[2, 4].unpack('N').should == [22]
31
+ encoded_message[4, 4].unpack('N').should == [22]
32
32
  end
33
33
 
34
34
  it 'encodes the object as JSON' do
35
- encoded_message[6..-1].should == '{"foo":"bar","baz":42}'
35
+ encoded_message[8..-1].should == '{"foo":"bar","baz":42}'
36
36
  end
37
37
  end
38
38
 
@@ -99,21 +99,34 @@ module Ione
99
99
  42
100
100
  end
101
101
 
102
- let :frame do
102
+ let :v1_frame do
103
103
  %(\x01\x2a\x00\x00\x00\x16{"foo":"bar","baz":42})
104
104
  end
105
105
 
106
- it 'decoding a encoded frame returns the original message' do
107
- frm = codec.encode(message, channel)
108
- msg, ch, _ = codec.decode(Ione::ByteBuffer.new(frm), nil)
109
- msg.should == message
110
- ch.should == channel
106
+ let :v2_frame do
107
+ %(\x02\x00\x00\x2a\x00\x00\x00\x16{"foo":"bar","baz":42})
111
108
  end
112
109
 
113
- it 'encoding a message gives the original frame' do
114
- msg, ch, _ = codec.decode(Ione::ByteBuffer.new(frame), nil)
115
- frm = codec.encode(msg, ch)
116
- frm.should == frame
110
+ context 'with a v1 frame' do
111
+ it 'decodes a frame' do
112
+ msg, ch, _ = codec.decode(Ione::ByteBuffer.new(v1_frame), nil)
113
+ msg.should == message
114
+ end
115
+ end
116
+
117
+ context 'with a v2 frame' do
118
+ it 'decoding a encoded frame returns the original message' do
119
+ frm = codec.encode(message, channel)
120
+ msg, ch, _ = codec.decode(Ione::ByteBuffer.new(frm), nil)
121
+ msg.should == message
122
+ ch.should == channel
123
+ end
124
+
125
+ it 'encoding a message gives the original frame' do
126
+ msg, ch, _ = codec.decode(Ione::ByteBuffer.new(v2_frame), nil)
127
+ frm = codec.encode(msg, ch)
128
+ frm.should == v2_frame
129
+ end
117
130
  end
118
131
  end
119
132
  end
@@ -125,13 +138,13 @@ module Ione
125
138
 
126
139
  describe '#encode' do
127
140
  it 'calls #dump on the delegate' do
128
- codec.encode({'foo' => 'bar'}, 0)[6..-1].should == '{"foo":"bar"}'
141
+ codec.encode({'foo' => 'bar'}, 0)[8..-1].should == '{"foo":"bar"}'
129
142
  end
130
143
  end
131
144
 
132
145
  describe '#decode' do
133
146
  it 'calls #load on the delegate' do
134
- buffer = Ione::ByteBuffer.new(%(\x01\x00\x00\x00\x00\x0d{"foo":"bar"}))
147
+ buffer = Ione::ByteBuffer.new(%(\x02\x00\x00\x00\x00\x00\x00\x0d{"foo":"bar"}))
135
148
  message, _, _ = codec.decode(buffer, nil)
136
149
  message.should eql('foo' => 'bar')
137
150
  end
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.pre0
4
+ version: 1.0.0.pre1
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-05-28 00:00:00.000000000 Z
11
+ date: 2014-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ione