google-cloud-pubsub 3.4.0 → 3.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2706c0520b9cd5aba014d233fddf25b3c5265cc35a30b65719ed91647a271bef
4
- data.tar.gz: d6f9a3e8c9b74a08bd344582ce7c7e9b5a4bc03cebe0243ffb09ac17cdcc0afd
3
+ metadata.gz: 5694017436fe99de18ba1d45e73ecdb0941b0183e115c3d02cad10085dcaae2c
4
+ data.tar.gz: b4a7a35193d6b9e75614474dc6510d1c978bbaa49fc2973123da33ea38b33116
5
5
  SHA512:
6
- metadata.gz: 426ac33d83d07732c22854216b673def03cf2b5813718a5e57428546f0e5499178d6848e75473da2e71169b79d76246879663d94f1ce8415758737efe6f5817a
7
- data.tar.gz: b5def0275205fa9bd13ea1f463ce50992903ba2c423817f22401675ecfc1eb55e1ef57f9e97178d83659ee40fdc1e87a21075aa4180dfed177526e487d043327
6
+ metadata.gz: f49b732799a033333beef767ac6667d603d066ff5bc5f7207b7c05ab127a4996e95f472a9377826ac2fc5898f6b31f710d2408473f611b1114815968edd91f95
7
+ data.tar.gz: 3cb7429db18f8d79c4ff6d1b0a1e488ebf090268fcb9e7ad35f19f4dced3e77c1616c89a8b6d070a9e9e4fc9fd57a3db0cdff2fb44cfc4f8432e57fe6c8719d2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Release History
2
2
 
3
+ ### 3.5.0 (2026-09-14)
4
+
5
+ #### Features
6
+
7
+ * Support subscriber shutdown options ([#36480](https://github.com/googleapis/google-cloud-ruby/issues/36480))
8
+ #### Documentation
9
+
10
+ * Add subscriber shutdown documentation and examples ([#36482](https://github.com/googleapis/google-cloud-ruby/issues/36482))
11
+
12
+ ### 3.4.1 (2026-08-24)
13
+
14
+ #### Bug Fixes
15
+
16
+ * Propagate logger to v1 admin and schema clients ([#32440](https://github.com/googleapis/google-cloud-ruby/issues/32440)) ([#36298](https://github.com/googleapis/google-cloud-ruby/issues/36298))
17
+
3
18
  ### 3.4.0 (2026-07-29)
4
19
 
5
20
  #### Features
@@ -379,7 +379,7 @@ module Google
379
379
  grpc = @service.publish topic_name,
380
380
  items.map(&:msg),
381
381
  compress: compress && batch.total_message_bytes >= compression_bytes_threshold
382
- service.logger.log_batch "publish-batch", reason, "publish", items.count, items.sum(&:bytesize)
382
+ service.internal_logger.log_batch "publish-batch", reason, "publish", items.count, items.sum(&:bytesize)
383
383
  items.zip Array(grpc.message_ids) do |item, id|
384
384
  @flow_controller.release item.bytesize
385
385
  next unless item.callback
@@ -122,7 +122,7 @@ module Google
122
122
  grpc = service.publish topic_name,
123
123
  messages,
124
124
  compress: compress && total_message_bytes >= compression_bytes_threshold
125
- service.logger.log_batch "publish-batch", reason, "publish", messages.count, @total_message_bytes
125
+ service.internal_logger.log_batch "publish-batch", reason, "publish", messages.count, @total_message_bytes
126
126
  to_gcloud_messages Array(grpc.message_ids)
127
127
  end
128
128
  end
@@ -31,7 +31,7 @@ module Google
31
31
  def log level, subtag, &message_block
32
32
  return unless VALID_LOG_LEVELS.include?(level) && block_given?
33
33
  # Only log if the logger is explicitly tagged for 'pubsub'.
34
- return unless @logger && @logger.progname == LOG_NAME
34
+ return unless @logger.respond_to?(:progname) && @logger.progname == LOG_NAME
35
35
 
36
36
  @logger.public_send(level, "#{LOG_NAME}:#{subtag}", &message_block)
37
37
  end
@@ -50,7 +50,7 @@ module Google
50
50
  end
51
51
 
52
52
  def ack_ids
53
- @inventory.keys
53
+ synchronize { @inventory.keys }
54
54
  end
55
55
 
56
56
  def add *rec_msgs
@@ -86,7 +86,7 @@ module Google
86
86
  extension_time = Time.new - extension
87
87
  expired, keep = @inventory.partition { |_ack_id, item| item.pulled_at < extension_time }
88
88
  @inventory = keep.to_h
89
- stream.subscriber.service.logger.log_expiry expired
89
+ stream.subscriber.service.internal_logger.log_expiry expired
90
90
  @wait_cond.broadcast
91
91
  end
92
92
  end
@@ -109,6 +109,31 @@ module Google
109
109
  end
110
110
  end
111
111
 
112
+ ##
113
+ # @private
114
+ # Blocks until the inventory is empty or until timeout expires.
115
+ #
116
+ # @param [Numeric, nil] timeout Maximum time in seconds to wait, or nil to wait indefinitely.
117
+ # @return [Boolean] true if inventory became empty, false if timed out.
118
+ def wait_until_empty timeout = nil
119
+ synchronize do
120
+ return true if @inventory.empty?
121
+
122
+ if timeout
123
+ target_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
124
+ while !@inventory.empty? && !@stopped
125
+ remaining = target_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
126
+ break if remaining <= 0
127
+
128
+ @wait_cond.wait remaining
129
+ end
130
+ else
131
+ @wait_cond.wait_while { !@inventory.empty? && !@stopped }
132
+ end
133
+ @inventory.empty?
134
+ end
135
+ end
136
+
112
137
  def start
113
138
  @background_thread ||= Thread.new { background_run }
114
139
 
@@ -111,11 +111,16 @@ module Google
111
111
  self
112
112
  end
113
113
 
114
+ ##
115
+ # @private
116
+ # Stops pulling messages from the subscription.
117
+ #
118
+ # @return [Stream] self for chaining.
114
119
  def stop
115
120
  synchronize do
116
121
  break if @stopped
117
122
 
118
- subscriber.service.logger.log :info, "subscriber-streams" do
123
+ subscriber.service.internal_logger.log :info, "subscriber-streams" do
119
124
  "stopping stream for subscription #{@subscriber.subscription_name}"
120
125
  end
121
126
  # Close the stream by pushing the sentinel value.
@@ -129,22 +134,45 @@ module Google
129
134
 
130
135
  @keepalive_monitor.stop
131
136
 
132
- # Now that the reception thread is stopped, immediately stop the
133
- # callback thread pool. All queued callbacks will see the stream
134
- # is stopped and perform a noop.
135
- @callback_thread_pool.shutdown
136
-
137
- # Once all the callbacks are stopped, we can stop the inventory.
138
- @inventory.stop
137
+ # When :nack_immediately, release all queued messages immediately and shut down callback pool.
138
+ if nack_immediately?
139
+ nack_unprocessed_messages!
140
+ @callback_thread_pool.shutdown
141
+ end
139
142
  end
140
143
 
141
144
  self
142
145
  end
143
146
 
147
+ ##
148
+ # @private
149
+ # Nacks all messages currently held in inventory.
150
+ def nack_unprocessed_messages!
151
+ synchronize do
152
+ ack_ids = @inventory.ack_ids
153
+ return if ack_ids.empty?
154
+
155
+ @subscriber.buffer.modify_ack_deadline 0, ack_ids
156
+ @subscriber.buffer.flush!
157
+ @inventory.remove ack_ids
158
+ end
159
+ end
160
+
144
161
  def stopped?
145
162
  synchronize { @stopped }
146
163
  end
147
164
 
165
+ ##
166
+ # @private
167
+ # Returns whether the subscriber is stopped and configured to nack unprocessed messages immediately.
168
+ #
169
+ # @return [Boolean]
170
+ def nack_immediately?
171
+ return false unless @stopped
172
+
173
+ @subscriber.shutdown_behavior == :nack_immediately
174
+ end
175
+
148
176
  def stream_open?
149
177
  synchronize { @stream_open }
150
178
  end
@@ -157,14 +185,26 @@ module Google
157
185
  !stopped?
158
186
  end
159
187
 
188
+ ##
189
+ # @private
190
+ # Blocks until all received messages are processed, or until timeout expires.
191
+ #
192
+ # @param [Numeric, nil] timeout The maximum seconds to wait, or nil to wait indefinitely.
193
+ # @return [Stream] self for chaining.
160
194
  def wait! timeout = nil
161
- # Wait for all queued callbacks to be processed.
162
- @callback_thread_pool.wait_for_termination timeout
195
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout if timeout
196
+ emptied = @inventory.wait_until_empty timeout
197
+ nack_unprocessed_messages! unless emptied
163
198
 
199
+ @callback_thread_pool.shutdown
200
+ pool_timeout = [deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max if deadline
201
+ @callback_thread_pool.wait_for_termination pool_timeout
202
+
203
+ # Once all callbacks are finished and inventory is clear, stop the inventory.
204
+ @inventory.stop
164
205
  self
165
206
  end
166
207
 
167
-
168
208
  def request_queue_active?
169
209
  !@request_queue.nil?
170
210
  end
@@ -185,13 +225,13 @@ module Google
185
225
  end
186
226
 
187
227
  def log_info msg
188
- subscriber.service.logger.log :info, "subscriber-streams" do
228
+ subscriber.service.internal_logger.log :info, "subscriber-streams" do
189
229
  msg
190
230
  end
191
231
  end
192
232
 
193
233
  def log_error msg
194
- subscriber.service.logger.log :error, "subscriber-streams" do
234
+ subscriber.service.internal_logger.log :error, "subscriber-streams" do
195
235
  msg
196
236
  end
197
237
  end
@@ -289,7 +329,7 @@ module Google
289
329
  synchronize do
290
330
  # Don't allow a stream to restart if already stopped
291
331
  if @stopped
292
- subscriber.service.logger.log :debug, "subscriber-streams" do
332
+ subscriber.service.internal_logger.log :debug, "subscriber-streams" do
293
333
  "not filling stream for subscription #{@subscriber.subscription_name} because stream is already" \
294
334
  " stopped"
295
335
  end
@@ -319,7 +359,7 @@ module Google
319
359
  @stream_open = false
320
360
  end
321
361
  enum = @subscriber.service.streaming_pull @request_queue.each, options
322
- subscriber.service.logger.log :info, "subscriber-streams" do
362
+ subscriber.service.internal_logger.log :info, "subscriber-streams" do
323
363
  "rpc: streamingPull, subscription: #{@subscriber.subscription_name}, stream opened"
324
364
  end
325
365
 
@@ -396,7 +436,7 @@ module Google
396
436
  GRPC::ResourceExhausted, GRPC::Unauthenticated,
397
437
  GRPC::Unavailable => e
398
438
  status_code = e.respond_to?(:code) ? e.code : e.class.name
399
- subscriber.service.logger.log :error, "subscriber-streams" do
439
+ subscriber.service.internal_logger.log :error, "subscriber-streams" do
400
440
  "Subscriber stream for subscription #{@subscriber.subscription_name} has ended with status " \
401
441
  "#{status_code}; will be retried."
402
442
  end
@@ -404,13 +444,13 @@ module Google
404
444
  backoff_and_wait!
405
445
  retry
406
446
  rescue RestartStream
407
- subscriber.service.logger.log :info, "subscriber-streams" do
447
+ subscriber.service.internal_logger.log :info, "subscriber-streams" do
408
448
  "Subscriber stream for subscription #{@subscriber.subscription_name} has ended; will be retried."
409
449
  end
410
450
  backoff_and_wait!
411
451
  retry
412
452
  rescue StandardError => e
413
- subscriber.service.logger.log :error, "subscriber-streams" do
453
+ subscriber.service.internal_logger.log :error, "subscriber-streams" do
414
454
  "error on stream for subscription #{@subscriber.subscription_name}: #{e.inspect}"
415
455
  end
416
456
  @subscriber.error! e
@@ -463,19 +503,19 @@ module Google
463
503
  end
464
504
 
465
505
  def perform_callback_sync rec_msg
466
- subscriber.service.logger.log :info, "callback-delivery" do
506
+ subscriber.service.internal_logger.log :info, "callback-delivery" do
467
507
  "message (ID #{rec_msg.message_id}, ackID #{rec_msg.ack_id}) delivery to user callbacks"
468
508
  end
469
- @subscriber.callback.call rec_msg unless stopped?
509
+ @subscriber.callback.call rec_msg unless nack_immediately?
470
510
  rescue StandardError => e
471
- subscriber.service.logger.log :info, "callback-exceptions" do
511
+ subscriber.service.internal_logger.log :info, "callback-exceptions" do
472
512
  "message (ID #{rec_msg.message_id}, ackID #{rec_msg.ack_id}) caused a user callback exception: " \
473
513
  "#{e.inspect}"
474
514
  end
475
515
  @subscriber.error! e
476
516
  ensure
477
517
  release rec_msg
478
- if @sequencer && running?
518
+ if @sequencer && !nack_immediately?
479
519
  begin
480
520
  @sequencer.next rec_msg
481
521
  rescue OrderedMessageDeliveryError => e
@@ -498,7 +538,7 @@ module Google
498
538
  return unless pause_streaming?
499
539
 
500
540
  @paused = true
501
- subscriber.service.logger.log :info, "subscriber-flow-control" do
541
+ subscriber.service.internal_logger.log :info, "subscriber-flow-control" do
502
542
  "subscriber for #{@subscriber.subscription_name} is client-side flow control blocked"
503
543
  end
504
544
  end
@@ -519,7 +559,7 @@ module Google
519
559
  # leaving last_pong_at stale. Updating timestamp guarantees the monitor thread will not trigger an immediate
520
560
  # false-positive restart while the reader thread wakes up to drain buffered frames.
521
561
  @keepalive_monitor.record_pong!
522
- subscriber.service.logger.log :info, "subscriber-flow-control" do
562
+ subscriber.service.internal_logger.log :info, "subscriber-flow-control" do
523
563
  "subscriber for #{@subscriber.subscription_name} is unblocking client-side flow control"
524
564
  end
525
565
  # Signal to the background thread that we are unpaused
@@ -320,7 +320,9 @@ module Google
320
320
  reason
321
321
  end
322
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
323
+ @subscriber.service.internal_logger.log_batch(
324
+ "ack-batch", new_reason, "ack", req.ack_ids.length, req.to_proto.bytesize
325
+ )
324
326
  end
325
327
  end
326
328
  requests[:modify_ack_deadline] =
@@ -329,7 +331,9 @@ module Google
329
331
  type = mod_deadline.zero? ? "nack" : "modack"
330
332
  new_reason = mod_ack_reqs.length > 1 ? "#{reason} and partitioned for exceeding max bytes" : reason
331
333
  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
334
+ @subscriber.service.internal_logger.log_batch(
335
+ "ack-batch", new_reason, type, req.ack_ids.length, req.to_proto.bytesize
336
+ )
333
337
  end
334
338
  mod_ack_reqs
335
339
  end.flatten
@@ -60,6 +60,12 @@ module Google
60
60
  # acknowledgement ({ReceivedMessage#ack!}) and delay messages
61
61
  # ({ReceivedMessage#nack!}, {ReceivedMessage#modify_ack_deadline!}).
62
62
  # Default is 4.
63
+ # @attr_reader [Symbol] shutdown_behavior The strategy used to handle
64
+ # unprocessed messages when stopping the subscriber (`:wait_for_processing`
65
+ # or `:nack_immediately`). Default is `:wait_for_processing`.
66
+ # @attr_reader [Numeric, nil] shutdown_timeout The maximum number of
67
+ # seconds to wait during shutdown before forcing remaining messages
68
+ # to be nacked. Default is `nil`.
63
69
  #
64
70
  class MessageListener
65
71
  include MonitorMixin
@@ -71,6 +77,8 @@ module Google
71
77
  attr_reader :message_ordering
72
78
  attr_reader :callback_threads
73
79
  attr_reader :push_threads
80
+ attr_reader :shutdown_behavior
81
+ attr_reader :shutdown_timeout
74
82
 
75
83
  ##
76
84
  # @private Implementation attributes.
@@ -83,7 +91,7 @@ module Google
83
91
  ##
84
92
  # @private Create an empty {MessageListener} object.
85
93
  def initialize subscription_name, callback, deadline: nil, message_ordering: nil, streams: nil, inventory: nil,
86
- threads: {}, service: nil
94
+ threads: {}, shutdown_behavior: :wait_for_processing, shutdown_timeout: nil, service: nil
87
95
  super() # to init MonitorMixin
88
96
 
89
97
  @callback = callback
@@ -91,6 +99,16 @@ module Google
91
99
  @subscription_name = subscription_name
92
100
  @deadline = deadline || 60
93
101
  @streams = streams || 1
102
+ @shutdown_behavior = shutdown_behavior || :wait_for_processing
103
+ @shutdown_timeout = shutdown_timeout
104
+
105
+ unless [:wait_for_processing, :nack_immediately].include? @shutdown_behavior
106
+ raise ArgumentError, "Invalid shutdown_behavior: #{@shutdown_behavior.inspect}"
107
+ end
108
+ if @shutdown_timeout && (!@shutdown_timeout.is_a?(Numeric) || @shutdown_timeout.negative?)
109
+ raise ArgumentError, "Invalid shutdown_timeout: #{@shutdown_timeout.inspect}"
110
+ end
111
+
94
112
  coerce_inventory inventory
95
113
  @message_ordering = message_ordering
96
114
  @callback_threads = Integer(threads[:callback] || 8)
@@ -145,7 +163,7 @@ module Google
145
163
  @started = false
146
164
  @stopped = true
147
165
  @stream_pool.map(&:stop)
148
- wait_stop_buffer_thread!
166
+ wait_stop_buffer_thread! @shutdown_timeout
149
167
  self
150
168
  end
151
169
  end
@@ -160,12 +178,14 @@ module Google
160
178
  # stopped.
161
179
  #
162
180
  # @param [Number, nil] timeout The number of seconds to block until the
163
- # subscriber is fully stopped. Default will block indefinitely.
181
+ # subscriber is fully stopped. Default will block indefinitely or use
182
+ # configured `shutdown_timeout`.
164
183
  #
165
184
  # @return [MessageListener] returns self so calls can be chained.
166
185
  #
167
186
  def wait! timeout = nil
168
- wait_stop_buffer_thread!
187
+ timeout ||= @shutdown_timeout
188
+ wait_stop_buffer_thread! timeout
169
189
  @wait_stop_buffer_thread.join timeout
170
190
  self
171
191
  end
@@ -178,11 +198,13 @@ module Google
178
198
  # The same as calling {#stop} and {#wait!}.
179
199
  #
180
200
  # @param [Number, nil] timeout The number of seconds to block until the
181
- # listener is fully stopped. Default will block indefinitely.
201
+ # listener is fully stopped. Default will block indefinitely or use
202
+ # configured `shutdown_timeout`.
182
203
  #
183
204
  # @return [MessageListener] returns self so calls can be chained.
184
205
  #
185
206
  def stop! timeout = nil
207
+ timeout ||= @shutdown_timeout
186
208
  stop
187
209
  wait! timeout
188
210
  end
@@ -365,10 +387,10 @@ module Google
365
387
 
366
388
  ##
367
389
  # Starts a new thread to call wait! (blocking) on each Stream and then stop the TimedUnaryBuffer.
368
- def wait_stop_buffer_thread!
390
+ def wait_stop_buffer_thread! timeout = nil
369
391
  synchronize do
370
392
  @wait_stop_buffer_thread ||= Thread.new do
371
- @stream_pool.map(&:wait!)
393
+ @stream_pool.each { |s| s.wait! timeout }
372
394
  # Shutdown the buffer TimerTask (and flush the buffer) after the streams are all stopped.
373
395
  @buffer.stop
374
396
  end
@@ -43,7 +43,7 @@ module Google
43
43
 
44
44
  ##
45
45
  # @private The InternalLogger object.
46
- attr_reader :logger
46
+ attr_reader :internal_logger
47
47
 
48
48
  ##
49
49
  # Creates a new Service instance.
@@ -55,6 +55,7 @@ module Google
55
55
  @client_id = SecureRandom.uuid.freeze
56
56
  @universe_domain = universe_domain || ENV["GOOGLE_CLOUD_UNIVERSE_DOMAIN"] || "googleapis.com"
57
57
  @logger = logger
58
+ @internal_logger = InternalLogger.new logger
58
59
  end
59
60
 
60
61
  def subscription_admin
@@ -67,6 +68,7 @@ module Google
67
68
  config.lib_name = "gccl"
68
69
  config.lib_version = Google::Cloud::PubSub::VERSION
69
70
  config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}" }
71
+ config.logger = @logger if @logger
70
72
  end
71
73
  end
72
74
  attr_accessor :mocked_subscription_admin
@@ -81,6 +83,7 @@ module Google
81
83
  config.lib_name = "gccl"
82
84
  config.lib_version = Google::Cloud::PubSub::VERSION
83
85
  config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}" }
86
+ config.logger = @logger if @logger
84
87
  end
85
88
  end
86
89
  attr_accessor :mocked_topic_admin
@@ -94,6 +97,7 @@ module Google
94
97
  config.lib_name = "gccl"
95
98
  config.lib_version = Google::Cloud::PubSub::VERSION
96
99
  config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}" }
100
+ config.logger = @logger if @logger
97
101
  end
98
102
  iam
99
103
  end
@@ -110,6 +114,7 @@ module Google
110
114
  config.lib_name = "gccl"
111
115
  config.lib_version = Google::Cloud::PubSub::VERSION
112
116
  config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}" }
117
+ config.logger = @logger if @logger
113
118
  end
114
119
  end
115
120
  attr_accessor :mocked_schemas
@@ -147,7 +152,7 @@ module Google
147
152
  ##
148
153
  # Acknowledges receipt of a message.
149
154
  def acknowledge subscription, *ack_ids
150
- logger.log_ack_nack ack_ids, "ack"
155
+ internal_logger.log_ack_nack ack_ids, "ack"
151
156
  subscription_admin.acknowledge_internal subscription: subscription_path(subscription),
152
157
  ack_ids: ack_ids
153
158
  end
@@ -156,7 +161,7 @@ module Google
156
161
  # Modifies the ack deadline for a specific message.
157
162
  def modify_ack_deadline subscription, ids, deadline
158
163
  if deadline.zero?
159
- logger.log_ack_nack Array(ids), "nack"
164
+ internal_logger.log_ack_nack Array(ids), "nack"
160
165
  end
161
166
  subscription_admin.modify_ack_deadline_internal subscription: subscription_path(subscription),
162
167
  ack_ids: Array(ids),
@@ -203,7 +208,6 @@ module Google
203
208
  rpc.timeout = timeout if rpc.respond_to? :timeout=
204
209
  end
205
210
  end
206
-
207
211
  end
208
212
  end
209
213
  Pubsub = PubSub unless const_defined? :Pubsub
@@ -324,6 +324,16 @@ module Google
324
324
  # acknowledgement ({ReceivedMessage#ack!}) and modify ack deadline
325
325
  # messages ({ReceivedMessage#nack!},
326
326
  # {ReceivedMessage#modify_ack_deadline!}). Default is 4.
327
+ # @param [Symbol] shutdown_behavior The strategy used to handle
328
+ # unprocessed messages when stopping the subscriber.
329
+ # Supported values:
330
+ # * `:wait_for_processing` (default) - Waits for in-flight callbacks
331
+ # to complete and acknowledge or nack messages.
332
+ # * `:nack_immediately` - Immediately nacks all unprocessed messages
333
+ # back to Pub/Sub for rapid redelivery to other subscribers.
334
+ # @param [Numeric, nil] shutdown_timeout The maximum number of seconds
335
+ # to wait during shutdown before forcing remaining in-flight messages
336
+ # to be nacked. Default is `nil` (blocks indefinitely until callbacks finish).
327
337
  #
328
338
  # @yield [received_message] a block for processing new messages
329
339
  # @yieldparam [ReceivedMessage] received_message the newly received
@@ -408,13 +418,52 @@ module Google
408
418
  # # Shut down the subscriber when ready to stop receiving messages.
409
419
  # listener.stop!
410
420
  #
411
- def listen deadline: nil, message_ordering: nil, streams: nil, inventory: nil, threads: {}, &block
421
+ # @example Immediately release unprocessed messages on shutdown:
422
+ # require "google/cloud/pubsub"
423
+ #
424
+ # pubsub = Google::Cloud::PubSub.new
425
+ #
426
+ # subscriber = pubsub.subscriber "my-topic-sub"
427
+ #
428
+ # listener = subscriber.listen shutdown_behavior: :nack_immediately do |received_message|
429
+ # # process message
430
+ # puts "Data: #{received_message.message.data}"
431
+ # received_message.acknowledge!
432
+ # end
433
+ #
434
+ # listener.start
435
+ #
436
+ # # Shut down immediately, releasing (nacking) any unprocessed messages
437
+ # listener.stop!
438
+ #
439
+ # @example Gracefully shut down with a custom timeout:
440
+ # require "google/cloud/pubsub"
441
+ #
442
+ # pubsub = Google::Cloud::PubSub.new
443
+ #
444
+ # subscriber = pubsub.subscriber "my-topic-sub"
445
+ #
446
+ # listener = subscriber.listen shutdown_behavior: :wait_for_processing, shutdown_timeout: 30 do |received_message|
447
+ # # process message
448
+ # puts "Data: #{received_message.message.data}"
449
+ # received_message.acknowledge!
450
+ # end
451
+ #
452
+ # listener.start
453
+ #
454
+ # # Wait up to 30 seconds for in-flight callbacks before forcing remaining to nack
455
+ # listener.stop!
456
+ #
457
+ def listen deadline: nil, message_ordering: nil, streams: nil, inventory: nil, threads: {},
458
+ shutdown_behavior: :wait_for_processing, shutdown_timeout: nil, &block
412
459
  ensure_service!
413
460
  deadline ||= self.deadline
414
461
  message_ordering = message_ordering? if message_ordering.nil?
415
462
 
416
463
  MessageListener.new name, block, deadline: deadline, streams: streams, inventory: inventory,
417
- message_ordering: message_ordering, threads: threads, service: service
464
+ message_ordering: message_ordering, threads: threads,
465
+ shutdown_behavior: shutdown_behavior, shutdown_timeout: shutdown_timeout,
466
+ service: service
418
467
  end
419
468
 
420
469
  ##
@@ -16,7 +16,7 @@
16
16
  module Google
17
17
  module Cloud
18
18
  module PubSub
19
- VERSION = "3.4.0".freeze
19
+ VERSION = "3.5.0".freeze
20
20
  end
21
21
 
22
22
  Pubsub = PubSub unless const_defined? :Pubsub
@@ -122,7 +122,6 @@ module Google
122
122
  project_id = project_id.to_s # Always cast to a string
123
123
  raise ArgumentError, "project_id is missing" if project_id.empty?
124
124
 
125
- logger = Google::Cloud::PubSub::InternalLogger.new logger
126
125
  service = PubSub::Service.new project_id, credentials,
127
126
  host: endpoint,
128
127
  timeout: timeout,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: google-cloud-pubsub
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.4.0
4
+ version: 3.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Moore