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.
- checksums.yaml +7 -0
- data/.yardopts +18 -0
- data/AUTHENTICATION.md +177 -0
- data/CHANGELOG.md +538 -0
- data/CODE_OF_CONDUCT.md +40 -0
- data/CONTRIBUTING.md +188 -0
- data/EMULATOR.md +37 -0
- data/LICENSE +201 -0
- data/LOGGING.md +32 -0
- data/OVERVIEW.md +557 -0
- data/TROUBLESHOOTING.md +31 -0
- data/lib/google-cloud-pubsub.rb +139 -0
- data/lib/google/cloud/pubsub.rb +173 -0
- data/lib/google/cloud/pubsub/async_publisher.rb +399 -0
- data/lib/google/cloud/pubsub/async_publisher/batch.rb +309 -0
- data/lib/google/cloud/pubsub/batch_publisher.rb +99 -0
- data/lib/google/cloud/pubsub/convert.rb +91 -0
- data/lib/google/cloud/pubsub/credentials.rb +47 -0
- data/lib/google/cloud/pubsub/errors.rb +85 -0
- data/lib/google/cloud/pubsub/message.rb +158 -0
- data/lib/google/cloud/pubsub/policy.rb +187 -0
- data/lib/google/cloud/pubsub/project.rb +393 -0
- data/lib/google/cloud/pubsub/publish_result.rb +103 -0
- data/lib/google/cloud/pubsub/received_message.rb +297 -0
- data/lib/google/cloud/pubsub/retry_policy.rb +90 -0
- data/lib/google/cloud/pubsub/service.rb +514 -0
- data/lib/google/cloud/pubsub/snapshot.rb +202 -0
- data/lib/google/cloud/pubsub/snapshot/list.rb +178 -0
- data/lib/google/cloud/pubsub/subscriber.rb +399 -0
- data/lib/google/cloud/pubsub/subscriber/enumerator_queue.rb +54 -0
- data/lib/google/cloud/pubsub/subscriber/inventory.rb +166 -0
- data/lib/google/cloud/pubsub/subscriber/sequencer.rb +115 -0
- data/lib/google/cloud/pubsub/subscriber/stream.rb +401 -0
- data/lib/google/cloud/pubsub/subscriber/timed_unary_buffer.rb +231 -0
- data/lib/google/cloud/pubsub/subscription.rb +1279 -0
- data/lib/google/cloud/pubsub/subscription/list.rb +205 -0
- data/lib/google/cloud/pubsub/subscription/push_config.rb +244 -0
- data/lib/google/cloud/pubsub/topic.rb +934 -0
- data/lib/google/cloud/pubsub/topic/list.rb +171 -0
- data/lib/google/cloud/pubsub/v1.rb +17 -0
- data/lib/google/cloud/pubsub/v1/credentials.rb +41 -0
- data/lib/google/cloud/pubsub/v1/doc/google/iam/v1/iam_policy.rb +21 -0
- data/lib/google/cloud/pubsub/v1/doc/google/iam/v1/options.rb +21 -0
- data/lib/google/cloud/pubsub/v1/doc/google/iam/v1/policy.rb +21 -0
- data/lib/google/cloud/pubsub/v1/doc/google/protobuf/duration.rb +91 -0
- data/lib/google/cloud/pubsub/v1/doc/google/protobuf/empty.rb +29 -0
- data/lib/google/cloud/pubsub/v1/doc/google/protobuf/field_mask.rb +222 -0
- data/lib/google/cloud/pubsub/v1/doc/google/protobuf/timestamp.rb +113 -0
- data/lib/google/cloud/pubsub/v1/doc/google/pubsub/v1/pubsub.rb +833 -0
- data/lib/google/cloud/pubsub/v1/doc/google/type/expr.rb +19 -0
- data/lib/google/cloud/pubsub/v1/publisher_client.rb +928 -0
- data/lib/google/cloud/pubsub/v1/publisher_client_config.json +120 -0
- data/lib/google/cloud/pubsub/v1/subscriber_client.rb +1466 -0
- data/lib/google/cloud/pubsub/v1/subscriber_client_config.json +153 -0
- data/lib/google/cloud/pubsub/version.rb +24 -0
- data/lib/google/pubsub/v1/pubsub_pb.rb +269 -0
- data/lib/google/pubsub/v1/pubsub_services_pb.rb +215 -0
- 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
|