ione-rpc 1.0.0.pre0 → 1.0.0.pre1

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