google-cloud-pubsub 0.30.2 → 0.31.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8bc2cbfb8b149d600fbdf725934ccef18019853c3c371d34d964a47532640b33
4
- data.tar.gz: 836fae2f33841cd070137fab258d208b456018214a25335f6e7848b1c9e0270b
3
+ metadata.gz: 3817e592071ccc0badbc1c511c848341de65d0e5fb4b9feba8c528074cd71724
4
+ data.tar.gz: 9e789ffcc98b3672040ff67f5e198f6f214cde83a205d6893982ca87b5e99e2b
5
5
  SHA512:
6
- metadata.gz: 55cf18d0b63d2f68f28db33f85dd21ded29e960e3cd871278b9abfedfa86829b49e3ea6ba96a7ad1fad15f96da65a2d75fb3f9c2dcd59025f1aba753878a79ca
7
- data.tar.gz: ce9772ea6d63bc664739c58ee56c32f84c44f9662c6ba9800a9aacd1a7e09eea9757649ca3290f8db894a07a4401ec4fd9f9573d5c4c7230b8e91ac0141c5fb1
6
+ metadata.gz: 52e115e0f6bf8968976a853064e30088374ea1836410785e3042fe00324f065e085d54e7b1502a34cd8fde8dc4395b901a95293856846f8b5b6f0af421c11586
7
+ data.tar.gz: 2cf2bf6b75e926fcb0fda9ba22cae4a49a75b0fc7d408faf61ecc90fbd5a424e496369780eadee1b3fa90a448b37052933d70ee6b790dbed81ecbaed18380bae
data/README.md CHANGED
@@ -50,6 +50,28 @@ subscriber.start
50
50
  subscriber.stop.wait!
51
51
  ```
52
52
 
53
+ ## Enabling Logging
54
+
55
+ To enable logging for this library, set the logger for the underlying [gRPC](https://github.com/grpc/grpc/tree/master/src/ruby) library. The logger that you set may be a Ruby stdlib [`Logger`](https://ruby-doc.org/stdlib-2.5.0/libdoc/logger/rdoc/Logger.html) as shown below, or a [`Google::Cloud::Logging::Logger`](https://googlecloudplatform.github.io/google-cloud-ruby/#/docs/google-cloud-logging/latest/google/cloud/logging/logger) that will write logs to [Stackdriver Logging](https://cloud.google.com/logging/). See [grpc/logconfig.rb](https://github.com/grpc/grpc/blob/master/src/ruby/lib/grpc/logconfig.rb) and the gRPC [spec_helper.rb](https://github.com/grpc/grpc/blob/master/src/ruby/spec/spec_helper.rb) for additional information.
56
+
57
+ Configuring a Ruby stdlib logger:
58
+
59
+ ```ruby
60
+ require "logger"
61
+
62
+ module MyLogger
63
+ LOGGER = Logger.new $stderr, level: Logger::WARN
64
+ def logger
65
+ LOGGER
66
+ end
67
+ end
68
+
69
+ # Define a gRPC module-level logger method before grpc/logconfig.rb loads.
70
+ module GRPC
71
+ extend MyLogger
72
+ end
73
+ ```
74
+
53
75
  ## Supported Ruby Versions
54
76
 
55
77
  This library is supported on Ruby 2.0+.
@@ -51,6 +51,39 @@ module Google
51
51
  # To learn more about Pub/Sub, read the [Google Cloud Pub/Sub Overview
52
52
  # ](https://cloud.google.com/pubsub/overview).
53
53
  #
54
+ # ## Enabling Logging
55
+ #
56
+ # To enable logging for this library, set the logger for the underlying
57
+ # [gRPC](https://github.com/grpc/grpc/tree/master/src/ruby) library. The
58
+ # logger that you set may be a Ruby stdlib
59
+ # [`Logger`](https://ruby-doc.org/stdlib-2.5.0/libdoc/logger/rdoc/Logger.html)
60
+ # as shown below, or a
61
+ # [`Google::Cloud::Logging::Logger`](https://googlecloudplatform.github.io/google-cloud-ruby/#/docs/google-cloud-logging/latest/google/cloud/logging/logger)
62
+ # that will write logs to [Stackdriver
63
+ # Logging](https://cloud.google.com/logging/). See
64
+ # [grpc/logconfig.rb](https://github.com/grpc/grpc/blob/master/src/ruby/lib/grpc/logconfig.rb)
65
+ # and the gRPC
66
+ # [spec_helper.rb](https://github.com/grpc/grpc/blob/master/src/ruby/spec/spec_helper.rb)
67
+ # for additional information.
68
+ #
69
+ # Configuring a Ruby stdlib logger:
70
+ #
71
+ # ```ruby
72
+ # require "logger"
73
+ #
74
+ # module MyLogger
75
+ # LOGGER = Logger.new $stderr, level: Logger::WARN
76
+ # def logger
77
+ # LOGGER
78
+ # end
79
+ # end
80
+ #
81
+ # # Define a gRPC module-level logger method before grpc/logconfig.rb loads.
82
+ # module GRPC
83
+ # extend MyLogger
84
+ # end
85
+ # ```
86
+ #
54
87
  # ## Retrieving Topics
55
88
  #
56
89
  # A Topic is a named resource to which messages are sent by publishers.
@@ -267,7 +267,8 @@ module Google
267
267
  data = data.read
268
268
  end
269
269
  # Convert data to encoded byte array to match the protobuf defn
270
- data_bytes = String(data).dup.force_encoding("ASCII-8BIT").freeze
270
+ data_bytes = \
271
+ String(data).dup.force_encoding(Encoding::ASCII_8BIT).freeze
271
272
 
272
273
  # Convert attributes to strings to match the protobuf definition
273
274
  attributes = Hash[attributes.map { |k, v| [String(k), String(v)] }]
@@ -84,7 +84,8 @@ module Google
84
84
  data = data.read
85
85
  end
86
86
  # Convert data to encoded byte array to match the protobuf defn
87
- data_bytes = String(data).dup.force_encoding("ASCII-8BIT").freeze
87
+ data_bytes = \
88
+ String(data).dup.force_encoding(Encoding::ASCII_8BIT).freeze
88
89
 
89
90
  # Convert attributes to strings to match the protobuf definition
90
91
  attributes = Hash[attributes.map { |k, v| [String(k), String(v)] }]
@@ -65,7 +65,7 @@ module Google
65
65
  attributes = Hash[attributes.map { |k, v| [String(k), String(v)] }]
66
66
 
67
67
  @grpc = Google::Pubsub::V1::PubsubMessage.new(
68
- data: String(data).encode("ASCII-8BIT"),
68
+ data: String(data).dup.force_encoding(Encoding::ASCII_8BIT),
69
69
  attributes: attributes
70
70
  )
71
71
  end
@@ -104,7 +104,7 @@ module Google
104
104
  # @private New Message from a Google::Pubsub::V1::PubsubMessage object.
105
105
  def self.from_grpc grpc
106
106
  new.tap do |m|
107
- m.instance_variable_set "@grpc", grpc
107
+ m.instance_variable_set :@grpc, grpc
108
108
  end
109
109
  end
110
110
  end
@@ -150,10 +150,10 @@ module Google
150
150
  Snapshot.from_grpc grpc, service
151
151
  end)
152
152
  token = grpc_list.next_page_token
153
- token = nil if token == ""
154
- subs.instance_variable_set "@token", token
155
- subs.instance_variable_set "@service", service
156
- subs.instance_variable_set "@max", max
153
+ token = nil if token == "".freeze
154
+ subs.instance_variable_set :@token, token
155
+ subs.instance_variable_set :@service, service
156
+ subs.instance_variable_set :@max, max
157
157
  subs
158
158
  end
159
159
 
@@ -22,9 +22,9 @@ module Google
22
22
  class Subscriber
23
23
  ##
24
24
  # @private
25
- # # AsyncPusher
25
+ # # AsyncStreamPusher
26
26
  #
27
- class AsyncPusher
27
+ class AsyncStreamPusher
28
28
  include MonitorMixin
29
29
 
30
30
  attr_reader :batch
@@ -108,9 +108,7 @@ module Google
108
108
  @background_thread.kill if @background_thread
109
109
  end
110
110
 
111
- return nil if @batch.nil?
112
-
113
- @batch.request
111
+ push_batch_request!
114
112
  end
115
113
 
116
114
  def started?
@@ -131,14 +129,14 @@ module Google
131
129
  next
132
130
  end
133
131
 
134
- time_since_first_publish = Time.now - @batch_created_at
135
- if time_since_first_publish > @interval
132
+ time_since_batch_creation = Time.now - @batch_created_at
133
+ if time_since_batch_creation > @interval
136
134
  # interval met, publish the batch...
137
135
  push_batch_request!
138
136
  @cond.wait
139
137
  else
140
138
  # still waiting for the interval to publish the batch...
141
- @cond.wait(@interval - time_since_first_publish)
139
+ @cond.wait(@interval - time_since_batch_creation)
142
140
  end
143
141
  end
144
142
  end
@@ -147,10 +145,7 @@ module Google
147
145
  def push_batch_request!
148
146
  return unless @batch
149
147
 
150
- request = @batch.request
151
- Concurrent::Future.new(executor: @stream.push_thread_pool) do
152
- @stream.push request
153
- end.execute
148
+ @stream.push @batch.request
154
149
 
155
150
  @batch = nil
156
151
  @batch_created_at = nil
@@ -0,0 +1,251 @@
1
+ # Copyright 2018 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
+ require "concurrent"
18
+
19
+ module Google
20
+ module Cloud
21
+ module Pubsub
22
+ class Subscriber
23
+ ##
24
+ # @private
25
+ # # AsyncUnaryPusher
26
+ #
27
+ class AsyncUnaryPusher
28
+ include MonitorMixin
29
+
30
+ attr_reader :batch
31
+ attr_reader :max_bytes, :interval
32
+
33
+ def initialize stream, max_bytes: 10000000, interval: 1.0
34
+ @stream = stream
35
+
36
+ @max_bytes = max_bytes
37
+ @interval = interval
38
+
39
+ @cond = new_cond
40
+
41
+ # init MonitorMixin
42
+ super()
43
+ end
44
+
45
+ def acknowledge ack_ids
46
+ return true if ack_ids.empty?
47
+
48
+ synchronize do
49
+ ack_ids.each do |ack_id|
50
+ if @batch.nil?
51
+ @batch = Batch.new max_bytes: @max_bytes
52
+ @batch.ack ack_id
53
+ else
54
+ unless @batch.try_ack ack_id
55
+ push_batch_request!
56
+
57
+ @batch = Batch.new max_bytes: @max_bytes
58
+ @batch.ack ack_id
59
+ end
60
+ end
61
+
62
+ @batch_created_at ||= Time.now
63
+ @background_thread ||= Thread.new { run_background }
64
+
65
+ push_batch_request! if @batch.ready?
66
+ end
67
+
68
+ @cond.signal
69
+ end
70
+
71
+ nil
72
+ end
73
+
74
+ def delay deadline, ack_ids
75
+ return true if ack_ids.empty?
76
+
77
+ synchronize do
78
+ ack_ids.each do |ack_id|
79
+ if @batch.nil?
80
+ @batch = Batch.new max_bytes: @max_bytes
81
+ @batch.delay deadline, ack_id
82
+ else
83
+ unless @batch.try_delay deadline, ack_id
84
+ push_batch_request!
85
+
86
+ @batch = Batch.new max_bytes: @max_bytes
87
+ @batch.delay deadline, ack_id
88
+ end
89
+ end
90
+
91
+ @batch_created_at ||= Time.now
92
+ @background_thread ||= Thread.new { run_background }
93
+
94
+ push_batch_request! if @batch.ready?
95
+ end
96
+
97
+ @cond.signal
98
+ end
99
+
100
+ nil
101
+ end
102
+
103
+ def stop
104
+ synchronize do
105
+ @stopped = true
106
+
107
+ # Stop any background activity, clean up happens in wait!
108
+ @background_thread.kill if @background_thread
109
+ end
110
+
111
+ push_batch_request!
112
+ end
113
+
114
+ def started?
115
+ !stopped?
116
+ end
117
+
118
+ def stopped?
119
+ synchronize { @stopped }
120
+ end
121
+
122
+ protected
123
+
124
+ def run_background
125
+ synchronize do
126
+ until @stopped
127
+ if @batch.nil?
128
+ @cond.wait
129
+ next
130
+ end
131
+
132
+ time_since_batch_creation = Time.now - @batch_created_at
133
+ if time_since_batch_creation > @interval
134
+ # interval met, publish the batch...
135
+ push_batch_request!
136
+ @cond.wait
137
+ else
138
+ # still waiting for the interval to publish the batch...
139
+ @cond.wait(@interval - time_since_batch_creation)
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ def push_batch_request!
146
+ return unless @batch
147
+
148
+ service = @stream.subscriber.service
149
+ name = @stream.subscriber.subscription_name
150
+
151
+ if @batch.ack?
152
+ ack_ids = @batch.ack_ids
153
+ Concurrent::Future.new(executor: @stream.push_thread_pool) do
154
+ service.acknowledge name, *ack_ids
155
+ end.execute
156
+ end
157
+ if @batch.delay?
158
+ @batch.modify_deadline_hash.each do |delay_seconds, delay_ack_ids|
159
+ Concurrent::Future.new(executor: @stream.push_thread_pool) do
160
+ service.modify_ack_deadline name, delay_ack_ids, delay_seconds
161
+ end.execute
162
+ end
163
+ end
164
+
165
+ @batch = nil
166
+ @batch_created_at = nil
167
+ end
168
+
169
+ class Batch
170
+ attr_reader :max_bytes, :request
171
+
172
+ def initialize max_bytes: 10000000
173
+ @max_bytes = max_bytes
174
+ @request = Google::Pubsub::V1::StreamingPullRequest.new
175
+ @total_message_bytes = 0
176
+ end
177
+
178
+ def ack ack_id
179
+ @request.ack_ids << ack_id
180
+ @total_message_bytes += addl_ack_bytes ack_id
181
+ end
182
+
183
+ def try_ack ack_id
184
+ addl_bytes = addl_ack_bytes ack_id
185
+ return false if total_message_bytes + addl_bytes >= @max_bytes
186
+
187
+ ack ack_id
188
+ true
189
+ end
190
+
191
+ def addl_ack_bytes ack_id
192
+ ack_id.bytesize + 2
193
+ end
194
+
195
+ def delay deadline, ack_id
196
+ @request.modify_deadline_seconds << deadline
197
+ @request.modify_deadline_ack_ids << ack_id
198
+ @total_message_bytes += addl_delay_bytes deadline, ack_id
199
+ end
200
+
201
+ def try_delay deadline, ack_id
202
+ addl_bytes = addl_delay_bytes deadline, ack_id
203
+ return false if total_message_bytes + addl_bytes >= @max_bytes
204
+
205
+ delay deadline, ack_id
206
+ true
207
+ end
208
+
209
+ def addl_delay_bytes deadline, ack_id
210
+ bytes_for_int(deadline) + ack_id.bytesize + 4
211
+ end
212
+
213
+ def bytes_for_int num
214
+ # Ruby 2.0 does not have Integer#bit_length
215
+ return [num].pack("s").bytesize unless num.respond_to? :bit_length
216
+
217
+ (num.bit_length / 8.0).ceil
218
+ end
219
+
220
+ def ready?
221
+ total_message_bytes >= @max_bytes
222
+ end
223
+
224
+ def ack?
225
+ @request.ack_ids.any?
226
+ end
227
+
228
+ def delay?
229
+ @request.modify_deadline_ack_ids.any?
230
+ end
231
+
232
+ def ack_ids
233
+ @request.ack_ids
234
+ end
235
+
236
+ def modify_deadline_hash
237
+ grouped_hash = @request.modify_deadline_ack_ids.zip(
238
+ @request.modify_deadline_seconds
239
+ ).group_by { |_ack_id, seconds| seconds }
240
+ Hash[grouped_hash.map { |k, v| [k, v.map(&:first)] }]
241
+ end
242
+
243
+ def total_message_bytes
244
+ @total_message_bytes
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
@@ -13,7 +13,7 @@
13
13
  # limitations under the License.
14
14
 
15
15
 
16
- require "google/cloud/pubsub/subscriber/async_pusher"
16
+ require "google/cloud/pubsub/subscriber/async_unary_pusher"
17
17
  require "google/cloud/pubsub/subscriber/enumerator_queue"
18
18
  require "google/cloud/pubsub/service"
19
19
  require "google/cloud/errors"
@@ -53,6 +53,15 @@ module Google
53
53
  @push_thread_pool = Concurrent::FixedThreadPool.new \
54
54
  subscriber.push_threads
55
55
 
56
+ @stream_keepalive_task = Concurrent::TimerTask.new(
57
+ execution_interval: 30
58
+ ) do
59
+ # push empty request every 30 seconds to keep stream alive
60
+ unless inventory.empty?
61
+ push Google::Pubsub::V1::StreamingPullRequest.new
62
+ end
63
+ end.execute
64
+
56
65
  super() # to init MonitorMixin
57
66
  end
58
67
 
@@ -98,14 +107,7 @@ module Google
98
107
 
99
108
  # Once all the callbacks are complete, we can stop the publisher
100
109
  # and send the final request to the steeam.
101
- if @async_pusher
102
- request = @async_pusher.stop
103
- if request
104
- Concurrent::Future.new(executor: @push_thread_pool) do
105
- @request_queue.push request
106
- end.execute
107
- end
108
- end
110
+ @async_pusher.stop if @async_pusher # will push current batch
109
111
 
110
112
  # Close the push thread pool now that the pusher is closed.
111
113
  @push_thread_pool.shutdown
@@ -125,7 +127,7 @@ module Google
125
127
  return true if ack_ids.empty?
126
128
 
127
129
  synchronize do
128
- @async_pusher ||= AsyncPusher.new self
130
+ @async_pusher ||= AsyncUnaryPusher.new self
129
131
  @async_pusher.acknowledge ack_ids
130
132
  @inventory.remove ack_ids
131
133
  unpause_streaming!
@@ -141,7 +143,7 @@ module Google
141
143
  return true if mod_ack_ids.empty?
142
144
 
143
145
  synchronize do
144
- @async_pusher ||= AsyncPusher.new self
146
+ @async_pusher ||= AsyncUnaryPusher.new self
145
147
  @async_pusher.delay deadline, mod_ack_ids
146
148
  @inventory.remove mod_ack_ids
147
149
  unpause_streaming!
@@ -168,7 +170,7 @@ module Google
168
170
  synchronize do
169
171
  return true if @inventory.empty?
170
172
 
171
- @async_pusher ||= AsyncPusher.new self
173
+ @async_pusher ||= AsyncUnaryPusher.new self
172
174
  @async_pusher.delay subscriber.deadline, @inventory.ack_ids
173
175
  end
174
176
 
@@ -211,7 +213,7 @@ module Google
211
213
 
212
214
  synchronize do
213
215
  # Create receipt of received messages reception
214
- @async_pusher ||= AsyncPusher.new self
216
+ @async_pusher ||= AsyncUnaryPusher.new self
215
217
  @async_pusher.delay subscriber.deadline, received_ack_ids
216
218
 
217
219
  # Add received messages to inventory
@@ -155,10 +155,10 @@ module Google
155
155
  Subscription.from_grpc grpc, service
156
156
  end)
157
157
  token = grpc_list.next_page_token
158
- token = nil if token == ""
159
- subs.instance_variable_set "@token", token
160
- subs.instance_variable_set "@service", service
161
- subs.instance_variable_set "@max", max
158
+ token = nil if token == "".freeze
159
+ subs.instance_variable_set :@token, token
160
+ subs.instance_variable_set :@service, service
161
+ subs.instance_variable_set :@max, max
162
162
  subs
163
163
  end
164
164
 
@@ -170,11 +170,11 @@ module Google
170
170
  Subscription.new_lazy grpc, service
171
171
  end)
172
172
  token = grpc_list.next_page_token
173
- token = nil if token == ""
174
- subs.instance_variable_set "@token", token
175
- subs.instance_variable_set "@service", service
176
- subs.instance_variable_set "@topic", topic
177
- subs.instance_variable_set "@max", max
173
+ token = nil if token == "".freeze
174
+ subs.instance_variable_set :@token, token
175
+ subs.instance_variable_set :@service, service
176
+ subs.instance_variable_set :@topic, topic
177
+ subs.instance_variable_set :@max, max
178
178
  subs
179
179
  end
180
180
 
@@ -149,10 +149,10 @@ module Google
149
149
  Topic.from_grpc grpc, service
150
150
  end)
151
151
  token = grpc_list.next_page_token
152
- token = nil if token == ""
153
- topics.instance_variable_set "@token", token
154
- topics.instance_variable_set "@service", service
155
- topics.instance_variable_set "@max", max
152
+ token = nil if token == "".freeze
153
+ topics.instance_variable_set :@token, token
154
+ topics.instance_variable_set :@service, service
155
+ topics.instance_variable_set :@max, max
156
156
  topics
157
157
  end
158
158
 
@@ -17,7 +17,7 @@ module Google
17
17
  # rubocop:disable LineLength
18
18
 
19
19
  ##
20
- # # Ruby Client for Google Cloud Pub/Sub API ([Alpha](https://github.com/GoogleCloudPlatform/google-cloud-ruby#versioning))
20
+ # # Ruby Client for Google Cloud Pub/Sub API ([Beta](https://github.com/GoogleCloudPlatform/google-cloud-ruby#versioning))
21
21
  #
22
22
  # [Google Cloud Pub/Sub API][Product Documentation]:
23
23
  # Provides reliable, many-to-many, asynchronous messaging between applications.
@@ -72,4 +72,4 @@ module Google
72
72
  end
73
73
  end
74
74
  end
75
- end
75
+ end
@@ -16,7 +16,7 @@
16
16
  module Google
17
17
  module Cloud
18
18
  module Pubsub
19
- VERSION = "0.30.2".freeze
19
+ VERSION = "0.31.1".freeze
20
20
  end
21
21
  end
22
22
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: google-cloud-pubsub
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.30.2
4
+ version: 0.31.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Moore
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-04-02 00:00:00.000000000 Z
12
+ date: 2018-08-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: google-cloud-core
@@ -219,7 +219,8 @@ files:
219
219
  - lib/google/cloud/pubsub/snapshot.rb
220
220
  - lib/google/cloud/pubsub/snapshot/list.rb
221
221
  - lib/google/cloud/pubsub/subscriber.rb
222
- - lib/google/cloud/pubsub/subscriber/async_pusher.rb
222
+ - lib/google/cloud/pubsub/subscriber/async_stream_pusher.rb
223
+ - lib/google/cloud/pubsub/subscriber/async_unary_pusher.rb
223
224
  - lib/google/cloud/pubsub/subscriber/enumerator_queue.rb
224
225
  - lib/google/cloud/pubsub/subscriber/stream.rb
225
226
  - lib/google/cloud/pubsub/subscription.rb
@@ -259,7 +260,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
259
260
  version: '0'
260
261
  requirements: []
261
262
  rubyforge_project:
262
- rubygems_version: 2.7.6
263
+ rubygems_version: 2.7.7
263
264
  signing_key:
264
265
  specification_version: 4
265
266
  summary: API Client library for Google Cloud Pub/Sub