google-cloud-pubsub 0.26.0 → 0.27.0
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.
- checksums.yaml +4 -4
- data/lib/google/cloud/pubsub.rb +67 -31
- data/lib/google/cloud/pubsub/async_publisher.rb +328 -0
- data/lib/google/cloud/pubsub/batch_publisher.rb +98 -0
- data/lib/google/cloud/pubsub/convert.rb +63 -0
- data/lib/google/cloud/pubsub/message.rb +13 -4
- data/lib/google/cloud/pubsub/project.rb +64 -184
- data/lib/google/cloud/pubsub/publish_result.rb +96 -0
- data/lib/google/cloud/pubsub/received_message.rb +36 -2
- data/lib/google/cloud/pubsub/service.rb +25 -28
- data/lib/google/cloud/pubsub/subscriber.rb +185 -0
- data/lib/google/cloud/pubsub/subscriber/async_pusher.rb +220 -0
- data/lib/google/cloud/pubsub/subscriber/enumerator_queue.rb +52 -0
- data/lib/google/cloud/pubsub/subscriber/stream.rb +376 -0
- data/lib/google/cloud/pubsub/subscription.rb +102 -72
- data/lib/google/cloud/pubsub/topic.rb +125 -32
- data/lib/google/cloud/pubsub/v1/publisher_client_config.json +1 -1
- data/lib/google/cloud/pubsub/v1/subscriber_client_config.json +10 -1
- data/lib/google/cloud/pubsub/version.rb +1 -1
- metadata +38 -3
- data/lib/google/cloud/pubsub/topic/publisher.rb +0 -86
@@ -0,0 +1,220 @@
|
|
1
|
+
# Copyright 2017 Google Inc. All rights reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require "monitor"
|
17
|
+
require "concurrent"
|
18
|
+
|
19
|
+
module Google
|
20
|
+
module Cloud
|
21
|
+
module Pubsub
|
22
|
+
class Subscriber
|
23
|
+
##
|
24
|
+
# @private
|
25
|
+
# # AsyncPusher
|
26
|
+
#
|
27
|
+
class AsyncPusher
|
28
|
+
include MonitorMixin
|
29
|
+
|
30
|
+
attr_reader :batch
|
31
|
+
attr_reader :max_bytes, :interval
|
32
|
+
|
33
|
+
def initialize stream, max_bytes: 10000000, interval: 0.25
|
34
|
+
@stream = stream
|
35
|
+
|
36
|
+
@max_bytes = max_bytes
|
37
|
+
@interval = interval
|
38
|
+
|
39
|
+
@cond = new_cond
|
40
|
+
|
41
|
+
# init MonitorMixin
|
42
|
+
super()
|
43
|
+
end
|
44
|
+
|
45
|
+
def acknowledge ack_ids
|
46
|
+
return true if ack_ids.empty?
|
47
|
+
|
48
|
+
synchronize do
|
49
|
+
ack_ids.each do |ack_id|
|
50
|
+
if @batch.nil?
|
51
|
+
@batch = Batch.new max_bytes: @max_bytes
|
52
|
+
@batch.ack ack_id
|
53
|
+
else
|
54
|
+
unless @batch.try_ack ack_id
|
55
|
+
push_batch_request!
|
56
|
+
|
57
|
+
@batch = Batch.new max_bytes: @max_bytes
|
58
|
+
@batch.ack ack_id
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
@batch_created_at ||= Time.now
|
63
|
+
@background_thread ||= Thread.new { run_background }
|
64
|
+
|
65
|
+
push_batch_request! if @batch.ready?
|
66
|
+
end
|
67
|
+
|
68
|
+
@cond.signal
|
69
|
+
end
|
70
|
+
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def delay deadline, ack_ids
|
75
|
+
return true if ack_ids.empty?
|
76
|
+
|
77
|
+
synchronize do
|
78
|
+
ack_ids.each do |ack_id|
|
79
|
+
if @batch.nil?
|
80
|
+
@batch = Batch.new max_bytes: @max_bytes
|
81
|
+
@batch.delay deadline, ack_id
|
82
|
+
else
|
83
|
+
unless @batch.try_delay deadline, ack_id
|
84
|
+
push_batch_request!
|
85
|
+
|
86
|
+
@batch = Batch.new max_bytes: @max_bytes
|
87
|
+
@batch.delay deadline, ack_id
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
@batch_created_at ||= Time.now
|
92
|
+
@background_thread ||= Thread.new { run_background }
|
93
|
+
|
94
|
+
push_batch_request! if @batch.ready?
|
95
|
+
end
|
96
|
+
|
97
|
+
@cond.signal
|
98
|
+
end
|
99
|
+
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
|
103
|
+
def stop
|
104
|
+
synchronize do
|
105
|
+
break if @stopped
|
106
|
+
|
107
|
+
@stopped = true
|
108
|
+
push_batch_request!
|
109
|
+
@cond.signal
|
110
|
+
end
|
111
|
+
|
112
|
+
self
|
113
|
+
end
|
114
|
+
|
115
|
+
def wait!
|
116
|
+
synchronize do
|
117
|
+
@background_thread.join if @background_thread
|
118
|
+
end
|
119
|
+
|
120
|
+
self
|
121
|
+
end
|
122
|
+
|
123
|
+
def flush
|
124
|
+
synchronize do
|
125
|
+
push_batch_request!
|
126
|
+
@cond.signal
|
127
|
+
end
|
128
|
+
|
129
|
+
self
|
130
|
+
end
|
131
|
+
|
132
|
+
def started?
|
133
|
+
!stopped?
|
134
|
+
end
|
135
|
+
|
136
|
+
def stopped?
|
137
|
+
synchronize { @stopped }
|
138
|
+
end
|
139
|
+
|
140
|
+
protected
|
141
|
+
|
142
|
+
def run_background
|
143
|
+
synchronize do
|
144
|
+
until @stopped
|
145
|
+
if @batch.nil?
|
146
|
+
@cond.wait
|
147
|
+
next
|
148
|
+
end
|
149
|
+
|
150
|
+
time_since_first_publish = Time.now - @batch_created_at
|
151
|
+
if time_since_first_publish > @interval
|
152
|
+
# interval met, publish the batch...
|
153
|
+
push_batch_request!
|
154
|
+
@cond.wait
|
155
|
+
else
|
156
|
+
# still waiting for the interval to publish the batch...
|
157
|
+
@cond.wait(@interval - time_since_first_publish)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def push_batch_request!
|
164
|
+
return unless @batch
|
165
|
+
|
166
|
+
request = @batch.request
|
167
|
+
Concurrent::Future.new(executor: @stream.push_thread_pool) do
|
168
|
+
@stream.push request
|
169
|
+
end.execute
|
170
|
+
|
171
|
+
@batch = nil
|
172
|
+
@batch_created_at = nil
|
173
|
+
end
|
174
|
+
|
175
|
+
class Batch
|
176
|
+
attr_reader :max_bytes, :request
|
177
|
+
|
178
|
+
def initialize max_bytes: 10000000
|
179
|
+
@max_bytes = max_bytes
|
180
|
+
@request = Google::Pubsub::V1::StreamingPullRequest.new
|
181
|
+
end
|
182
|
+
|
183
|
+
def ack ack_id
|
184
|
+
@request.ack_ids << ack_id
|
185
|
+
end
|
186
|
+
|
187
|
+
def try_ack ack_id
|
188
|
+
addl_bytes = ack_id.size
|
189
|
+
return false if total_message_bytes + addl_bytes >= @max_bytes
|
190
|
+
|
191
|
+
ack ack_id
|
192
|
+
true
|
193
|
+
end
|
194
|
+
|
195
|
+
def delay deadline, ack_id
|
196
|
+
@request.modify_deadline_seconds << deadline
|
197
|
+
@request.modify_deadline_ack_ids << ack_id
|
198
|
+
end
|
199
|
+
|
200
|
+
def try_delay deadline, ack_id
|
201
|
+
addl_bytes = deadline.to_s.size + ack_id.size
|
202
|
+
return false if total_message_bytes + addl_bytes >= @max_bytes
|
203
|
+
|
204
|
+
delay deadline, ack_id
|
205
|
+
true
|
206
|
+
end
|
207
|
+
|
208
|
+
def ready?
|
209
|
+
total_message_bytes >= @max_bytes
|
210
|
+
end
|
211
|
+
|
212
|
+
def total_message_bytes
|
213
|
+
request.to_proto.size
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Copyright 2017 Google Inc. All rights reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require "thread"
|
17
|
+
|
18
|
+
module Google
|
19
|
+
module Cloud
|
20
|
+
module Pubsub
|
21
|
+
class Subscriber
|
22
|
+
# @private
|
23
|
+
class EnumeratorQueue
|
24
|
+
def initialize sentinel = nil
|
25
|
+
@queue = Queue.new
|
26
|
+
@sentinel = sentinel
|
27
|
+
end
|
28
|
+
|
29
|
+
def push obj
|
30
|
+
@queue.push obj
|
31
|
+
end
|
32
|
+
|
33
|
+
def dump_queue
|
34
|
+
objs = []
|
35
|
+
objs << @queue.pop until @queue.empty?
|
36
|
+
objs
|
37
|
+
end
|
38
|
+
|
39
|
+
def each
|
40
|
+
return enum_for(:each) unless block_given?
|
41
|
+
|
42
|
+
loop do
|
43
|
+
obj = @queue.pop
|
44
|
+
break if obj.equal? @sentinel
|
45
|
+
yield obj
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,376 @@
|
|
1
|
+
# Copyright 2017 Google Inc. All rights reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
|
16
|
+
require "google/cloud/pubsub/subscriber/async_pusher"
|
17
|
+
require "google/cloud/pubsub/subscriber/enumerator_queue"
|
18
|
+
require "google/cloud/pubsub/service"
|
19
|
+
require "google/cloud/errors"
|
20
|
+
require "monitor"
|
21
|
+
require "concurrent"
|
22
|
+
|
23
|
+
module Google
|
24
|
+
module Cloud
|
25
|
+
module Pubsub
|
26
|
+
class Subscriber
|
27
|
+
##
|
28
|
+
# @private
|
29
|
+
class Stream
|
30
|
+
include MonitorMixin
|
31
|
+
|
32
|
+
##
|
33
|
+
# @private Implementation attributes.
|
34
|
+
attr_reader :callback_thread_pool, :push_thread_pool
|
35
|
+
|
36
|
+
##
|
37
|
+
# Subscriber attributes.
|
38
|
+
attr_reader :subscriber
|
39
|
+
|
40
|
+
##
|
41
|
+
# @private Create an empty Subscriber::Stream object.
|
42
|
+
def initialize subscriber
|
43
|
+
@subscriber = subscriber
|
44
|
+
|
45
|
+
@request_queue = nil
|
46
|
+
@stopped = nil
|
47
|
+
@paused = nil
|
48
|
+
@pause_cond = new_cond
|
49
|
+
|
50
|
+
@inventory = Inventory.new self, subscriber.stream_inventory
|
51
|
+
@callback_thread_pool = Concurrent::FixedThreadPool.new \
|
52
|
+
subscriber.callback_threads
|
53
|
+
@push_thread_pool = Concurrent::FixedThreadPool.new \
|
54
|
+
subscriber.push_threads
|
55
|
+
|
56
|
+
super() # to init MonitorMixin
|
57
|
+
end
|
58
|
+
|
59
|
+
def start
|
60
|
+
synchronize do
|
61
|
+
break if @request_queue
|
62
|
+
|
63
|
+
start_streaming!
|
64
|
+
end
|
65
|
+
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def stop
|
70
|
+
synchronize do
|
71
|
+
break if @stopped
|
72
|
+
break if @request_queue.nil?
|
73
|
+
|
74
|
+
@request_queue.push self
|
75
|
+
@inventory.stop
|
76
|
+
@stopped = true
|
77
|
+
end
|
78
|
+
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
def stopped?
|
83
|
+
synchronize { @stopped }
|
84
|
+
end
|
85
|
+
|
86
|
+
def paused?
|
87
|
+
synchronize { @paused }
|
88
|
+
end
|
89
|
+
|
90
|
+
def wait!
|
91
|
+
synchronize do
|
92
|
+
@background_thread.join if @background_thread
|
93
|
+
|
94
|
+
@callback_thread_pool.shutdown
|
95
|
+
@callback_thread_pool.wait_for_termination
|
96
|
+
|
97
|
+
@async_pusher.stop.wait! if @async_pusher
|
98
|
+
|
99
|
+
@push_thread_pool.shutdown
|
100
|
+
@push_thread_pool.wait_for_termination
|
101
|
+
end
|
102
|
+
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# @private
|
108
|
+
def acknowledge *messages
|
109
|
+
ack_ids = coerce_ack_ids messages
|
110
|
+
return true if ack_ids.empty?
|
111
|
+
|
112
|
+
synchronize do
|
113
|
+
@async_pusher ||= AsyncPusher.new self
|
114
|
+
@async_pusher.acknowledge ack_ids
|
115
|
+
@inventory.remove ack_ids
|
116
|
+
unpause_streaming!
|
117
|
+
end
|
118
|
+
|
119
|
+
true
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# @private
|
124
|
+
def delay deadline, *messages
|
125
|
+
mod_ack_ids = coerce_ack_ids messages
|
126
|
+
return true if mod_ack_ids.empty?
|
127
|
+
|
128
|
+
synchronize do
|
129
|
+
@async_pusher ||= AsyncPusher.new self
|
130
|
+
@async_pusher.delay deadline, mod_ack_ids
|
131
|
+
@inventory.remove mod_ack_ids
|
132
|
+
unpause_streaming!
|
133
|
+
end
|
134
|
+
|
135
|
+
true
|
136
|
+
end
|
137
|
+
|
138
|
+
def async_pusher
|
139
|
+
synchronize { @async_pusher }
|
140
|
+
end
|
141
|
+
|
142
|
+
def push request
|
143
|
+
synchronize { @request_queue.push request }
|
144
|
+
end
|
145
|
+
|
146
|
+
def inventory
|
147
|
+
synchronize { @inventory }
|
148
|
+
end
|
149
|
+
|
150
|
+
##
|
151
|
+
# @private
|
152
|
+
def delay_inventory!
|
153
|
+
synchronize do
|
154
|
+
return true if @inventory.empty?
|
155
|
+
|
156
|
+
@async_pusher ||= AsyncPusher.new self
|
157
|
+
@async_pusher.delay subscriber.deadline, @inventory.ack_ids
|
158
|
+
end
|
159
|
+
|
160
|
+
true
|
161
|
+
end
|
162
|
+
|
163
|
+
# @private
|
164
|
+
def to_s
|
165
|
+
format "(inventory: %i, status: %s)", inventory.count, status
|
166
|
+
end
|
167
|
+
|
168
|
+
# @private
|
169
|
+
def inspect
|
170
|
+
"#<#{self.class.name} #{self}>"
|
171
|
+
end
|
172
|
+
|
173
|
+
protected
|
174
|
+
|
175
|
+
# rubocop:disable all
|
176
|
+
|
177
|
+
def background_run enum
|
178
|
+
until synchronize { @stopped }
|
179
|
+
synchronize do
|
180
|
+
if @paused
|
181
|
+
@pause_cond.wait
|
182
|
+
next
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
begin
|
187
|
+
# Cannot syncronize the enumerator, causes deadlock
|
188
|
+
response = enum.next
|
189
|
+
response.received_messages.each do |rec_msg_grpc|
|
190
|
+
rec_msg = ReceivedMessage.from_grpc(rec_msg_grpc, self)
|
191
|
+
synchronize do
|
192
|
+
@inventory.add rec_msg.ack_id
|
193
|
+
|
194
|
+
perform_callback_async rec_msg
|
195
|
+
end
|
196
|
+
end
|
197
|
+
synchronize { pause_streaming! }
|
198
|
+
rescue StopIteration
|
199
|
+
break
|
200
|
+
end
|
201
|
+
end
|
202
|
+
rescue GRPC::DeadlineExceeded, GRPC::Unavailable, GRPC::Cancelled
|
203
|
+
# The GAPIC layer will raise DeadlineExceeded when stream is opened
|
204
|
+
# longer than the timeout value it is configured for. When this
|
205
|
+
# happends, restart the stream stealthly.
|
206
|
+
# Also stealthly restart the stream on Unavailable and Cancelled.
|
207
|
+
synchronize { start_streaming! }
|
208
|
+
rescue => e
|
209
|
+
fail Google::Cloud::Error.from_error(e)
|
210
|
+
end
|
211
|
+
|
212
|
+
# rubocop:enable all
|
213
|
+
|
214
|
+
def perform_callback_async rec_msg
|
215
|
+
Concurrent::Future.new(executor: callback_thread_pool) do
|
216
|
+
subscriber.callback.call rec_msg
|
217
|
+
end.execute
|
218
|
+
end
|
219
|
+
|
220
|
+
def start_streaming!
|
221
|
+
# signal to the previous queue to shut down
|
222
|
+
old_queue = []
|
223
|
+
old_queue = @request_queue.dump_queue if @request_queue
|
224
|
+
|
225
|
+
@request_queue = EnumeratorQueue.new self
|
226
|
+
@request_queue.push initial_input_request
|
227
|
+
old_queue.each { |obj| @request_queue.push obj }
|
228
|
+
output_enum = subscriber.service.streaming_pull @request_queue.each
|
229
|
+
|
230
|
+
@stopped = nil
|
231
|
+
@paused = nil
|
232
|
+
|
233
|
+
# create new background thread to handle new enumerator
|
234
|
+
@background_thread = Thread.new(output_enum) do |enum|
|
235
|
+
background_run enum
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def pause_streaming!
|
240
|
+
return unless pause_streaming?
|
241
|
+
|
242
|
+
@paused = true
|
243
|
+
end
|
244
|
+
|
245
|
+
def pause_streaming?
|
246
|
+
return if @paused
|
247
|
+
|
248
|
+
@inventory.full?
|
249
|
+
end
|
250
|
+
|
251
|
+
def unpause_streaming!
|
252
|
+
return unless unpause_streaming?
|
253
|
+
|
254
|
+
@paused = nil
|
255
|
+
# signal to the background thread that we are unpaused
|
256
|
+
@pause_cond.broadcast
|
257
|
+
end
|
258
|
+
|
259
|
+
def unpause_streaming?
|
260
|
+
return if @paused.nil?
|
261
|
+
|
262
|
+
@inventory.count < @inventory.limit*0.8
|
263
|
+
end
|
264
|
+
|
265
|
+
def initial_input_request
|
266
|
+
Google::Pubsub::V1::StreamingPullRequest.new.tap do |req|
|
267
|
+
req.subscription = subscriber.subscription_name
|
268
|
+
req.stream_ack_deadline_seconds = subscriber.deadline
|
269
|
+
req.modify_deadline_ack_ids += @inventory.ack_ids
|
270
|
+
req.modify_deadline_seconds += \
|
271
|
+
@inventory.ack_ids.map { subscriber.deadline }
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
##
|
276
|
+
# Makes sure the values are the `ack_id`. If given several
|
277
|
+
# {ReceivedMessage} objects extract the `ack_id` values.
|
278
|
+
def coerce_ack_ids messages
|
279
|
+
Array(messages).flatten.map do |msg|
|
280
|
+
msg.respond_to?(:ack_id) ? msg.ack_id : msg.to_s
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def status
|
285
|
+
return "not started" if @background_thread.nil?
|
286
|
+
|
287
|
+
status = @background_thread.status
|
288
|
+
return "error" if status.nil?
|
289
|
+
return "stopped" if status == false
|
290
|
+
status
|
291
|
+
end
|
292
|
+
|
293
|
+
##
|
294
|
+
# @private
|
295
|
+
class Inventory
|
296
|
+
include MonitorMixin
|
297
|
+
|
298
|
+
attr_reader :stream, :limit
|
299
|
+
|
300
|
+
def initialize stream, limit
|
301
|
+
@stream = stream
|
302
|
+
@limit = limit
|
303
|
+
@_ack_ids = []
|
304
|
+
@wait_cond = new_cond
|
305
|
+
|
306
|
+
super()
|
307
|
+
end
|
308
|
+
|
309
|
+
def ack_ids
|
310
|
+
@_ack_ids
|
311
|
+
end
|
312
|
+
|
313
|
+
def add *ack_ids
|
314
|
+
ack_ids = Array(ack_ids).flatten
|
315
|
+
synchronize do
|
316
|
+
@_ack_ids += ack_ids
|
317
|
+
@background_thread ||= Thread.new { background_run }
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def remove *ack_ids
|
322
|
+
ack_ids = Array(ack_ids).flatten
|
323
|
+
synchronize do
|
324
|
+
@_ack_ids -= ack_ids
|
325
|
+
if @_ack_ids.empty?
|
326
|
+
if @background_thread
|
327
|
+
@background_thread.kill
|
328
|
+
@background_thread = nil
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
def count
|
335
|
+
synchronize do
|
336
|
+
@_ack_ids.count
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def empty?
|
341
|
+
synchronize do
|
342
|
+
@_ack_ids.empty?
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def stop
|
347
|
+
synchronize do
|
348
|
+
@stopped = true
|
349
|
+
@background_thread.kill if @background_thread
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def full?
|
354
|
+
count >= limit
|
355
|
+
end
|
356
|
+
|
357
|
+
protected
|
358
|
+
|
359
|
+
def background_run
|
360
|
+
until synchronize { @stopped }
|
361
|
+
delay = calc_delay
|
362
|
+
synchronize { @wait_cond.wait delay }
|
363
|
+
|
364
|
+
stream.delay_inventory!
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
def calc_delay
|
369
|
+
(stream.subscriber.deadline - 3) * rand(0.8..0.9)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|