onstomp 1.0.3 → 1.0.4

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