onstomp 1.0.3 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/.rspec
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
--colour
|
2
|
-
--format
|
2
|
+
--format p
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Changes
|
2
2
|
|
3
|
+
## 1.0.4
|
4
|
+
* fixed major oversight when using ruby 1.8.7 / jruby with an SSL connection.
|
5
|
+
it will use blocking read/write methods if nonblocking methods are unavailable
|
6
|
+
* added write and read block timeouts. if there is no write activity for a period
|
7
|
+
of time (default of 120 seconds), the connection is assumed to be dead. read
|
8
|
+
activity is handled similarly, but only when performing the CONNECT/CONNECTED
|
9
|
+
exchange, after that we have no way of knowing if the broker just doesn't
|
10
|
+
have any data for us (unless using a STOMP 1.1 connection with heartbeating)
|
11
|
+
* refactored common failover buffer code into lib/onstomp/failover/buffers/base
|
12
|
+
|
3
13
|
## 1.0.3
|
4
14
|
* change how failover spawns new connections when failing over.
|
5
15
|
|
data/Gemfile
CHANGED
data/lib/onstomp/client.rb
CHANGED
@@ -47,6 +47,17 @@ class OnStomp::Client
|
|
47
47
|
# @return [Class]
|
48
48
|
attr_configurable_processor :processor
|
49
49
|
|
50
|
+
# The number of seconds to wait before a write-blocked connection is
|
51
|
+
# considered dead. Defaults to 120 seconds.
|
52
|
+
# @return [Fixnum]
|
53
|
+
attr_configurable_int :write_timeout, :default => 120
|
54
|
+
|
55
|
+
# The number of seconds to wait before a connection that is read-blocked
|
56
|
+
# during the {OnStomp::Connections::Base#connect connect} phase is
|
57
|
+
# considered dead. Defaults to 120 seconds.
|
58
|
+
# @return [Fixnum]
|
59
|
+
attr_configurable_int :read_timeout, :default => 120
|
60
|
+
|
50
61
|
# @api gem:1 STOMP:1.0,1.1
|
51
62
|
# Creates a new client for the specified uri and optional hash of options.
|
52
63
|
# @param [String,URI] uri
|
@@ -71,7 +82,8 @@ class OnStomp::Client
|
|
71
82
|
@connection = OnStomp::Connections.connect self, headers,
|
72
83
|
{ :'accept-version' => @versions.join(','), :host => @host,
|
73
84
|
:'heart-beat' => @heartbeats.join(','), :login => @login,
|
74
|
-
:passcode => @passcode }, pending_connection_events
|
85
|
+
:passcode => @passcode }, pending_connection_events,
|
86
|
+
read_timeout, write_timeout
|
75
87
|
processor_inst.start
|
76
88
|
self
|
77
89
|
end
|
data/lib/onstomp/connections.rb
CHANGED
@@ -44,8 +44,8 @@ module OnStomp::Connections
|
|
44
44
|
# negotiated protocol version
|
45
45
|
# @raise [OnStomp::OnStompError] if negotiating the connection raises an
|
46
46
|
# such an error.
|
47
|
-
def self.connect client, u_head, c_head, con_cbs
|
48
|
-
init_con = create_connection('1.0', nil, client)
|
47
|
+
def self.connect client, u_head, c_head, con_cbs, r_time, w_time
|
48
|
+
init_con = create_connection('1.0', nil, client, r_time, w_time)
|
49
49
|
ver, connected = init_con.connect client, u_head, c_head
|
50
50
|
begin
|
51
51
|
negotiate_connection(ver, init_con, client).tap do |final_con|
|
@@ -61,21 +61,25 @@ module OnStomp::Connections
|
|
61
61
|
private
|
62
62
|
def self.negotiate_connection vers, con, client
|
63
63
|
supports_protocol?(vers,con) ? con :
|
64
|
-
create_connection(vers, con.socket, client
|
64
|
+
create_connection(vers, con.socket, client, con.read_timeout,
|
65
|
+
con.write_timeout)
|
65
66
|
end
|
66
67
|
|
67
68
|
def self.supports_protocol? ver, con
|
68
69
|
con.is_a? PROTOCOL_VERSIONS[ver]
|
69
70
|
end
|
70
71
|
|
71
|
-
def self.create_connection ver, sock, client
|
72
|
+
def self.create_connection ver, sock, client, rt, wt
|
72
73
|
unless sock
|
73
74
|
meth = client.ssl ? :ssl :
|
74
75
|
client.uri.respond_to?(:onstomp_socket_type) ?
|
75
76
|
client.uri.onstomp_socket_type : :tcp
|
76
77
|
sock = __send__(:"create_socket_#{meth}", client)
|
77
78
|
end
|
78
|
-
PROTOCOL_VERSIONS[ver].new
|
79
|
+
PROTOCOL_VERSIONS[ver].new(sock, client).tap do |con|
|
80
|
+
con.read_timeout = rt
|
81
|
+
con.write_timeout = wt
|
82
|
+
end
|
79
83
|
end
|
80
84
|
|
81
85
|
def self.create_socket_tcp client
|
@@ -5,6 +5,7 @@ class OnStomp::Connections::Base
|
|
5
5
|
include OnStomp::Interfaces::ConnectionEvents
|
6
6
|
attr_reader :version, :socket, :client
|
7
7
|
attr_reader :last_transmitted_at, :last_received_at
|
8
|
+
attr_accessor :write_timeout, :read_timeout
|
8
9
|
|
9
10
|
# The approximate maximum number of bytes to write per call to
|
10
11
|
# {#io_process_write}.
|
@@ -26,6 +27,9 @@ class OnStomp::Connections::Base
|
|
26
27
|
@read_buffer = []
|
27
28
|
@client = client
|
28
29
|
@connection_up = false
|
30
|
+
@write_timeout = nil
|
31
|
+
@read_timeout = nil
|
32
|
+
setup_non_blocking_methods
|
29
33
|
end
|
30
34
|
|
31
35
|
# Performs any necessary configuration of the connection from the CONNECTED
|
@@ -71,9 +75,10 @@ class OnStomp::Connections::Base
|
|
71
75
|
until client_con
|
72
76
|
io_process_write { |f| client_con ||= f }
|
73
77
|
end
|
78
|
+
@last_received_at = Time.now
|
74
79
|
broker_con = nil
|
75
80
|
until broker_con
|
76
|
-
io_process_read { |f| broker_con ||= f }
|
81
|
+
io_process_read(true) { |f| broker_con ||= f }
|
77
82
|
end
|
78
83
|
raise OnStomp::ConnectFailedError if broker_con.command != 'CONNECTED'
|
79
84
|
vers = broker_con.header?(:version) ? broker_con[:version] : '1.0'
|
@@ -93,6 +98,20 @@ class OnStomp::Connections::Base
|
|
93
98
|
end
|
94
99
|
end
|
95
100
|
|
101
|
+
# Number of milliseconds since data was last transmitted to the broker or
|
102
|
+
# `nil` if no data has been transmitted when the method is called.
|
103
|
+
# @return [Fixnum, nil]
|
104
|
+
def duration_since_transmitted
|
105
|
+
last_transmitted_at && ((Time.now - last_transmitted_at)*1000).to_i
|
106
|
+
end
|
107
|
+
|
108
|
+
# Number of milliseconds since data was last received from the broker or
|
109
|
+
# `nil` if no data has been received when the method is called.
|
110
|
+
# @return [Fixnum, nil]
|
111
|
+
def duration_since_received
|
112
|
+
last_received_at && ((Time.now - last_received_at)*1000).to_i
|
113
|
+
end
|
114
|
+
|
96
115
|
# Flushes the write buffer by invoking {#io_process_write} until the
|
97
116
|
# buffer is empty.
|
98
117
|
def flush_write_buffer
|
@@ -122,6 +141,7 @@ class OnStomp::Connections::Base
|
|
122
141
|
# @param [OnStomp::Components::Frame]
|
123
142
|
def push_write_buffer data, frame
|
124
143
|
@write_mutex.synchronize {
|
144
|
+
@last_write_activity = Time.now if @write_buffer.empty?
|
125
145
|
@write_buffer << [data, frame] unless @closing
|
126
146
|
}
|
127
147
|
end
|
@@ -146,34 +166,34 @@ class OnStomp::Connections::Base
|
|
146
166
|
# sent to the head of the write buffer to be processed first the next time
|
147
167
|
# this method is invoked.
|
148
168
|
def io_process_write
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
169
|
+
if ready_for_write?
|
170
|
+
to_shift = @write_buffer.length / 3
|
171
|
+
written = 0
|
172
|
+
while written < MAX_BYTES_PER_WRITE
|
173
|
+
data, frame = shift_write_buffer
|
174
|
+
break unless data && connected?
|
175
|
+
begin
|
176
|
+
w = write_nonblock data
|
177
|
+
rescue Errno::EINTR, Errno::EAGAIN, Errno::EWOULDBLOCK
|
178
|
+
# writing will either block, or cannot otherwise be completed,
|
179
|
+
# put data back and try again some other day
|
180
|
+
unshift_write_buffer data, frame
|
181
|
+
break
|
182
|
+
rescue Exception
|
183
|
+
triggered_close $!.message, :terminated
|
184
|
+
raise
|
185
|
+
end
|
186
|
+
written += w
|
187
|
+
@last_write_activity = @last_transmitted_at = Time.now
|
188
|
+
if w < data.length
|
189
|
+
unshift_write_buffer data[w..-1], frame
|
190
|
+
else
|
191
|
+
yield frame if block_given?
|
192
|
+
client.dispatch_transmitted frame
|
172
193
|
end
|
173
194
|
end
|
174
|
-
|
175
|
-
triggered_close
|
176
|
-
raise
|
195
|
+
elsif write_timeout_exceeded?
|
196
|
+
triggered_close 'write blocked', :blocked
|
177
197
|
end
|
178
198
|
if @write_buffer.empty? && @closing
|
179
199
|
triggered_close 'client disconnected'
|
@@ -184,37 +204,123 @@ class OnStomp::Connections::Base
|
|
184
204
|
# and the socket is ready for reading. The received data will be pushed
|
185
205
|
# to the end of a read buffer, which is then sent to the connection's
|
186
206
|
# {OnStomp::Connections::Serializers serializer} for processing.
|
187
|
-
def io_process_read
|
188
|
-
|
189
|
-
|
190
|
-
if data =
|
207
|
+
def io_process_read(check_timeout=false)
|
208
|
+
if ready_for_read?
|
209
|
+
begin
|
210
|
+
if data = read_nonblock
|
191
211
|
@read_buffer << data
|
192
212
|
@last_received_at = Time.now
|
193
213
|
serializer.bytes_to_frame(@read_buffer) do |frame|
|
194
214
|
yield frame if block_given?
|
195
215
|
client.dispatch_received frame
|
196
216
|
end
|
197
|
-
else
|
198
|
-
triggered_close $!.message, :terminated
|
199
217
|
end
|
218
|
+
rescue Errno::EINTR, Errno::EAGAIN, Errno::EWOULDBLOCK
|
219
|
+
# do not
|
220
|
+
rescue EOFError
|
221
|
+
triggered_close $!.message
|
222
|
+
rescue Exception
|
223
|
+
triggered_close $!.message, :terminated
|
224
|
+
raise
|
200
225
|
end
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
226
|
+
elsif check_timeout && read_timeout_exceeded?
|
227
|
+
triggered_close 'read blocked', :blocked
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
private
|
232
|
+
def duration_since_write_activity
|
233
|
+
Time.now - @last_write_activity
|
234
|
+
end
|
235
|
+
|
236
|
+
# Returns true if the connection has buffered data to write and the
|
237
|
+
# socket is ready to be written to. If checking the socket's state raises
|
238
|
+
# an exception, the connection will be closed (triggering an
|
239
|
+
# `on_terminated` event) and the error will be re-raised.
|
240
|
+
def ready_for_write?
|
241
|
+
begin
|
242
|
+
@write_buffer.length > 0 && IO.select(nil, [socket], nil, 0.1)
|
205
243
|
rescue Exception
|
206
244
|
triggered_close $!.message, :terminated
|
207
245
|
raise
|
208
246
|
end
|
209
247
|
end
|
210
248
|
|
211
|
-
|
249
|
+
# Returns true if the connection has buffered data to write and the
|
250
|
+
# socket is ready to be written to. If checking the socket's state raises
|
251
|
+
# an exception, the connection will be closed (triggering an
|
252
|
+
# `on_terminated` event) and the error will be re-raised.
|
253
|
+
def ready_for_read?
|
254
|
+
begin
|
255
|
+
connected? && IO.select([socket], nil, nil, 0.1)
|
256
|
+
rescue Exception
|
257
|
+
triggered_close $!.message, :terminated
|
258
|
+
raise
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# Returns true if a `write_timeout` has been set, the connection has buffered
|
263
|
+
# data to write, and `duration_since_transmitted` is greater than
|
264
|
+
# `write_timeout`
|
265
|
+
def write_timeout_exceeded?
|
266
|
+
@write_timeout && @write_buffer.length > 0 &&
|
267
|
+
duration_since_write_activity > @write_timeout
|
268
|
+
end
|
269
|
+
|
270
|
+
# Returns true if a `read_timeout` has been set and
|
271
|
+
# `duration_since_received` is greater than `read_timeout`
|
272
|
+
# This is only used when establishing the connection through the CONNECT/
|
273
|
+
# CONNECTED handshake. After that, it is up to heart-beating.
|
274
|
+
def read_timeout_exceeded?
|
275
|
+
@read_timeout && duration_since_received > (@read_timeout*1000)
|
276
|
+
end
|
277
|
+
|
212
278
|
def triggered_close msg, *evs
|
213
279
|
@connection_up = false
|
214
280
|
@closing = false
|
281
|
+
# unless socket.closed?
|
282
|
+
# socket.to_io.shutdown(2) rescue nil
|
283
|
+
#
|
284
|
+
# end
|
215
285
|
socket.close rescue nil
|
216
286
|
evs.each { |ev| trigger_connection_event ev, msg }
|
217
287
|
trigger_connection_event :closed, msg
|
218
288
|
@write_buffer.clear
|
219
289
|
end
|
290
|
+
|
291
|
+
# OpenSSL sockets in Ruby 1.8.7 and JRuby (as of jruby-openssl 0.7.3)
|
292
|
+
# do NOT support non-blocking IO natively. Such a hack, and such a huge
|
293
|
+
# oversight on my part. We define some methods on this instance to use
|
294
|
+
# the right read/write operations. Fortunately, this gets done at
|
295
|
+
# initialization and only has to happen once.
|
296
|
+
def setup_non_blocking_methods
|
297
|
+
read_mod = @socket.respond_to?(:read_nonblock) ? NonblockingRead :
|
298
|
+
BlockingRead
|
299
|
+
write_mod = @socket.respond_to?(:write_nonblock) ? NonblockingWrite :
|
300
|
+
BlockingWrite
|
301
|
+
self.extend read_mod
|
302
|
+
self.extend write_mod
|
303
|
+
end
|
304
|
+
|
305
|
+
module NonblockingRead
|
306
|
+
def read_nonblock
|
307
|
+
socket.read_nonblock MAX_BYTES_PER_READ
|
308
|
+
end
|
309
|
+
end
|
310
|
+
module NonblockingWrite
|
311
|
+
def write_nonblock data
|
312
|
+
socket.write_nonblock data
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
module BlockingRead
|
317
|
+
def read_nonblock
|
318
|
+
socket.readpartial MAX_BYTES_PER_READ
|
319
|
+
end
|
320
|
+
end
|
321
|
+
module BlockingWrite
|
322
|
+
def write_nonblock data
|
323
|
+
socket.write data
|
324
|
+
end
|
325
|
+
end
|
220
326
|
end
|
@@ -51,20 +51,6 @@ module OnStomp::Connections::Heartbeating
|
|
51
51
|
end
|
52
52
|
@heartbeat_broker_limit
|
53
53
|
end
|
54
|
-
|
55
|
-
# Number of milliseconds since data was last transmitted to the broker or
|
56
|
-
# `nil` if no data has been transmitted when the method is called.
|
57
|
-
# @return [Fixnum, nil]
|
58
|
-
def duration_since_transmitted
|
59
|
-
last_transmitted_at && ((Time.now - last_transmitted_at)*1000).to_i
|
60
|
-
end
|
61
|
-
|
62
|
-
# Number of milliseconds since data was last received from the broker or
|
63
|
-
# `nil` if no data has been received when the method is called.
|
64
|
-
# @return [Fixnum, nil]
|
65
|
-
def duration_since_received
|
66
|
-
last_received_at && ((Time.now - last_received_at)*1000).to_i
|
67
|
-
end
|
68
54
|
|
69
55
|
# Returns true if client-side heartbeating is disabled, or
|
70
56
|
# {#duration_since_transmitted} has not exceeded {#heartbeat_client_limit}
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# The base class for all buffers. This class exists mostly as a factoring
|
4
|
+
# out of the code shared between the {OnStomp::Failover::Buffers::Written}
|
5
|
+
# and {OnStomp::Failover::Buffers::Receipts} buffers.
|
6
|
+
class OnStomp::Failover::Buffers::Base
|
7
|
+
def initialize failover
|
8
|
+
@failover = failover
|
9
|
+
@buffer_mutex = Mutex.new
|
10
|
+
@buffer = []
|
11
|
+
@txs = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the number of frames currently sitting in the buffer.
|
15
|
+
# @return [Fixnum]
|
16
|
+
def buffered
|
17
|
+
@buffer.length
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def add_to_buffer f, heads={}
|
22
|
+
@buffer_mutex.synchronize do
|
23
|
+
unless f.header? :'x-onstomp-failover-replay'
|
24
|
+
f.headers.reverse_merge! heads
|
25
|
+
@buffer << f
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_to_transactions f, heads={}
|
31
|
+
@txs[f[:transaction]] = true
|
32
|
+
add_to_buffer f, heads
|
33
|
+
end
|
34
|
+
|
35
|
+
def remove_from_transactions f
|
36
|
+
tx = f[:transaction]
|
37
|
+
if @txs.delete tx
|
38
|
+
@buffer_mutex.synchronize do
|
39
|
+
@buffer.reject! { |bf| bf[:transaction] == tx }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def remove_subscribe_from_buffer f
|
45
|
+
@buffer_mutex.synchronize do
|
46
|
+
@buffer.reject! { |bf| bf.command == 'SUBSCRIBE' && bf[:id] == f[:id] }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def replay_buffer client
|
51
|
+
replay_frames = @buffer_mutex.synchronize do
|
52
|
+
@buffer.select { |f| f[:'x-onstomp-failover-replay'] = '1'; true }
|
53
|
+
end
|
54
|
+
|
55
|
+
replay_frames.each do |f|
|
56
|
+
client.transmit f
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -6,79 +6,38 @@
|
|
6
6
|
# {OnStomp::Failover::Client failover} client reconnects.
|
7
7
|
# @todo Quite a lot of this code is shared between Written and Receipts,
|
8
8
|
# we'll want to factor the common stuff out.
|
9
|
-
class OnStomp::Failover::Buffers::Receipts
|
9
|
+
class OnStomp::Failover::Buffers::Receipts < OnStomp::Failover::Buffers::Base
|
10
10
|
def initialize failover
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
failover.
|
18
|
-
|
19
|
-
|
20
|
-
failover.before_begin &method(:buffer_transaction)
|
11
|
+
super
|
12
|
+
[:send, :commit, :abort, :subscribe].each do |ev|
|
13
|
+
failover.__send__(:"before_#{ev}") do |f, *_|
|
14
|
+
add_to_buffer f, {:receipt => OnStomp.next_serial}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
failover.before_begin do |f, *_|
|
18
|
+
add_to_transactions f, {:receipt => OnStomp.next_serial}
|
19
|
+
end
|
21
20
|
# We can scrub the subscription before UNSUBSCRIBE is fully written
|
22
21
|
# because if we replay before UNSUBSCRIBE was sent, we still don't
|
23
22
|
# want to be subscribed when we reconnect.
|
24
|
-
failover.before_unsubscribe
|
25
|
-
|
26
|
-
|
27
|
-
failover.on_failover_connected &method(:replay)
|
28
|
-
end
|
29
|
-
|
30
|
-
# Adds a frame to a buffer so that it may be replayed if the
|
31
|
-
# {OnStomp::Failover::Client failover} client re-connects
|
32
|
-
def buffer_frame f, *_
|
33
|
-
@buffer_mutex.synchronize do
|
34
|
-
# Don't re-buffer frames that are being replayed.
|
35
|
-
unless f.header? :'x-onstomp-failover-replay'
|
36
|
-
# Create a receipt header, unless the frame already has one.
|
37
|
-
f[:receipt] = OnStomp.next_serial unless f.header?(:receipt)
|
38
|
-
@buffer << f
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# Records the start of a transaction so that it may be replayed if the
|
44
|
-
# {OnStomp::Failover::Client failover} client re-connects
|
45
|
-
def buffer_transaction f, *_
|
46
|
-
@txs[f[:transaction]] = true
|
47
|
-
buffer_frame f
|
48
|
-
end
|
49
|
-
|
50
|
-
# Removes the recorded transaction from the buffer after it has been
|
51
|
-
# written the broker socket so that it will not be replayed when the
|
52
|
-
# {OnStomp::Failover::Client failover} client re-connects
|
53
|
-
def debuffer_transaction f
|
54
|
-
tx = f[:transaction]
|
55
|
-
if @txs.delete tx
|
56
|
-
@buffer_mutex.synchronize do
|
57
|
-
@buffer.reject! { |bf| bf[:transaction] == tx }
|
58
|
-
end
|
23
|
+
failover.before_unsubscribe do |f, *_|
|
24
|
+
remove_subscribe_from_buffer f
|
59
25
|
end
|
26
|
+
failover.on_receipt { |r, *_| debuffer_frame r }
|
27
|
+
failover.on_failover_connected { |f,c,*_| replay_buffer c }
|
60
28
|
end
|
61
29
|
|
62
|
-
# Removes the matching SUBSCRIBE frame from the buffer after the
|
63
|
-
# UNSUBSCRIBE has been added to the connection's write buffer
|
64
|
-
# so that it will not be replayed when the
|
65
|
-
# {OnStomp::Failover::Client failover} client re-connects
|
66
|
-
def debuffer_subscription f, *_
|
67
|
-
@buffer_mutex.synchronize do
|
68
|
-
@buffer.reject! { |bf| bf.command == 'SUBSCRIBE' && bf[:id] == f[:id] }
|
69
|
-
end
|
70
|
-
end
|
71
30
|
|
72
31
|
# Removes frames that neither transactional nor SUBSCRIBEs from the buffer
|
73
32
|
# by looking the buffered frames up by their `receipt` header.
|
74
|
-
def debuffer_frame r
|
33
|
+
def debuffer_frame r
|
75
34
|
orig = @buffer_mutex.synchronize do
|
76
35
|
@buffer.detect { |f| f[:receipt] == r[:'receipt-id'] }
|
77
36
|
end
|
78
37
|
if orig
|
79
38
|
# COMMIT and ABORT debuffer the whole transaction sequence
|
80
39
|
if ['COMMIT', 'ABORT'].include? orig.command
|
81
|
-
|
40
|
+
remove_from_transactions orig
|
82
41
|
# Otherwise, if this isn't part of a transaction, debuffer the
|
83
42
|
# particular frame (if it's not a SUBSCRIBE)
|
84
43
|
elsif orig.command != 'SUBSCRIBE' && !orig.header?(:transaction)
|
@@ -86,16 +45,4 @@ class OnStomp::Failover::Buffers::Receipts
|
|
86
45
|
end
|
87
46
|
end
|
88
47
|
end
|
89
|
-
|
90
|
-
# Called when the {OnStomp::Failover::Client failover} client triggers
|
91
|
-
# `on_failover_connected` to start replaying any frames in the buffer.
|
92
|
-
def replay fail, client, *_
|
93
|
-
replay_frames = @buffer_mutex.synchronize do
|
94
|
-
@buffer.select { |f| f[:'x-onstomp-failover-replay'] = '1'; true }
|
95
|
-
end
|
96
|
-
|
97
|
-
replay_frames.each do |f|
|
98
|
-
client.transmit f
|
99
|
-
end
|
100
|
-
end
|
101
48
|
end
|