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
@@ -0,0 +1,309 @@
|
|
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, :ordering_key
|
29
|
+
|
30
|
+
def initialize publisher, ordering_key
|
31
|
+
@publisher = publisher
|
32
|
+
@ordering_key = ordering_key
|
33
|
+
@items = []
|
34
|
+
@queue = []
|
35
|
+
@default_message_bytes = publisher.topic_name.bytesize + 2
|
36
|
+
@total_message_bytes = @default_message_bytes
|
37
|
+
@publishing = false
|
38
|
+
@stopping = false
|
39
|
+
@canceled = false
|
40
|
+
|
41
|
+
# init MonitorMixin
|
42
|
+
super()
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Adds a message and callback to the batch.
|
47
|
+
#
|
48
|
+
# The method will indicate how the message is added. It will either be
|
49
|
+
# added to the active list of items, it will be queued to be picked up
|
50
|
+
# once the active publishing job has been completed, or it will
|
51
|
+
# indicate that the batch is full and a publishing job should be
|
52
|
+
# created.
|
53
|
+
#
|
54
|
+
# @param [Google::Cloud::PubSub::V1::PubsubMessage] msg The message.
|
55
|
+
# @param [Proc, nil] callback The callback.
|
56
|
+
#
|
57
|
+
# @return [Symbol] The state of the batch.
|
58
|
+
#
|
59
|
+
# * `:added` - Added to the active list of items to be published.
|
60
|
+
# * `:queued` - Batch is publishing, and the messsage is queued.
|
61
|
+
# * `:full` - Batch is full and ready to be published, and the
|
62
|
+
# message is queued.
|
63
|
+
#
|
64
|
+
def add msg, callback
|
65
|
+
synchronize do
|
66
|
+
raise AsyncPublisherStopped if @stopping
|
67
|
+
raise OrderingKeyError, @ordering_key if @canceled
|
68
|
+
|
69
|
+
if @publishing
|
70
|
+
queue_add msg, callback
|
71
|
+
:queued
|
72
|
+
elsif try_add msg, callback
|
73
|
+
:added
|
74
|
+
else
|
75
|
+
queue_add msg, callback
|
76
|
+
:full
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Marks the batch to be published.
|
83
|
+
#
|
84
|
+
# The method will indicate whether a new publishing job should be
|
85
|
+
# started to publish the batch. See {publishing?}.
|
86
|
+
#
|
87
|
+
# @param [Boolean] stop Indicates whether the batch should also be
|
88
|
+
# marked for stopping, and any existing publish job should publish
|
89
|
+
# all items until the batch is empty.
|
90
|
+
#
|
91
|
+
# @return [Boolean] Returns whether a new publishing job should be
|
92
|
+
# started to publish the batch. If the batch is already being
|
93
|
+
# published then this will return `false`.
|
94
|
+
#
|
95
|
+
def publish! stop: nil
|
96
|
+
synchronize do
|
97
|
+
@stopping = true if stop
|
98
|
+
|
99
|
+
return false if @canceled
|
100
|
+
|
101
|
+
# If we are already publishing, do not indicate a new job needs to
|
102
|
+
# be started.
|
103
|
+
return false if @publishing
|
104
|
+
|
105
|
+
@publishing = !(@items.empty? && @queue.empty?)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Indicates whether the batch has an active publishing job.
|
111
|
+
#
|
112
|
+
# @return [Boolean]
|
113
|
+
#
|
114
|
+
def publishing?
|
115
|
+
# This probably does not need to be synchronized
|
116
|
+
@publishing
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
# Indicates whether the batch has been stopped and all items will be
|
121
|
+
# published until the batch is empty.
|
122
|
+
#
|
123
|
+
# @return [Boolean]
|
124
|
+
#
|
125
|
+
def stopping?
|
126
|
+
# This does not need to be synchronized because nothing un-stops
|
127
|
+
@stopping
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# Rebalances the batch by moving any queued items that will fit into
|
132
|
+
# the active item list.
|
133
|
+
#
|
134
|
+
# This method is only intended to be used by the active publishing
|
135
|
+
# job.
|
136
|
+
#
|
137
|
+
def rebalance!
|
138
|
+
synchronize do
|
139
|
+
return [] if @canceled
|
140
|
+
|
141
|
+
until @queue.empty?
|
142
|
+
item = @queue.first
|
143
|
+
if try_add item.msg, item.callback
|
144
|
+
@queue.shift
|
145
|
+
next
|
146
|
+
end
|
147
|
+
break
|
148
|
+
end
|
149
|
+
|
150
|
+
@items
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# rubocop:disable Metrics/MethodLength
|
155
|
+
|
156
|
+
##
|
157
|
+
# Resets the batch after a successful publish. This clears the active
|
158
|
+
# item list and moves the queued items that will fit into the active
|
159
|
+
# item list.
|
160
|
+
#
|
161
|
+
# If the batch has enough queued items to fill the batch again, the
|
162
|
+
# publishing job should continue to publish the reset batch until the
|
163
|
+
# batch indicated it should stop.
|
164
|
+
#
|
165
|
+
# This method is only intended to be used by the active publishing
|
166
|
+
# job.
|
167
|
+
#
|
168
|
+
# @return [Boolean] Whether the active publishing job should continue
|
169
|
+
# publishing after the reset.
|
170
|
+
#
|
171
|
+
def reset!
|
172
|
+
synchronize do
|
173
|
+
@items = []
|
174
|
+
@total_message_bytes = @default_message_bytes
|
175
|
+
|
176
|
+
if @canceled
|
177
|
+
@queue = []
|
178
|
+
@publishing = false
|
179
|
+
return false
|
180
|
+
end
|
181
|
+
|
182
|
+
until @queue.empty?
|
183
|
+
item = @queue.first
|
184
|
+
added = try_add item.msg, item.callback
|
185
|
+
break unless added
|
186
|
+
@queue.shift
|
187
|
+
end
|
188
|
+
|
189
|
+
return false unless @publishing
|
190
|
+
if @items.empty?
|
191
|
+
@publishing = false
|
192
|
+
return false
|
193
|
+
else
|
194
|
+
return true if stopping?
|
195
|
+
if @queue.empty?
|
196
|
+
@publishing = false
|
197
|
+
return false
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
true
|
202
|
+
end
|
203
|
+
|
204
|
+
# rubocop:enable Metrics/MethodLength
|
205
|
+
|
206
|
+
##
|
207
|
+
# Cancel the batch and hault futher batches until resumed. See
|
208
|
+
# {#resume!} and {#canceled?}.
|
209
|
+
#
|
210
|
+
# @return [Array<Item}] All items, including queued items
|
211
|
+
#
|
212
|
+
def cancel!
|
213
|
+
synchronize do
|
214
|
+
@canceled = true
|
215
|
+
@items + @queue
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
##
|
220
|
+
# Resume the batch and proceed to publish messages. See {#cancel!} and
|
221
|
+
# {#canceled?}.
|
222
|
+
#
|
223
|
+
# @return [Boolean] Whether the batch was resumed.
|
224
|
+
#
|
225
|
+
def resume!
|
226
|
+
synchronize do
|
227
|
+
# Return false if the batch is not canceled
|
228
|
+
return false unless @canceled
|
229
|
+
|
230
|
+
@items = []
|
231
|
+
@queue = []
|
232
|
+
@total_message_bytes = @default_message_bytes
|
233
|
+
@publishing = false
|
234
|
+
@canceled = false
|
235
|
+
end
|
236
|
+
true
|
237
|
+
end
|
238
|
+
|
239
|
+
##
|
240
|
+
# Indicates whether the batch has been canceled due to an error while
|
241
|
+
# publishing. See {#cancel!} and {#resume!}.
|
242
|
+
#
|
243
|
+
# @return [Boolean]
|
244
|
+
#
|
245
|
+
def canceled?
|
246
|
+
# This does not need to be synchronized because nothing un-stops
|
247
|
+
synchronize { @canceled }
|
248
|
+
end
|
249
|
+
|
250
|
+
##
|
251
|
+
# Determines whether the batch is empty and ready to be culled.
|
252
|
+
#
|
253
|
+
def empty?
|
254
|
+
synchronize do
|
255
|
+
return false if @publishing || @canceled || @stopping
|
256
|
+
|
257
|
+
@items.empty? && @queue.empty?
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
protected
|
262
|
+
|
263
|
+
def items_add msg, callback
|
264
|
+
item = Item.new msg, callback
|
265
|
+
@items << item
|
266
|
+
@total_message_bytes += item.bytesize + 2
|
267
|
+
end
|
268
|
+
|
269
|
+
def try_add msg, callback
|
270
|
+
if @items.empty?
|
271
|
+
# Always add when empty, even if bytesize is bigger than total
|
272
|
+
items_add msg, callback
|
273
|
+
return true
|
274
|
+
end
|
275
|
+
new_message_count = total_message_count + 1
|
276
|
+
new_message_bytes = total_message_bytes + msg.to_proto.bytesize + 2
|
277
|
+
if new_message_count > @publisher.max_messages ||
|
278
|
+
new_message_bytes >= @publisher.max_bytes
|
279
|
+
return false
|
280
|
+
end
|
281
|
+
items_add msg, callback
|
282
|
+
true
|
283
|
+
end
|
284
|
+
|
285
|
+
def queue_add msg, callback
|
286
|
+
item = Item.new msg, callback
|
287
|
+
@queue << item
|
288
|
+
end
|
289
|
+
|
290
|
+
def total_message_count
|
291
|
+
@items.count
|
292
|
+
end
|
293
|
+
|
294
|
+
def total_message_bytes
|
295
|
+
@total_message_bytes
|
296
|
+
end
|
297
|
+
|
298
|
+
Item = Struct.new :msg, :callback do
|
299
|
+
def bytesize
|
300
|
+
msg.to_proto.bytesize
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
Pubsub = PubSub unless const_defined? :Pubsub
|
308
|
+
end
|
309
|
+
end
|
@@ -84,14 +84,12 @@ module Google
|
|
84
84
|
data = data.read
|
85
85
|
end
|
86
86
|
# Convert data to encoded byte array to match the protobuf defn
|
87
|
-
data_bytes =
|
88
|
-
String(data).dup.force_encoding(Encoding::ASCII_8BIT).freeze
|
87
|
+
data_bytes = String(data).dup.force_encoding(Encoding::ASCII_8BIT).freeze
|
89
88
|
|
90
89
|
# Convert attributes to strings to match the protobuf definition
|
91
90
|
attributes = Hash[attributes.map { |k, v| [String(k), String(v)] }]
|
92
91
|
|
93
|
-
Google::Cloud::PubSub::V1::PubsubMessage.new data:
|
94
|
-
attributes: attributes
|
92
|
+
Google::Cloud::PubSub::V1::PubsubMessage.new data: data_bytes, attributes: attributes
|
95
93
|
end
|
96
94
|
end
|
97
95
|
end
|
@@ -28,9 +28,7 @@ module Google
|
|
28
28
|
# Force the object to be a Time object.
|
29
29
|
time = time.to_time
|
30
30
|
|
31
|
-
Google::Protobuf::Timestamp.new
|
32
|
-
seconds: time.to_i,
|
33
|
-
nanos: time.nsec
|
31
|
+
Google::Protobuf::Timestamp.new seconds: time.to_i, nanos: time.nsec
|
34
32
|
end
|
35
33
|
|
36
34
|
def timestamp_to_time timestamp
|
@@ -42,9 +40,7 @@ module Google
|
|
42
40
|
def number_to_duration number
|
43
41
|
return nil if number.nil?
|
44
42
|
|
45
|
-
Google::Protobuf::Duration.new
|
46
|
-
seconds: number.to_i,
|
47
|
-
nanos: (number.remainder(1) * 1000000000).round
|
43
|
+
Google::Protobuf::Duration.new seconds: number.to_i, nanos: (number.remainder(1) * 1_000_000_000).round
|
48
44
|
end
|
49
45
|
|
50
46
|
def duration_to_number duration
|
@@ -52,7 +48,37 @@ module Google
|
|
52
48
|
|
53
49
|
return duration.seconds if duration.nanos.zero?
|
54
50
|
|
55
|
-
duration.seconds + (duration.nanos /
|
51
|
+
duration.seconds + (duration.nanos / 1_000_000_000.0)
|
52
|
+
end
|
53
|
+
|
54
|
+
def pubsub_message data, attributes, ordering_key, extra_attrs
|
55
|
+
# TODO: allow data to be a Message object,
|
56
|
+
# then ensure attributes and ordering_key are nil
|
57
|
+
if data.is_a?(::Hash) && (attributes.nil? || attributes.empty?)
|
58
|
+
attributes = data.merge extra_attrs
|
59
|
+
data = nil
|
60
|
+
else
|
61
|
+
attributes = Hash(attributes).merge extra_attrs
|
62
|
+
end
|
63
|
+
# Convert IO-ish objects to strings
|
64
|
+
if data.respond_to?(:read) && data.respond_to?(:rewind)
|
65
|
+
data.rewind
|
66
|
+
data = data.read
|
67
|
+
end
|
68
|
+
# Convert data to encoded byte array to match the protobuf defn
|
69
|
+
data_bytes = String(data).dup.force_encoding(Encoding::ASCII_8BIT).freeze
|
70
|
+
|
71
|
+
# Convert attributes to strings to match the protobuf definition
|
72
|
+
attributes = Hash[attributes.map { |k, v| [String(k), String(v)] }]
|
73
|
+
|
74
|
+
# Ordering Key must always be a string
|
75
|
+
ordering_key = String(ordering_key).freeze
|
76
|
+
|
77
|
+
Google::Cloud::PubSub::V1::PubsubMessage.new(
|
78
|
+
data: data_bytes,
|
79
|
+
attributes: attributes,
|
80
|
+
ordering_key: ordering_key
|
81
|
+
)
|
56
82
|
end
|
57
83
|
end
|
58
84
|
|
@@ -0,0 +1,85 @@
|
|
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 "google/cloud/errors"
|
17
|
+
|
18
|
+
module Google
|
19
|
+
module Cloud
|
20
|
+
module PubSub
|
21
|
+
##
|
22
|
+
# Indicates that the {AsyncPublisher} has been stopped and cannot accept
|
23
|
+
# messages to publish.
|
24
|
+
#
|
25
|
+
class AsyncPublisherStopped < Google::Cloud::Error
|
26
|
+
def initialize message = "Can't publish when stopped."
|
27
|
+
super message
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Indicates that the {AsyncPublisher} has not been enabled to publish
|
33
|
+
# messages with an ordering key. Use
|
34
|
+
# {AsyncPublisher#enable_message_ordering!} to enable publishing ordered
|
35
|
+
# messages.
|
36
|
+
#
|
37
|
+
class OrderedMessagesDisabled < Google::Cloud::Error
|
38
|
+
def initialize message = "Ordered messages are disabled."
|
39
|
+
super message
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Indicates that the {Subscriber} for a {Subscription} with message
|
45
|
+
# ordering enabled has observed that a message has been delivered out of
|
46
|
+
# order.
|
47
|
+
#
|
48
|
+
class OrderedMessageDeliveryError < Google::Cloud::Error
|
49
|
+
attr_reader :ordered_message
|
50
|
+
|
51
|
+
def initialize ordered_message
|
52
|
+
@ordered_message = ordered_message
|
53
|
+
|
54
|
+
super "Ordered message delivered out of order."
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Indicates that messages using the {#ordering_key} are not being
|
60
|
+
# published due to error. Future calls to {Topic#publish_async} with the
|
61
|
+
# {#ordering_key} will fail with this error.
|
62
|
+
#
|
63
|
+
# To allow future messages with the {#ordering_key} to be published, the
|
64
|
+
# {#ordering_key} must be passed to {Topic#resume_publish}.
|
65
|
+
#
|
66
|
+
# If this error is retrieved from {PublishResult#error}, inspect `cause`
|
67
|
+
# for the error raised while publishing.
|
68
|
+
#
|
69
|
+
# @!attribute [r] ordering_key
|
70
|
+
# @return [String] The ordering key that is in a failed state.
|
71
|
+
#
|
72
|
+
class OrderingKeyError < Google::Cloud::Error
|
73
|
+
attr_reader :ordering_key
|
74
|
+
|
75
|
+
def initialize ordering_key
|
76
|
+
@ordering_key = ordering_key
|
77
|
+
|
78
|
+
super "Can't publish message using #{ordering_key}."
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
Pubsub = PubSub unless const_defined? :Pubsub
|
84
|
+
end
|
85
|
+
end
|