google-cloud-pubsub 2.22.0 → 3.2.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/AUTHENTICATION.md +12 -1
- data/CHANGELOG.md +69 -0
- data/OVERVIEW.md +189 -145
- data/lib/google/cloud/pubsub/admin_clients.rb +116 -0
- data/lib/google/cloud/pubsub/async_publisher.rb +20 -19
- data/lib/google/cloud/pubsub/batch_publisher.rb +7 -5
- data/lib/google/cloud/pubsub/errors.rb +3 -3
- data/lib/google/cloud/pubsub/internal_logger.rb +76 -0
- data/lib/google/cloud/pubsub/message.rb +8 -8
- data/lib/google/cloud/pubsub/{subscriber → message_listener}/enumerator_queue.rb +1 -1
- data/lib/google/cloud/pubsub/{subscriber → message_listener}/inventory.rb +13 -10
- data/lib/google/cloud/pubsub/{subscriber → message_listener}/sequencer.rb +1 -1
- data/lib/google/cloud/pubsub/{subscriber → message_listener}/stream.rb +61 -18
- data/lib/google/cloud/pubsub/{subscriber → message_listener}/timed_unary_buffer.rb +29 -9
- data/lib/google/cloud/pubsub/message_listener.rb +414 -0
- data/lib/google/cloud/pubsub/project.rb +102 -530
- data/lib/google/cloud/pubsub/publisher.rb +424 -0
- data/lib/google/cloud/pubsub/received_message.rb +50 -45
- data/lib/google/cloud/pubsub/service.rb +34 -385
- data/lib/google/cloud/pubsub/subscriber.rb +442 -279
- data/lib/google/cloud/pubsub/version.rb +1 -1
- data/lib/google/cloud/pubsub.rb +37 -19
- data/lib/google-cloud-pubsub.rb +27 -8
- metadata +19 -26
- data/lib/google/cloud/pubsub/policy.rb +0 -188
- data/lib/google/cloud/pubsub/retry_policy.rb +0 -88
- data/lib/google/cloud/pubsub/schema/list.rb +0 -180
- data/lib/google/cloud/pubsub/schema.rb +0 -378
- data/lib/google/cloud/pubsub/snapshot/list.rb +0 -178
- data/lib/google/cloud/pubsub/snapshot.rb +0 -205
- data/lib/google/cloud/pubsub/subscription/list.rb +0 -205
- data/lib/google/cloud/pubsub/subscription/push_config.rb +0 -268
- data/lib/google/cloud/pubsub/subscription.rb +0 -1467
- data/lib/google/cloud/pubsub/topic/list.rb +0 -171
- data/lib/google/cloud/pubsub/topic.rb +0 -1100
|
@@ -12,10 +12,9 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
require "google/cloud/pubsub/
|
|
17
|
-
require "google/cloud/pubsub/
|
|
18
|
-
require "google/cloud/pubsub/subscriber/inventory"
|
|
15
|
+
require "google/cloud/pubsub/message_listener/sequencer"
|
|
16
|
+
require "google/cloud/pubsub/message_listener/enumerator_queue"
|
|
17
|
+
require "google/cloud/pubsub/message_listener/inventory"
|
|
19
18
|
require "google/cloud/pubsub/service"
|
|
20
19
|
require "google/cloud/errors"
|
|
21
20
|
require "monitor"
|
|
@@ -24,7 +23,7 @@ require "concurrent"
|
|
|
24
23
|
module Google
|
|
25
24
|
module Cloud
|
|
26
25
|
module PubSub
|
|
27
|
-
class
|
|
26
|
+
class MessageListener
|
|
28
27
|
##
|
|
29
28
|
# @private
|
|
30
29
|
class Stream
|
|
@@ -73,7 +72,12 @@ module Google
|
|
|
73
72
|
execution_interval: 30
|
|
74
73
|
) do
|
|
75
74
|
# push empty request every 30 seconds to keep stream alive
|
|
76
|
-
|
|
75
|
+
unless inventory.empty?
|
|
76
|
+
subscriber.service.logger.log :info, "subscriber-streams" do
|
|
77
|
+
"sending keepAlive to stream for subscription #{@subscriber.subscription_name}"
|
|
78
|
+
end
|
|
79
|
+
push Google::Cloud::PubSub::V1::StreamingPullRequest.new
|
|
80
|
+
end
|
|
77
81
|
end.execute
|
|
78
82
|
end
|
|
79
83
|
|
|
@@ -93,6 +97,9 @@ module Google
|
|
|
93
97
|
synchronize do
|
|
94
98
|
break if @stopped
|
|
95
99
|
|
|
100
|
+
subscriber.service.logger.log :info, "subscriber-streams" do
|
|
101
|
+
"stopping stream for subscription #{@subscriber.subscription_name}"
|
|
102
|
+
end
|
|
96
103
|
# Close the stream by pushing the sentinel value.
|
|
97
104
|
# The unary pusher does not use the stream, so it can close here.
|
|
98
105
|
@request_queue&.push self
|
|
@@ -138,8 +145,9 @@ module Google
|
|
|
138
145
|
ack_ids = coerce_ack_ids messages
|
|
139
146
|
return true if ack_ids.empty?
|
|
140
147
|
|
|
148
|
+
removed_items = {}
|
|
141
149
|
synchronize do
|
|
142
|
-
@inventory.remove ack_ids
|
|
150
|
+
removed_items = @inventory.remove ack_ids
|
|
143
151
|
@subscriber.buffer.acknowledge ack_ids, callback
|
|
144
152
|
end
|
|
145
153
|
|
|
@@ -152,8 +160,9 @@ module Google
|
|
|
152
160
|
mod_ack_ids = coerce_ack_ids messages
|
|
153
161
|
return true if mod_ack_ids.empty?
|
|
154
162
|
|
|
163
|
+
removed_items = {}
|
|
155
164
|
synchronize do
|
|
156
|
-
@inventory.remove mod_ack_ids
|
|
165
|
+
removed_items = @inventory.remove mod_ack_ids
|
|
157
166
|
@subscriber.buffer.modify_ack_deadline deadline, mod_ack_ids, callback
|
|
158
167
|
end
|
|
159
168
|
|
|
@@ -215,7 +224,13 @@ module Google
|
|
|
215
224
|
def background_run
|
|
216
225
|
synchronize do
|
|
217
226
|
# Don't allow a stream to restart if already stopped
|
|
218
|
-
|
|
227
|
+
if @stopped
|
|
228
|
+
subscriber.service.logger.log :debug, "subscriber-streams" do
|
|
229
|
+
"not filling stream for subscription #{@subscriber.subscription_name} because stream is already" \
|
|
230
|
+
" stopped"
|
|
231
|
+
end
|
|
232
|
+
return
|
|
233
|
+
end
|
|
219
234
|
|
|
220
235
|
@stopped = false
|
|
221
236
|
@paused = false
|
|
@@ -233,6 +248,9 @@ module Google
|
|
|
233
248
|
# Call the StreamingPull API to get the response enumerator
|
|
234
249
|
options = { :"metadata" => { :"x-goog-request-params" => @subscriber.subscription_name } }
|
|
235
250
|
enum = @subscriber.service.streaming_pull @request_queue.each, options
|
|
251
|
+
subscriber.service.logger.log :info, "subscriber-streams" do
|
|
252
|
+
"rpc: streamingPull, subscription: #{@subscriber.subscription_name}, stream opened"
|
|
253
|
+
end
|
|
236
254
|
|
|
237
255
|
loop do
|
|
238
256
|
synchronize do
|
|
@@ -287,13 +305,23 @@ module Google
|
|
|
287
305
|
stop
|
|
288
306
|
rescue GRPC::Cancelled, GRPC::DeadlineExceeded, GRPC::Internal,
|
|
289
307
|
GRPC::ResourceExhausted, GRPC::Unauthenticated,
|
|
290
|
-
GRPC::Unavailable
|
|
308
|
+
GRPC::Unavailable => e
|
|
309
|
+
status_code = e.respond_to?(:code) ? e.code : e.class.name
|
|
310
|
+
subscriber.service.logger.log :error, "subscriber-streams" do
|
|
311
|
+
"Subscriber stream for subscription #{@subscriber.subscription_name} has ended with status " \
|
|
312
|
+
"#{status_code}; will be retried."
|
|
313
|
+
end
|
|
291
314
|
# Restart the stream with an incremental back for a retriable error.
|
|
292
|
-
|
|
293
315
|
retry
|
|
294
316
|
rescue RestartStream
|
|
317
|
+
subscriber.service.logger.log :info, "subscriber-streams" do
|
|
318
|
+
"Subscriber stream for subscription #{@subscriber.subscription_name} has ended; will be retried."
|
|
319
|
+
end
|
|
295
320
|
retry
|
|
296
321
|
rescue StandardError => e
|
|
322
|
+
subscriber.service.logger.log :error, "subscriber-streams" do
|
|
323
|
+
"error on stream for subscription #{@subscriber.subscription_name}: #{e.inspect}"
|
|
324
|
+
end
|
|
297
325
|
@subscriber.error! e
|
|
298
326
|
|
|
299
327
|
retry
|
|
@@ -336,13 +364,22 @@ module Google
|
|
|
336
364
|
return unless callback_thread_pool.running?
|
|
337
365
|
|
|
338
366
|
Concurrent::Promises.future_on(
|
|
339
|
-
callback_thread_pool,
|
|
367
|
+
callback_thread_pool,
|
|
368
|
+
rec_msg,
|
|
369
|
+
&method(:perform_callback_sync)
|
|
340
370
|
)
|
|
341
371
|
end
|
|
342
372
|
|
|
343
373
|
def perform_callback_sync rec_msg
|
|
374
|
+
subscriber.service.logger.log :info, "callback-delivery" do
|
|
375
|
+
"message (ID #{rec_msg.message_id}, ackID #{rec_msg.ack_id}) delivery to user callbacks"
|
|
376
|
+
end
|
|
344
377
|
@subscriber.callback.call rec_msg unless stopped?
|
|
345
378
|
rescue StandardError => e
|
|
379
|
+
subscriber.service.logger.log :info, "callback-exceptions" do
|
|
380
|
+
"message (ID #{rec_msg.message_id}, ackID #{rec_msg.ack_id}) caused a user callback exception: " \
|
|
381
|
+
"#{e.inspect}"
|
|
382
|
+
end
|
|
346
383
|
@subscriber.error! e
|
|
347
384
|
ensure
|
|
348
385
|
release rec_msg
|
|
@@ -369,11 +406,14 @@ module Google
|
|
|
369
406
|
return unless pause_streaming?
|
|
370
407
|
|
|
371
408
|
@paused = true
|
|
409
|
+
subscriber.service.logger.log :info, "subscriber-flow-control" do
|
|
410
|
+
"subscriber for #{@subscriber.subscription_name} is client-side flow control blocked"
|
|
411
|
+
end
|
|
372
412
|
end
|
|
373
413
|
|
|
374
414
|
def pause_streaming?
|
|
375
|
-
return if @stopped
|
|
376
|
-
return if @paused
|
|
415
|
+
return false if @stopped
|
|
416
|
+
return false if @paused
|
|
377
417
|
|
|
378
418
|
@inventory.full?
|
|
379
419
|
end
|
|
@@ -382,13 +422,16 @@ module Google
|
|
|
382
422
|
return unless unpause_streaming?
|
|
383
423
|
|
|
384
424
|
@paused = nil
|
|
425
|
+
subscriber.service.logger.log :info, "subscriber-flow-control" do
|
|
426
|
+
"subscriber for #{@subscriber.subscription_name} is unblocking client-side flow control"
|
|
427
|
+
end
|
|
385
428
|
# signal to the background thread that we are unpaused
|
|
386
429
|
@pause_cond.broadcast
|
|
387
430
|
end
|
|
388
431
|
|
|
389
432
|
def unpause_streaming?
|
|
390
|
-
return if @stopped
|
|
391
|
-
return if @paused.nil?
|
|
433
|
+
return false if @stopped
|
|
434
|
+
return false if @paused.nil?
|
|
392
435
|
|
|
393
436
|
@inventory.count < @inventory.limit * 0.8
|
|
394
437
|
end
|
|
@@ -400,8 +443,8 @@ module Google
|
|
|
400
443
|
req.modify_deadline_ack_ids += @inventory.ack_ids
|
|
401
444
|
req.modify_deadline_seconds += @inventory.ack_ids.map { @subscriber.deadline }
|
|
402
445
|
req.client_id = @subscriber.service.client_id
|
|
403
|
-
req.max_outstanding_messages = @inventory.
|
|
404
|
-
req.max_outstanding_bytes = @inventory.
|
|
446
|
+
req.max_outstanding_messages = @inventory.limit
|
|
447
|
+
req.max_outstanding_bytes = @inventory.bytesize
|
|
405
448
|
end
|
|
406
449
|
end
|
|
407
450
|
|
|
@@ -22,7 +22,7 @@ require "retriable"
|
|
|
22
22
|
module Google
|
|
23
23
|
module Cloud
|
|
24
24
|
module PubSub
|
|
25
|
-
class
|
|
25
|
+
class MessageListener
|
|
26
26
|
##
|
|
27
27
|
# @private
|
|
28
28
|
class TimedUnaryBuffer
|
|
@@ -64,7 +64,7 @@ module Google
|
|
|
64
64
|
@retry_thread_pool = Concurrent::ThreadPoolExecutor.new max_threads: @subscriber.callback_threads
|
|
65
65
|
@callback_thread_pool = Concurrent::ThreadPoolExecutor.new max_threads: @subscriber.callback_threads
|
|
66
66
|
@task = Concurrent::TimerTask.new execution_interval: interval do
|
|
67
|
-
flush!
|
|
67
|
+
flush! reason: "interval timeout"
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -108,9 +108,9 @@ module Google
|
|
|
108
108
|
true
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
-
def flush!
|
|
111
|
+
def flush! reason: "manual flush"
|
|
112
112
|
# Grab requests from the buffer and release synchronize ASAP
|
|
113
|
-
requests = flush_requests!
|
|
113
|
+
requests = flush_requests! reason
|
|
114
114
|
return if requests.empty?
|
|
115
115
|
|
|
116
116
|
# Perform the RCP calls concurrently
|
|
@@ -167,7 +167,7 @@ module Google
|
|
|
167
167
|
@task.shutdown
|
|
168
168
|
@retry_thread_pool.shutdown
|
|
169
169
|
@callback_thread_pool.shutdown
|
|
170
|
-
flush!
|
|
170
|
+
flush! reason: "shutdown"
|
|
171
171
|
self
|
|
172
172
|
end
|
|
173
173
|
|
|
@@ -296,7 +296,9 @@ module Google
|
|
|
296
296
|
end
|
|
297
297
|
end
|
|
298
298
|
|
|
299
|
-
|
|
299
|
+
# rubocop:disable Metrics/AbcSize
|
|
300
|
+
|
|
301
|
+
def flush_requests! reason
|
|
300
302
|
prev_reg =
|
|
301
303
|
synchronize do
|
|
302
304
|
return {} if @register.empty?
|
|
@@ -308,16 +310,34 @@ module Google
|
|
|
308
310
|
groups = prev_reg.each_pair.group_by { |_ack_id, delay| delay }
|
|
309
311
|
req_hash = groups.transform_values { |v| v.map(&:first) }
|
|
310
312
|
|
|
311
|
-
requests = { acknowledge: [] }
|
|
313
|
+
requests = { acknowledge: [], modify_ack_deadline: [] }
|
|
312
314
|
ack_ids = Array(req_hash.delete(:ack)) # ack has no deadline set
|
|
313
|
-
|
|
315
|
+
if ack_ids.any?
|
|
316
|
+
requests[:acknowledge] = create_acknowledge_requests ack_ids
|
|
317
|
+
new_reason = if requests[:acknowledge].length > 1
|
|
318
|
+
"#{reason} and partitioned for exceeding max bytes"
|
|
319
|
+
else
|
|
320
|
+
reason
|
|
321
|
+
end
|
|
322
|
+
requests[:acknowledge].each do |req|
|
|
323
|
+
@subscriber.service.logger.log_batch "ack-batch", new_reason, "ack", req.ack_ids.length, req.to_proto.bytesize
|
|
324
|
+
end
|
|
325
|
+
end
|
|
314
326
|
requests[:modify_ack_deadline] =
|
|
315
327
|
req_hash.map do |mod_deadline, mod_ack_ids|
|
|
316
|
-
create_modify_ack_deadline_requests mod_deadline, mod_ack_ids
|
|
328
|
+
mod_ack_reqs = create_modify_ack_deadline_requests mod_deadline, mod_ack_ids
|
|
329
|
+
type = mod_deadline.zero? ? "nack" : "modack"
|
|
330
|
+
new_reason = mod_ack_reqs.length > 1 ? "#{reason} and partitioned for exceeding max bytes" : reason
|
|
331
|
+
mod_ack_reqs.each do |req|
|
|
332
|
+
@subscriber.service.logger.log_batch "ack-batch", new_reason, type, req.ack_ids.length, req.to_proto.bytesize
|
|
333
|
+
end
|
|
334
|
+
mod_ack_reqs
|
|
317
335
|
end.flatten
|
|
318
336
|
requests
|
|
319
337
|
end
|
|
320
338
|
|
|
339
|
+
# rubocop:enable Metrics/AbcSize
|
|
340
|
+
|
|
321
341
|
def create_acknowledge_requests ack_ids
|
|
322
342
|
req = Google::Cloud::PubSub::V1::AcknowledgeRequest.new(
|
|
323
343
|
subscription: subscription_name,
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
# Copyright 2017 Google LLC
|
|
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
|
+
# https://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/service"
|
|
17
|
+
require "google/cloud/pubsub/subscriber"
|
|
18
|
+
require "google/cloud/pubsub/message_listener/stream"
|
|
19
|
+
require "google/cloud/pubsub/message_listener/timed_unary_buffer"
|
|
20
|
+
require "monitor"
|
|
21
|
+
|
|
22
|
+
module Google
|
|
23
|
+
module Cloud
|
|
24
|
+
module PubSub
|
|
25
|
+
##
|
|
26
|
+
# MessageListener object used to stream and process messages from a
|
|
27
|
+
# Subscriber. See {Google::Cloud::PubSub::Subscriber#listen}
|
|
28
|
+
#
|
|
29
|
+
# @example
|
|
30
|
+
# require "google/cloud/pubsub"
|
|
31
|
+
#
|
|
32
|
+
# pubsub = Google::Cloud::PubSub.new
|
|
33
|
+
#
|
|
34
|
+
# subscriber = pubsub.subscriber "my-topic-sub"
|
|
35
|
+
#
|
|
36
|
+
# listener = subscriber.listen do |received_message|
|
|
37
|
+
# # process message
|
|
38
|
+
# received_message.acknowledge!
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# # Start background threads that will call the block passed to listen.
|
|
42
|
+
# listener.start
|
|
43
|
+
#
|
|
44
|
+
# # Shut down the subscriber when ready to stop receiving messages.
|
|
45
|
+
# listener.stop!
|
|
46
|
+
#
|
|
47
|
+
# @attr_reader [String] subscription_name The name of the subscription the
|
|
48
|
+
# messages are pulled from.
|
|
49
|
+
# @attr_reader [Proc] callback The procedure that will handle the messages
|
|
50
|
+
# received from the subscription.
|
|
51
|
+
# @attr_reader [Numeric] deadline The default number of seconds the stream
|
|
52
|
+
# will hold received messages before modifying the message's ack
|
|
53
|
+
# deadline. The minimum is 10, the maximum is 600. Default is 60.
|
|
54
|
+
# @attr_reader [Boolean] message_ordering Whether message ordering has
|
|
55
|
+
# been enabled.
|
|
56
|
+
# @attr_reader [Integer] streams The number of concurrent streams to open
|
|
57
|
+
# to pull messages from the subscription. Default is 1.
|
|
58
|
+
# @attr_reader [Integer] callback_threads The number of threads used to
|
|
59
|
+
# handle the received messages. Default is 8.
|
|
60
|
+
# @attr_reader [Integer] push_threads The number of threads to handle
|
|
61
|
+
# acknowledgement ({ReceivedMessage#ack!}) and delay messages
|
|
62
|
+
# ({ReceivedMessage#nack!}, {ReceivedMessage#modify_ack_deadline!}).
|
|
63
|
+
# Default is 4.
|
|
64
|
+
#
|
|
65
|
+
class MessageListener
|
|
66
|
+
include MonitorMixin
|
|
67
|
+
|
|
68
|
+
attr_reader :subscription_name
|
|
69
|
+
attr_reader :callback
|
|
70
|
+
attr_reader :deadline
|
|
71
|
+
attr_reader :streams
|
|
72
|
+
attr_reader :message_ordering
|
|
73
|
+
attr_reader :callback_threads
|
|
74
|
+
attr_reader :push_threads
|
|
75
|
+
|
|
76
|
+
##
|
|
77
|
+
# @private Implementation attributes.
|
|
78
|
+
attr_reader :stream_pool, :thread_pool, :buffer, :service
|
|
79
|
+
|
|
80
|
+
##
|
|
81
|
+
# @private Implementation attributes.
|
|
82
|
+
attr_accessor :exactly_once_delivery_enabled
|
|
83
|
+
|
|
84
|
+
##
|
|
85
|
+
# @private Create an empty {MessageListener} object.
|
|
86
|
+
def initialize subscription_name, callback, deadline: nil, message_ordering: nil, streams: nil, inventory: nil,
|
|
87
|
+
threads: {}, service: nil
|
|
88
|
+
super() # to init MonitorMixin
|
|
89
|
+
|
|
90
|
+
@callback = callback
|
|
91
|
+
@error_callbacks = []
|
|
92
|
+
@subscription_name = subscription_name
|
|
93
|
+
@deadline = deadline || 60
|
|
94
|
+
@streams = streams || 1
|
|
95
|
+
coerce_inventory inventory
|
|
96
|
+
@message_ordering = message_ordering
|
|
97
|
+
@callback_threads = Integer(threads[:callback] || 8)
|
|
98
|
+
@push_threads = Integer(threads[:push] || 4)
|
|
99
|
+
@exactly_once_delivery_enabled = nil
|
|
100
|
+
|
|
101
|
+
@service = service
|
|
102
|
+
|
|
103
|
+
@started = @stopped = nil
|
|
104
|
+
|
|
105
|
+
stream_pool = Array.new @streams do
|
|
106
|
+
Thread.new { Stream.new self }
|
|
107
|
+
end
|
|
108
|
+
@stream_pool = stream_pool.map(&:value)
|
|
109
|
+
|
|
110
|
+
@buffer = TimedUnaryBuffer.new self
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
##
|
|
114
|
+
# Starts the listener pulling from the subscription and processing the
|
|
115
|
+
# received messages.
|
|
116
|
+
#
|
|
117
|
+
# @return [MessageListener] returns self so calls can be chained.
|
|
118
|
+
#
|
|
119
|
+
def start
|
|
120
|
+
start_pool = synchronize do
|
|
121
|
+
@started = true
|
|
122
|
+
@stopped = false
|
|
123
|
+
|
|
124
|
+
# Start the buffer before the streams are all started
|
|
125
|
+
@buffer.start
|
|
126
|
+
@stream_pool.map do |stream|
|
|
127
|
+
Thread.new { stream.start }
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
start_pool.map(&:join)
|
|
131
|
+
|
|
132
|
+
self
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
##
|
|
136
|
+
# Immediately stops the listener. No new messages will be pulled from
|
|
137
|
+
# the subscription. Use {#wait!} to block until all received messages have
|
|
138
|
+
# been processed or released: All actions taken on received messages that
|
|
139
|
+
# have not yet been sent to the API will be sent to the API. All received
|
|
140
|
+
# but unprocessed messages will be released back to the API and redelivered.
|
|
141
|
+
#
|
|
142
|
+
# @return [MessageListener] returns self so calls can be chained.
|
|
143
|
+
#
|
|
144
|
+
def stop
|
|
145
|
+
synchronize do
|
|
146
|
+
@started = false
|
|
147
|
+
@stopped = true
|
|
148
|
+
@stream_pool.map(&:stop)
|
|
149
|
+
wait_stop_buffer_thread!
|
|
150
|
+
self
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
##
|
|
155
|
+
# Blocks until the listener is fully stopped and all received messages
|
|
156
|
+
# have been processed or released, or until `timeout` seconds have
|
|
157
|
+
# passed.
|
|
158
|
+
#
|
|
159
|
+
# Does not stop the listener. To stop the listener, first call
|
|
160
|
+
# {#stop} and then call {#wait!} to block until the listener is
|
|
161
|
+
# stopped.
|
|
162
|
+
#
|
|
163
|
+
# @param [Number, nil] timeout The number of seconds to block until the
|
|
164
|
+
# subscriber is fully stopped. Default will block indefinitely.
|
|
165
|
+
#
|
|
166
|
+
# @return [MessageListener] returns self so calls can be chained.
|
|
167
|
+
#
|
|
168
|
+
def wait! timeout = nil
|
|
169
|
+
wait_stop_buffer_thread!
|
|
170
|
+
@wait_stop_buffer_thread.join timeout
|
|
171
|
+
self
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
##
|
|
175
|
+
# Stop this listener and block until the listener is fully stopped
|
|
176
|
+
# and all received messages have been processed or released, or until
|
|
177
|
+
# `timeout` seconds have passed.
|
|
178
|
+
#
|
|
179
|
+
# The same as calling {#stop} and {#wait!}.
|
|
180
|
+
#
|
|
181
|
+
# @param [Number, nil] timeout The number of seconds to block until the
|
|
182
|
+
# listener is fully stopped. Default will block indefinitely.
|
|
183
|
+
#
|
|
184
|
+
# @return [MessageListener] returns self so calls can be chained.
|
|
185
|
+
#
|
|
186
|
+
def stop! timeout = nil
|
|
187
|
+
stop
|
|
188
|
+
wait! timeout
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
##
|
|
192
|
+
# Whether the listener has been started.
|
|
193
|
+
#
|
|
194
|
+
# @return [boolean] `true` when started, `false` otherwise.
|
|
195
|
+
#
|
|
196
|
+
def started?
|
|
197
|
+
synchronize { @started }
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
##
|
|
201
|
+
# Whether the listener has been stopped.
|
|
202
|
+
#
|
|
203
|
+
# @return [boolean] `true` when stopped, `false` otherwise.
|
|
204
|
+
#
|
|
205
|
+
def stopped?
|
|
206
|
+
synchronize { @stopped }
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
##
|
|
210
|
+
# Register to be notified of errors when raised.
|
|
211
|
+
#
|
|
212
|
+
# If an unhandled error has occurred the listener will attempt to
|
|
213
|
+
# recover from the error and resume listening.
|
|
214
|
+
#
|
|
215
|
+
# Multiple error handlers can be added.
|
|
216
|
+
#
|
|
217
|
+
# @yield [callback] The block to be called when an error is raised.
|
|
218
|
+
# @yieldparam [Exception] error The error raised.
|
|
219
|
+
#
|
|
220
|
+
# @example
|
|
221
|
+
# require "google/cloud/pubsub"
|
|
222
|
+
#
|
|
223
|
+
# pubsub = Google::Cloud::PubSub.new
|
|
224
|
+
#
|
|
225
|
+
# subscriber = pubsub.subscriber "my-topic-sub"
|
|
226
|
+
#
|
|
227
|
+
# listener = subscriber.listen do |received_message|
|
|
228
|
+
# # process message
|
|
229
|
+
# received_message.acknowledge!
|
|
230
|
+
# end
|
|
231
|
+
#
|
|
232
|
+
# # Register to be notified when unhandled errors occur.
|
|
233
|
+
# listener.on_error do |error|
|
|
234
|
+
# # log error
|
|
235
|
+
# puts error
|
|
236
|
+
# end
|
|
237
|
+
#
|
|
238
|
+
# # Start listening for messages and errors.
|
|
239
|
+
# listener.start
|
|
240
|
+
#
|
|
241
|
+
# # Shut down the subscriber when ready to stop receiving messages.
|
|
242
|
+
# listener.stop!
|
|
243
|
+
#
|
|
244
|
+
def on_error &block
|
|
245
|
+
synchronize do
|
|
246
|
+
@error_callbacks << block
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
##
|
|
251
|
+
# The most recent unhandled error to occur while listening to messages
|
|
252
|
+
# on the listener.
|
|
253
|
+
#
|
|
254
|
+
# If an unhandled error has occurred the listener will attempt to
|
|
255
|
+
# recover from the error and resume listening.
|
|
256
|
+
#
|
|
257
|
+
# @return [Exception, nil] error The most recent error raised.
|
|
258
|
+
#
|
|
259
|
+
# @example
|
|
260
|
+
# require "google/cloud/pubsub"
|
|
261
|
+
#
|
|
262
|
+
# pubsub = Google::Cloud::PubSub.new
|
|
263
|
+
#
|
|
264
|
+
# subscriber = pubsub.subscriber "my-topic-sub"
|
|
265
|
+
#
|
|
266
|
+
# listener = subscriber.listen do |received_message|
|
|
267
|
+
# # process message
|
|
268
|
+
# received_message.acknowledge!
|
|
269
|
+
# end
|
|
270
|
+
#
|
|
271
|
+
# # Start listening for messages and errors.
|
|
272
|
+
# listener.start
|
|
273
|
+
#
|
|
274
|
+
# # If an error was raised, it can be retrieved here:
|
|
275
|
+
# listener.last_error #=> nil
|
|
276
|
+
#
|
|
277
|
+
# # Shut down the subscriber when ready to stop receiving messages.
|
|
278
|
+
# listener.stop!
|
|
279
|
+
#
|
|
280
|
+
def last_error
|
|
281
|
+
synchronize { @last_error }
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
##
|
|
285
|
+
# The number of received messages to be collected by listener. Default is 1,000.
|
|
286
|
+
#
|
|
287
|
+
# @return [Integer] The maximum number of messages.
|
|
288
|
+
#
|
|
289
|
+
def max_outstanding_messages
|
|
290
|
+
@inventory[:max_outstanding_messages]
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
##
|
|
294
|
+
# The total byte size of received messages to be collected by listener. Default is 100,000,000 (100MB).
|
|
295
|
+
#
|
|
296
|
+
# @return [Integer] The maximum number of bytes.
|
|
297
|
+
#
|
|
298
|
+
def max_outstanding_bytes
|
|
299
|
+
@inventory[:max_outstanding_bytes]
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
##
|
|
303
|
+
# The number of seconds that received messages can be held awaiting processing. Default is 3,600 (1 hour).
|
|
304
|
+
#
|
|
305
|
+
# @return [Integer] The maximum number of seconds.
|
|
306
|
+
#
|
|
307
|
+
def max_total_lease_duration
|
|
308
|
+
@inventory[:max_total_lease_duration]
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
##
|
|
312
|
+
# The maximum amount of time in seconds for a single lease extension attempt. Bounds the delay before a message
|
|
313
|
+
# redelivery if the listener fails to extend the deadline. Default is 0 (disabled).
|
|
314
|
+
#
|
|
315
|
+
# @return [Integer] The maximum number of seconds.
|
|
316
|
+
#
|
|
317
|
+
def max_duration_per_lease_extension
|
|
318
|
+
@inventory[:max_duration_per_lease_extension]
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
##
|
|
322
|
+
# The minimum amount of time in seconds for a single lease extension attempt. Bounds the delay before a message
|
|
323
|
+
# redelivery if the listener fails to extend the deadline. Default is 0 (disabled).
|
|
324
|
+
#
|
|
325
|
+
# @return [Integer] The minimum number of seconds.
|
|
326
|
+
#
|
|
327
|
+
def min_duration_per_lease_extension
|
|
328
|
+
@inventory[:min_duration_per_lease_extension]
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
##
|
|
332
|
+
# @private
|
|
333
|
+
def stream_inventory
|
|
334
|
+
{
|
|
335
|
+
limit: @inventory[:max_outstanding_messages].fdiv(@streams).ceil,
|
|
336
|
+
bytesize: @inventory[:max_outstanding_bytes].fdiv(@streams).ceil,
|
|
337
|
+
extension: @inventory[:max_total_lease_duration],
|
|
338
|
+
max_duration_per_lease_extension: @inventory[:max_duration_per_lease_extension],
|
|
339
|
+
min_duration_per_lease_extension: @inventory[:min_duration_per_lease_extension]
|
|
340
|
+
}
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# @private returns error object from the stream thread.
|
|
344
|
+
def error! error
|
|
345
|
+
error_callbacks = synchronize do
|
|
346
|
+
@last_error = error
|
|
347
|
+
@error_callbacks
|
|
348
|
+
end
|
|
349
|
+
error_callbacks = default_error_callbacks if error_callbacks.empty?
|
|
350
|
+
error_callbacks.each { |error_callback| error_callback.call error }
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
##
|
|
354
|
+
# @private
|
|
355
|
+
def to_s
|
|
356
|
+
"(subscription: #{subscription_name}, streams: [#{stream_pool.map(&:to_s).join(', ')}])"
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
##
|
|
360
|
+
# @private
|
|
361
|
+
def inspect
|
|
362
|
+
"#<#{self.class.name} #{self}>"
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
protected
|
|
366
|
+
|
|
367
|
+
##
|
|
368
|
+
# Starts a new thread to call wait! (blocking) on each Stream and then stop the TimedUnaryBuffer.
|
|
369
|
+
def wait_stop_buffer_thread!
|
|
370
|
+
synchronize do
|
|
371
|
+
@wait_stop_buffer_thread ||= Thread.new do
|
|
372
|
+
@stream_pool.map(&:wait!)
|
|
373
|
+
# Shutdown the buffer TimerTask (and flush the buffer) after the streams are all stopped.
|
|
374
|
+
@buffer.stop
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def coerce_inventory inventory
|
|
380
|
+
@inventory = inventory
|
|
381
|
+
if @inventory.is_a? Hash
|
|
382
|
+
@inventory = @inventory.dup
|
|
383
|
+
# Support deprecated field names
|
|
384
|
+
@inventory[:max_outstanding_messages] ||= @inventory.delete :limit
|
|
385
|
+
@inventory[:max_outstanding_bytes] ||= @inventory.delete :bytesize
|
|
386
|
+
@inventory[:max_total_lease_duration] ||= @inventory.delete :extension
|
|
387
|
+
else
|
|
388
|
+
@inventory = { max_outstanding_messages: @inventory }
|
|
389
|
+
end
|
|
390
|
+
@inventory[:max_outstanding_messages] = Integer(@inventory[:max_outstanding_messages] || 1000)
|
|
391
|
+
@inventory[:max_outstanding_bytes] = Integer(@inventory[:max_outstanding_bytes] || 100_000_000)
|
|
392
|
+
@inventory[:max_total_lease_duration] = Integer(@inventory[:max_total_lease_duration] || 3600)
|
|
393
|
+
@inventory[:max_duration_per_lease_extension] = Integer(@inventory[:max_duration_per_lease_extension] || 0)
|
|
394
|
+
@inventory[:min_duration_per_lease_extension] = Integer(@inventory[:min_duration_per_lease_extension] || 0)
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def default_error_callbacks
|
|
398
|
+
# This is memoized to reduce calls to the configuration.
|
|
399
|
+
@default_error_callbacks ||= begin
|
|
400
|
+
error_callback = Google::Cloud::PubSub.configure.on_error
|
|
401
|
+
error_callback ||= Google::Cloud.configure.on_error
|
|
402
|
+
if error_callback
|
|
403
|
+
[error_callback]
|
|
404
|
+
else
|
|
405
|
+
[]
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
Pubsub = PubSub unless const_defined? :Pubsub
|
|
413
|
+
end
|
|
414
|
+
end
|