google-cloud-pubsub 0.26.0 → 2.6.1
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 +5 -5
- data/.yardopts +12 -2
- data/AUTHENTICATION.md +178 -0
- data/CHANGELOG.md +659 -0
- data/CODE_OF_CONDUCT.md +40 -0
- data/CONTRIBUTING.md +187 -0
- data/EMULATOR.md +37 -0
- data/LICENSE +2 -2
- data/LOGGING.md +32 -0
- data/OVERVIEW.md +528 -0
- data/TROUBLESHOOTING.md +31 -0
- data/lib/google/cloud/pubsub/async_publisher/batch.rb +310 -0
- data/lib/google/cloud/pubsub/async_publisher.rb +402 -0
- data/lib/google/cloud/pubsub/batch_publisher.rb +100 -0
- data/lib/google/cloud/pubsub/convert.rb +91 -0
- data/lib/google/cloud/pubsub/credentials.rb +26 -10
- data/lib/google/cloud/pubsub/errors.rb +85 -0
- data/lib/google/cloud/pubsub/message.rb +80 -17
- data/lib/google/cloud/pubsub/policy.rb +17 -14
- data/lib/google/cloud/pubsub/project.rb +364 -250
- data/lib/google/cloud/pubsub/publish_result.rb +103 -0
- data/lib/google/cloud/pubsub/received_message.rb +162 -24
- data/lib/google/cloud/pubsub/retry_policy.rb +88 -0
- data/lib/google/cloud/pubsub/schema/list.rb +180 -0
- data/lib/google/cloud/pubsub/schema.rb +310 -0
- data/lib/google/cloud/pubsub/service.rb +281 -265
- data/lib/google/cloud/pubsub/snapshot/list.rb +21 -21
- data/lib/google/cloud/pubsub/snapshot.rb +55 -15
- data/lib/google/cloud/pubsub/subscriber/enumerator_queue.rb +54 -0
- data/lib/google/cloud/pubsub/subscriber/inventory.rb +173 -0
- data/lib/google/cloud/pubsub/subscriber/sequencer.rb +115 -0
- data/lib/google/cloud/pubsub/subscriber/stream.rb +400 -0
- data/lib/google/cloud/pubsub/subscriber/timed_unary_buffer.rb +230 -0
- data/lib/google/cloud/pubsub/subscriber.rb +417 -0
- data/lib/google/cloud/pubsub/subscription/list.rb +28 -28
- data/lib/google/cloud/pubsub/subscription/push_config.rb +268 -0
- data/lib/google/cloud/pubsub/subscription.rb +900 -172
- data/lib/google/cloud/pubsub/topic/list.rb +21 -21
- data/lib/google/cloud/pubsub/topic.rb +674 -95
- data/lib/google/cloud/pubsub/version.rb +6 -4
- data/lib/google/cloud/pubsub.rb +104 -439
- data/lib/google-cloud-pubsub.rb +60 -29
- metadata +88 -50
- data/README.md +0 -69
- data/lib/google/cloud/pubsub/topic/publisher.rb +0 -86
- data/lib/google/cloud/pubsub/v1/doc/google/protobuf/duration.rb +0 -77
- data/lib/google/cloud/pubsub/v1/doc/google/protobuf/field_mask.rb +0 -223
- data/lib/google/cloud/pubsub/v1/doc/google/protobuf/timestamp.rb +0 -81
- data/lib/google/cloud/pubsub/v1/doc/google/pubsub/v1/pubsub.rb +0 -503
- data/lib/google/cloud/pubsub/v1/publisher_client.rb +0 -605
- data/lib/google/cloud/pubsub/v1/publisher_client_config.json +0 -96
- data/lib/google/cloud/pubsub/v1/subscriber_client.rb +0 -1104
- data/lib/google/cloud/pubsub/v1/subscriber_client_config.json +0 -127
- data/lib/google/cloud/pubsub/v1.rb +0 -17
- data/lib/google/pubsub/v1/pubsub_pb.rb +0 -187
- data/lib/google/pubsub/v1/pubsub_services_pb.rb +0 -159
data/TROUBLESHOOTING.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Troubleshooting
|
2
|
+
|
3
|
+
## Where can I get more help?
|
4
|
+
|
5
|
+
### Ask the Community
|
6
|
+
|
7
|
+
If you have a question about how to use a Google Cloud client library in your
|
8
|
+
project or are stuck in the Developer's console and don't know where to turn,
|
9
|
+
it's possible your questions have already been addressed by the community.
|
10
|
+
|
11
|
+
First, check out the appropriate tags on StackOverflow:
|
12
|
+
- [`google-cloud-platform+ruby+pubsub`][so-ruby]
|
13
|
+
|
14
|
+
Next, try searching through the issues on GitHub:
|
15
|
+
|
16
|
+
- [`api:pubsub` issues][gh-search-ruby]
|
17
|
+
|
18
|
+
Still nothing?
|
19
|
+
|
20
|
+
### Ask the Developers
|
21
|
+
|
22
|
+
If you're experiencing a bug with the code, or have an idea for how it can be
|
23
|
+
improved, *please* create a new issue on GitHub so we can talk about it.
|
24
|
+
|
25
|
+
- [New issue][gh-ruby]
|
26
|
+
|
27
|
+
[so-ruby]: http://stackoverflow.com/questions/tagged/google-cloud-platform+ruby+pubsub
|
28
|
+
|
29
|
+
[gh-search-ruby]: https://github.com/googleapis/google-cloud-ruby/issues?q=label%3A%22api%3A+pubsub%22
|
30
|
+
|
31
|
+
[gh-ruby]: https://github.com/googleapis/google-cloud-ruby/issues/new
|
@@ -0,0 +1,310 @@
|
|
1
|
+
# Copyright 2019 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 "monitor"
|
17
|
+
require "google/cloud/pubsub/errors"
|
18
|
+
|
19
|
+
module Google
|
20
|
+
module Cloud
|
21
|
+
module PubSub
|
22
|
+
class AsyncPublisher
|
23
|
+
##
|
24
|
+
# @private
|
25
|
+
class Batch
|
26
|
+
include MonitorMixin
|
27
|
+
|
28
|
+
attr_reader :items
|
29
|
+
attr_reader :ordering_key
|
30
|
+
|
31
|
+
def initialize publisher, ordering_key
|
32
|
+
# init MonitorMixin
|
33
|
+
super()
|
34
|
+
|
35
|
+
@publisher = publisher
|
36
|
+
@ordering_key = ordering_key
|
37
|
+
@items = []
|
38
|
+
@queue = []
|
39
|
+
@default_message_bytes = publisher.topic_name.bytesize + 2
|
40
|
+
@total_message_bytes = @default_message_bytes
|
41
|
+
@publishing = false
|
42
|
+
@stopping = false
|
43
|
+
@canceled = false
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Adds a message and callback to the batch.
|
48
|
+
#
|
49
|
+
# The method will indicate how the message is added. It will either be
|
50
|
+
# added to the active list of items, it will be queued to be picked up
|
51
|
+
# once the active publishing job has been completed, or it will
|
52
|
+
# indicate that the batch is full and a publishing job should be
|
53
|
+
# created.
|
54
|
+
#
|
55
|
+
# @param [Google::Cloud::PubSub::V1::PubsubMessage] msg The message.
|
56
|
+
# @param [Proc, nil] callback The callback.
|
57
|
+
#
|
58
|
+
# @return [Symbol] The state of the batch.
|
59
|
+
#
|
60
|
+
# * `:added` - Added to the active list of items to be published.
|
61
|
+
# * `:queued` - Batch is publishing, and the messsage is queued.
|
62
|
+
# * `:full` - Batch is full and ready to be published, and the
|
63
|
+
# message is queued.
|
64
|
+
#
|
65
|
+
def add msg, callback
|
66
|
+
synchronize do
|
67
|
+
raise AsyncPublisherStopped if @stopping
|
68
|
+
raise OrderingKeyError, @ordering_key if @canceled
|
69
|
+
|
70
|
+
if @publishing
|
71
|
+
queue_add msg, callback
|
72
|
+
:queued
|
73
|
+
elsif try_add msg, callback
|
74
|
+
:added
|
75
|
+
else
|
76
|
+
queue_add msg, callback
|
77
|
+
:full
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Marks the batch to be published.
|
84
|
+
#
|
85
|
+
# The method will indicate whether a new publishing job should be
|
86
|
+
# started to publish the batch. See {publishing?}.
|
87
|
+
#
|
88
|
+
# @param [Boolean] stop Indicates whether the batch should also be
|
89
|
+
# marked for stopping, and any existing publish job should publish
|
90
|
+
# all items until the batch is empty.
|
91
|
+
#
|
92
|
+
# @return [Boolean] Returns whether a new publishing job should be
|
93
|
+
# started to publish the batch. If the batch is already being
|
94
|
+
# published then this will return `false`.
|
95
|
+
#
|
96
|
+
def publish! stop: nil
|
97
|
+
synchronize do
|
98
|
+
@stopping = true if stop
|
99
|
+
|
100
|
+
return false if @canceled
|
101
|
+
|
102
|
+
# If we are already publishing, do not indicate a new job needs to
|
103
|
+
# be started.
|
104
|
+
return false if @publishing
|
105
|
+
|
106
|
+
@publishing = !(@items.empty? && @queue.empty?)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Indicates whether the batch has an active publishing job.
|
112
|
+
#
|
113
|
+
# @return [Boolean]
|
114
|
+
#
|
115
|
+
def publishing?
|
116
|
+
# This probably does not need to be synchronized
|
117
|
+
@publishing
|
118
|
+
end
|
119
|
+
|
120
|
+
##
|
121
|
+
# Indicates whether the batch has been stopped and all items will be
|
122
|
+
# published until the batch is empty.
|
123
|
+
#
|
124
|
+
# @return [Boolean]
|
125
|
+
#
|
126
|
+
def stopping?
|
127
|
+
# This does not need to be synchronized because nothing un-stops
|
128
|
+
@stopping
|
129
|
+
end
|
130
|
+
|
131
|
+
##
|
132
|
+
# Rebalances the batch by moving any queued items that will fit into
|
133
|
+
# the active item list.
|
134
|
+
#
|
135
|
+
# This method is only intended to be used by the active publishing
|
136
|
+
# job.
|
137
|
+
#
|
138
|
+
def rebalance!
|
139
|
+
synchronize do
|
140
|
+
return [] if @canceled
|
141
|
+
|
142
|
+
until @queue.empty?
|
143
|
+
item = @queue.first
|
144
|
+
if try_add item.msg, item.callback
|
145
|
+
@queue.shift
|
146
|
+
next
|
147
|
+
end
|
148
|
+
break
|
149
|
+
end
|
150
|
+
|
151
|
+
@items
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# rubocop:disable Metrics/MethodLength
|
156
|
+
|
157
|
+
##
|
158
|
+
# Resets the batch after a successful publish. This clears the active
|
159
|
+
# item list and moves the queued items that will fit into the active
|
160
|
+
# item list.
|
161
|
+
#
|
162
|
+
# If the batch has enough queued items to fill the batch again, the
|
163
|
+
# publishing job should continue to publish the reset batch until the
|
164
|
+
# batch indicated it should stop.
|
165
|
+
#
|
166
|
+
# This method is only intended to be used by the active publishing
|
167
|
+
# job.
|
168
|
+
#
|
169
|
+
# @return [Boolean] Whether the active publishing job should continue
|
170
|
+
# publishing after the reset.
|
171
|
+
#
|
172
|
+
def reset!
|
173
|
+
synchronize do
|
174
|
+
@items = []
|
175
|
+
@total_message_bytes = @default_message_bytes
|
176
|
+
|
177
|
+
if @canceled
|
178
|
+
@queue = []
|
179
|
+
@publishing = false
|
180
|
+
return false
|
181
|
+
end
|
182
|
+
|
183
|
+
until @queue.empty?
|
184
|
+
item = @queue.first
|
185
|
+
added = try_add item.msg, item.callback
|
186
|
+
break unless added
|
187
|
+
@queue.shift
|
188
|
+
end
|
189
|
+
|
190
|
+
return false unless @publishing
|
191
|
+
if @items.empty?
|
192
|
+
@publishing = false
|
193
|
+
return false
|
194
|
+
else
|
195
|
+
return true if stopping?
|
196
|
+
if @queue.empty?
|
197
|
+
@publishing = false
|
198
|
+
return false
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
true
|
203
|
+
end
|
204
|
+
|
205
|
+
# rubocop:enable Metrics/MethodLength
|
206
|
+
|
207
|
+
##
|
208
|
+
# Cancel the batch and hault futher batches until resumed. See
|
209
|
+
# {#resume!} and {#canceled?}.
|
210
|
+
#
|
211
|
+
# @return [Array<Item}] All items, including queued items
|
212
|
+
#
|
213
|
+
def cancel!
|
214
|
+
synchronize do
|
215
|
+
@canceled = true
|
216
|
+
@items + @queue
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
##
|
221
|
+
# Resume the batch and proceed to publish messages. See {#cancel!} and
|
222
|
+
# {#canceled?}.
|
223
|
+
#
|
224
|
+
# @return [Boolean] Whether the batch was resumed.
|
225
|
+
#
|
226
|
+
def resume!
|
227
|
+
synchronize do
|
228
|
+
# Return false if the batch is not canceled
|
229
|
+
return false unless @canceled
|
230
|
+
|
231
|
+
@items = []
|
232
|
+
@queue = []
|
233
|
+
@total_message_bytes = @default_message_bytes
|
234
|
+
@publishing = false
|
235
|
+
@canceled = false
|
236
|
+
end
|
237
|
+
true
|
238
|
+
end
|
239
|
+
|
240
|
+
##
|
241
|
+
# Indicates whether the batch has been canceled due to an error while
|
242
|
+
# publishing. See {#cancel!} and {#resume!}.
|
243
|
+
#
|
244
|
+
# @return [Boolean]
|
245
|
+
#
|
246
|
+
def canceled?
|
247
|
+
# This does not need to be synchronized because nothing un-stops
|
248
|
+
synchronize { @canceled }
|
249
|
+
end
|
250
|
+
|
251
|
+
##
|
252
|
+
# Determines whether the batch is empty and ready to be culled.
|
253
|
+
#
|
254
|
+
def empty?
|
255
|
+
synchronize do
|
256
|
+
return false if @publishing || @canceled || @stopping
|
257
|
+
|
258
|
+
@items.empty? && @queue.empty?
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
protected
|
263
|
+
|
264
|
+
def items_add msg, callback
|
265
|
+
item = Item.new msg, callback
|
266
|
+
@items << item
|
267
|
+
@total_message_bytes += item.bytesize + 2
|
268
|
+
end
|
269
|
+
|
270
|
+
def try_add msg, callback
|
271
|
+
if @items.empty?
|
272
|
+
# Always add when empty, even if bytesize is bigger than total
|
273
|
+
items_add msg, callback
|
274
|
+
return true
|
275
|
+
end
|
276
|
+
new_message_count = total_message_count + 1
|
277
|
+
new_message_bytes = total_message_bytes + msg.to_proto.bytesize + 2
|
278
|
+
if new_message_count > @publisher.max_messages ||
|
279
|
+
new_message_bytes >= @publisher.max_bytes
|
280
|
+
return false
|
281
|
+
end
|
282
|
+
items_add msg, callback
|
283
|
+
true
|
284
|
+
end
|
285
|
+
|
286
|
+
def queue_add msg, callback
|
287
|
+
item = Item.new msg, callback
|
288
|
+
@queue << item
|
289
|
+
end
|
290
|
+
|
291
|
+
def total_message_count
|
292
|
+
@items.count
|
293
|
+
end
|
294
|
+
|
295
|
+
def total_message_bytes
|
296
|
+
@total_message_bytes
|
297
|
+
end
|
298
|
+
|
299
|
+
Item = Struct.new :msg, :callback do
|
300
|
+
def bytesize
|
301
|
+
msg.to_proto.bytesize
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
Pubsub = PubSub unless const_defined? :Pubsub
|
309
|
+
end
|
310
|
+
end
|
@@ -0,0 +1,402 @@
|
|
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 "monitor"
|
17
|
+
require "concurrent"
|
18
|
+
require "google/cloud/pubsub/errors"
|
19
|
+
require "google/cloud/pubsub/async_publisher/batch"
|
20
|
+
require "google/cloud/pubsub/publish_result"
|
21
|
+
require "google/cloud/pubsub/service"
|
22
|
+
require "google/cloud/pubsub/convert"
|
23
|
+
|
24
|
+
module Google
|
25
|
+
module Cloud
|
26
|
+
module PubSub
|
27
|
+
##
|
28
|
+
# Used to publish multiple messages in batches to a topic. See
|
29
|
+
# {Google::Cloud::PubSub::Topic#async_publisher}
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# require "google/cloud/pubsub"
|
33
|
+
#
|
34
|
+
# pubsub = Google::Cloud::PubSub.new
|
35
|
+
#
|
36
|
+
# topic = pubsub.topic "my-topic"
|
37
|
+
# topic.publish_async "task completed" do |result|
|
38
|
+
# if result.succeeded?
|
39
|
+
# log_publish_success result.data
|
40
|
+
# else
|
41
|
+
# log_publish_failure result.data, result.error
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# topic.async_publisher.stop!
|
46
|
+
#
|
47
|
+
# @attr_reader [String] topic_name The name of the topic the messages are published to. The value is a
|
48
|
+
# fully-qualified topic name in the form `projects/{project_id}/topics/{topic_id}`.
|
49
|
+
# @attr_reader [Integer] max_bytes The maximum size of messages to be collected before the batch is published.
|
50
|
+
# Default is 1,000,000 (1MB).
|
51
|
+
# @attr_reader [Integer] max_messages The maximum number of messages to be collected before the batch is
|
52
|
+
# published. Default is 100.
|
53
|
+
# @attr_reader [Numeric] interval The number of seconds to collect messages before the batch is published. Default
|
54
|
+
# is 0.01.
|
55
|
+
# @attr_reader [Numeric] publish_threads The number of threads used to publish messages. Default is 2.
|
56
|
+
# @attr_reader [Numeric] callback_threads The number of threads to handle the published messages' callbacks.
|
57
|
+
# Default is 4.
|
58
|
+
#
|
59
|
+
class AsyncPublisher
|
60
|
+
include MonitorMixin
|
61
|
+
|
62
|
+
attr_reader :topic_name
|
63
|
+
attr_reader :max_bytes
|
64
|
+
attr_reader :max_messages
|
65
|
+
attr_reader :interval
|
66
|
+
attr_reader :publish_threads
|
67
|
+
attr_reader :callback_threads
|
68
|
+
##
|
69
|
+
# @private Implementation accessors
|
70
|
+
attr_reader :service, :batch, :publish_thread_pool,
|
71
|
+
:callback_thread_pool
|
72
|
+
|
73
|
+
##
|
74
|
+
# @private Create a new instance of the object.
|
75
|
+
def initialize topic_name, service, max_bytes: 1_000_000, max_messages: 100, interval: 0.01, threads: {}
|
76
|
+
# init MonitorMixin
|
77
|
+
super()
|
78
|
+
@topic_name = service.topic_path topic_name
|
79
|
+
@service = service
|
80
|
+
|
81
|
+
@max_bytes = max_bytes
|
82
|
+
@max_messages = max_messages
|
83
|
+
@interval = interval
|
84
|
+
@publish_threads = (threads[:publish] || 2).to_i
|
85
|
+
@callback_threads = (threads[:callback] || 4).to_i
|
86
|
+
|
87
|
+
@published_at = nil
|
88
|
+
@publish_thread_pool = Concurrent::ThreadPoolExecutor.new max_threads: @publish_threads
|
89
|
+
@callback_thread_pool = Concurrent::ThreadPoolExecutor.new max_threads: @callback_threads
|
90
|
+
|
91
|
+
@ordered = false
|
92
|
+
@batches = {}
|
93
|
+
@cond = new_cond
|
94
|
+
|
95
|
+
@thread = Thread.new { run_background }
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Add a message to the async publisher to be published to the topic.
|
100
|
+
# Messages will be collected in batches and published together.
|
101
|
+
# See {Google::Cloud::PubSub::Topic#publish_async}
|
102
|
+
#
|
103
|
+
# @param [String, File] data The message payload. This will be converted
|
104
|
+
# to bytes encoded as ASCII-8BIT.
|
105
|
+
# @param [Hash] attributes Optional attributes for the message.
|
106
|
+
# @param [String] ordering_key Identifies related messages for which
|
107
|
+
# publish order should be respected.
|
108
|
+
# @yield [result] the callback for when the message has been published
|
109
|
+
# @yieldparam [PublishResult] result the result of the asynchronous
|
110
|
+
# publish
|
111
|
+
# @raise [Google::Cloud::PubSub::AsyncPublisherStopped] when the
|
112
|
+
# publisher is stopped. (See {#stop} and {#stopped?}.)
|
113
|
+
# @raise [Google::Cloud::PubSub::OrderedMessagesDisabled] when
|
114
|
+
# publishing a message with an `ordering_key` but ordered messages are
|
115
|
+
# not enabled. (See {#message_ordering?} and
|
116
|
+
# {#enable_message_ordering!}.)
|
117
|
+
# @raise [Google::Cloud::PubSub::OrderingKeyError] when publishing a
|
118
|
+
# message with an `ordering_key` that has already failed when
|
119
|
+
# publishing. Use {#resume_publish} to allow this `ordering_key` to be
|
120
|
+
# published again.
|
121
|
+
#
|
122
|
+
def publish data = nil, attributes = nil, ordering_key: nil, **extra_attrs, &callback
|
123
|
+
msg = Convert.pubsub_message data, attributes, ordering_key, extra_attrs
|
124
|
+
|
125
|
+
synchronize do
|
126
|
+
raise AsyncPublisherStopped if @stopped
|
127
|
+
raise OrderedMessagesDisabled if !@ordered && !msg.ordering_key.empty? # default is empty string
|
128
|
+
|
129
|
+
batch = resolve_batch_for_message msg
|
130
|
+
raise OrderingKeyError, batch.ordering_key if batch.canceled?
|
131
|
+
batch_action = batch.add msg, callback
|
132
|
+
if batch_action == :full
|
133
|
+
publish_batches!
|
134
|
+
elsif @published_at.nil?
|
135
|
+
# Set initial time to now to start the background counter
|
136
|
+
@published_at = Time.now
|
137
|
+
end
|
138
|
+
@cond.signal
|
139
|
+
end
|
140
|
+
|
141
|
+
nil
|
142
|
+
end
|
143
|
+
|
144
|
+
##
|
145
|
+
# Begins the process of stopping the publisher. Messages already in
|
146
|
+
# the queue will be published, but no new messages can be added. Use
|
147
|
+
# {#wait!} to block until the publisher is fully stopped and all
|
148
|
+
# pending messages have been published.
|
149
|
+
#
|
150
|
+
# @return [AsyncPublisher] returns self so calls can be chained.
|
151
|
+
def stop
|
152
|
+
synchronize do
|
153
|
+
break if @stopped
|
154
|
+
|
155
|
+
@stopped = true
|
156
|
+
publish_batches! stop: true
|
157
|
+
@cond.signal
|
158
|
+
@publish_thread_pool.shutdown
|
159
|
+
end
|
160
|
+
|
161
|
+
self
|
162
|
+
end
|
163
|
+
|
164
|
+
##
|
165
|
+
# Blocks until the publisher is fully stopped, all pending messages have
|
166
|
+
# been published, and all callbacks have completed, or until `timeout`
|
167
|
+
# seconds have passed.
|
168
|
+
#
|
169
|
+
# Does not stop the publisher. To stop the publisher, first call {#stop}
|
170
|
+
# and then call {#wait!} to block until the publisher is stopped
|
171
|
+
#
|
172
|
+
# @param [Number, nil] timeout The number of seconds to block until the
|
173
|
+
# publisher is fully stopped. Default will block indefinitely.
|
174
|
+
#
|
175
|
+
# @return [AsyncPublisher] returns self so calls can be chained.
|
176
|
+
def wait! timeout = nil
|
177
|
+
synchronize do
|
178
|
+
@publish_thread_pool.wait_for_termination timeout
|
179
|
+
|
180
|
+
@callback_thread_pool.shutdown
|
181
|
+
@callback_thread_pool.wait_for_termination timeout
|
182
|
+
end
|
183
|
+
|
184
|
+
self
|
185
|
+
end
|
186
|
+
|
187
|
+
##
|
188
|
+
# Stop this publisher and block until the publisher is fully stopped,
|
189
|
+
# all pending messages have been published, and all callbacks have
|
190
|
+
# completed, or until `timeout` seconds have passed.
|
191
|
+
#
|
192
|
+
# The same as calling {#stop} and {#wait!}.
|
193
|
+
#
|
194
|
+
# @param [Number, nil] timeout The number of seconds to block until the
|
195
|
+
# publisher is fully stopped. Default will block indefinitely.
|
196
|
+
#
|
197
|
+
# @return [AsyncPublisher] returns self so calls can be chained.
|
198
|
+
def stop! timeout = nil
|
199
|
+
stop
|
200
|
+
wait! timeout
|
201
|
+
end
|
202
|
+
|
203
|
+
##
|
204
|
+
# Forces all messages in the current batch to be published
|
205
|
+
# immediately.
|
206
|
+
#
|
207
|
+
# @return [AsyncPublisher] returns self so calls can be chained.
|
208
|
+
def flush
|
209
|
+
synchronize do
|
210
|
+
publish_batches!
|
211
|
+
@cond.signal
|
212
|
+
end
|
213
|
+
|
214
|
+
self
|
215
|
+
end
|
216
|
+
|
217
|
+
##
|
218
|
+
# Whether the publisher has been started.
|
219
|
+
#
|
220
|
+
# @return [boolean] `true` when started, `false` otherwise.
|
221
|
+
def started?
|
222
|
+
!stopped?
|
223
|
+
end
|
224
|
+
|
225
|
+
##
|
226
|
+
# Whether the publisher has been stopped.
|
227
|
+
#
|
228
|
+
# @return [boolean] `true` when stopped, `false` otherwise.
|
229
|
+
def stopped?
|
230
|
+
synchronize { @stopped }
|
231
|
+
end
|
232
|
+
|
233
|
+
##
|
234
|
+
# Enables message ordering for messages with ordering keys. When
|
235
|
+
# enabled, messages published with the same `ordering_key` will be
|
236
|
+
# delivered in the order they were published.
|
237
|
+
#
|
238
|
+
# See {#message_ordering?}. See {Topic#publish_async},
|
239
|
+
# {Subscription#listen}, and {Message#ordering_key}.
|
240
|
+
#
|
241
|
+
def enable_message_ordering!
|
242
|
+
synchronize { @ordered = true }
|
243
|
+
end
|
244
|
+
|
245
|
+
##
|
246
|
+
# Whether message ordering for messages with ordering keys has been
|
247
|
+
# enabled. When enabled, messages published with the same `ordering_key`
|
248
|
+
# will be delivered in the order they were published. When disabled,
|
249
|
+
# messages may be delivered in any order.
|
250
|
+
#
|
251
|
+
# See {#enable_message_ordering!}. See {Topic#publish_async},
|
252
|
+
# {Subscription#listen}, and {Message#ordering_key}.
|
253
|
+
#
|
254
|
+
# @return [Boolean]
|
255
|
+
#
|
256
|
+
def message_ordering?
|
257
|
+
synchronize { @ordered }
|
258
|
+
end
|
259
|
+
|
260
|
+
##
|
261
|
+
# Resume publishing ordered messages for the provided ordering key.
|
262
|
+
#
|
263
|
+
# @param [String] ordering_key Identifies related messages for which
|
264
|
+
# publish order should be respected.
|
265
|
+
#
|
266
|
+
# @return [boolean] `true` when resumed, `false` otherwise.
|
267
|
+
#
|
268
|
+
def resume_publish ordering_key
|
269
|
+
synchronize do
|
270
|
+
batch = resolve_batch_for_ordering_key ordering_key
|
271
|
+
return if batch.nil?
|
272
|
+
batch.resume!
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
protected
|
277
|
+
|
278
|
+
def run_background
|
279
|
+
synchronize do
|
280
|
+
until @stopped
|
281
|
+
if @published_at.nil?
|
282
|
+
@cond.wait
|
283
|
+
next
|
284
|
+
end
|
285
|
+
|
286
|
+
time_since_first_publish = Time.now - @published_at
|
287
|
+
if time_since_first_publish > @interval
|
288
|
+
# interval met, flush the batches...
|
289
|
+
publish_batches!
|
290
|
+
@cond.wait
|
291
|
+
else
|
292
|
+
# still waiting for the interval to publish the batch...
|
293
|
+
timeout = @interval - time_since_first_publish
|
294
|
+
@cond.wait timeout
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def resolve_batch_for_message msg
|
301
|
+
@batches[msg.ordering_key] ||= Batch.new self, msg.ordering_key
|
302
|
+
end
|
303
|
+
|
304
|
+
def resolve_batch_for_ordering_key ordering_key
|
305
|
+
@batches[ordering_key]
|
306
|
+
end
|
307
|
+
|
308
|
+
def publish_batches! stop: nil
|
309
|
+
@batches.reject! { |_ordering_key, batch| batch.empty? }
|
310
|
+
@batches.each_value do |batch|
|
311
|
+
ready = batch.publish! stop: stop
|
312
|
+
publish_batch_async @topic_name, batch if ready
|
313
|
+
end
|
314
|
+
# Set published_at to nil to wait indefinitely
|
315
|
+
@published_at = nil
|
316
|
+
end
|
317
|
+
|
318
|
+
def publish_batch_async topic_name, batch
|
319
|
+
# TODO: raise unless @publish_thread_pool.running?
|
320
|
+
return unless @publish_thread_pool.running?
|
321
|
+
|
322
|
+
Concurrent::Promises.future_on(
|
323
|
+
@publish_thread_pool, topic_name, batch
|
324
|
+
) { |t, b| publish_batch_sync t, b }
|
325
|
+
end
|
326
|
+
|
327
|
+
# rubocop:disable Metrics/AbcSize
|
328
|
+
# rubocop:disable Metrics/MethodLength
|
329
|
+
|
330
|
+
def publish_batch_sync topic_name, batch
|
331
|
+
# The only batch methods that are safe to call from the loop are
|
332
|
+
# rebalance! and reset! because they are the only methods that are
|
333
|
+
# synchronized.
|
334
|
+
loop do
|
335
|
+
items = batch.rebalance!
|
336
|
+
|
337
|
+
unless items.empty?
|
338
|
+
grpc = @service.publish topic_name, items.map(&:msg)
|
339
|
+
items.zip Array(grpc.message_ids) do |item, id|
|
340
|
+
next unless item.callback
|
341
|
+
|
342
|
+
item.msg.message_id = id
|
343
|
+
publish_result = PublishResult.from_grpc item.msg
|
344
|
+
execute_callback_async item.callback, publish_result
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
break unless batch.reset!
|
349
|
+
end
|
350
|
+
rescue StandardError => e
|
351
|
+
items = batch.items
|
352
|
+
|
353
|
+
unless batch.ordering_key.empty?
|
354
|
+
retry if publish_batch_error_retryable? e
|
355
|
+
# Cancel the batch if the error is not to be retried.
|
356
|
+
begin
|
357
|
+
raise OrderingKeyError, batch.ordering_key
|
358
|
+
rescue OrderingKeyError => e
|
359
|
+
# The existing e variable is not set to OrderingKeyError
|
360
|
+
# Get all unsent messages for the callback
|
361
|
+
items = batch.cancel!
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
items.each do |item|
|
366
|
+
next unless item.callback
|
367
|
+
|
368
|
+
publish_result = PublishResult.from_error item.msg, e
|
369
|
+
execute_callback_async item.callback, publish_result
|
370
|
+
end
|
371
|
+
|
372
|
+
# publish will retry indefinitely, as long as there are unsent items.
|
373
|
+
retry if batch.reset!
|
374
|
+
end
|
375
|
+
|
376
|
+
# rubocop:enable Metrics/AbcSize
|
377
|
+
# rubocop:enable Metrics/MethodLength
|
378
|
+
|
379
|
+
PUBLISH_RETRY_ERRORS = [
|
380
|
+
GRPC::Cancelled, GRPC::DeadlineExceeded, GRPC::Internal,
|
381
|
+
GRPC::ResourceExhausted, GRPC::Unauthenticated, GRPC::Unavailable
|
382
|
+
].freeze
|
383
|
+
|
384
|
+
def publish_batch_error_retryable? error
|
385
|
+
PUBLISH_RETRY_ERRORS.any? { |klass| error.is_a? klass }
|
386
|
+
end
|
387
|
+
|
388
|
+
def execute_callback_async callback, publish_result
|
389
|
+
return unless @callback_thread_pool.running?
|
390
|
+
|
391
|
+
Concurrent::Promises.future_on(
|
392
|
+
@callback_thread_pool, callback, publish_result
|
393
|
+
) do |cback, p_result|
|
394
|
+
cback.call p_result
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
Pubsub = PubSub unless const_defined? :Pubsub
|
401
|
+
end
|
402
|
+
end
|