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
@@ -100,6 +100,48 @@ module Google
|
|
100
100
|
end
|
101
101
|
alias publish_time published_at
|
102
102
|
|
103
|
+
##
|
104
|
+
# Identifies related messages for which publish order should be
|
105
|
+
# respected.
|
106
|
+
#
|
107
|
+
# Google Cloud Pub/Sub ordering keys provide the ability to ensure
|
108
|
+
# related messages are sent to subscribers in the order in which they
|
109
|
+
# were published. Messages can be tagged with an ordering key, a string
|
110
|
+
# that identifies related messages for which publish order should be
|
111
|
+
# respected. The service guarantees that, for a given ordering key and
|
112
|
+
# publisher, messages are sent to subscribers in the order in which they
|
113
|
+
# were published. Ordering does not require sacrificing high throughput
|
114
|
+
# or scalability, as the service automatically distributes messages for
|
115
|
+
# different ordering keys across subscribers.
|
116
|
+
#
|
117
|
+
# See {Topic#publish_async} and {Subscription#listen}.
|
118
|
+
#
|
119
|
+
# @return [String]
|
120
|
+
#
|
121
|
+
def ordering_key
|
122
|
+
@grpc.ordering_key
|
123
|
+
end
|
124
|
+
|
125
|
+
# @private
|
126
|
+
def hash
|
127
|
+
@grpc.hash
|
128
|
+
end
|
129
|
+
|
130
|
+
# @private
|
131
|
+
def eql? other
|
132
|
+
return false unless other.is_a? self.class
|
133
|
+
@grpc.hash == other.hash
|
134
|
+
end
|
135
|
+
# @private
|
136
|
+
alias == eql?
|
137
|
+
|
138
|
+
# @private
|
139
|
+
def <=> other
|
140
|
+
return nil unless other.is_a? self.class
|
141
|
+
other_grpc = other.instance_variable_get :@grpc
|
142
|
+
@grpc <=> other_grpc
|
143
|
+
end
|
144
|
+
|
103
145
|
##
|
104
146
|
# @private New Message from a Google::Cloud::PubSub::V1::PubsubMessage
|
105
147
|
# object.
|
@@ -205,8 +205,7 @@ module Google
|
|
205
205
|
# pubsub = Google::Cloud::PubSub.new
|
206
206
|
# topic = pubsub.create_topic "my-topic"
|
207
207
|
#
|
208
|
-
def create_topic topic_name, labels: nil, kms_key: nil,
|
209
|
-
persistence_regions: nil, async: nil
|
208
|
+
def create_topic topic_name, labels: nil, kms_key: nil, persistence_regions: nil, async: nil
|
210
209
|
ensure_service!
|
211
210
|
grpc = service.create_topic topic_name,
|
212
211
|
labels: labels,
|
@@ -291,9 +290,7 @@ module Google
|
|
291
290
|
def subscription subscription_name, project: nil, skip_lookup: nil
|
292
291
|
ensure_service!
|
293
292
|
options = { project: project }
|
294
|
-
if skip_lookup
|
295
|
-
return Subscription.from_name subscription_name, service, options
|
296
|
-
end
|
293
|
+
return Subscription.from_name subscription_name, service, options if skip_lookup
|
297
294
|
grpc = service.get_subscription subscription_name
|
298
295
|
Subscription.from_grpc grpc, service
|
299
296
|
rescue Google::Cloud::NotFoundError
|
@@ -17,7 +17,12 @@ module Google
|
|
17
17
|
module Cloud
|
18
18
|
module PubSub
|
19
19
|
##
|
20
|
-
#
|
20
|
+
# The result of a publish operation. The message object is available on
|
21
|
+
# {#message} and will have {#message_id} assigned by the API.
|
22
|
+
#
|
23
|
+
# When the publish operation was successful the result will be marked
|
24
|
+
# {#succeeded?}. Otherwise, the result will be marked {#failed?} and the
|
25
|
+
# error raised will be availabe on {#error}.
|
21
26
|
#
|
22
27
|
class PublishResult
|
23
28
|
##
|
@@ -91,6 +91,28 @@ module Google
|
|
91
91
|
end
|
92
92
|
alias msg_id message_id
|
93
93
|
|
94
|
+
##
|
95
|
+
# Identifies related messages for which publish order should be
|
96
|
+
# respected.
|
97
|
+
#
|
98
|
+
# Google Cloud Pub/Sub ordering keys provide the ability to ensure
|
99
|
+
# related messages are sent to subscribers in the order in which they
|
100
|
+
# were published. Messages can be tagged with an ordering key, a string
|
101
|
+
# that identifies related messages for which publish order should be
|
102
|
+
# respected. The service guarantees that, for a given ordering key and
|
103
|
+
# publisher, messages are sent to subscribers in the order in which they
|
104
|
+
# were published. Ordering does not require sacrificing high throughput
|
105
|
+
# or scalability, as the service automatically distributes messages for
|
106
|
+
# different ordering keys across subscribers.
|
107
|
+
#
|
108
|
+
# See {Topic#publish_async} and {Subscription#listen}.
|
109
|
+
#
|
110
|
+
# @return [String]
|
111
|
+
#
|
112
|
+
def ordering_key
|
113
|
+
message.ordering_key
|
114
|
+
end
|
115
|
+
|
94
116
|
##
|
95
117
|
# The time at which the message was published.
|
96
118
|
def published_at
|
@@ -192,6 +214,26 @@ module Google
|
|
192
214
|
alias nack! reject!
|
193
215
|
alias ignore! reject!
|
194
216
|
|
217
|
+
# @private
|
218
|
+
def hash
|
219
|
+
@grpc.hash
|
220
|
+
end
|
221
|
+
|
222
|
+
# @private
|
223
|
+
def eql? other
|
224
|
+
return false unless other.is_a? self.class
|
225
|
+
@grpc.hash == other.hash
|
226
|
+
end
|
227
|
+
# @private
|
228
|
+
alias == eql?
|
229
|
+
|
230
|
+
# @private
|
231
|
+
def <=> other
|
232
|
+
return nil unless other.is_a? self.class
|
233
|
+
other_grpc = other.instance_variable_get :@grpc
|
234
|
+
@grpc <=> other_grpc
|
235
|
+
end
|
236
|
+
|
195
237
|
##
|
196
238
|
# @private New ReceivedMessage from a
|
197
239
|
# Google::Cloud::PubSub::V1::ReceivedMessage object.
|
@@ -48,15 +48,14 @@ module Google
|
|
48
48
|
def chan_args
|
49
49
|
{ "grpc.max_send_message_length" => -1,
|
50
50
|
"grpc.max_receive_message_length" => -1,
|
51
|
-
"grpc.keepalive_time_ms" =>
|
51
|
+
"grpc.keepalive_time_ms" => 300_000,
|
52
52
|
"grpc.service_config_disable_resolution" => 1 }
|
53
53
|
end
|
54
54
|
|
55
55
|
def chan_creds
|
56
56
|
return credentials if insecure?
|
57
57
|
require "grpc"
|
58
|
-
GRPC::Core::ChannelCredentials.new.compose
|
59
|
-
GRPC::Core::CallCredentials.new credentials.client.updater_proc
|
58
|
+
GRPC::Core::ChannelCredentials.new.compose GRPC::Core::CallCredentials.new credentials.client.updater_proc
|
60
59
|
end
|
61
60
|
|
62
61
|
def subscriber
|
@@ -126,8 +125,7 @@ module Google
|
|
126
125
|
|
127
126
|
##
|
128
127
|
# Creates the given topic with the given name.
|
129
|
-
def create_topic topic_name, labels: nil, kms_key_name: nil,
|
130
|
-
persistence_regions: nil, options: {}
|
128
|
+
def create_topic topic_name, labels: nil, kms_key_name: nil, persistence_regions: nil, options: {}
|
131
129
|
if persistence_regions
|
132
130
|
message_storage_policy = {
|
133
131
|
allowed_persistence_regions: Array(persistence_regions)
|
@@ -147,8 +145,7 @@ module Google
|
|
147
145
|
def update_topic topic_obj, *fields
|
148
146
|
mask = Google::Protobuf::FieldMask.new paths: fields.map(&:to_s)
|
149
147
|
execute do
|
150
|
-
publisher.update_topic
|
151
|
-
topic_obj, mask, options: default_options
|
148
|
+
publisher.update_topic topic_obj, mask, options: default_options
|
152
149
|
end
|
153
150
|
end
|
154
151
|
|
@@ -240,6 +237,7 @@ module Google
|
|
240
237
|
retain_acked = options[:retain_acked]
|
241
238
|
mrd = Convert.number_to_duration options[:retention]
|
242
239
|
labels = options[:labels]
|
240
|
+
message_ordering = options[:message_ordering]
|
243
241
|
|
244
242
|
execute do
|
245
243
|
subscriber.create_subscription \
|
@@ -249,6 +247,7 @@ module Google
|
|
249
247
|
retain_acked_messages: retain_acked,
|
250
248
|
message_retention_duration: mrd,
|
251
249
|
labels: labels,
|
250
|
+
enable_message_ordering: message_ordering,
|
252
251
|
options: default_options
|
253
252
|
end
|
254
253
|
end
|
@@ -256,8 +255,7 @@ module Google
|
|
256
255
|
def update_subscription subscription_obj, *fields
|
257
256
|
mask = Google::Protobuf::FieldMask.new paths: fields.map(&:to_s)
|
258
257
|
execute do
|
259
|
-
subscriber.update_subscription
|
260
|
-
subscription_obj, mask, options: default_options
|
258
|
+
subscriber.update_subscription subscription_obj, mask, options: default_options
|
261
259
|
end
|
262
260
|
end
|
263
261
|
|
@@ -363,8 +361,7 @@ module Google
|
|
363
361
|
def update_snapshot snapshot_obj, *fields
|
364
362
|
mask = Google::Protobuf::FieldMask.new paths: fields.map(&:to_s)
|
365
363
|
execute do
|
366
|
-
subscriber.update_snapshot
|
367
|
-
snapshot_obj, mask, options: default_options
|
364
|
+
subscriber.update_snapshot snapshot_obj, mask, options: default_options
|
368
365
|
end
|
369
366
|
end
|
370
367
|
|
@@ -387,9 +384,7 @@ module Google
|
|
387
384
|
time = Convert.time_to_timestamp time_or_snapshot
|
388
385
|
subscriber.seek subscription, time: time, options: default_options
|
389
386
|
else
|
390
|
-
if time_or_snapshot.is_a? Snapshot
|
391
|
-
time_or_snapshot = time_or_snapshot.name
|
392
|
-
end
|
387
|
+
time_or_snapshot = time_or_snapshot.name if time_or_snapshot.is_a? Snapshot
|
393
388
|
subscriber.seek subscription,
|
394
389
|
snapshot: snapshot_path(time_or_snapshot),
|
395
390
|
options: default_options
|
@@ -465,14 +460,12 @@ module Google
|
|
465
460
|
end
|
466
461
|
|
467
462
|
def snapshot_path snapshot_name, options = {}
|
468
|
-
if snapshot_name.nil? || snapshot_name.to_s.include?("/")
|
469
|
-
return snapshot_name
|
470
|
-
end
|
463
|
+
return snapshot_name if snapshot_name.nil? || snapshot_name.to_s.include?("/")
|
471
464
|
"#{project_path options}/snapshots/#{snapshot_name}"
|
472
465
|
end
|
473
466
|
|
474
467
|
def inspect
|
475
|
-
"#<#{self.class.name} #{@project}>"
|
468
|
+
"#<#{self.class.name} (#{@project})>"
|
476
469
|
end
|
477
470
|
|
478
471
|
protected
|
@@ -127,15 +127,13 @@ module Google
|
|
127
127
|
#
|
128
128
|
def all request_limit: nil
|
129
129
|
request_limit = request_limit.to_i if request_limit
|
130
|
-
unless block_given?
|
131
|
-
return enum_for :all, request_limit: request_limit
|
132
|
-
end
|
130
|
+
return enum_for :all, request_limit: request_limit unless block_given?
|
133
131
|
results = self
|
134
132
|
loop do
|
135
133
|
results.each { |r| yield r }
|
136
134
|
if request_limit
|
137
135
|
request_limit -= 1
|
138
|
-
break if request_limit
|
136
|
+
break if request_limit.negative?
|
139
137
|
end
|
140
138
|
break unless results.next?
|
141
139
|
results = results.next
|
@@ -50,6 +50,8 @@ module Google
|
|
50
50
|
# @attr_reader [Numeric] deadline The default number of seconds the stream
|
51
51
|
# will hold received messages before modifying the message's ack
|
52
52
|
# deadline. The minimum is 10, the maximum is 600. Default is 60.
|
53
|
+
# @attr_reader [Boolean] message_ordering Whether message ordering has
|
54
|
+
# been enabled.
|
53
55
|
# @attr_reader [Integer] streams The number of concurrent streams to open
|
54
56
|
# to pull messages from the subscription. Default is 4.
|
55
57
|
# @attr_reader [Integer] inventory The number of received messages to be
|
@@ -65,7 +67,8 @@ module Google
|
|
65
67
|
include MonitorMixin
|
66
68
|
|
67
69
|
attr_reader :subscription_name, :callback, :deadline, :streams,
|
68
|
-
:inventory, :
|
70
|
+
:inventory, :message_ordering, :callback_threads,
|
71
|
+
:push_threads
|
69
72
|
|
70
73
|
##
|
71
74
|
# @private Implementation attributes.
|
@@ -74,14 +77,16 @@ module Google
|
|
74
77
|
|
75
78
|
##
|
76
79
|
# @private Create an empty {Subscriber} object.
|
77
|
-
def initialize subscription_name, callback, deadline: nil,
|
78
|
-
|
80
|
+
def initialize subscription_name, callback, deadline: nil,
|
81
|
+
message_ordering: nil, streams: nil, inventory: nil,
|
82
|
+
threads: {}, service: nil
|
79
83
|
@callback = callback
|
80
84
|
@error_callbacks = []
|
81
85
|
@subscription_name = subscription_name
|
82
86
|
@deadline = deadline || 60
|
83
87
|
@streams = streams || 4
|
84
88
|
@inventory = inventory || 1000
|
89
|
+
@message_ordering = message_ordering
|
85
90
|
@callback_threads = (threads[:callback] || 8).to_i
|
86
91
|
@push_threads = (threads[:push] || 4).to_i
|
87
92
|
|
@@ -292,8 +297,7 @@ module Google
|
|
292
297
|
##
|
293
298
|
# @private
|
294
299
|
def to_s
|
295
|
-
"(subscription: #{subscription_name}, "
|
296
|
-
"streams: [#{stream_pool.map(&:to_s).join(', ')}])"
|
300
|
+
"(subscription: #{subscription_name}, streams: [#{stream_pool.map(&:to_s).join(', ')}])"
|
297
301
|
end
|
298
302
|
|
299
303
|
##
|
@@ -101,26 +101,24 @@ module Google
|
|
101
101
|
def background_run
|
102
102
|
delay_target = nil
|
103
103
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
delay_target = nil
|
108
|
-
|
109
|
-
@wait_cond.wait # wait until broadcast
|
110
|
-
next
|
111
|
-
end
|
104
|
+
until stopped?
|
105
|
+
if empty?
|
106
|
+
delay_target = nil
|
112
107
|
|
113
|
-
|
114
|
-
|
108
|
+
synchronize { @wait_cond.wait } # wait until broadcast
|
109
|
+
next
|
110
|
+
end
|
115
111
|
|
116
|
-
|
117
|
-
|
118
|
-
stream.renew_lease!
|
119
|
-
next
|
120
|
-
end
|
112
|
+
delay_target ||= calc_target
|
113
|
+
delay_gap = delay_target - Time.now
|
121
114
|
|
122
|
-
|
115
|
+
unless delay_gap.positive?
|
116
|
+
delay_target = calc_target
|
117
|
+
stream.renew_lease!
|
118
|
+
next
|
123
119
|
end
|
120
|
+
|
121
|
+
synchronize { @wait_cond.wait delay_gap }
|
124
122
|
end
|
125
123
|
end
|
126
124
|
|
@@ -0,0 +1,115 @@
|
|
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
|
+
|
18
|
+
module Google
|
19
|
+
module Cloud
|
20
|
+
module PubSub
|
21
|
+
class Subscriber
|
22
|
+
##
|
23
|
+
# @private The sequencer's job is simple, keep track of all the
|
24
|
+
# streams's recieved message and deliver the messages with an
|
25
|
+
# ordering_key in the order they were recieved. The sequencer ensures
|
26
|
+
# only one callback can be performed at a time per ordering_key.
|
27
|
+
class Sequencer
|
28
|
+
include MonitorMixin
|
29
|
+
|
30
|
+
##
|
31
|
+
# @private Create an empty Subscriber::Sequencer object.
|
32
|
+
def initialize &block
|
33
|
+
raise ArgumentError if block.nil?
|
34
|
+
|
35
|
+
@seq_hash = Hash.new { |hash, key| hash[key] = [] }
|
36
|
+
@process_callback = block
|
37
|
+
|
38
|
+
super() # to init MonitorMixin
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# @private Add a ReceivedMessage to the sequencer.
|
43
|
+
def add message
|
44
|
+
# Messages without ordering_key are not managed by the sequencer
|
45
|
+
if message.ordering_key.empty?
|
46
|
+
@process_callback.call message
|
47
|
+
return
|
48
|
+
end
|
49
|
+
|
50
|
+
perform_callback = synchronize do
|
51
|
+
# The purpose of this block is to add the message to the
|
52
|
+
# sequencer, and to return whether the message should be processed
|
53
|
+
# immediately, or whether it will be processed later by #next. We
|
54
|
+
# want to ensure that these operations happen atomically.
|
55
|
+
|
56
|
+
@seq_hash[message.ordering_key].push message
|
57
|
+
@seq_hash[message.ordering_key].count == 1
|
58
|
+
end
|
59
|
+
|
60
|
+
@process_callback.call message if perform_callback
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# @private Indicate a ReceivedMessage was processed, and the next in
|
65
|
+
# the queue can now be processed.
|
66
|
+
def next message
|
67
|
+
# Messages without ordering_key are not managed by the sequencer
|
68
|
+
return if message.ordering_key.empty?
|
69
|
+
|
70
|
+
next_message = synchronize do
|
71
|
+
# The purpose of this block is to remove the message that was
|
72
|
+
# processed from the sequencer, and to return the next message to
|
73
|
+
# be processed. We want to ensure that these operations happen
|
74
|
+
# atomically.
|
75
|
+
|
76
|
+
# The message should be at index 0, so this should be a very quick
|
77
|
+
# operation.
|
78
|
+
if @seq_hash[message.ordering_key].first != message
|
79
|
+
# Raising this error will stop the other messages with this
|
80
|
+
# ordering key from being processed by the callback (delivered).
|
81
|
+
raise OrderedMessageDeliveryError, message
|
82
|
+
end
|
83
|
+
|
84
|
+
# Remove the message
|
85
|
+
@seq_hash[message.ordering_key].shift
|
86
|
+
|
87
|
+
# Retrieve the next message to be processed, or nil if empty
|
88
|
+
next_msg = @seq_hash[message.ordering_key].first
|
89
|
+
|
90
|
+
# Remove the ordering_key from hash when empty
|
91
|
+
@seq_hash.delete message.ordering_key if next_msg.nil?
|
92
|
+
|
93
|
+
# Return the next message to be processed, or nil if empty
|
94
|
+
next_msg
|
95
|
+
end
|
96
|
+
|
97
|
+
@process_callback.call next_message unless next_message.nil?
|
98
|
+
end
|
99
|
+
|
100
|
+
# @private
|
101
|
+
def to_s
|
102
|
+
"#{@seq_hash.count}/#{@seq_hash.values.sum(&:count)}"
|
103
|
+
end
|
104
|
+
|
105
|
+
# @private
|
106
|
+
def inspect
|
107
|
+
"#<#{self.class.name} (#{self})>"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
Pubsub = PubSub unless const_defined? :Pubsub
|
114
|
+
end
|
115
|
+
end
|