google-cloud-pubsub 1.9.0

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