google-cloud-pubsub 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  ##