onstomp 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,70 +4,30 @@
4
4
  # {OnStomp::Client client}'s {OnStomp::Connections::Base connection} and
5
5
  # replays the ones that were not when the
6
6
  # {OnStomp::Failover::Client failover} client reconnects.
7
- class OnStomp::Failover::Buffers::Written
7
+ class OnStomp::Failover::Buffers::Written < OnStomp::Failover::Buffers::Base
8
8
  def initialize failover
9
- @failover = failover
10
- @buffer_mutex = Mutex.new
11
- @buffer = []
12
- @txs = {}
13
-
14
- failover.before_send &method(:buffer_frame)
15
- failover.before_commit &method(:buffer_frame)
16
- failover.before_abort &method(:buffer_frame)
17
- failover.before_subscribe &method(:buffer_frame)
18
- failover.before_begin &method(:buffer_transaction)
19
- # We can scrub the subscription before UNSUBSCRIBE is fully written
20
- # because if we replay before UNSUBSCRIBE was sent, we still don't
21
- # want to be subscribed when we reconnect.
22
- failover.before_unsubscribe &method(:debuffer_subscription)
23
- # We only want to scrub the transactions if ABORT or COMMIT was
24
- # at least written fully to the socket.
25
- failover.on_commit &method(:debuffer_transaction)
26
- failover.on_abort &method(:debuffer_transaction)
27
- failover.on_send &method(:debuffer_non_transactional_frame)
28
-
29
- failover.on_failover_connected &method(:replay)
30
- end
31
-
32
- # Adds a frame to a buffer so that it may be replayed if the
33
- # {OnStomp::Failover::Client failover} client re-connects
34
- def buffer_frame f, *_
35
- @buffer_mutex.synchronize do
36
- unless f.header? :'x-onstomp-failover-replay'
37
- @buffer << f
9
+ super
10
+ [:send, :commit, :abort, :subscribe].each do |ev|
11
+ failover.__send__(:"before_#{ev}") do |f, *_|
12
+ add_to_buffer f
38
13
  end
39
14
  end
40
- end
41
-
42
- # Records the start of a transaction so that it may be replayed if the
43
- # {OnStomp::Failover::Client failover} client re-connects
44
- def buffer_transaction f, *_
45
- @txs[f[:transaction]] = true
46
- buffer_frame f
47
- end
48
-
49
- # Removes the recorded transaction from the buffer after it has been
50
- # written the broker socket so that it will not be replayed when the
51
- # {OnStomp::Failover::Client failover} client re-connects
52
- def debuffer_transaction f, *_
53
- tx = f[:transaction]
54
- if @txs.delete tx
55
- @buffer_mutex.synchronize do
56
- @buffer.reject! { |bf| bf[:transaction] == tx }
15
+ # We only want to scrub the transactions if ABORT or COMMIT was
16
+ # at least written fully to the socket.
17
+ [:commit, :abort].each do |ev|
18
+ failover.__send__(:"on_#{ev}") do |f,*_|
19
+ remove_from_transactions f
57
20
  end
58
21
  end
22
+ failover.before_begin { |f, *_| add_to_transactions f }
23
+ # We can scrub the subscription before UNSUBSCRIBE is fully written
24
+ # because if we replay before UNSUBSCRIBE was sent, we still don't
25
+ # want to be subscribed when we reconnect.
26
+ failover.before_unsubscribe { |f, *_| remove_subscribe_from_buffer f }
27
+ failover.on_send &method(:debuffer_non_transactional_frame)
28
+ failover.on_failover_connected { |f,c,*_| replay_buffer c }
59
29
  end
60
-
61
- # Removes the matching SUBSCRIBE frame from the buffer after the
62
- # UNSUBSCRIBE has been added to the connection's write buffer
63
- # so that it will not be replayed when the
64
- # {OnStomp::Failover::Client failover} client re-connects
65
- def debuffer_subscription f, *_
66
- @buffer_mutex.synchronize do
67
- @buffer.reject! { |bf| bf.command == 'SUBSCRIBE' && bf[:id] == f[:id] }
68
- end
69
- end
70
-
30
+
71
31
  # Removes a frame that is not part of a transaction from the buffer
72
32
  # after it has been written the broker socket so that it will not be
73
33
  # replayed when the {OnStomp::Failover::Client failover} client re-connects
@@ -76,16 +36,4 @@ class OnStomp::Failover::Buffers::Written
76
36
  @buffer_mutex.synchronize { @buffer.delete f }
77
37
  end
78
38
  end
79
-
80
- # Called when the {OnStomp::Failover::Client failover} client triggers
81
- # `on_failover_connected` to start replaying any frames in the buffer.
82
- def replay fail, client, *_
83
- replay_frames = @buffer_mutex.synchronize do
84
- @buffer.select { |f| f[:'x-onstomp-failover-replay'] = '1'; true }
85
- end
86
-
87
- replay_frames.each do |f|
88
- client.transmit f
89
- end
90
- end
91
39
  end
@@ -14,13 +14,6 @@ module OnStomp::Failover::FailoverConfigurable
14
14
  # Provides attribute methods for {OnStomp::Failover::Client failover}
15
15
  # clients.
16
16
  module ClassMethods
17
- # Creates readable and writeable attributes that are automatically
18
- # converted into integers.
19
- def attr_configurable_int *args, &block
20
- trans = attr_configurable_wrap lambda { |v| v.to_i }, block
21
- attr_configurable_single(*args, &trans)
22
- end
23
-
24
17
  # Creates readable and writeable attributes that are automatically
25
18
  # converted into boolean values. Assigning the attributes any of
26
19
  # `true`, `'true'`, `'1'` or `1` will set the attribute to `true`, all
@@ -41,7 +41,9 @@ module OnStomp::Failover::URI
41
41
  # @return [FAILOVER]
42
42
  def parse uri_str
43
43
  if uri_str =~ FAILOVER_REG
44
- self.new $1.split(','), $2
44
+ uris = $1
45
+ query = $2
46
+ self.new uris.split(','), query
45
47
  else
46
48
  raise OnStomp::Failover::InvalidFailoverURIError, uri_str.inspect
47
49
  end
@@ -125,7 +125,7 @@ module OnStomp::Interfaces::ClientEvents
125
125
  # @endgroup
126
126
 
127
127
  # Helpers for setting up connection events through a client
128
- [:established, :terminated, :died, :closed].each do |ev|
128
+ [:established, :terminated, :died, :closed, :blocked].each do |ev|
129
129
  module_eval <<-EOS
130
130
  def on_connection_#{ev}(&cb)
131
131
  if connection
@@ -32,6 +32,14 @@ module OnStomp::Interfaces::ConnectionEvents
32
32
  # the event (in general the same as `client.connection`)
33
33
  create_event_methods :terminated, :on
34
34
  # @api gem:1 STOMP:1.0,1.1
35
+ # Binds a callback to be invoked when a connection has been blocked
36
+ # on writing for more than the allowed duration, closing the connection.
37
+ # @yield [client, connection] callback invoked when event is triggered
38
+ # @yieldparam [OnStomp::Client] client
39
+ # @yieldparam [OnStomp::Connections::Base] connection that triggered
40
+ # the event (in general the same as `client.connection`)
41
+ create_event_methods :blocked, :on
42
+ # @api gem:1 STOMP:1.0,1.1
35
43
  # Binds a callback to be invoked when a connection has been closed, either
36
44
  # through a graceful disconnect or unexpectedly.
37
45
  # @note If connection is closed unexpectedly, {#on_died} is triggered first,
@@ -70,6 +70,13 @@ module OnStomp::Interfaces::UriConfigurable
70
70
  attr_configurable(*args, &trans)
71
71
  end
72
72
 
73
+ # Creates readable and writeable attributes that are automatically
74
+ # converted into integers.
75
+ def attr_configurable_int *args, &block
76
+ trans = attr_configurable_wrap lambda { |v| v.to_i }, block
77
+ attr_configurable_single(*args, &trans)
78
+ end
79
+
73
80
  # Creates a group readable and writeable attributes that can be set
74
81
  # by a URI query parameter sharing the same name, a property of a URI or
75
82
  # a default value. The value of this attribute will be transformed by
@@ -7,7 +7,7 @@ module OnStomp
7
7
  # Minor / feature version
8
8
  MINOR = 0
9
9
  # Patch version
10
- PATCH = 3
10
+ PATCH = 4
11
11
  # Complete version
12
12
  VERSION = "#{MAJOR}.#{MINOR}.#{PATCH}"
13
13
  end
@@ -34,6 +34,8 @@ module OnStomp
34
34
  client.login.should == ''
35
35
  client.passcode.should == ''
36
36
  client.heartbeats.should == [0, 0]
37
+ client.write_timeout.should == 120
38
+ client.read_timeout.should == 120
37
39
  client.processor.should == OnStomp::Components::ThreadedProcessor
38
40
  end
39
41
  it "should be configurable by options" do
@@ -43,6 +45,8 @@ module OnStomp
43
45
  client_options[:login] = 'my login'
44
46
  client_options[:passcode] = 'sup3r s3cr3t'
45
47
  client_options[:processor] = processor_class
48
+ client_options[:write_timeout] = 50
49
+ client_options[:read_timeout] = 70
46
50
  client_options[:ssl] = { :cert_path => '/path/to/certs' }
47
51
  client.versions.should == ['1.1']
48
52
  client.heartbeats.should == [90, 110]
@@ -50,6 +54,8 @@ module OnStomp
50
54
  client.login.should == 'my login'
51
55
  client.passcode.should == 'sup3r s3cr3t'
52
56
  client.processor.should == processor_class
57
+ client.write_timeout.should == 50
58
+ client.read_timeout.should == 70
53
59
  client.ssl.should == { :cert_path => '/path/to/certs' }
54
60
  end
55
61
  it "should be configurable by query" do
@@ -59,11 +65,15 @@ module OnStomp
59
65
  client_uri << '&login=query%20login'
60
66
  client_uri << '&passcode=qu3ry%20s3cr3t'
61
67
  client_uri << '&processor=OnStomp::Connections'
68
+ client_uri << '&write_timeout=30'
69
+ client_uri << '&read_timeout=50'
62
70
  client.versions.should == ['1.0']
63
71
  client.heartbeats.should == [80, 210]
64
72
  client.host.should == 'query host'
65
73
  client.login.should == 'query login'
66
74
  client.passcode.should == 'qu3ry s3cr3t'
75
+ client.write_timeout.should == 30
76
+ client.read_timeout.should == 50
67
77
  client.processor.should == OnStomp::Connections
68
78
  end
69
79
  it "should be configurable through parts of the URI" do
@@ -106,7 +116,7 @@ module OnStomp
106
116
  OnStomp::Connections.should_receive(:connect).with(client, headers,
107
117
  { :'accept-version' => '1.1', :host => 'my host',
108
118
  :'heart-beat' => '30,110', :login => 'my login',
109
- :passcode => 's3cr3t' }, pending_events).and_return(connection)
119
+ :passcode => 's3cr3t' }, pending_events, 30, 50).and_return(connection)
110
120
  processor.should_receive(:start)
111
121
  client.stub(:pending_connection_events => pending_events)
112
122
  client.versions = '1.1'
@@ -114,6 +124,8 @@ module OnStomp
114
124
  client.login = 'my login'
115
125
  client.passcode = 's3cr3t'
116
126
  client.heartbeats = [30,110]
127
+ client.read_timeout = 30
128
+ client.write_timeout = 50
117
129
  client.connect(headers)
118
130
  client.connection.should == connection
119
131
  end
@@ -136,7 +136,7 @@ module OnStomp::Components::Scopes
136
136
  :transaction => 't-1234')).and_return { |f| f }
137
137
  client.should_receive(:transmit).with(an_onstomp_frame('SEND',
138
138
  {:destination => '/queue/test', :transaction => 't-1234'},
139
- 'my body'), an_instance_of(Hash)).and_return { |f| f }
139
+ 'my body'), an_instance_of(Hash)).and_return { |f,*_| f }
140
140
 
141
141
  scope.perform { |t| t.send("/queue/test", "my body") }
142
142
  end
@@ -146,7 +146,7 @@ module OnStomp::Components::Scopes
146
146
  :transaction => 't-1234')).and_return { |f| f }
147
147
  client.should_receive(:transmit).with(an_onstomp_frame('SEND',
148
148
  {:destination => '/queue/test', :transaction => 't-1234'},
149
- 'my body'), an_instance_of(Hash)).and_return { |f| f }
149
+ 'my body'), an_instance_of(Hash)).and_return { |f,*_| f }
150
150
 
151
151
  scope.perform { |t| t.send("/queue/test", "my body") }
152
152
  end
@@ -157,7 +157,7 @@ module OnStomp::Components::Scopes
157
157
  :transaction => 't-1234')).and_return { |f| f }
158
158
  client.should_receive(:transmit).with(an_onstomp_frame('SEND',
159
159
  {:destination => '/queue/test', :transaction => 't-1234'},
160
- 'my body'), an_instance_of(Hash)).and_return { |f| f }
160
+ 'my body'), an_instance_of(Hash)).and_return { |f,*_| f }
161
161
 
162
162
  scope.perform { |t| t.send("/queue/test", "my body"); t.commit }
163
163
  end
@@ -168,7 +168,7 @@ module OnStomp::Components::Scopes
168
168
  :transaction => 't-1234')).and_return { |f| f }
169
169
  client.should_receive(:transmit).with(an_onstomp_frame('SEND',
170
170
  {:destination => '/queue/test', :transaction => 't-1234'},
171
- 'my body'), an_instance_of(Hash)).and_return { |f| f }
171
+ 'my body'), an_instance_of(Hash)).and_return { |f,*_| f }
172
172
 
173
173
  scope.perform { |t| t.send("/queue/test", "my body"); t.abort }
174
174
  end
@@ -179,7 +179,7 @@ module OnStomp::Components::Scopes
179
179
  :transaction => 't-1234')).and_return { |f| f }
180
180
  client.should_receive(:transmit).with(an_onstomp_frame('SEND',
181
181
  {:destination => '/queue/test', :transaction => 't-1234'},
182
- 'my body'), an_instance_of(Hash)).and_return { |f| f }
182
+ 'my body'), an_instance_of(Hash)).and_return { |f,*_| f }
183
183
 
184
184
  lambda {
185
185
  scope.perform { |t|
@@ -195,7 +195,7 @@ module OnStomp::Components::Scopes
195
195
  :transaction => 't-1234')).and_return { |f| f }
196
196
  client.should_receive(:transmit).with(an_onstomp_frame('SEND',
197
197
  {:destination => '/queue/test', :transaction => 't-1234'},
198
- 'my body'), an_instance_of(Hash)).and_return { |f| f }
198
+ 'my body'), an_instance_of(Hash)).and_return { |f,*_| f }
199
199
 
200
200
  lambda {
201
201
  scope.perform { |t|
@@ -212,7 +212,7 @@ module OnStomp::Components::Scopes
212
212
  :transaction => 't-1234')).and_return { |f| f }
213
213
  client.should_receive(:transmit).with(an_onstomp_frame('SEND',
214
214
  {:destination => '/queue/test', :transaction => 't-1234'},
215
- 'my body'), an_instance_of(Hash)).and_return { |f| f }
215
+ 'my body'), an_instance_of(Hash)).and_return { |f,*_| f }
216
216
 
217
217
  lambda {
218
218
  scope.perform { |t|
@@ -4,7 +4,7 @@ require 'spec_helper'
4
4
  module OnStomp::Connections
5
5
  describe Base do
6
6
  let(:io) {
7
- mock('io', :close => nil)
7
+ mock('io', :close => nil, :read_nonblock => nil, :write_nonblock => nil)
8
8
  }
9
9
  let(:client) {
10
10
  mock('client', :dispatch_transmitted => nil,
@@ -29,11 +29,7 @@ module OnStomp::Connections
29
29
  lambda { connection.lame_lame }.should raise_error(NameError)
30
30
  end
31
31
  end
32
-
33
- describe ".configure" do
34
-
35
- end
36
-
32
+
37
33
  describe ".connected?" do
38
34
  it "should be connected if io is not closed" do
39
35
  io.stub(:closed? => false)
@@ -43,6 +39,30 @@ module OnStomp::Connections
43
39
  end
44
40
  end
45
41
 
42
+ describe ".duration_since_transmitted" do
43
+ it "should be nil if last_transmitted_at is nil" do
44
+ connection.stub(:last_transmitted_at => nil)
45
+ connection.duration_since_transmitted.should be_nil
46
+ end
47
+ it "should be the difference between now and the last_transmitted_at in milliseconds" do
48
+ Time.stub(:now => 10)
49
+ connection.stub(:last_transmitted_at => 8.5)
50
+ connection.duration_since_transmitted.should == 1500
51
+ end
52
+ end
53
+
54
+ describe ".duration_since_received" do
55
+ it "should be nil if last_received_at is nil" do
56
+ connection.stub(:last_received_at => nil)
57
+ connection.duration_since_received.should be_nil
58
+ end
59
+ it "should be the difference between now and the last_received_at in milliseconds" do
60
+ Time.stub(:now => 10)
61
+ connection.stub(:last_received_at => 6)
62
+ connection.duration_since_received.should == 4000
63
+ end
64
+ end
65
+
46
66
  describe ".close" do
47
67
  it "should close the socket if blocking is true" do
48
68
  io.should_receive(:close)
@@ -195,6 +215,18 @@ module OnStomp::Connections
195
215
  lambda { connection.io_process_write }.should raise_error(Exception)
196
216
  triggered.should be_true
197
217
  end
218
+ it "should trigger a blocked close if the write timeout is exceeded" do
219
+ triggered = false
220
+ connection.on_blocked { triggered = true }
221
+ connection.write_timeout = 10
222
+ Time.stub(:now => 31)
223
+ connection.push_write_buffer 'FRAME_SERIALIZED', frame
224
+ Time.stub(:now => 77)
225
+ IO.stub(:select => false)
226
+ io.should_receive(:close)
227
+ connection.io_process_write
228
+ triggered.should be_true
229
+ end
198
230
  end
199
231
  describe ".io_process_read" do
200
232
  before(:each) do
@@ -262,7 +294,7 @@ module OnStomp::Connections
262
294
  io.should_receive(:close)
263
295
  lambda { connection.io_process_read }.should raise_error(IOError)
264
296
  end
265
- it "should close the connection and re-raise if an EOFError is raised" do
297
+ it "should close the connection and re-raise if an SystemCallError is raised" do
266
298
  connection.stub(:connected? => true)
267
299
  IO.stub(:select => true)
268
300
  io.should_receive(:read_nonblock).with(Base::MAX_BYTES_PER_READ).and_raise(SystemCallError.new('msg', 13))
@@ -279,6 +311,122 @@ module OnStomp::Connections
279
311
  lambda { connection.io_process_read }.should raise_error(Exception)
280
312
  triggered.should be_true
281
313
  end
314
+ it "should trigger a blocked close if checking timeout and it is exceeded" do
315
+ triggered = false
316
+ connection.on_blocked { triggered = true }
317
+ IO.stub(:select => false)
318
+ connection.stub(:read_timeout_exceeded? => true, :connected? => true)
319
+ io.should_receive(:close)
320
+ connection.io_process_read(true)
321
+ triggered.should be_true
322
+ end
323
+ end
324
+
325
+ describe "read helpers" do
326
+ it "should not be ready for read if not connected" do
327
+ IO.stub(:select => true)
328
+ connection.stub(:connected? => false)
329
+ connection.__send__(:ready_for_read?).should be_false
330
+ end
331
+ it "should not be ready for read if IO.select is nil" do
332
+ IO.stub(:select => nil)
333
+ connection.stub(:connected? => true)
334
+ connection.__send__(:ready_for_read?).should be_false
335
+ end
336
+ it "should be ready for read if connected and selectable" do
337
+ IO.stub(:select => true)
338
+ connection.stub(:connected? => true)
339
+ connection.__send__(:ready_for_read?).should be_true
340
+ end
341
+ it "should close and trigger terminated event if error is raised" do
342
+ triggered = false
343
+ connection.on_terminated { triggered = true }
344
+ IO.stub(:select).and_raise(IOError)
345
+ connection.stub(:connected? => true)
346
+ lambda {
347
+ connection.__send__(:ready_for_read?)
348
+ }.should raise_error(IOError)
349
+ triggered.should be_true
350
+ end
351
+ it "should not exceed the timeout if no timeout is set" do
352
+ connection.read_timeout = nil
353
+ connection.__send__(:read_timeout_exceeded?).should be_false
354
+ end
355
+ it "should not exceed the timeout if duration is less than timeout" do
356
+ connection.read_timeout = 10
357
+ connection.stub(:duration_since_received => 9000)
358
+ connection.__send__(:read_timeout_exceeded?).should be_false
359
+ end
360
+ it "should not exceed the timeout if duration is equal to timeout" do
361
+ connection.read_timeout = 10
362
+ connection.stub(:duration_since_received => 10000)
363
+ connection.__send__(:read_timeout_exceeded?).should be_false
364
+ end
365
+ it "should exceed the timeout if duration is greater than timeout" do
366
+ connection.read_timeout = 10
367
+ connection.stub(:duration_since_received => 10001)
368
+ connection.__send__(:read_timeout_exceeded?).should be_true
369
+ end
370
+ end
371
+
372
+ describe "write helpers" do
373
+ it "should not be ready for write if buffer is empty" do
374
+ IO.stub(:select => true)
375
+ connection.__send__(:ready_for_write?).should be_false
376
+ end
377
+ it "should not be ready for write if IO.select is nil" do
378
+ IO.stub(:select => nil)
379
+ connection.push_write_buffer 'FRAME_SERIALIZED', frame
380
+ connection.__send__(:ready_for_write?).should be_false
381
+ end
382
+ it "should be ready for write if there's buffer data and selectable" do
383
+ IO.stub(:select => true)
384
+ connection.push_write_buffer 'FRAME_SERIALIZED', frame
385
+ connection.__send__(:ready_for_write?).should be_true
386
+ end
387
+ it "should close and trigger terminated event if error is raised" do
388
+ triggered = false
389
+ connection.on_terminated { triggered = true }
390
+ IO.stub(:select).and_raise(IOError)
391
+ connection.push_write_buffer 'FRAME_SERIALIZED', frame
392
+ lambda {
393
+ connection.__send__(:ready_for_write?)
394
+ }.should raise_error(IOError)
395
+ triggered.should be_true
396
+ end
397
+ it "should not exceed the timeout if no timeout is set" do
398
+ connection.write_timeout = nil
399
+ connection.push_write_buffer 'FRAME_SERIALIZED', frame
400
+ connection.__send__(:write_timeout_exceeded?).should be_false
401
+ end
402
+ it "should not exceed the timeout if duration is less than timeout" do
403
+ connection.write_timeout = 10
404
+ Time.stub(:now => 59)
405
+ connection.push_write_buffer 'FRAME_SERIALIZED', frame
406
+ Time.stub(:now => 61)
407
+ connection.__send__(:write_timeout_exceeded?).should be_false
408
+ end
409
+ it "should not exceed the timeout if duration is equal to timeout" do
410
+ connection.write_timeout = 10
411
+ Time.stub(:now => 59)
412
+ connection.push_write_buffer 'FRAME_SERIALIZED', frame
413
+ Time.stub(:now => 69)
414
+ connection.__send__(:write_timeout_exceeded?).should be_false
415
+ end
416
+ it "should not exceed the timeout if the duratio is greater but there's no buffered data" do
417
+ connection.write_timeout = 1
418
+ connection.stub(:duration_since_transmitted => 5000)
419
+ connection.__send__(:write_timeout_exceeded?).should be_false
420
+ end
421
+ it "should exceed the timeout if buffered and duration is greater than timeout" do
422
+ Time.stub(:now => 59)
423
+ connection.write_timeout = 10
424
+ connection.push_write_buffer 'FRAME_SERIALIZED', frame
425
+ # This proves that not all calls to push_write_buffer reset the clock.
426
+ Time.stub(:now => 70)
427
+ connection.push_write_buffer 'FRAME_SERIALIZED', frame
428
+ connection.__send__(:write_timeout_exceeded?).should be_true
429
+ end
282
430
  end
283
431
 
284
432
  describe ".connect" do
@@ -294,7 +442,7 @@ module OnStomp::Connections
294
442
  connection.should_receive(:connect_frame).and_return(connect_frame)
295
443
  connection.should_receive(:write_frame_nonblock).with(connect_frame)
296
444
  connection.should_receive(:io_process_write).and_yield(connect_frame)
297
- connection.should_receive(:io_process_read).and_yield(connected_frame)
445
+ connection.should_receive(:io_process_read).with(true).and_yield(connected_frame)
298
446
  connected_frame.command = 'NOT CONNECTED'
299
447
  lambda { connection.connect(client, *headers) }.should raise_error(OnStomp::ConnectFailedError)
300
448
  end
@@ -302,7 +450,7 @@ module OnStomp::Connections
302
450
  connection.should_receive(:connect_frame).and_return(connect_frame)
303
451
  connection.should_receive(:write_frame_nonblock).with(connect_frame)
304
452
  connection.should_receive(:io_process_write).and_yield(connect_frame)
305
- connection.should_receive(:io_process_read).and_yield(connected_frame)
453
+ connection.should_receive(:io_process_read).with(true).and_yield(connected_frame)
306
454
  connected_frame[:version] = '1.9'
307
455
  client.stub(:versions => [ '1.0', '1.1' ])
308
456
  lambda { connection.connect(client, *headers) }.should raise_error(OnStomp::UnsupportedProtocolVersionError)
@@ -311,7 +459,7 @@ module OnStomp::Connections
311
459
  connection.should_receive(:connect_frame).and_return(connect_frame)
312
460
  connection.should_receive(:write_frame_nonblock).with(connect_frame)
313
461
  connection.should_receive(:io_process_write).and_yield(connect_frame)
314
- connection.should_receive(:io_process_read).and_yield(connected_frame)
462
+ connection.should_receive(:io_process_read).with(true).and_yield(connected_frame)
315
463
  client.stub(:versions => [ '1.0', '1.1' ])
316
464
  connection.connect(client, *headers).should == ['1.0', connected_frame]
317
465
  end
@@ -319,7 +467,7 @@ module OnStomp::Connections
319
467
  connection.should_receive(:connect_frame).and_return(connect_frame)
320
468
  connection.should_receive(:write_frame_nonblock).with(connect_frame)
321
469
  connection.should_receive(:io_process_write).and_yield(connect_frame)
322
- connection.should_receive(:io_process_read).and_yield(connected_frame)
470
+ connection.should_receive(:io_process_read).with(true).and_yield(connected_frame)
323
471
  connected_frame[:version] = '2.3'
324
472
  client.stub(:versions => [ '1.0', '2.3' ])
325
473
  connection.connect(client, *headers).should == ['2.3', connected_frame]
@@ -330,7 +478,7 @@ module OnStomp::Connections
330
478
  :write_frame_nonblock => connect_frame)
331
479
  client.stub(:versions => ['1.0', '1.1'])
332
480
  connection.stub(:io_process_write).and_yield(connect_frame)
333
- connection.stub(:io_process_read).and_yield(connected_frame)
481
+ connection.stub(:io_process_read).with(true).and_yield(connected_frame)
334
482
  triggered = 0
335
483
  connection.on_died { |cl, cn| triggered += 1 }
336
484
  connection.connect client
@@ -360,5 +508,39 @@ module OnStomp::Connections
360
508
  connection.version.should == '1.0'
361
509
  end
362
510
  end
511
+
512
+ describe "non-blocking IO wrappers" do
513
+ before(:each) do
514
+ io.stub(:closed? => false)
515
+ io.stub(:read_nonblock => nil, :write_nonblock => 16)
516
+ end
517
+
518
+ it "should use read_nonblock if IO responds to read_nonblock" do
519
+ io.should_receive(:read_nonblock).with(Base::MAX_BYTES_PER_READ)
520
+ connection.stub(:ready_for_read? => true)
521
+ connection.io_process_read
522
+ end
523
+ it "should use readpartial if IO does not respond to read_nonblock" do
524
+ io.unstub(:read_nonblock)
525
+ io.should_receive(:readpartial).with(Base::MAX_BYTES_PER_READ)
526
+ connection.stub(:ready_for_read? => true)
527
+ connection.io_process_read
528
+ end
529
+
530
+ it "should use write_nonblock if IO responds to write_nonblock" do
531
+ io.should_receive(:write_nonblock).with("FRAME_SERIALIZED")
532
+ connection.stub(:ready_for_write? => true)
533
+ connection.push_write_buffer 'FRAME_SERIALIZED', frame
534
+ connection.io_process_write
535
+ end
536
+ it "should use readpartial if IO does not respond to read_nonblock" do
537
+ io.unstub(:write_nonblock)
538
+ io.should_not respond_to(:write_nonblock)
539
+ io.should_receive(:write).with("FRAME_SERIALIZED") { 16 }
540
+ connection.push_write_buffer 'FRAME_SERIALIZED', frame
541
+ connection.stub(:ready_for_write? => true)
542
+ connection.io_process_write
543
+ end
544
+ end
363
545
  end
364
546
  end