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.
- data/.rspec +1 -1
- data/CHANGELOG.md +10 -0
- data/Gemfile +5 -1
- data/lib/onstomp/client.rb +13 -1
- data/lib/onstomp/connections.rb +9 -5
- data/lib/onstomp/connections/base.rb +144 -38
- data/lib/onstomp/connections/heartbeating.rb +0 -14
- data/lib/onstomp/failover/buffers.rb +1 -0
- data/lib/onstomp/failover/buffers/base.rb +59 -0
- data/lib/onstomp/failover/buffers/receipts.rb +16 -69
- data/lib/onstomp/failover/buffers/written.rb +18 -70
- data/lib/onstomp/failover/failover_configurable.rb +0 -7
- data/lib/onstomp/failover/uri.rb +3 -1
- data/lib/onstomp/interfaces/client_events.rb +1 -1
- data/lib/onstomp/interfaces/connection_events.rb +8 -0
- data/lib/onstomp/interfaces/uri_configurable.rb +7 -0
- data/lib/onstomp/version.rb +1 -1
- data/spec/onstomp/client_spec.rb +13 -1
- data/spec/onstomp/components/scopes/transaction_scope_spec.rb +7 -7
- data/spec/onstomp/connections/base_spec.rb +194 -12
- data/spec/onstomp/connections/heartbeating_spec.rb +0 -24
- data/spec/onstomp/connections_spec.rb +19 -7
- data/spec/onstomp/full_stacks/failover_spec.rb +49 -33
- data/spec/onstomp/full_stacks/onstomp_spec.rb +45 -4
- data/spec/onstomp/full_stacks/onstomp_ssh_spec.rb +10 -2
- data/spec/onstomp/full_stacks/test_broker.rb +21 -18
- metadata +2 -1
@@ -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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
data/lib/onstomp/failover/uri.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/onstomp/version.rb
CHANGED
data/spec/onstomp/client_spec.rb
CHANGED
@@ -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
|
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
|