google-cloud-pubsub 1.9.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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +18 -0
  3. data/AUTHENTICATION.md +177 -0
  4. data/CHANGELOG.md +538 -0
  5. data/CODE_OF_CONDUCT.md +40 -0
  6. data/CONTRIBUTING.md +188 -0
  7. data/EMULATOR.md +37 -0
  8. data/LICENSE +201 -0
  9. data/LOGGING.md +32 -0
  10. data/OVERVIEW.md +557 -0
  11. data/TROUBLESHOOTING.md +31 -0
  12. data/lib/google-cloud-pubsub.rb +139 -0
  13. data/lib/google/cloud/pubsub.rb +173 -0
  14. data/lib/google/cloud/pubsub/async_publisher.rb +399 -0
  15. data/lib/google/cloud/pubsub/async_publisher/batch.rb +309 -0
  16. data/lib/google/cloud/pubsub/batch_publisher.rb +99 -0
  17. data/lib/google/cloud/pubsub/convert.rb +91 -0
  18. data/lib/google/cloud/pubsub/credentials.rb +47 -0
  19. data/lib/google/cloud/pubsub/errors.rb +85 -0
  20. data/lib/google/cloud/pubsub/message.rb +158 -0
  21. data/lib/google/cloud/pubsub/policy.rb +187 -0
  22. data/lib/google/cloud/pubsub/project.rb +393 -0
  23. data/lib/google/cloud/pubsub/publish_result.rb +103 -0
  24. data/lib/google/cloud/pubsub/received_message.rb +297 -0
  25. data/lib/google/cloud/pubsub/retry_policy.rb +90 -0
  26. data/lib/google/cloud/pubsub/service.rb +514 -0
  27. data/lib/google/cloud/pubsub/snapshot.rb +202 -0
  28. data/lib/google/cloud/pubsub/snapshot/list.rb +178 -0
  29. data/lib/google/cloud/pubsub/subscriber.rb +399 -0
  30. data/lib/google/cloud/pubsub/subscriber/enumerator_queue.rb +54 -0
  31. data/lib/google/cloud/pubsub/subscriber/inventory.rb +166 -0
  32. data/lib/google/cloud/pubsub/subscriber/sequencer.rb +115 -0
  33. data/lib/google/cloud/pubsub/subscriber/stream.rb +401 -0
  34. data/lib/google/cloud/pubsub/subscriber/timed_unary_buffer.rb +231 -0
  35. data/lib/google/cloud/pubsub/subscription.rb +1279 -0
  36. data/lib/google/cloud/pubsub/subscription/list.rb +205 -0
  37. data/lib/google/cloud/pubsub/subscription/push_config.rb +244 -0
  38. data/lib/google/cloud/pubsub/topic.rb +934 -0
  39. data/lib/google/cloud/pubsub/topic/list.rb +171 -0
  40. data/lib/google/cloud/pubsub/v1.rb +17 -0
  41. data/lib/google/cloud/pubsub/v1/credentials.rb +41 -0
  42. data/lib/google/cloud/pubsub/v1/doc/google/iam/v1/iam_policy.rb +21 -0
  43. data/lib/google/cloud/pubsub/v1/doc/google/iam/v1/options.rb +21 -0
  44. data/lib/google/cloud/pubsub/v1/doc/google/iam/v1/policy.rb +21 -0
  45. data/lib/google/cloud/pubsub/v1/doc/google/protobuf/duration.rb +91 -0
  46. data/lib/google/cloud/pubsub/v1/doc/google/protobuf/empty.rb +29 -0
  47. data/lib/google/cloud/pubsub/v1/doc/google/protobuf/field_mask.rb +222 -0
  48. data/lib/google/cloud/pubsub/v1/doc/google/protobuf/timestamp.rb +113 -0
  49. data/lib/google/cloud/pubsub/v1/doc/google/pubsub/v1/pubsub.rb +833 -0
  50. data/lib/google/cloud/pubsub/v1/doc/google/type/expr.rb +19 -0
  51. data/lib/google/cloud/pubsub/v1/publisher_client.rb +928 -0
  52. data/lib/google/cloud/pubsub/v1/publisher_client_config.json +120 -0
  53. data/lib/google/cloud/pubsub/v1/subscriber_client.rb +1466 -0
  54. data/lib/google/cloud/pubsub/v1/subscriber_client_config.json +153 -0
  55. data/lib/google/cloud/pubsub/version.rb +24 -0
  56. data/lib/google/pubsub/v1/pubsub_pb.rb +269 -0
  57. data/lib/google/pubsub/v1/pubsub_services_pb.rb +215 -0
  58. metadata +337 -0
@@ -0,0 +1,54 @@
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
+ module Google
17
+ module Cloud
18
+ module PubSub
19
+ class Subscriber
20
+ # @private
21
+ class EnumeratorQueue
22
+ def initialize sentinel = nil
23
+ @queue = Queue.new
24
+ @sentinel = sentinel
25
+ end
26
+
27
+ def push obj
28
+ @queue.push obj
29
+ end
30
+
31
+ def quit_and_dump_queue
32
+ objs = []
33
+ objs << @queue.pop until @queue.empty?
34
+ # Signal that the enumerator is ready to end
35
+ @queue.push @sentinel
36
+ objs
37
+ end
38
+
39
+ def each
40
+ return enum_for :each unless block_given?
41
+
42
+ loop do
43
+ obj = @queue.pop
44
+ break if obj.equal? @sentinel
45
+ yield obj
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ Pubsub = PubSub unless const_defined? :Pubsub
53
+ end
54
+ end
@@ -0,0 +1,166 @@
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 "monitor"
17
+
18
+ module Google
19
+ module Cloud
20
+ module PubSub
21
+ class Subscriber
22
+ ##
23
+ # @private
24
+ class Inventory
25
+ InventoryItem = Struct.new :bytesize, :pulled_at do
26
+ def self.from rec_msg
27
+ new rec_msg.to_proto.bytesize, Time.now
28
+ end
29
+ end
30
+
31
+ include MonitorMixin
32
+
33
+ attr_reader :stream, :limit, :bytesize, :extension, :max_duration_per_lease_extension
34
+
35
+ def initialize stream, limit:, bytesize:, extension:, max_duration_per_lease_extension:
36
+ super()
37
+ @stream = stream
38
+ @limit = limit
39
+ @bytesize = bytesize
40
+ @extension = extension
41
+ @max_duration_per_lease_extension = max_duration_per_lease_extension
42
+ @inventory = {}
43
+ @wait_cond = new_cond
44
+ end
45
+
46
+ def ack_ids
47
+ @inventory.keys
48
+ end
49
+
50
+ def add *rec_msgs
51
+ rec_msgs.flatten!
52
+ rec_msgs.compact!
53
+ return if rec_msgs.empty?
54
+
55
+ synchronize do
56
+ rec_msgs.each do |rec_msg|
57
+ @inventory[rec_msg.ack_id] = InventoryItem.from rec_msg
58
+ end
59
+ @wait_cond.broadcast
60
+ end
61
+ end
62
+
63
+ def remove *ack_ids
64
+ ack_ids.flatten!
65
+ ack_ids.compact!
66
+ return if ack_ids.empty?
67
+
68
+ synchronize do
69
+ @inventory.delete_if { |ack_id, _| ack_ids.include? ack_id }
70
+ @wait_cond.broadcast
71
+ end
72
+ end
73
+
74
+ def remove_expired!
75
+ synchronize do
76
+ extension_time = Time.new - extension
77
+ @inventory.delete_if { |_ack_id, item| item.pulled_at < extension_time }
78
+ @wait_cond.broadcast
79
+ end
80
+ end
81
+
82
+ def count
83
+ synchronize do
84
+ @inventory.count
85
+ end
86
+ end
87
+
88
+ def total_bytesize
89
+ synchronize do
90
+ @inventory.values.sum(&:bytesize)
91
+ end
92
+ end
93
+
94
+ def empty?
95
+ synchronize do
96
+ @inventory.empty?
97
+ end
98
+ end
99
+
100
+ def start
101
+ @background_thread ||= Thread.new { background_run }
102
+
103
+ self
104
+ end
105
+
106
+ def stop
107
+ synchronize do
108
+ @stopped = true
109
+ @wait_cond.broadcast
110
+ end
111
+
112
+ self
113
+ end
114
+
115
+ def stopped?
116
+ synchronize { @stopped }
117
+ end
118
+
119
+ def full?
120
+ synchronize do
121
+ @inventory.count >= limit || @inventory.values.sum(&:bytesize) >= bytesize
122
+ end
123
+ end
124
+
125
+ protected
126
+
127
+ def background_run
128
+ delay_target = nil
129
+
130
+ until stopped?
131
+ if empty?
132
+ delay_target = nil
133
+
134
+ synchronize { @wait_cond.wait } # wait until broadcast
135
+ next
136
+ end
137
+
138
+ delay_target ||= calc_target
139
+ delay_gap = delay_target - Time.now
140
+
141
+ unless delay_gap.positive?
142
+ delay_target = calc_target
143
+ stream.renew_lease!
144
+ next
145
+ end
146
+
147
+ synchronize { @wait_cond.wait delay_gap }
148
+ end
149
+ end
150
+
151
+ def calc_target
152
+ Time.now + calc_delay
153
+ end
154
+
155
+ def calc_delay
156
+ delay = (stream.subscriber.deadline - 3) * rand(0.8..0.9)
157
+ delay = [delay, max_duration_per_lease_extension].min if max_duration_per_lease_extension.positive?
158
+ delay
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ Pubsub = PubSub unless const_defined? :Pubsub
165
+ end
166
+ end
@@ -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,401 @@
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, GRPC::Core::CallError
275
+ # Restart the stream with an incremental back for a retriable error.
276
+ # Also when GRPC raises the internal CallError.
277
+
278
+ retry
279
+ rescue RestartStream
280
+ retry
281
+ rescue StandardError => e
282
+ @subscriber.error! e
283
+
284
+ retry
285
+ end
286
+
287
+ # rubocop:enable all
288
+
289
+ def register_callback rec_msg
290
+ if @sequencer
291
+ # Add the message to the sequencer to invoke the callback.
292
+ @sequencer.add rec_msg
293
+ else
294
+ # Call user provided code for received message
295
+ perform_callback_async rec_msg
296
+ end
297
+ end
298
+
299
+ def perform_callback_async rec_msg
300
+ return unless callback_thread_pool.running?
301
+
302
+ Concurrent::Promises.future_on(
303
+ callback_thread_pool, rec_msg, &method(:perform_callback_sync)
304
+ )
305
+ end
306
+
307
+ def perform_callback_sync rec_msg
308
+ @subscriber.callback.call rec_msg unless stopped?
309
+ rescue StandardError => e
310
+ @subscriber.error! e
311
+ ensure
312
+ release rec_msg
313
+ if @sequencer && running?
314
+ begin
315
+ @sequencer.next rec_msg
316
+ rescue OrderedMessageDeliveryError => e
317
+ @subscriber.error! e
318
+ end
319
+ end
320
+ end
321
+
322
+ def start_streaming!
323
+ # A Stream will only ever have one background thread. If the thread
324
+ # dies because it was stopped, or because of an unhandled error that
325
+ # could not be recovered from, so be it.
326
+ return if @background_thread
327
+
328
+ # create new background thread to handle new enumerator
329
+ @background_thread = Thread.new { background_run }
330
+ end
331
+
332
+ def pause_streaming!
333
+ return unless pause_streaming?
334
+
335
+ @paused = true
336
+ end
337
+
338
+ def pause_streaming?
339
+ return if @stopped
340
+ return if @paused
341
+
342
+ @inventory.full?
343
+ end
344
+
345
+ def unpause_streaming!
346
+ return unless unpause_streaming?
347
+
348
+ @paused = nil
349
+ # signal to the background thread that we are unpaused
350
+ @pause_cond.broadcast
351
+ end
352
+
353
+ def unpause_streaming?
354
+ return if @stopped
355
+ return if @paused.nil?
356
+
357
+ @inventory.count < @inventory.limit * 0.8
358
+ end
359
+
360
+ def initial_input_request
361
+ Google::Cloud::PubSub::V1::StreamingPullRequest.new.tap do |req|
362
+ req.subscription = @subscriber.subscription_name
363
+ req.stream_ack_deadline_seconds = @subscriber.deadline
364
+ req.modify_deadline_ack_ids += @inventory.ack_ids
365
+ req.modify_deadline_seconds += @inventory.ack_ids.map { @subscriber.deadline }
366
+ req.client_id = @subscriber.service.client_id
367
+ req.max_outstanding_messages = @inventory.limit
368
+ req.max_outstanding_bytes = @inventory.bytesize
369
+ end
370
+ end
371
+
372
+ ##
373
+ # Makes sure the values are the `ack_id`. If given several
374
+ # {ReceivedMessage} objects extract the `ack_id` values.
375
+ def coerce_ack_ids messages
376
+ Array(messages).flatten.map do |msg|
377
+ msg.respond_to?(:ack_id) ? msg.ack_id : msg.to_s
378
+ end
379
+ end
380
+
381
+ def status
382
+ return "stopped" if stopped?
383
+ return "paused" if paused?
384
+ "running"
385
+ end
386
+
387
+ def thread_status
388
+ return "not started" if @background_thread.nil?
389
+
390
+ status = @background_thread.status
391
+ return "error" if status.nil?
392
+ return "stopped" if status == false
393
+ status
394
+ end
395
+ end
396
+ end
397
+ end
398
+
399
+ Pubsub = PubSub unless const_defined? :Pubsub
400
+ end
401
+ end