google-cloud-pubsub 1.0.2 → 1.1.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.
@@ -13,6 +13,7 @@
13
13
  # limitations under the License.
14
14
 
15
15
 
16
+ require "google/cloud/pubsub/subscriber/sequencer"
16
17
  require "google/cloud/pubsub/subscriber/enumerator_queue"
17
18
  require "google/cloud/pubsub/subscriber/inventory"
18
19
  require "google/cloud/pubsub/service"
@@ -34,9 +35,17 @@ module Google
34
35
  attr_reader :callback_thread_pool
35
36
 
36
37
  ##
37
- # Subscriber attributes.
38
+ # @private Subscriber attributes.
38
39
  attr_reader :subscriber
39
40
 
41
+ ##
42
+ # @private Inventory.
43
+ attr_reader :inventory
44
+
45
+ ##
46
+ # @private Sequencer.
47
+ attr_reader :sequencer
48
+
40
49
  ##
41
50
  # @private Create an empty Subscriber::Stream object.
42
51
  def initialize subscriber
@@ -48,16 +57,16 @@ module Google
48
57
  @pause_cond = new_cond
49
58
 
50
59
  @inventory = Inventory.new self, @subscriber.stream_inventory
51
- @callback_thread_pool = Concurrent::ThreadPoolExecutor.new \
52
- max_threads: @subscriber.callback_threads
60
+
61
+ @sequencer = Sequencer.new(&method(:perform_callback_async)) if subscriber.message_ordering
62
+
63
+ @callback_thread_pool = Concurrent::ThreadPoolExecutor.new max_threads: @subscriber.callback_threads
53
64
 
54
65
  @stream_keepalive_task = Concurrent::TimerTask.new(
55
66
  execution_interval: 30
56
67
  ) do
57
68
  # push empty request every 30 seconds to keep stream alive
58
- unless inventory.empty?
59
- push Google::Cloud::PubSub::V1::StreamingPullRequest.new
60
- end
69
+ push Google::Cloud::PubSub::V1::StreamingPullRequest.new unless inventory.empty?
61
70
  end.execute
62
71
 
63
72
  super() # to init MonitorMixin
@@ -81,7 +90,7 @@ module Google
81
90
 
82
91
  # Close the stream by pushing the sentinel value.
83
92
  # The unary pusher does not use the stream, so it can close here.
84
- @request_queue.push self unless @request_queue.nil?
93
+ @request_queue&.push self
85
94
 
86
95
  # Signal to the background thread that we are stopped.
87
96
  @stopped = true
@@ -107,6 +116,10 @@ module Google
107
116
  synchronize { @paused }
108
117
  end
109
118
 
119
+ def running?
120
+ !stopped?
121
+ end
122
+
110
123
  def wait! timeout = nil
111
124
  # Wait for all queued callbacks to be processed.
112
125
  @callback_thread_pool.wait_for_termination timeout
@@ -162,10 +175,6 @@ module Google
162
175
  synchronize { @request_queue.push request }
163
176
  end
164
177
 
165
- def inventory
166
- synchronize { @inventory }
167
- end
168
-
169
178
  ##
170
179
  # @private
171
180
  def renew_lease!
@@ -182,8 +191,8 @@ module Google
182
191
 
183
192
  # @private
184
193
  def to_s
185
- "(inventory: #{@inventory.count}, " \
186
- "status: #{status}, thread: #{thread_status})"
194
+ seq_str = "sequenced: #{sequencer}, " if sequencer
195
+ "(inventory: #{@inventory.count}, #{seq_str}status: #{status}, thread: #{thread_status})"
187
196
  end
188
197
 
189
198
  # @private
@@ -237,6 +246,7 @@ module Google
237
246
  # Create a list of all the received ack_id values
238
247
  received_ack_ids = response.received_messages.map(&:ack_id)
239
248
 
249
+ # Use synchronize so both changes happen atomically
240
250
  synchronize do
241
251
  # Create receipt of received messages reception
242
252
  @subscriber.buffer.modify_ack_deadline @subscriber.deadline,
@@ -248,10 +258,8 @@ module Google
248
258
 
249
259
  response.received_messages.each do |rec_msg_grpc|
250
260
  rec_msg = ReceivedMessage.from_grpc(rec_msg_grpc, self)
251
- synchronize do
252
- # Call user provided code for received message
253
- perform_callback_async rec_msg
254
- end
261
+ # No need to synchronize the callback future
262
+ register_callback rec_msg
255
263
  end
256
264
  synchronize { pause_streaming! }
257
265
  rescue StopIteration
@@ -282,18 +290,35 @@ module Google
282
290
 
283
291
  # rubocop:enable all
284
292
 
293
+ def register_callback rec_msg
294
+ if @sequencer
295
+ # Add the message to the sequencer to invoke the callback.
296
+ @sequencer.add rec_msg
297
+ else
298
+ # Call user provided code for received message
299
+ perform_callback_async rec_msg
300
+ end
301
+ end
302
+
285
303
  def perform_callback_async rec_msg
286
304
  return unless callback_thread_pool.running?
287
305
 
288
306
  Concurrent::Promises.future_on(
289
- callback_thread_pool, self, rec_msg
290
- ) do |stream, msg|
307
+ callback_thread_pool, rec_msg, &method(:perform_callback_sync)
308
+ )
309
+ end
310
+
311
+ def perform_callback_sync rec_msg
312
+ @subscriber.callback.call rec_msg unless stopped?
313
+ rescue StandardError => e
314
+ @subscriber.error! e
315
+ ensure
316
+ release rec_msg
317
+ if @sequencer && running?
291
318
  begin
292
- stream.subscriber.callback.call msg unless stream.stopped?
293
- rescue StandardError => callback_error
294
- stream.subscriber.error! callback_error
295
- ensure
296
- stream.release msg
319
+ @sequencer.next rec_msg
320
+ rescue OrderedMessageDeliveryError => e
321
+ @subscriber.error! e
297
322
  end
298
323
  end
299
324
  end
@@ -341,8 +366,7 @@ module Google
341
366
  req.subscription = @subscriber.subscription_name
342
367
  req.stream_ack_deadline_seconds = @subscriber.deadline
343
368
  req.modify_deadline_ack_ids += @inventory.ack_ids
344
- req.modify_deadline_seconds += \
345
- @inventory.ack_ids.map { @subscriber.deadline }
369
+ req.modify_deadline_seconds += @inventory.ack_ids.map { @subscriber.deadline }
346
370
  end
347
371
  end
348
372
 
@@ -27,7 +27,7 @@ module Google
27
27
 
28
28
  attr_reader :max_bytes, :interval
29
29
 
30
- def initialize subscriber, max_bytes: 500000, interval: 1.0
30
+ def initialize subscriber, max_bytes: 500_000, interval: 1.0
31
31
  super() # to init MonitorMixin
32
32
 
33
33
  @subscriber = subscriber
@@ -91,15 +91,13 @@ module Google
91
91
  with_threadpool do |pool|
92
92
  requests[:acknowledge].each do |ack_req|
93
93
  add_future pool do
94
- @subscriber.service.acknowledge \
95
- ack_req.subscription, *ack_req.ack_ids
94
+ @subscriber.service.acknowledge ack_req.subscription, *ack_req.ack_ids
96
95
  end
97
96
  end
98
97
  requests[:modify_ack_deadline].each do |mod_ack_req|
99
98
  add_future pool do
100
- @subscriber.service.modify_ack_deadline \
101
- mod_ack_req.subscription, mod_ack_req.ack_ids,
102
- mod_ack_req.ack_deadline_seconds
99
+ @subscriber.service.modify_ack_deadline mod_ack_req.subscription, mod_ack_req.ack_ids,
100
+ mod_ack_req.ack_deadline_seconds
103
101
  end
104
102
  end
105
103
  end
@@ -144,9 +142,7 @@ module Google
144
142
 
145
143
  requests = { acknowledge: [] }
146
144
  ack_ids = Array(req_hash.delete(:ack)) # ack has no deadline set
147
- if ack_ids.any?
148
- requests[:acknowledge] = create_acknowledge_requests ack_ids
149
- end
145
+ requests[:acknowledge] = create_acknowledge_requests ack_ids if ack_ids.any?
150
146
  requests[:modify_ack_deadline] =
151
147
  req_hash.map do |mod_deadline, mod_ack_ids|
152
148
  create_modify_ack_deadline_requests mod_deadline, mod_ack_ids
@@ -201,8 +197,7 @@ module Google
201
197
  end
202
198
 
203
199
  def with_threadpool
204
- pool = Concurrent::ThreadPoolExecutor.new \
205
- max_threads: @subscriber.push_threads
200
+ pool = Concurrent::ThreadPoolExecutor.new max_threads: @subscriber.push_threads
206
201
 
207
202
  yield pool
208
203
 
@@ -213,8 +208,8 @@ module Google
213
208
  pool.kill
214
209
  begin
215
210
  raise "Timeout making subscriber API calls"
216
- rescue StandardError => error
217
- error! error
211
+ rescue StandardError => e
212
+ error! e
218
213
  end
219
214
  end
220
215
 
@@ -222,8 +217,8 @@ module Google
222
217
  Concurrent::Promises.future_on pool do
223
218
  begin
224
219
  yield
225
- rescue StandardError => error
226
- error! error
220
+ rescue StandardError => e
221
+ error! e
227
222
  end
228
223
  end
229
224
  end
@@ -117,10 +117,8 @@ module Google
117
117
  # @param [Integer] new_deadline The new deadline value.
118
118
  #
119
119
  def deadline= new_deadline
120
- update_grpc = Google::Cloud::PubSub::V1::Subscription.new \
121
- name: name, ack_deadline_seconds: new_deadline
122
- @grpc = service.update_subscription update_grpc,
123
- :ack_deadline_seconds
120
+ update_grpc = Google::Cloud::PubSub::V1::Subscription.new name: name, ack_deadline_seconds: new_deadline
121
+ @grpc = service.update_subscription update_grpc, :ack_deadline_seconds
124
122
  @resource_name = nil
125
123
  end
126
124
 
@@ -148,10 +146,9 @@ module Google
148
146
  # value.
149
147
  #
150
148
  def retain_acked= new_retain_acked
151
- update_grpc = Google::Cloud::PubSub::V1::Subscription.new \
152
- name: name, retain_acked_messages: !(!new_retain_acked)
153
- @grpc = service.update_subscription update_grpc,
154
- :retain_acked_messages
149
+ update_grpc = Google::Cloud::PubSub::V1::Subscription.new name: name,
150
+ retain_acked_messages: !(!new_retain_acked)
151
+ @grpc = service.update_subscription update_grpc, :retain_acked_messages
155
152
  @resource_name = nil
156
153
  end
157
154
 
@@ -181,10 +178,9 @@ module Google
181
178
  #
182
179
  def retention= new_retention
183
180
  new_retention_duration = Convert.number_to_duration new_retention
184
- update_grpc = Google::Cloud::PubSub::V1::Subscription.new \
185
- name: name, message_retention_duration: new_retention_duration
186
- @grpc = service.update_subscription update_grpc,
187
- :message_retention_duration
181
+ update_grpc = Google::Cloud::PubSub::V1::Subscription.new name: name,
182
+ message_retention_duration: new_retention_duration
183
+ @grpc = service.update_subscription update_grpc, :message_retention_duration
188
184
  @resource_name = nil
189
185
  end
190
186
 
@@ -200,7 +196,7 @@ module Google
200
196
  #
201
197
  def endpoint
202
198
  ensure_grpc!
203
- @grpc.push_config.push_endpoint if @grpc.push_config
199
+ @grpc.push_config&.push_endpoint
204
200
  end
205
201
 
206
202
  ##
@@ -271,8 +267,7 @@ module Google
271
267
  new_config = config.to_grpc
272
268
 
273
269
  if old_config != new_config # has the object been changed?
274
- update_grpc = Google::Cloud::PubSub::V1::Subscription.new \
275
- name: name, push_config: new_config
270
+ update_grpc = Google::Cloud::PubSub::V1::Subscription.new name: name, push_config: new_config
276
271
  @grpc = service.update_subscription update_grpc, :push_config
277
272
  end
278
273
  end
@@ -312,8 +307,7 @@ module Google
312
307
  #
313
308
  def labels= new_labels
314
309
  raise ArgumentError, "Value must be a Hash" if new_labels.nil?
315
- update_grpc = Google::Cloud::PubSub::V1::Subscription.new \
316
- name: name, labels: new_labels
310
+ update_grpc = Google::Cloud::PubSub::V1::Subscription.new name: name, labels: new_labels
317
311
  @grpc = service.update_subscription update_grpc, :labels
318
312
  @resource_name = nil
319
313
  end
@@ -350,16 +344,34 @@ module Google
350
344
  # to unset.
351
345
  #
352
346
  def expires_in= ttl
353
- new_expiration_policy = Google::Pubsub::V1::ExpirationPolicy.new(
354
- ttl: Convert.number_to_duration(ttl)
355
- )
347
+ new_expiration_policy = Google::Pubsub::V1::ExpirationPolicy.new ttl: Convert.number_to_duration(ttl)
356
348
 
357
- update_grpc = Google::Cloud::PubSub::V1::Subscription.new \
358
- name: name, expiration_policy: new_expiration_policy
349
+ update_grpc = Google::Cloud::PubSub::V1::Subscription.new name: name, expiration_policy: new_expiration_policy
359
350
  @grpc = service.update_subscription update_grpc, :expiration_policy
360
351
  @resource_name = nil
361
352
  end
362
353
 
354
+ ##
355
+ # Whether message ordering has been enabled. When enabled, messages
356
+ # published with the same `ordering_key` will be delivered in the order
357
+ # they were published. When disabled, messages may be delivered in any
358
+ # order.
359
+ #
360
+ # @note At the time of this release, ordering keys are not yet publicly
361
+ # enabled and requires special project enablements.
362
+ #
363
+ # See {Topic#publish_async}, {#listen}, and {Message#ordering_key}.
364
+ #
365
+ # Makes an API call to retrieve the retain_acked value when called on a
366
+ # reference object. See {#reference?}.
367
+ #
368
+ # @return [Boolean]
369
+ #
370
+ def message_ordering?
371
+ ensure_grpc!
372
+ @grpc.enable_message_ordering
373
+ end
374
+
363
375
  ##
364
376
  # Determines whether the subscription exists in the Pub/Sub service.
365
377
  #
@@ -508,13 +520,40 @@ module Google
508
520
  # message will be removed from the subscriber and made available for
509
521
  # redelivery after the callback is completed.
510
522
  #
523
+ # Google Cloud Pub/Sub ordering keys provide the ability to ensure
524
+ # related messages are sent to subscribers in the order in which they
525
+ # were published. Messages can be tagged with an ordering key, a string
526
+ # that identifies related messages for which publish order should be
527
+ # respected. The service guarantees that, for a given ordering key and
528
+ # publisher, messages are sent to subscribers in the order in which they
529
+ # were published. Ordering does not require sacrificing high throughput
530
+ # or scalability, as the service automatically distributes messages for
531
+ # different ordering keys across subscribers.
532
+ #
533
+ # To use ordering keys, the subscription must be created with message
534
+ # ordering enabled (See {Topic#subscribe} and {#message_ordering?})
535
+ # before calling {#listen}. When enabled, the subscriber will deliver
536
+ # messages with the same `ordering_key` in the order they were
537
+ # published.
538
+ #
539
+ # @note At the time of this release, ordering keys are not yet publicly
540
+ # enabled and requires special project enablements.
541
+ #
511
542
  # @param [Numeric] deadline The default number of seconds the stream
512
543
  # will hold received messages before modifying the message's ack
513
544
  # deadline. The minimum is 10, the maximum is 600. Default is
514
545
  # {#deadline}. Optional.
515
546
  #
516
- # Makes an API call to retrieve the deadline value when called on a
517
- # reference object. See {#reference?}.
547
+ # When using a reference object an API call will be made to retrieve
548
+ # the default deadline value for the subscription when this argument
549
+ # is not provided. See {#reference?}.
550
+ # @param [Boolean] message_ordering Whether message ordering has been
551
+ # enabled. The value provided must match the value set on the Pub/Sub
552
+ # service. See {#message_ordering?}. Optional.
553
+ #
554
+ # When using a reference object an API call will be made to retrieve
555
+ # the default message_ordering value for the subscription when this
556
+ # argument is not provided. See {#reference?}.
518
557
  # @param [Integer] streams The number of concurrent streams to open to
519
558
  # pull messages from the subscription. Default is 4. Optional.
520
559
  # @param [Integer] inventory The number of received messages to be
@@ -571,14 +610,36 @@ module Google
571
610
  # # Start background threads that will call block passed to listen.
572
611
  # subscriber.start
573
612
  #
574
- def listen deadline: nil, streams: nil, inventory: nil, threads: {},
575
- &block
613
+ # # Shut down the subscriber when ready to stop receiving messages.
614
+ # subscriber.stop.wait!
615
+ #
616
+ # @example Ordered messages are supported using ordering_key:
617
+ # require "google/cloud/pubsub"
618
+ #
619
+ # pubsub = Google::Cloud::PubSub.new
620
+ #
621
+ # sub = pubsub.subscription "my-ordered-topic-sub"
622
+ # sub.message_ordering? #=> true
623
+ #
624
+ # subscriber = sub.listen do |received_message|
625
+ # # messsages with the same ordering_key are received
626
+ # # in the order in which they were published.
627
+ # received_message.acknowledge!
628
+ # end
629
+ #
630
+ # # Start background threads that will call block passed to listen.
631
+ # subscriber.start
632
+ #
633
+ # # Shut down the subscriber when ready to stop receiving messages.
634
+ # subscriber.stop.wait!
635
+ #
636
+ def listen deadline: nil, message_ordering: nil, streams: nil, inventory: nil, threads: {}, &block
576
637
  ensure_service!
577
638
  deadline ||= self.deadline
639
+ message_ordering = message_ordering? if message_ordering.nil?
578
640
 
579
- Subscriber.new name, block, deadline: deadline, streams: streams,
580
- inventory: inventory, threads: threads,
581
- service: service
641
+ Subscriber.new name, block, deadline: deadline, streams: streams, inventory: inventory,
642
+ message_ordering: message_ordering, threads: threads, service: service
582
643
  end
583
644
 
584
645
  ##
@@ -132,15 +132,13 @@ module Google
132
132
  #
133
133
  def all request_limit: nil
134
134
  request_limit = request_limit.to_i if request_limit
135
- unless block_given?
136
- return enum_for :all, request_limit: request_limit
137
- end
135
+ return enum_for :all, request_limit: request_limit unless block_given?
138
136
  results = self
139
137
  loop do
140
138
  results.each { |r| yield r }
141
139
  if request_limit
142
140
  request_limit -= 1
143
- break if request_limit < 0
141
+ break if request_limit.negative?
144
142
  end
145
143
  break unless results.next?
146
144
  results = results.next
@@ -126,8 +126,7 @@ module Google
126
126
  #
127
127
  def labels= new_labels
128
128
  raise ArgumentError, "Value must be a Hash" if new_labels.nil?
129
- update_grpc = Google::Cloud::PubSub::V1::Topic.new \
130
- name: name, labels: new_labels
129
+ update_grpc = Google::Cloud::PubSub::V1::Topic.new name: name, labels: new_labels
131
130
  @grpc = service.update_topic update_grpc, :labels
132
131
  @resource_name = nil
133
132
  end
@@ -176,8 +175,7 @@ module Google
176
175
  # topic.kms_key = key_name
177
176
  #
178
177
  def kms_key= new_kms_key_name
179
- update_grpc = Google::Cloud::PubSub::V1::Topic.new \
180
- name: name, kms_key_name: new_kms_key_name
178
+ update_grpc = Google::Cloud::PubSub::V1::Topic.new name: name, kms_key_name: new_kms_key_name
181
179
  @grpc = service.update_topic update_grpc, :kms_key_name
182
180
  @resource_name = nil
183
181
  end
@@ -229,9 +227,7 @@ module Google
229
227
  #
230
228
  def persistence_regions= new_persistence_regions
231
229
  update_grpc = Google::Cloud::PubSub::V1::Topic.new \
232
- name: name, message_storage_policy: {
233
- allowed_persistence_regions: Array(new_persistence_regions)
234
- }
230
+ name: name, message_storage_policy: { allowed_persistence_regions: Array(new_persistence_regions) }
235
231
  @grpc = service.update_topic update_grpc, :message_storage_policy
236
232
  @resource_name = nil
237
233
  end
@@ -287,6 +283,8 @@ module Google
287
283
  # values are optional. Label keys must start with a letter and each
288
284
  # label in the list must have a different key. See [Creating and
289
285
  # Managing Labels](https://cloud.google.com/pubsub/docs/labels).
286
+ # @param [Boolean] message_ordering Whether to enable message ordering
287
+ # on the subscription.
290
288
  #
291
289
  # @return [Google::Cloud::PubSub::Subscription]
292
290
  #
@@ -309,11 +307,11 @@ module Google
309
307
  # deadline: 120,
310
308
  # endpoint: "https://example.com/push"
311
309
  #
312
- def subscribe subscription_name, deadline: nil, retain_acked: false,
313
- retention: nil, endpoint: nil, labels: nil
310
+ def subscribe subscription_name, deadline: nil, retain_acked: false, retention: nil, endpoint: nil, labels: nil,
311
+ message_ordering: nil
314
312
  ensure_service!
315
- options = { deadline: deadline, retain_acked: retain_acked,
316
- retention: retention, endpoint: endpoint, labels: labels }
313
+ options = { deadline: deadline, retain_acked: retain_acked, retention: retention, endpoint: endpoint,
314
+ labels: labels, message_ordering: message_ordering }
317
315
  grpc = service.create_subscription name, subscription_name, options
318
316
  Subscription.from_grpc grpc, service
319
317
  end
@@ -355,9 +353,7 @@ module Google
355
353
  #
356
354
  def subscription subscription_name, skip_lookup: nil
357
355
  ensure_service!
358
- if skip_lookup
359
- return Subscription.from_name subscription_name, service
360
- end
356
+ return Subscription.from_name subscription_name, service if skip_lookup
361
357
  grpc = service.get_subscription subscription_name
362
358
  Subscription.from_grpc grpc, service
363
359
  rescue Google::Cloud::NotFoundError
@@ -473,17 +469,48 @@ module Google
473
469
  end
474
470
 
475
471
  ##
476
- # Publishes a message asynchronously to the topic.
472
+ # Publishes a message asynchronously to the topic using
473
+ # {#async_publisher}.
477
474
  #
478
475
  # The message payload must not be empty; it must contain either a
479
476
  # non-empty data field, or at least one attribute.
480
477
  #
478
+ # Google Cloud Pub/Sub ordering keys provide the ability to ensure
479
+ # related messages are sent to subscribers in the order in which they
480
+ # were published. Messages can be tagged with an ordering key, a string
481
+ # that identifies related messages for which publish order should be
482
+ # respected. The service guarantees that, for a given ordering key and
483
+ # publisher, messages are sent to subscribers in the order in which they
484
+ # were published. Ordering does not require sacrificing high throughput
485
+ # or scalability, as the service automatically distributes messages for
486
+ # different ordering keys across subscribers.
487
+ #
488
+ # To use ordering keys, specify `ordering_key`. Before specifying
489
+ # `ordering_key` on a message a call to `#enable_message_ordering!` must
490
+ # be made or an error will be raised.
491
+ #
492
+ # @note At the time of this release, ordering keys are not yet publicly
493
+ # enabled and requires special project enablements.
494
+ #
481
495
  # @param [String, File] data The message payload. This will be converted
482
496
  # to bytes encoded as ASCII-8BIT.
483
497
  # @param [Hash] attributes Optional attributes for the message.
498
+ # @param [String] ordering_key Identifies related messages for which
499
+ # publish order should be respected.
484
500
  # @yield [result] the callback for when the message has been published
485
501
  # @yieldparam [PublishResult] result the result of the asynchronous
486
502
  # publish
503
+ # @raise [Google::Cloud::PubSub::AsyncPublisherStopped] when the
504
+ # publisher is stopped. (See {AsyncPublisher#stop} and
505
+ # {AsyncPublisher#stopped?}.)
506
+ # @raise [Google::Cloud::PubSub::OrderedMessagesDisabled] when
507
+ # publishing a message with an `ordering_key` but ordered messages are
508
+ # not enabled. (See {#message_ordering?} and
509
+ # {#enable_message_ordering!}.)
510
+ # @raise [Google::Cloud::PubSub::OrderingKeyError] when publishing a
511
+ # message with an `ordering_key` that has already failed when
512
+ # publishing. Use {#resume_publish} to allow this `ordering_key` to be
513
+ # published again.
487
514
  #
488
515
  # @example
489
516
  # require "google/cloud/pubsub"
@@ -499,6 +526,7 @@ module Google
499
526
  # end
500
527
  # end
501
528
  #
529
+ # # Shut down the publisher when ready to stop publishing messages.
502
530
  # topic.async_publisher.stop.wait!
503
531
  #
504
532
  # @example A message can be published using a File object:
@@ -510,6 +538,7 @@ module Google
510
538
  # file = File.open "message.txt", mode: "rb"
511
539
  # topic.publish_async file
512
540
  #
541
+ # # Shut down the publisher when ready to stop publishing messages.
513
542
  # topic.async_publisher.stop.wait!
514
543
  #
515
544
  # @example Additionally, a message can be published with attributes:
@@ -521,13 +550,76 @@ module Google
521
550
  # topic.publish_async "task completed",
522
551
  # foo: :bar, this: :that
523
552
  #
553
+ # # Shut down the publisher when ready to stop publishing messages.
524
554
  # topic.async_publisher.stop.wait!
525
555
  #
526
- def publish_async data = nil, attributes = {}, &block
556
+ # @example Ordered messages are supported using ordering_key:
557
+ # require "google/cloud/pubsub"
558
+ #
559
+ # pubsub = Google::Cloud::PubSub.new
560
+ #
561
+ # topic = pubsub.topic "my-ordered-topic"
562
+ #
563
+ # # Ensure that message ordering is enabled.
564
+ # topic.enable_message_ordering!
565
+ #
566
+ # # Publish an ordered message with an ordering key.
567
+ # topic.publish_async "task completed",
568
+ # ordering_key: "task-key"
569
+ #
570
+ # # Shut down the publisher when ready to stop publishing messages.
571
+ # topic.async_publisher.stop.wait!
572
+ #
573
+ def publish_async data = nil, attributes = nil, ordering_key: nil, **extra_attrs, &callback
527
574
  ensure_service!
528
575
 
529
576
  @async_publisher ||= AsyncPublisher.new name, service, @async_opts
530
- @async_publisher.publish data, attributes, &block
577
+ @async_publisher.publish data, attributes, ordering_key: ordering_key, **extra_attrs, &callback
578
+ end
579
+
580
+ ##
581
+ # Enables message ordering for messages with ordering keys on the
582
+ # {#async_publisher}. When enabled, messages published with the same
583
+ # `ordering_key` will be delivered in the order they were published.
584
+ #
585
+ # @note At the time of this release, ordering keys are not yet publicly
586
+ # enabled and requires special project enablements.
587
+ #
588
+ # See {#message_ordering?}. See {#publish_async},
589
+ # {Subscription#listen}, and {Message#ordering_key}.
590
+ #
591
+ def enable_message_ordering!
592
+ @async_publisher ||= AsyncPublisher.new name, service, @async_opts
593
+ @async_publisher.enable_message_ordering!
594
+ end
595
+
596
+ ##
597
+ # Whether message ordering for messages with ordering keys has been
598
+ # enabled on the {#async_publisher}. When enabled, messages published
599
+ # with the same `ordering_key` will be delivered in the order they were
600
+ # published. When disabled, messages may be delivered in any order.
601
+ #
602
+ # See {#enable_message_ordering!}. See {#publish_async},
603
+ # {Subscription#listen}, and {Message#ordering_key}.
604
+ #
605
+ # @return [Boolean]
606
+ #
607
+ def message_ordering?
608
+ @async_publisher ||= AsyncPublisher.new name, service, @async_opts
609
+ @async_publisher.message_ordering?
610
+ end
611
+
612
+ ##
613
+ # Resume publishing ordered messages for the provided ordering key.
614
+ #
615
+ # @param [String] ordering_key Identifies related messages for which
616
+ # publish order should be respected.
617
+ #
618
+ # @return [boolean] `true` when resumed, `false` otherwise.
619
+ #
620
+ def resume_publish ordering_key
621
+ @async_publisher ||= AsyncPublisher.new name, service, @async_opts
622
+ @async_publisher.resume_publish ordering_key
531
623
  end
532
624
 
533
625
  ##