google-cloud-pubsub 0.26.0 → 2.6.1

Sign up to get free protection for your applications and to get access to all the features.
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