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,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