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,202 @@
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/errors"
17
+ require "google/cloud/pubsub/snapshot/list"
18
+
19
+ module Google
20
+ module Cloud
21
+ module PubSub
22
+ ##
23
+ # # Snapshot
24
+ #
25
+ # A named resource created from a subscription to retain a stream of
26
+ # messages from a topic. A snapshot is guaranteed to retain:
27
+ #
28
+ # * The existing backlog on the subscription. More precisely, this is
29
+ # defined as the messages in the subscription's backlog that are
30
+ # unacknowledged upon the successful completion of the
31
+ # `create_snapshot` operation; as well as:
32
+ # * Any messages published to the subscription's topic following the
33
+ # successful completion of the `create_snapshot` operation.
34
+ #
35
+ # @example
36
+ # require "google/cloud/pubsub"
37
+ #
38
+ # pubsub = Google::Cloud::PubSub.new
39
+ # sub = pubsub.subscription "my-sub"
40
+ #
41
+ # snapshot = sub.create_snapshot "my-snapshot"
42
+ # snapshot.name #=> "projects/my-project/snapshots/my-snapshot"
43
+ #
44
+ class Snapshot
45
+ ##
46
+ # @private The Service object.
47
+ attr_accessor :service
48
+
49
+ ##
50
+ # @private The gRPC Google::Cloud::PubSub::V1::Snapshot object.
51
+ attr_accessor :grpc
52
+
53
+ ##
54
+ # @private Create an empty {Snapshot} object.
55
+ def initialize
56
+ @service = nil
57
+ @grpc = Google::Cloud::PubSub::V1::Snapshot.new
58
+ end
59
+
60
+ ##
61
+ # The name of the snapshot. Format is
62
+ # `projects/{project}/snapshots/{snap}`.
63
+ def name
64
+ @grpc.name
65
+ end
66
+
67
+ ##
68
+ # The {Topic} from which this snapshot is retaining messages.
69
+ #
70
+ # @return [Topic]
71
+ #
72
+ # @example
73
+ # require "google/cloud/pubsub"
74
+ #
75
+ # pubsub = Google::Cloud::PubSub.new
76
+ # sub = pubsub.subscription "my-sub"
77
+ #
78
+ # snapshot = sub.create_snapshot "my-snapshot"
79
+ # snapshot.topic.name #=> "projects/my-project/topics/my-topic"
80
+ #
81
+ def topic
82
+ Topic.from_name @grpc.topic, service
83
+ end
84
+
85
+ ##
86
+ # The snapshot is guaranteed to exist up until this time.
87
+ # A newly-created snapshot expires no later than 7 days from the time of
88
+ # its creation. Its exact lifetime is determined at creation by the
89
+ # existing backlog in the source subscription. Specifically, the
90
+ # lifetime of the snapshot is 7 days - (age of oldest unacked message in
91
+ # the subscription). For example, consider a subscription whose oldest
92
+ # unacked message is 3 days old. If a snapshot is created from this
93
+ # subscription, the snapshot -- which will always capture this 3-day-old
94
+ # backlog as long as the snapshot exists -- will expire in 4 days.
95
+ #
96
+ # @return [Time] The time until which the snapshot is guaranteed to
97
+ # exist.
98
+ #
99
+ # @example
100
+ # require "google/cloud/pubsub"
101
+ #
102
+ # pubsub = Google::Cloud::PubSub.new
103
+ # sub = pubsub.subscription "my-sub"
104
+ #
105
+ # snapshot = sub.create_snapshot "my-snapshot"
106
+ # snapshot.topic.name #=> "projects/my-project/topics/my-topic"
107
+ # snapshot.expiration_time
108
+ #
109
+ def expiration_time
110
+ self.class.timestamp_from_grpc @grpc.expire_time
111
+ end
112
+
113
+ ##
114
+ # A hash of user-provided labels associated with this snapshot.
115
+ # Labels can be used to organize and group snapshots.See [Creating and
116
+ # Managing Labels](https://cloud.google.com/pubsub/docs/labels).
117
+ #
118
+ # The returned hash is frozen and changes are not allowed. Use
119
+ # {#labels=} to update the labels for this snapshot.
120
+ #
121
+ # @return [Hash] The frozen labels hash.
122
+ #
123
+ def labels
124
+ @grpc.labels.to_h.freeze
125
+ end
126
+
127
+ ##
128
+ # Sets the hash of user-provided labels associated with this
129
+ # snapshot. Labels can be used to organize and group snapshots.
130
+ # Label keys and values can be no longer than 63 characters, can only
131
+ # contain lowercase letters, numeric characters, underscores and dashes.
132
+ # International characters are allowed. Label values are optional. Label
133
+ # keys must start with a letter and each label in the list must have a
134
+ # different key. See [Creating and Managing
135
+ # Labels](https://cloud.google.com/pubsub/docs/labels).
136
+ #
137
+ # @param [Hash] new_labels The new labels hash.
138
+ #
139
+ def labels= new_labels
140
+ raise ArgumentError, "Value must be a Hash" if new_labels.nil?
141
+ labels_map = Google::Protobuf::Map.new :string, :string
142
+ Hash(new_labels).each { |k, v| labels_map[String(k)] = String(v) }
143
+ update_grpc = @grpc.dup
144
+ update_grpc.labels = labels_map
145
+ @grpc = service.update_snapshot update_grpc, :labels
146
+ end
147
+
148
+ ##
149
+ # Removes an existing snapshot. All messages retained in the snapshot
150
+ # are immediately dropped. After a snapshot is deleted, a new one may be
151
+ # created with the same name, but the new one has no association with
152
+ # the old snapshot or its subscription, unless the same subscription is
153
+ # specified.
154
+ #
155
+ # @return [Boolean] Returns `true` if the snapshot was deleted.
156
+ #
157
+ # @example
158
+ # require "google/cloud/pubsub"
159
+ #
160
+ # pubsub = Google::Cloud::PubSub.new
161
+ #
162
+ # pubsub.snapshots.each do |snapshot|
163
+ # snapshot.delete
164
+ # end
165
+ #
166
+ def delete
167
+ ensure_service!
168
+ service.delete_snapshot name
169
+ true
170
+ end
171
+
172
+ ##
173
+ # @private New Snapshot from a Google::Cloud::PubSub::V1::Snapshot
174
+ # object.
175
+ def self.from_grpc grpc, service
176
+ new.tap do |f|
177
+ f.grpc = grpc
178
+ f.service = service
179
+ end
180
+ end
181
+
182
+ ##
183
+ # @private Get a Time object from a Google::Protobuf::Timestamp object.
184
+ def self.timestamp_from_grpc grpc_timestamp
185
+ return nil if grpc_timestamp.nil?
186
+ Time.at grpc_timestamp.seconds, Rational(grpc_timestamp.nanos, 1000)
187
+ end
188
+
189
+ protected
190
+
191
+ ##
192
+ # @private Raise an error unless an active connection to the service is
193
+ # available.
194
+ def ensure_service!
195
+ raise "Must have active connection to service" unless service
196
+ end
197
+ end
198
+ end
199
+
200
+ Pubsub = PubSub unless const_defined? :Pubsub
201
+ end
202
+ end
@@ -0,0 +1,178 @@
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 "delegate"
17
+
18
+ module Google
19
+ module Cloud
20
+ module PubSub
21
+ class Snapshot
22
+ ##
23
+ # Snapshot::List is a special case Array with additional values.
24
+ class List < DelegateClass(::Array)
25
+ ##
26
+ # If not empty, indicates that there are more snapshots
27
+ # that match the request and this value should be passed to
28
+ # the next {Google::Cloud::PubSub::Project#snapshots} to continue.
29
+ attr_accessor :token
30
+
31
+ ##
32
+ # @private Create a new Snapshot::List with an array of values.
33
+ def initialize arr = []
34
+ @prefix = nil
35
+ @token = nil
36
+ @max = nil
37
+ super arr
38
+ end
39
+
40
+ ##
41
+ # Whether there a next page of snapshots.
42
+ #
43
+ # @return [Boolean]
44
+ #
45
+ # @example
46
+ # require "google/cloud/pubsub"
47
+ #
48
+ # pubsub = Google::Cloud::PubSub.new
49
+ #
50
+ # snapshots = pubsub.snapshots
51
+ # if snapshots.next?
52
+ # next_snapshots = snapshots.next
53
+ # end
54
+ #
55
+ def next?
56
+ !token.nil?
57
+ end
58
+
59
+ ##
60
+ # Retrieve the next page of snapshots.
61
+ #
62
+ # @return [Snapshot::List]
63
+ #
64
+ # @example
65
+ # require "google/cloud/pubsub"
66
+ #
67
+ # pubsub = Google::Cloud::PubSub.new
68
+ #
69
+ # snapshots = pubsub.snapshots
70
+ # if snapshots.next?
71
+ # next_snapshots = snapshots.next
72
+ # end
73
+ #
74
+ def next
75
+ return nil unless next?
76
+ ensure_service!
77
+ next_snapshots
78
+ end
79
+
80
+ ##
81
+ # Retrieves remaining results by repeatedly invoking {#next} until
82
+ # {#next?} returns `false`. Calls the given block once for each
83
+ # result, which is passed as the argument to the block.
84
+ #
85
+ # An Enumerator is returned if no block is given.
86
+ #
87
+ # This method will make repeated API calls until all remaining results
88
+ # are retrieved. (Unlike `#each`, for example, which merely iterates
89
+ # over the results returned by a single API call.) Use with caution.
90
+ #
91
+ # @param [Integer] request_limit The upper limit of API requests to
92
+ # make to load all snapshots. Default is no limit.
93
+ # @yield [snapshot] The block for accessing each snapshot.
94
+ # @yieldparam [Snapshot] snapshot The snapshot object.
95
+ #
96
+ # @return [Enumerator]
97
+ #
98
+ # @example Iterating each snapshot by passing a block:
99
+ # require "google/cloud/pubsub"
100
+ #
101
+ # pubsub = Google::Cloud::PubSub.new
102
+ #
103
+ # snapshots = pubsub.snapshots
104
+ # snapshots.all do |snapshot|
105
+ # puts snapshot.name
106
+ # end
107
+ #
108
+ # @example Using the enumerator by not passing a block:
109
+ # require "google/cloud/pubsub"
110
+ #
111
+ # pubsub = Google::Cloud::PubSub.new
112
+ #
113
+ # snapshots = pubsub.snapshots
114
+ # all_names = snapshots.all.map do |snapshot|
115
+ # snapshot.name
116
+ # end
117
+ #
118
+ # @example Limit the number of API calls made:
119
+ # require "google/cloud/pubsub"
120
+ #
121
+ # pubsub = Google::Cloud::PubSub.new
122
+ #
123
+ # snapshots = pubsub.snapshots
124
+ # snapshots.all(request_limit: 10) do |snapshot|
125
+ # puts snapshot.name
126
+ # end
127
+ #
128
+ def all request_limit: nil
129
+ request_limit = request_limit.to_i if request_limit
130
+ return enum_for :all, request_limit: request_limit unless block_given?
131
+ results = self
132
+ loop do
133
+ results.each { |r| yield r }
134
+ if request_limit
135
+ request_limit -= 1
136
+ break if request_limit.negative?
137
+ end
138
+ break unless results.next?
139
+ results = results.next
140
+ end
141
+ end
142
+
143
+ ##
144
+ # @private New Snapshots::List from a
145
+ # Google::Cloud::PubSub::V1::ListSnapshotsRequest object.
146
+ def self.from_grpc grpc_list, service, max = nil
147
+ subs = new(Array(grpc_list.snapshots).map do |grpc|
148
+ Snapshot.from_grpc grpc, service
149
+ end)
150
+ token = grpc_list.next_page_token
151
+ token = nil if token == "".freeze
152
+ subs.instance_variable_set :@token, token
153
+ subs.instance_variable_set :@service, service
154
+ subs.instance_variable_set :@max, max
155
+ subs
156
+ end
157
+
158
+ protected
159
+
160
+ ##
161
+ # @private Raise an error unless an active connection to the service
162
+ # is available.
163
+ def ensure_service!
164
+ raise "Must have active connection to service" unless @service
165
+ end
166
+
167
+ def next_snapshots
168
+ options = { prefix: @prefix, token: @token, max: @max }
169
+ grpc = @service.list_snapshots options
170
+ self.class.from_grpc grpc, @service, @max
171
+ end
172
+ end
173
+ end
174
+ end
175
+
176
+ Pubsub = PubSub unless const_defined? :Pubsub
177
+ end
178
+ end
@@ -0,0 +1,399 @@
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/service"
17
+ require "google/cloud/pubsub/subscriber/stream"
18
+ require "google/cloud/pubsub/subscriber/timed_unary_buffer"
19
+ require "monitor"
20
+
21
+ module Google
22
+ module Cloud
23
+ module PubSub
24
+ ##
25
+ # Subscriber object used to stream and process messages from a
26
+ # Subscription. See {Google::Cloud::PubSub::Subscription#listen}
27
+ #
28
+ # @example
29
+ # require "google/cloud/pubsub"
30
+ #
31
+ # pubsub = Google::Cloud::PubSub.new
32
+ #
33
+ # sub = pubsub.subscription "my-topic-sub"
34
+ #
35
+ # subscriber = sub.listen do |received_message|
36
+ # # process message
37
+ # received_message.acknowledge!
38
+ # end
39
+ #
40
+ # # Start background threads that will call the block passed to listen.
41
+ # subscriber.start
42
+ #
43
+ # # Shut down the subscriber when ready to stop receiving messages.
44
+ # subscriber.stop.wait!
45
+ #
46
+ # @attr_reader [String] subscription_name The name of the subscription the
47
+ # messages are pulled from.
48
+ # @attr_reader [Proc] callback The procedure that will handle the messages
49
+ # received from the subscription.
50
+ # @attr_reader [Numeric] deadline The default number of seconds the stream
51
+ # will hold received messages before modifying the message's ack
52
+ # deadline. The minimum is 10, the maximum is 600. Default is 60.
53
+ # @attr_reader [Boolean] message_ordering Whether message ordering has
54
+ # been enabled.
55
+ # @attr_reader [Integer] streams The number of concurrent streams to open
56
+ # to pull messages from the subscription. Default is 4.
57
+ # @attr_reader [Integer] callback_threads The number of threads used to
58
+ # handle the received messages. Default is 8.
59
+ # @attr_reader [Integer] push_threads The number of threads to handle
60
+ # acknowledgement ({ReceivedMessage#ack!}) and delay messages
61
+ # ({ReceivedMessage#nack!}, {ReceivedMessage#modify_ack_deadline!}).
62
+ # Default is 4.
63
+ #
64
+ class Subscriber
65
+ include MonitorMixin
66
+
67
+ attr_reader :subscription_name, :callback, :deadline, :streams, :message_ordering, :callback_threads,
68
+ :push_threads
69
+
70
+ ##
71
+ # @private Implementation attributes.
72
+ attr_reader :stream_pool, :thread_pool, :buffer, :service
73
+
74
+ ##
75
+ # @private Create an empty {Subscriber} object.
76
+ def initialize subscription_name, callback, deadline: nil, message_ordering: nil, streams: nil, inventory: nil,
77
+ threads: {}, service: nil
78
+ super() # to init MonitorMixin
79
+
80
+ @callback = callback
81
+ @error_callbacks = []
82
+ @subscription_name = subscription_name
83
+ @deadline = deadline || 60
84
+ @streams = streams || 2
85
+ coerce_inventory inventory
86
+ @message_ordering = message_ordering
87
+ @callback_threads = Integer(threads[:callback] || 8)
88
+ @push_threads = Integer(threads[:push] || 4)
89
+
90
+ @service = service
91
+
92
+ @started = @stopped = nil
93
+
94
+ stream_pool = Array.new @streams do
95
+ Thread.new { Stream.new self }
96
+ end
97
+ @stream_pool = stream_pool.map(&:value)
98
+
99
+ @buffer = TimedUnaryBuffer.new self
100
+ end
101
+
102
+ ##
103
+ # Starts the subscriber pulling from the subscription and processing the
104
+ # received messages.
105
+ #
106
+ # @return [Subscriber] returns self so calls can be chained.
107
+ #
108
+ def start
109
+ start_pool = synchronize do
110
+ @started = true
111
+ @stopped = false
112
+
113
+ # Start the buffer before the streams are all started
114
+ @buffer.start
115
+ @stream_pool.map do |stream|
116
+ Thread.new { stream.start }
117
+ end
118
+ end
119
+ start_pool.map(&:join)
120
+
121
+ self
122
+ end
123
+
124
+ ##
125
+ # Immediately stops the subscriber. No new messages will be pulled from
126
+ # the subscription. All actions taken on received messages that have not
127
+ # yet been sent to the API will be sent to the API. All received but
128
+ # unprocessed messages will be released back to the API and redelivered.
129
+ # Use {#wait!} to block until the subscriber is fully stopped and all
130
+ # received messages have been processed or released.
131
+ #
132
+ # @return [Subscriber] returns self so calls can be chained.
133
+ #
134
+ def stop
135
+ stop_pool = synchronize do
136
+ @started = false
137
+ @stopped = true
138
+
139
+ @stream_pool.map do |stream|
140
+ Thread.new { stream.stop }
141
+ end
142
+ end
143
+ stop_pool.map(&:join)
144
+ # Stop the buffer after the streams are all stopped
145
+ synchronize { @buffer.stop }
146
+
147
+ self
148
+ end
149
+
150
+ ##
151
+ # Blocks until the subscriber is fully stopped and all received messages
152
+ # have been processed or released, or until `timeout` seconds have
153
+ # passed.
154
+ #
155
+ # Does not stop the subscriber. To stop the subscriber, first call
156
+ # {#stop} and then call {#wait!} to block until the subscriber is
157
+ # stopped.
158
+ #
159
+ # @param [Number, nil] timeout The number of seconds to block until the
160
+ # subscriber is fully stopped. Default will block indefinitely.
161
+ #
162
+ # @return [Subscriber] returns self so calls can be chained.
163
+ #
164
+ def wait! timeout = nil
165
+ wait_pool = synchronize do
166
+ @stream_pool.map do |stream|
167
+ Thread.new { stream.wait! timeout }
168
+ end
169
+ end
170
+ wait_pool.map(&:join)
171
+
172
+ self
173
+ end
174
+
175
+ ##
176
+ # Stop this subscriber and block until the subscriber is fully stopped
177
+ # and all received messages have been processed or released, or until
178
+ # `timeout` seconds have passed.
179
+ #
180
+ # The same as calling {#stop} and {#wait!}.
181
+ #
182
+ # @param [Number, nil] timeout The number of seconds to block until the
183
+ # subscriber is fully stopped. Default will block indefinitely.
184
+ #
185
+ # @return [Subscriber] returns self so calls can be chained.
186
+ #
187
+ def stop! timeout = nil
188
+ stop
189
+ wait! timeout
190
+ end
191
+
192
+ ##
193
+ # Whether the subscriber has been started.
194
+ #
195
+ # @return [boolean] `true` when started, `false` otherwise.
196
+ #
197
+ def started?
198
+ synchronize { @started }
199
+ end
200
+
201
+ ##
202
+ # Whether the subscriber has been stopped.
203
+ #
204
+ # @return [boolean] `true` when stopped, `false` otherwise.
205
+ #
206
+ def stopped?
207
+ synchronize { @stopped }
208
+ end
209
+
210
+ ##
211
+ # Register to be notified of errors when raised.
212
+ #
213
+ # If an unhandled error has occurred the subscriber will attempt to
214
+ # recover from the error and resume listening.
215
+ #
216
+ # Multiple error handlers can be added.
217
+ #
218
+ # @yield [callback] The block to be called when an error is raised.
219
+ # @yieldparam [Exception] error The error raised.
220
+ #
221
+ # @example
222
+ # require "google/cloud/pubsub"
223
+ #
224
+ # pubsub = Google::Cloud::PubSub.new
225
+ #
226
+ # sub = pubsub.subscription "my-topic-sub"
227
+ #
228
+ # subscriber = sub.listen do |received_message|
229
+ # # process message
230
+ # received_message.acknowledge!
231
+ # end
232
+ #
233
+ # # Register to be notified when unhandled errors occur.
234
+ # subscriber.on_error do |error|
235
+ # # log error
236
+ # puts error
237
+ # end
238
+ #
239
+ # # Start listening for messages and errors.
240
+ # subscriber.start
241
+ #
242
+ # # Shut down the subscriber when ready to stop receiving messages.
243
+ # subscriber.stop.wait!
244
+ #
245
+ def on_error &block
246
+ synchronize do
247
+ @error_callbacks << block
248
+ end
249
+ end
250
+
251
+ ##
252
+ # The most recent unhandled error to occur while listening to messages
253
+ # on the subscriber.
254
+ #
255
+ # If an unhandled error has occurred the subscriber will attempt to
256
+ # recover from the error and resume listening.
257
+ #
258
+ # @return [Exception, nil] error The most recent error raised.
259
+ #
260
+ # @example
261
+ # require "google/cloud/pubsub"
262
+ #
263
+ # pubsub = Google::Cloud::PubSub.new
264
+ #
265
+ # sub = pubsub.subscription "my-topic-sub"
266
+ #
267
+ # subscriber = sub.listen do |received_message|
268
+ # # process message
269
+ # received_message.acknowledge!
270
+ # end
271
+ #
272
+ # # Start listening for messages and errors.
273
+ # subscriber.start
274
+ #
275
+ # # If an error was raised, it can be retrieved here:
276
+ # subscriber.last_error #=> nil
277
+ #
278
+ # # Shut down the subscriber when ready to stop receiving messages.
279
+ # subscriber.stop.wait!
280
+ #
281
+ def last_error
282
+ synchronize { @last_error }
283
+ end
284
+
285
+ ##
286
+ # The number of received messages to be collected by subscriber. Default is 1,000.
287
+ #
288
+ # @return [Integer] The maximum number of messages.
289
+ #
290
+ def max_outstanding_messages
291
+ @inventory[:max_outstanding_messages]
292
+ end
293
+ # @deprecated Use {#max_outstanding_messages}.
294
+ alias inventory_limit max_outstanding_messages
295
+ # @deprecated Use {#max_outstanding_messages}.
296
+ alias inventory max_outstanding_messages
297
+
298
+ ##
299
+ # The total byte size of received messages to be collected by subscriber. Default is 100,000,000 (100MB).
300
+ #
301
+ # @return [Integer] The maximum number of bytes.
302
+ #
303
+ def max_outstanding_bytes
304
+ @inventory[:max_outstanding_bytes]
305
+ end
306
+ # @deprecated Use {#max_outstanding_bytes}.
307
+ alias inventory_bytesize max_outstanding_bytes
308
+
309
+ ##
310
+ # The number of seconds that received messages can be held awaiting processing. Default is 3,600 (1 hour).
311
+ #
312
+ # @return [Integer] The maximum number of seconds.
313
+ #
314
+ def max_total_lease_duration
315
+ @inventory[:max_total_lease_duration]
316
+ end
317
+ # @deprecated Use {#max_total_lease_duration}.
318
+ alias inventory_extension max_total_lease_duration
319
+
320
+ ##
321
+ # The maximum amount of time in seconds for a single lease extension attempt. Bounds the delay before a message
322
+ # redelivery if the subscriber fails to extend the deadline. Default is 0 (disabled).
323
+ #
324
+ # @return [Integer] The maximum number of seconds.
325
+ #
326
+ def max_duration_per_lease_extension
327
+ @inventory[:max_duration_per_lease_extension]
328
+ end
329
+
330
+ ##
331
+ # @private
332
+ def stream_inventory
333
+ {
334
+ limit: @inventory[:max_outstanding_messages].fdiv(@streams).ceil,
335
+ bytesize: @inventory[:max_outstanding_bytes].fdiv(@streams).ceil,
336
+ extension: @inventory[:max_total_lease_duration],
337
+ max_duration_per_lease_extension: @inventory[:max_duration_per_lease_extension]
338
+ }
339
+ end
340
+
341
+ # @private returns error object from the stream thread.
342
+ def error! error
343
+ error_callbacks = synchronize do
344
+ @last_error = error
345
+ @error_callbacks
346
+ end
347
+ error_callbacks = default_error_callbacks if error_callbacks.empty?
348
+ error_callbacks.each { |error_callback| error_callback.call error }
349
+ end
350
+
351
+ ##
352
+ # @private
353
+ def to_s
354
+ "(subscription: #{subscription_name}, streams: [#{stream_pool.map(&:to_s).join(', ')}])"
355
+ end
356
+
357
+ ##
358
+ # @private
359
+ def inspect
360
+ "#<#{self.class.name} #{self}>"
361
+ end
362
+
363
+ protected
364
+
365
+ def coerce_inventory inventory
366
+ @inventory = inventory
367
+ if @inventory.is_a? Hash
368
+ @inventory = @inventory.dup
369
+ # Support deprecated field names
370
+ @inventory[:max_outstanding_messages] ||= @inventory.delete :limit
371
+ @inventory[:max_outstanding_bytes] ||= @inventory.delete :bytesize
372
+ @inventory[:max_total_lease_duration] ||= @inventory.delete :extension
373
+ else
374
+ @inventory = { max_outstanding_messages: @inventory }
375
+ end
376
+ @inventory[:max_outstanding_messages] = Integer(@inventory[:max_outstanding_messages] || 1000)
377
+ @inventory[:max_outstanding_bytes] = Integer(@inventory[:max_outstanding_bytes] || 100_000_000)
378
+ @inventory[:max_total_lease_duration] = Integer(@inventory[:max_total_lease_duration] || 3600)
379
+ @inventory[:max_duration_per_lease_extension] = Integer(@inventory[:max_duration_per_lease_extension] || 0)
380
+ end
381
+
382
+ def default_error_callbacks
383
+ # This is memoized to reduce calls to the configuration.
384
+ @default_error_callbacks ||= begin
385
+ error_callback = Google::Cloud::PubSub.configure.on_error
386
+ error_callback ||= Google::Cloud.configure.on_error
387
+ if error_callback
388
+ [error_callback]
389
+ else
390
+ []
391
+ end
392
+ end
393
+ end
394
+ end
395
+ end
396
+
397
+ Pubsub = PubSub unless const_defined? :Pubsub
398
+ end
399
+ end