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.
Files changed (56) hide show
  1. checksums.yaml +5 -5
  2. data/.yardopts +12 -2
  3. data/AUTHENTICATION.md +178 -0
  4. data/CHANGELOG.md +659 -0
  5. data/CODE_OF_CONDUCT.md +40 -0
  6. data/CONTRIBUTING.md +187 -0
  7. data/EMULATOR.md +37 -0
  8. data/LICENSE +2 -2
  9. data/LOGGING.md +32 -0
  10. data/OVERVIEW.md +528 -0
  11. data/TROUBLESHOOTING.md +31 -0
  12. data/lib/google/cloud/pubsub/async_publisher/batch.rb +310 -0
  13. data/lib/google/cloud/pubsub/async_publisher.rb +402 -0
  14. data/lib/google/cloud/pubsub/batch_publisher.rb +100 -0
  15. data/lib/google/cloud/pubsub/convert.rb +91 -0
  16. data/lib/google/cloud/pubsub/credentials.rb +26 -10
  17. data/lib/google/cloud/pubsub/errors.rb +85 -0
  18. data/lib/google/cloud/pubsub/message.rb +80 -17
  19. data/lib/google/cloud/pubsub/policy.rb +17 -14
  20. data/lib/google/cloud/pubsub/project.rb +364 -250
  21. data/lib/google/cloud/pubsub/publish_result.rb +103 -0
  22. data/lib/google/cloud/pubsub/received_message.rb +162 -24
  23. data/lib/google/cloud/pubsub/retry_policy.rb +88 -0
  24. data/lib/google/cloud/pubsub/schema/list.rb +180 -0
  25. data/lib/google/cloud/pubsub/schema.rb +310 -0
  26. data/lib/google/cloud/pubsub/service.rb +281 -265
  27. data/lib/google/cloud/pubsub/snapshot/list.rb +21 -21
  28. data/lib/google/cloud/pubsub/snapshot.rb +55 -15
  29. data/lib/google/cloud/pubsub/subscriber/enumerator_queue.rb +54 -0
  30. data/lib/google/cloud/pubsub/subscriber/inventory.rb +173 -0
  31. data/lib/google/cloud/pubsub/subscriber/sequencer.rb +115 -0
  32. data/lib/google/cloud/pubsub/subscriber/stream.rb +400 -0
  33. data/lib/google/cloud/pubsub/subscriber/timed_unary_buffer.rb +230 -0
  34. data/lib/google/cloud/pubsub/subscriber.rb +417 -0
  35. data/lib/google/cloud/pubsub/subscription/list.rb +28 -28
  36. data/lib/google/cloud/pubsub/subscription/push_config.rb +268 -0
  37. data/lib/google/cloud/pubsub/subscription.rb +900 -172
  38. data/lib/google/cloud/pubsub/topic/list.rb +21 -21
  39. data/lib/google/cloud/pubsub/topic.rb +674 -95
  40. data/lib/google/cloud/pubsub/version.rb +6 -4
  41. data/lib/google/cloud/pubsub.rb +104 -439
  42. data/lib/google-cloud-pubsub.rb +60 -29
  43. metadata +88 -50
  44. data/README.md +0 -69
  45. data/lib/google/cloud/pubsub/topic/publisher.rb +0 -86
  46. data/lib/google/cloud/pubsub/v1/doc/google/protobuf/duration.rb +0 -77
  47. data/lib/google/cloud/pubsub/v1/doc/google/protobuf/field_mask.rb +0 -223
  48. data/lib/google/cloud/pubsub/v1/doc/google/protobuf/timestamp.rb +0 -81
  49. data/lib/google/cloud/pubsub/v1/doc/google/pubsub/v1/pubsub.rb +0 -503
  50. data/lib/google/cloud/pubsub/v1/publisher_client.rb +0 -605
  51. data/lib/google/cloud/pubsub/v1/publisher_client_config.json +0 -96
  52. data/lib/google/cloud/pubsub/v1/subscriber_client.rb +0 -1104
  53. data/lib/google/cloud/pubsub/v1/subscriber_client_config.json +0 -127
  54. data/lib/google/cloud/pubsub/v1.rb +0 -17
  55. data/lib/google/pubsub/v1/pubsub_pb.rb +0 -187
  56. data/lib/google/pubsub/v1/pubsub_services_pb.rb +0 -159
@@ -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
+ super() # to init MonitorMixin
36
+
37
+ @seq_hash = Hash.new { |hash, key| hash[key] = [] }
38
+ @process_callback = block
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
@@ -0,0 +1,400 @@
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 "google/cloud/pubsub/subscriber/sequencer"
17
+ require "google/cloud/pubsub/subscriber/enumerator_queue"
18
+ require "google/cloud/pubsub/subscriber/inventory"
19
+ require "google/cloud/pubsub/service"
20
+ require "google/cloud/errors"
21
+ require "monitor"
22
+ require "concurrent"
23
+
24
+ module Google
25
+ module Cloud
26
+ module PubSub
27
+ class Subscriber
28
+ ##
29
+ # @private
30
+ class Stream
31
+ include MonitorMixin
32
+
33
+ ##
34
+ # @private Implementation attributes.
35
+ attr_reader :callback_thread_pool
36
+
37
+ ##
38
+ # @private Subscriber attributes.
39
+ attr_reader :subscriber
40
+
41
+ ##
42
+ # @private Inventory.
43
+ attr_reader :inventory
44
+
45
+ ##
46
+ # @private Sequencer.
47
+ attr_reader :sequencer
48
+
49
+ ##
50
+ # @private Create an empty Subscriber::Stream object.
51
+ def initialize subscriber
52
+ super() # to init MonitorMixin
53
+
54
+ @subscriber = subscriber
55
+
56
+ @request_queue = nil
57
+ @stopped = nil
58
+ @paused = nil
59
+ @pause_cond = new_cond
60
+
61
+ @inventory = Inventory.new self, **@subscriber.stream_inventory
62
+
63
+ @sequencer = Sequencer.new(&method(:perform_callback_async)) if subscriber.message_ordering
64
+
65
+ @callback_thread_pool = Concurrent::ThreadPoolExecutor.new max_threads: @subscriber.callback_threads
66
+
67
+ @stream_keepalive_task = Concurrent::TimerTask.new(
68
+ execution_interval: 30
69
+ ) do
70
+ # push empty request every 30 seconds to keep stream alive
71
+ push Google::Cloud::PubSub::V1::StreamingPullRequest.new unless inventory.empty?
72
+ end.execute
73
+ end
74
+
75
+ def start
76
+ synchronize do
77
+ break if @background_thread
78
+
79
+ @inventory.start
80
+
81
+ start_streaming!
82
+ end
83
+
84
+ self
85
+ end
86
+
87
+ def stop
88
+ synchronize do
89
+ break if @stopped
90
+
91
+ # Close the stream by pushing the sentinel value.
92
+ # The unary pusher does not use the stream, so it can close here.
93
+ @request_queue&.push self
94
+
95
+ # Signal to the background thread that we are stopped.
96
+ @stopped = true
97
+ @pause_cond.broadcast
98
+
99
+ # Now that the reception thread is stopped, immediately stop the
100
+ # callback thread pool. All queued callbacks will see the stream
101
+ # is stopped and perform a noop.
102
+ @callback_thread_pool.shutdown
103
+
104
+ # Once all the callbacks are stopped, we can stop the inventory.
105
+ @inventory.stop
106
+ end
107
+
108
+ self
109
+ end
110
+
111
+ def stopped?
112
+ synchronize { @stopped }
113
+ end
114
+
115
+ def paused?
116
+ synchronize { @paused }
117
+ end
118
+
119
+ def running?
120
+ !stopped?
121
+ end
122
+
123
+ def wait! timeout = nil
124
+ # Wait for all queued callbacks to be processed.
125
+ @callback_thread_pool.wait_for_termination timeout
126
+
127
+ self
128
+ end
129
+
130
+ ##
131
+ # @private
132
+ def acknowledge *messages
133
+ ack_ids = coerce_ack_ids messages
134
+ return true if ack_ids.empty?
135
+
136
+ synchronize do
137
+ @inventory.remove ack_ids
138
+ @subscriber.buffer.acknowledge ack_ids
139
+ end
140
+
141
+ true
142
+ end
143
+
144
+ ##
145
+ # @private
146
+ def modify_ack_deadline deadline, *messages
147
+ mod_ack_ids = coerce_ack_ids messages
148
+ return true if mod_ack_ids.empty?
149
+
150
+ synchronize do
151
+ @inventory.remove mod_ack_ids
152
+ @subscriber.buffer.modify_ack_deadline deadline, mod_ack_ids
153
+ end
154
+
155
+ true
156
+ end
157
+
158
+ ##
159
+ # @private
160
+ def release *messages
161
+ ack_ids = coerce_ack_ids messages
162
+ return if ack_ids.empty?
163
+
164
+ synchronize do
165
+ # Remove from inventory if the message was not explicitly acked or
166
+ # nacked in the callback
167
+ @inventory.remove ack_ids
168
+ # Check whether to unpause the stream only after the callback is
169
+ # completed and the thread is being reclaimed.
170
+ unpause_streaming!
171
+ end
172
+ end
173
+
174
+ def push request
175
+ synchronize { @request_queue.push request }
176
+ end
177
+
178
+ ##
179
+ # @private
180
+ def renew_lease!
181
+ synchronize do
182
+ return true if @inventory.empty?
183
+
184
+ @inventory.remove_expired!
185
+ @subscriber.buffer.renew_lease @subscriber.deadline, @inventory.ack_ids
186
+ unpause_streaming!
187
+ end
188
+
189
+ true
190
+ end
191
+
192
+ # @private
193
+ def to_s
194
+ seq_str = "sequenced: #{sequencer}, " if sequencer
195
+ "(inventory: #{@inventory.count}, #{seq_str}status: #{status}, thread: #{thread_status})"
196
+ end
197
+
198
+ # @private
199
+ def inspect
200
+ "#<#{self.class.name} #{self}>"
201
+ end
202
+
203
+ protected
204
+
205
+ # @private
206
+ class RestartStream < StandardError; end
207
+
208
+ # rubocop:disable all
209
+
210
+ def background_run
211
+ synchronize do
212
+ # Don't allow a stream to restart if already stopped
213
+ return if @stopped
214
+
215
+ @stopped = false
216
+ @paused = false
217
+
218
+ # signal to the previous queue to shut down
219
+ old_queue = []
220
+ old_queue = @request_queue.quit_and_dump_queue if @request_queue
221
+
222
+ # Always create a new request queue
223
+ @request_queue = EnumeratorQueue.new self
224
+ @request_queue.push initial_input_request
225
+ old_queue.each { |obj| @request_queue.push obj }
226
+ end
227
+
228
+ # Call the StreamingPull API to get the response enumerator
229
+ enum = @subscriber.service.streaming_pull @request_queue.each
230
+
231
+ loop do
232
+ synchronize do
233
+ if @paused && !@stopped
234
+ @pause_cond.wait
235
+ next
236
+ end
237
+ end
238
+
239
+ # Break loop, close thread if stopped
240
+ break if synchronize { @stopped }
241
+
242
+ begin
243
+ # Cannot syncronize the enumerator, causes deadlock
244
+ response = enum.next
245
+
246
+ # Use synchronize so both changes happen atomically
247
+ synchronize do
248
+ # Create receipt of received messages reception
249
+ @subscriber.buffer.modify_ack_deadline @subscriber.deadline, response.received_messages.map(&:ack_id)
250
+
251
+ # Add received messages to inventory
252
+ @inventory.add response.received_messages
253
+ end
254
+
255
+ response.received_messages.each do |rec_msg_grpc|
256
+ rec_msg = ReceivedMessage.from_grpc(rec_msg_grpc, self)
257
+ # No need to synchronize the callback future
258
+ register_callback rec_msg
259
+ end
260
+ synchronize { pause_streaming! }
261
+ rescue StopIteration
262
+ break
263
+ end
264
+ end
265
+
266
+ # Has the loop broken but we aren't stopped?
267
+ # Could be GRPC has thrown an internal error, so restart.
268
+ raise RestartStream unless synchronize { @stopped }
269
+
270
+ # We must be stopped, tell the stream to quit.
271
+ stop
272
+ rescue GRPC::Cancelled, GRPC::DeadlineExceeded, GRPC::Internal,
273
+ GRPC::ResourceExhausted, GRPC::Unauthenticated,
274
+ GRPC::Unavailable
275
+ # Restart the stream with an incremental back for a retriable error.
276
+
277
+ retry
278
+ rescue RestartStream
279
+ retry
280
+ rescue StandardError => e
281
+ @subscriber.error! e
282
+
283
+ retry
284
+ end
285
+
286
+ # rubocop:enable all
287
+
288
+ def register_callback rec_msg
289
+ if @sequencer
290
+ # Add the message to the sequencer to invoke the callback.
291
+ @sequencer.add rec_msg
292
+ else
293
+ # Call user provided code for received message
294
+ perform_callback_async rec_msg
295
+ end
296
+ end
297
+
298
+ def perform_callback_async rec_msg
299
+ return unless callback_thread_pool.running?
300
+
301
+ Concurrent::Promises.future_on(
302
+ callback_thread_pool, rec_msg, &method(:perform_callback_sync)
303
+ )
304
+ end
305
+
306
+ def perform_callback_sync rec_msg
307
+ @subscriber.callback.call rec_msg unless stopped?
308
+ rescue StandardError => e
309
+ @subscriber.error! e
310
+ ensure
311
+ release rec_msg
312
+ if @sequencer && running?
313
+ begin
314
+ @sequencer.next rec_msg
315
+ rescue OrderedMessageDeliveryError => e
316
+ @subscriber.error! e
317
+ end
318
+ end
319
+ end
320
+
321
+ def start_streaming!
322
+ # A Stream will only ever have one background thread. If the thread
323
+ # dies because it was stopped, or because of an unhandled error that
324
+ # could not be recovered from, so be it.
325
+ return if @background_thread
326
+
327
+ # create new background thread to handle new enumerator
328
+ @background_thread = Thread.new { background_run }
329
+ end
330
+
331
+ def pause_streaming!
332
+ return unless pause_streaming?
333
+
334
+ @paused = true
335
+ end
336
+
337
+ def pause_streaming?
338
+ return if @stopped
339
+ return if @paused
340
+
341
+ @inventory.full?
342
+ end
343
+
344
+ def unpause_streaming!
345
+ return unless unpause_streaming?
346
+
347
+ @paused = nil
348
+ # signal to the background thread that we are unpaused
349
+ @pause_cond.broadcast
350
+ end
351
+
352
+ def unpause_streaming?
353
+ return if @stopped
354
+ return if @paused.nil?
355
+
356
+ @inventory.count < @inventory.limit * 0.8
357
+ end
358
+
359
+ def initial_input_request
360
+ Google::Cloud::PubSub::V1::StreamingPullRequest.new.tap do |req|
361
+ req.subscription = @subscriber.subscription_name
362
+ req.stream_ack_deadline_seconds = @subscriber.deadline
363
+ req.modify_deadline_ack_ids += @inventory.ack_ids
364
+ req.modify_deadline_seconds += @inventory.ack_ids.map { @subscriber.deadline }
365
+ req.client_id = @subscriber.service.client_id
366
+ req.max_outstanding_messages = @inventory.use_legacy_flow_control ? 0 : @inventory.limit
367
+ req.max_outstanding_bytes = @inventory.use_legacy_flow_control ? 0 : @inventory.bytesize
368
+ end
369
+ end
370
+
371
+ ##
372
+ # Makes sure the values are the `ack_id`. If given several
373
+ # {ReceivedMessage} objects extract the `ack_id` values.
374
+ def coerce_ack_ids messages
375
+ Array(messages).flatten.map do |msg|
376
+ msg.respond_to?(:ack_id) ? msg.ack_id : msg.to_s
377
+ end
378
+ end
379
+
380
+ def status
381
+ return "stopped" if stopped?
382
+ return "paused" if paused?
383
+ "running"
384
+ end
385
+
386
+ def thread_status
387
+ return "not started" if @background_thread.nil?
388
+
389
+ status = @background_thread.status
390
+ return "error" if status.nil?
391
+ return "stopped" if status == false
392
+ status
393
+ end
394
+ end
395
+ end
396
+ end
397
+
398
+ Pubsub = PubSub unless const_defined? :Pubsub
399
+ end
400
+ end