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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/CONTRIBUTING.md +1 -1
- data/OVERVIEW.md +83 -1
- data/lib/google-cloud-pubsub.rb +6 -14
- data/lib/google/cloud/pubsub.rb +5 -12
- data/lib/google/cloud/pubsub/async_publisher.rb +164 -132
- data/lib/google/cloud/pubsub/async_publisher/batch.rb +309 -0
- data/lib/google/cloud/pubsub/batch_publisher.rb +2 -4
- data/lib/google/cloud/pubsub/convert.rb +33 -7
- data/lib/google/cloud/pubsub/errors.rb +85 -0
- data/lib/google/cloud/pubsub/message.rb +42 -0
- data/lib/google/cloud/pubsub/project.rb +2 -5
- data/lib/google/cloud/pubsub/publish_result.rb +6 -1
- data/lib/google/cloud/pubsub/received_message.rb +42 -0
- data/lib/google/cloud/pubsub/service.rb +11 -18
- data/lib/google/cloud/pubsub/snapshot/list.rb +2 -4
- data/lib/google/cloud/pubsub/subscriber.rb +9 -5
- data/lib/google/cloud/pubsub/subscriber/inventory.rb +14 -16
- data/lib/google/cloud/pubsub/subscriber/sequencer.rb +115 -0
- data/lib/google/cloud/pubsub/subscriber/stream.rb +50 -26
- data/lib/google/cloud/pubsub/subscriber/timed_unary_buffer.rb +10 -15
- data/lib/google/cloud/pubsub/subscription.rb +90 -29
- data/lib/google/cloud/pubsub/subscription/list.rb +2 -4
- data/lib/google/cloud/pubsub/topic.rb +109 -17
- data/lib/google/cloud/pubsub/topic/list.rb +2 -4
- data/lib/google/cloud/pubsub/version.rb +1 -1
- metadata +39 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 60202a1d96c472dc3bb05992db2ed19da1e34077ceeb5e0a066126a69d569f30
|
4
|
+
data.tar.gz: b671179cf63643ec45598fe63b29440504f2d24ce9f2d8495ec0e16e66838809
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 01ab09622f6773bfe6a0a5b96171eebb348c71dffd13b06d48eb1754d7cfde3464f81d51051f4a60d5d72c7e39b1b9e746b8417f0d9b86b50dd54280978f3ca4
|
7
|
+
data.tar.gz: c0eeb49f436cd8e385ce16b37f5a93445dd29417729624edd572e5a5bc2454c8b3e4504edba9cd73ffe227be64b4b68bfcadceb6e65983b12dfccb222a2879e2
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
# Release History
|
2
2
|
|
3
|
+
### 1.1.0 / 2019-10-23
|
4
|
+
|
5
|
+
#### Features
|
6
|
+
|
7
|
+
* Add support for Ordering Keys
|
8
|
+
* Google Cloud Pub/Sub ordering keys provide the ability to ensure related
|
9
|
+
messages are sent to subscribers in the order in which they were published.
|
10
|
+
The service guarantees that, for a given ordering key and publisher, messages
|
11
|
+
are sent to subscribers in the order in which they were published.
|
12
|
+
* Note: At the time of this release, ordering keys are not yet publicly enabled
|
13
|
+
and requires special project enablements.
|
14
|
+
* Add Google::Cloud::PubSub::Topic#enable_message_ordering! method.
|
15
|
+
* Add Google::Cloud::PubSub::Topic#message_ordering? method.
|
16
|
+
* Add ordering_key argument to Google::Cloud::PubSub::Topic#publish_async method.
|
17
|
+
* Add Google::Cloud::PubSub::Topic#resume_publish method.
|
18
|
+
* Add message_ordering argument to Google::Cloud::PubSub::Topic#subscribe method.
|
19
|
+
* Add Google::Cloud::PubSub::Subscription#message_ordering? method.
|
20
|
+
* Update Ruby dependency to minimum of 2.4.
|
21
|
+
|
3
22
|
### 1.0.2 / 2019-10-10
|
4
23
|
|
5
24
|
#### Bug Fixes
|
data/CONTRIBUTING.md
CHANGED
@@ -24,7 +24,7 @@ be able to accept your pull requests.
|
|
24
24
|
In order to use the google-cloud-pubsub console and run the project's tests,
|
25
25
|
there is a small amount of setup:
|
26
26
|
|
27
|
-
1. Install Ruby. google-cloud-pubsub requires Ruby 2.
|
27
|
+
1. Install Ruby. google-cloud-pubsub requires Ruby 2.4+. You may choose to
|
28
28
|
manage your Ruby and gem installations with [RVM](https://rvm.io/),
|
29
29
|
[rbenv](https://github.com/rbenv/rbenv), or
|
30
30
|
[chruby](https://github.com/postmodern/chruby).
|
data/OVERVIEW.md
CHANGED
@@ -161,7 +161,7 @@ msgs = topic.publish do |batch|
|
|
161
161
|
end
|
162
162
|
```
|
163
163
|
|
164
|
-
## Receiving
|
164
|
+
## Receiving Messages
|
165
165
|
|
166
166
|
Messages can be streamed from a subscription with a subscriber object that is
|
167
167
|
created using `listen`. (See {Google::Cloud::PubSub::Subscription#listen
|
@@ -316,6 +316,88 @@ received_messages = sub.pull
|
|
316
316
|
sub.modify_ack_deadline 120, received_messages
|
317
317
|
```
|
318
318
|
|
319
|
+
## Using Ordering Keys
|
320
|
+
|
321
|
+
Google Cloud Pub/Sub ordering keys provide the ability to ensure related
|
322
|
+
messages are sent to subscribers in the order in which they were published.
|
323
|
+
Messages can be tagged with an ordering key, a string that identifies related
|
324
|
+
messages for which publish order should be respected. The service guarantees
|
325
|
+
that, for a given ordering key and publisher, messages are sent to subscribers
|
326
|
+
in the order in which they were published. Ordering does not require sacrificing
|
327
|
+
high throughput or scalability, as the service automatically distributes
|
328
|
+
messages for different ordering keys across subscribers.
|
329
|
+
|
330
|
+
Note: At the time of this release, ordering keys are not yet publicly enabled
|
331
|
+
and requires special project enablements.
|
332
|
+
|
333
|
+
### Publishing Ordered Messages
|
334
|
+
|
335
|
+
To use ordering keys when publishing messages, a call to
|
336
|
+
{Google::Cloud::PubSub::Topic#enable_message_ordering!
|
337
|
+
Topic#enable_message_ordering!} must be made and the `ordering_key` argument
|
338
|
+
must be provided when calling {Google::Cloud::PubSub::Topic#publish_async
|
339
|
+
Topic#publish_async}.
|
340
|
+
|
341
|
+
```ruby
|
342
|
+
require "google/cloud/pubsub"
|
343
|
+
|
344
|
+
pubsub = Google::Cloud::PubSub.new
|
345
|
+
|
346
|
+
topic = pubsub.topic "my-ordered-topic"
|
347
|
+
|
348
|
+
# Ensure that message ordering is enabled.
|
349
|
+
topic.enable_message_ordering!
|
350
|
+
|
351
|
+
# Publish an ordered message with an ordering key.
|
352
|
+
topic.publish_async "task completed",
|
353
|
+
ordering_key: "task-key"
|
354
|
+
|
355
|
+
# Shut down the publisher when ready to stop publishing messages.
|
356
|
+
topic.async_publisher.stop.wait!
|
357
|
+
```
|
358
|
+
|
359
|
+
### Handling errors with Ordered Keys
|
360
|
+
|
361
|
+
Ordered messages that fail to publish to the Pub/Sub API due to error will put
|
362
|
+
the `ordering_key` in a failed state, and future calls to
|
363
|
+
{Google::Cloud::PubSub::Topic#publish_async Topic#publish_async} with the
|
364
|
+
`ordering_key` will raise {Google::Cloud::PubSub::OrderingKeyError
|
365
|
+
OrderingKeyError}. To allow future messages with the `ordering_key` to be
|
366
|
+
published, the `ordering_key` must be passed to
|
367
|
+
{Google::Cloud::PubSub::Topic#resume_publish Topic#resume_publish}.
|
368
|
+
|
369
|
+
### Receiving Ordered Messages
|
370
|
+
|
371
|
+
To use ordering keys when subscribing to messages, the subscription must be
|
372
|
+
created with message ordering enabled (See
|
373
|
+
{Google::Cloud::PubSub::Topic#subscribe Topic#subscribe} and
|
374
|
+
{Google::Cloud::PubSub::Subscription#message_ordering?
|
375
|
+
Subscription#message_ordering?}) before calling
|
376
|
+
{Google::Cloud::PubSub::Subscription#listen Subscription#listen}. When enabled,
|
377
|
+
the subscriber will deliver messages with the same `ordering_key` in the order
|
378
|
+
they were published.
|
379
|
+
|
380
|
+
```ruby
|
381
|
+
require "google/cloud/pubsub"
|
382
|
+
|
383
|
+
pubsub = Google::Cloud::PubSub.new
|
384
|
+
|
385
|
+
sub = pubsub.subscription "my-ordered-topic-sub"
|
386
|
+
sub.message_ordering? #=> true
|
387
|
+
|
388
|
+
subscriber = sub.listen do |received_message|
|
389
|
+
# Messsages with the same ordering_key are received
|
390
|
+
# in the order in which they were published.
|
391
|
+
received_message.acknowledge!
|
392
|
+
end
|
393
|
+
|
394
|
+
# Start background threads that will call block passed to listen.
|
395
|
+
subscriber.start
|
396
|
+
|
397
|
+
# Shut down the subscriber when ready to stop receiving messages.
|
398
|
+
subscriber.stop.wait!
|
399
|
+
```
|
400
|
+
|
319
401
|
## Minimizing API calls before receiving and acknowledging messages
|
320
402
|
|
321
403
|
A subscription object can be created without making any API calls by providing
|
data/lib/google-cloud-pubsub.rb
CHANGED
@@ -64,9 +64,7 @@ module Google
|
|
64
64
|
#
|
65
65
|
def pubsub scope: nil, timeout: nil, client_config: nil
|
66
66
|
timeout ||= @timeout
|
67
|
-
Google::Cloud.pubsub @project, @keyfile, scope:
|
68
|
-
timeout: timeout,
|
69
|
-
client_config: client_config
|
67
|
+
Google::Cloud.pubsub @project, @keyfile, scope: scope, timeout: timeout, client_config: client_config
|
70
68
|
end
|
71
69
|
|
72
70
|
##
|
@@ -108,10 +106,8 @@ module Google
|
|
108
106
|
def self.pubsub project_id = nil, credentials = nil, scope: nil,
|
109
107
|
timeout: nil, client_config: nil
|
110
108
|
require "google/cloud/pubsub"
|
111
|
-
Google::Cloud::PubSub.new project_id: project_id,
|
112
|
-
|
113
|
-
scope: scope, timeout: timeout,
|
114
|
-
client_config: client_config
|
109
|
+
Google::Cloud::PubSub.new project_id: project_id, credentials: credentials,
|
110
|
+
scope: scope, timeout: timeout, client_config: client_config
|
115
111
|
end
|
116
112
|
end
|
117
113
|
end
|
@@ -123,8 +119,7 @@ Google::Cloud.configure.add_config! :pubsub do |config|
|
|
123
119
|
end
|
124
120
|
default_creds = Google::Cloud::Config.deferred do
|
125
121
|
Google::Cloud::Config.credentials_from_env(
|
126
|
-
"PUBSUB_CREDENTIALS", "PUBSUB_CREDENTIALS_JSON",
|
127
|
-
"PUBSUB_KEYFILE", "PUBSUB_KEYFILE_JSON"
|
122
|
+
"PUBSUB_CREDENTIALS", "PUBSUB_CREDENTIALS_JSON", "PUBSUB_KEYFILE", "PUBSUB_KEYFILE_JSON"
|
128
123
|
)
|
129
124
|
end
|
130
125
|
default_emulator = Google::Cloud::Config.deferred do
|
@@ -133,15 +128,12 @@ Google::Cloud.configure.add_config! :pubsub do |config|
|
|
133
128
|
|
134
129
|
config.add_field! :project_id, default_project, match: String, allow_nil: true
|
135
130
|
config.add_alias! :project, :project_id
|
136
|
-
config.add_field! :credentials, default_creds,
|
137
|
-
match: [String, Hash, Google::Auth::Credentials],
|
138
|
-
allow_nil: true
|
131
|
+
config.add_field! :credentials, default_creds, match: [String, Hash, Google::Auth::Credentials], allow_nil: true
|
139
132
|
config.add_alias! :keyfile, :credentials
|
140
133
|
config.add_field! :scope, nil, match: [String, Array]
|
141
134
|
config.add_field! :timeout, nil, match: Integer
|
142
135
|
config.add_field! :client_config, nil, match: Hash
|
143
|
-
config.add_field! :emulator_host, default_emulator,
|
144
|
-
match: String, allow_nil: true
|
136
|
+
config.add_field! :emulator_host, default_emulator, match: String, allow_nil: true
|
145
137
|
config.add_field! :on_error, nil, match: Proc
|
146
138
|
config.add_field! :endpoint, nil, match: String
|
147
139
|
end
|
data/lib/google/cloud/pubsub.rb
CHANGED
@@ -77,9 +77,8 @@ module Google
|
|
77
77
|
# topic = pubsub.topic "my-topic"
|
78
78
|
# topic.publish "task completed"
|
79
79
|
#
|
80
|
-
def self.new project_id: nil, credentials: nil, scope: nil, timeout: nil,
|
81
|
-
|
82
|
-
project: nil, keyfile: nil
|
80
|
+
def self.new project_id: nil, credentials: nil, scope: nil, timeout: nil, client_config: nil, endpoint: nil,
|
81
|
+
emulator_host: nil, project: nil, keyfile: nil
|
83
82
|
project_id ||= (project || default_project_id)
|
84
83
|
scope ||= configure.scope
|
85
84
|
timeout ||= configure.timeout
|
@@ -93,9 +92,7 @@ module Google
|
|
93
92
|
|
94
93
|
return PubSub::Project.new(
|
95
94
|
PubSub::Service.new(
|
96
|
-
project_id, :this_channel_is_insecure,
|
97
|
-
host: emulator_host, timeout: timeout,
|
98
|
-
client_config: client_config
|
95
|
+
project_id, :this_channel_is_insecure, host: emulator_host, timeout: timeout, client_config: client_config
|
99
96
|
)
|
100
97
|
)
|
101
98
|
end
|
@@ -105,17 +102,13 @@ module Google
|
|
105
102
|
credentials = PubSub::Credentials.new credentials, scope: scope
|
106
103
|
end
|
107
104
|
|
108
|
-
if credentials.respond_to? :project_id
|
109
|
-
project_id ||= credentials.project_id
|
110
|
-
end
|
105
|
+
project_id ||= credentials.project_id if credentials.respond_to? :project_id
|
111
106
|
project_id = project_id.to_s # Always cast to a string
|
112
107
|
raise ArgumentError, "project_id is missing" if project_id.empty?
|
113
108
|
|
114
109
|
PubSub::Project.new(
|
115
110
|
PubSub::Service.new(
|
116
|
-
project_id, credentials, timeout:
|
117
|
-
host: endpoint,
|
118
|
-
client_config: client_config
|
111
|
+
project_id, credentials, timeout: timeout, host: endpoint, client_config: client_config
|
119
112
|
)
|
120
113
|
)
|
121
114
|
end
|
@@ -15,8 +15,11 @@
|
|
15
15
|
|
16
16
|
require "monitor"
|
17
17
|
require "concurrent"
|
18
|
+
require "google/cloud/pubsub/errors"
|
19
|
+
require "google/cloud/pubsub/async_publisher/batch"
|
18
20
|
require "google/cloud/pubsub/publish_result"
|
19
21
|
require "google/cloud/pubsub/service"
|
22
|
+
require "google/cloud/pubsub/convert"
|
20
23
|
|
21
24
|
module Google
|
22
25
|
module Cloud
|
@@ -68,8 +71,7 @@ module Google
|
|
68
71
|
|
69
72
|
##
|
70
73
|
# @private Create a new instance of the object.
|
71
|
-
def initialize topic_name, service, max_bytes:
|
72
|
-
max_messages: 1000, interval: 0.25, threads: {}
|
74
|
+
def initialize topic_name, service, max_bytes: 10_000_000, max_messages: 1000, interval: 0.25, threads: {}
|
73
75
|
@topic_name = service.topic_path topic_name
|
74
76
|
@service = service
|
75
77
|
|
@@ -79,6 +81,14 @@ module Google
|
|
79
81
|
@publish_threads = (threads[:publish] || 4).to_i
|
80
82
|
@callback_threads = (threads[:callback] || 8).to_i
|
81
83
|
|
84
|
+
@published_at = nil
|
85
|
+
@publish_thread_pool = Concurrent::ThreadPoolExecutor.new max_threads: @publish_threads
|
86
|
+
@callback_thread_pool = Concurrent::ThreadPoolExecutor.new max_threads: @callback_threads
|
87
|
+
@thread = Thread.new { run_background }
|
88
|
+
|
89
|
+
@ordered = false
|
90
|
+
@batches = {}
|
91
|
+
|
82
92
|
@cond = new_cond
|
83
93
|
|
84
94
|
# init MonitorMixin
|
@@ -89,29 +99,45 @@ module Google
|
|
89
99
|
# Add a message to the async publisher to be published to the topic.
|
90
100
|
# Messages will be collected in batches and published together.
|
91
101
|
# See {Google::Cloud::PubSub::Topic#publish_async}
|
92
|
-
|
93
|
-
|
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
|
94
124
|
|
95
125
|
synchronize do
|
96
|
-
raise
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
107
137
|
end
|
108
|
-
|
109
|
-
init_resources!
|
110
|
-
|
111
|
-
publish_batch! if @batch.ready?
|
112
|
-
|
113
138
|
@cond.signal
|
114
139
|
end
|
140
|
+
|
115
141
|
nil
|
116
142
|
end
|
117
143
|
|
@@ -127,9 +153,9 @@ module Google
|
|
127
153
|
break if @stopped
|
128
154
|
|
129
155
|
@stopped = true
|
130
|
-
|
156
|
+
publish_batches! stop: true
|
131
157
|
@cond.signal
|
132
|
-
@publish_thread_pool.shutdown
|
158
|
+
@publish_thread_pool.shutdown
|
133
159
|
end
|
134
160
|
|
135
161
|
self
|
@@ -149,14 +175,10 @@ module Google
|
|
149
175
|
# @return [AsyncPublisher] returns self so calls can be chained.
|
150
176
|
def wait! timeout = nil
|
151
177
|
synchronize do
|
152
|
-
|
153
|
-
@publish_thread_pool.wait_for_termination timeout
|
154
|
-
end
|
178
|
+
@publish_thread_pool.wait_for_termination timeout
|
155
179
|
|
156
|
-
|
157
|
-
|
158
|
-
@callback_thread_pool.wait_for_termination timeout
|
159
|
-
end
|
180
|
+
@callback_thread_pool.shutdown
|
181
|
+
@callback_thread_pool.wait_for_termination timeout
|
160
182
|
end
|
161
183
|
|
162
184
|
self
|
@@ -185,7 +207,7 @@ module Google
|
|
185
207
|
# @return [AsyncPublisher] returns self so calls can be chained.
|
186
208
|
def flush
|
187
209
|
synchronize do
|
188
|
-
|
210
|
+
publish_batches!
|
189
211
|
@cond.signal
|
190
212
|
end
|
191
213
|
|
@@ -208,33 +230,63 @@ module Google
|
|
208
230
|
synchronize { @stopped }
|
209
231
|
end
|
210
232
|
|
211
|
-
|
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
|
212
244
|
|
213
|
-
|
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
|
214
259
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
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
|
222
274
|
end
|
223
275
|
|
224
|
-
|
276
|
+
protected
|
225
277
|
|
226
278
|
def run_background
|
227
279
|
synchronize do
|
228
280
|
until @stopped
|
229
|
-
if @
|
281
|
+
if @published_at.nil?
|
230
282
|
@cond.wait
|
231
283
|
next
|
232
284
|
end
|
233
285
|
|
234
|
-
time_since_first_publish = Time.now - @
|
286
|
+
time_since_first_publish = Time.now - @published_at
|
235
287
|
if time_since_first_publish > @interval
|
236
|
-
# interval met,
|
237
|
-
|
288
|
+
# interval met, flush the batches...
|
289
|
+
publish_batches!
|
238
290
|
@cond.wait
|
239
291
|
else
|
240
292
|
# still waiting for the interval to publish the batch...
|
@@ -245,40 +297,93 @@ module Google
|
|
245
297
|
end
|
246
298
|
end
|
247
299
|
|
248
|
-
def
|
249
|
-
|
300
|
+
def resolve_batch_for_message msg
|
301
|
+
@batches[msg.ordering_key] ||= Batch.new self, msg.ordering_key
|
302
|
+
end
|
250
303
|
|
251
|
-
|
252
|
-
@
|
253
|
-
|
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.values.each 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
|
254
316
|
end
|
255
317
|
|
256
318
|
def publish_batch_async topic_name, batch
|
319
|
+
# TODO: raise unless @publish_thread_pool.running?
|
257
320
|
return unless @publish_thread_pool.running?
|
258
321
|
|
259
322
|
Concurrent::Promises.future_on(
|
260
323
|
@publish_thread_pool, topic_name, batch
|
261
|
-
)
|
262
|
-
publish_batch_sync t_name, btch
|
263
|
-
end
|
324
|
+
) { |t, b| publish_batch_sync t, b }
|
264
325
|
end
|
265
326
|
|
327
|
+
# rubocop:disable Metrics/AbcSize
|
328
|
+
# rubocop:disable Metrics/MethodLength
|
329
|
+
|
266
330
|
def publish_batch_sync topic_name, batch
|
267
|
-
|
268
|
-
|
269
|
-
|
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
|
270
347
|
|
271
|
-
|
272
|
-
publish_result = PublishResult.from_grpc item.msg
|
273
|
-
execute_callback_async item.callback, publish_result
|
348
|
+
break unless batch.reset!
|
274
349
|
end
|
275
350
|
rescue StandardError => e
|
276
|
-
batch.items
|
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|
|
277
366
|
next unless item.callback
|
278
367
|
|
279
368
|
publish_result = PublishResult.from_error item.msg, e
|
280
369
|
execute_callback_async item.callback, publish_result
|
281
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
|
+
GRPC::Core::CallError
|
383
|
+
].freeze
|
384
|
+
|
385
|
+
def publish_batch_error_retryable? error
|
386
|
+
PUBLISH_RETRY_ERRORS.any? { |klass| error.is_a? klass }
|
282
387
|
end
|
283
388
|
|
284
389
|
def execute_callback_async callback, publish_result
|
@@ -290,79 +395,6 @@ module Google
|
|
290
395
|
cback.call p_result
|
291
396
|
end
|
292
397
|
end
|
293
|
-
|
294
|
-
def create_pubsub_message data, attributes
|
295
|
-
attributes ||= {}
|
296
|
-
if data.is_a?(::Hash) && attributes.empty?
|
297
|
-
attributes = data
|
298
|
-
data = nil
|
299
|
-
end
|
300
|
-
# Convert IO-ish objects to strings
|
301
|
-
if data.respond_to?(:read) && data.respond_to?(:rewind)
|
302
|
-
data.rewind
|
303
|
-
data = data.read
|
304
|
-
end
|
305
|
-
# Convert data to encoded byte array to match the protobuf defn
|
306
|
-
data_bytes = \
|
307
|
-
String(data).dup.force_encoding(Encoding::ASCII_8BIT).freeze
|
308
|
-
|
309
|
-
# Convert attributes to strings to match the protobuf definition
|
310
|
-
attributes = Hash[attributes.map { |k, v| [String(k), String(v)] }]
|
311
|
-
|
312
|
-
Google::Cloud::PubSub::V1::PubsubMessage.new data: data_bytes,
|
313
|
-
attributes: attributes
|
314
|
-
end
|
315
|
-
|
316
|
-
##
|
317
|
-
# @private
|
318
|
-
class Batch
|
319
|
-
attr_reader :messages, :callbacks
|
320
|
-
|
321
|
-
def initialize publisher
|
322
|
-
@publisher = publisher
|
323
|
-
@messages = []
|
324
|
-
@callbacks = []
|
325
|
-
@total_message_bytes = publisher.topic_name.bytesize + 2
|
326
|
-
end
|
327
|
-
|
328
|
-
def add msg, callback
|
329
|
-
@messages << msg
|
330
|
-
@callbacks << callback
|
331
|
-
@total_message_bytes += msg.to_proto.bytesize + 2
|
332
|
-
end
|
333
|
-
|
334
|
-
def try_add msg, callback
|
335
|
-
new_message_count = total_message_count + 1
|
336
|
-
new_message_bytes = total_message_bytes + msg.to_proto.bytesize + 2
|
337
|
-
if new_message_count > @publisher.max_messages ||
|
338
|
-
new_message_bytes >= @publisher.max_bytes
|
339
|
-
return false
|
340
|
-
end
|
341
|
-
add msg, callback
|
342
|
-
true
|
343
|
-
end
|
344
|
-
|
345
|
-
def ready?
|
346
|
-
total_message_count >= @publisher.max_messages ||
|
347
|
-
total_message_bytes >= @publisher.max_bytes
|
348
|
-
end
|
349
|
-
|
350
|
-
def total_message_count
|
351
|
-
@messages.count
|
352
|
-
end
|
353
|
-
|
354
|
-
def total_message_bytes
|
355
|
-
@total_message_bytes
|
356
|
-
end
|
357
|
-
|
358
|
-
def items
|
359
|
-
@messages.zip(@callbacks).map do |msg, callback|
|
360
|
-
Item.new msg, callback
|
361
|
-
end
|
362
|
-
end
|
363
|
-
|
364
|
-
Item = Struct.new :msg, :callback
|
365
|
-
end
|
366
398
|
end
|
367
399
|
end
|
368
400
|
|