google-cloud-pubsub 1.1.3 → 2.0.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHENTICATION.md +2 -1
  3. data/CHANGELOG.md +145 -0
  4. data/EMULATOR.md +1 -1
  5. data/TROUBLESHOOTING.md +2 -8
  6. data/lib/google/cloud/pubsub/async_publisher.rb +16 -21
  7. data/lib/google/cloud/pubsub/credentials.rb +2 -2
  8. data/lib/google/cloud/pubsub/project.rb +18 -26
  9. data/lib/google/cloud/pubsub/received_message.rb +38 -0
  10. data/lib/google/cloud/pubsub/retry_policy.rb +90 -0
  11. data/lib/google/cloud/pubsub/service.rb +125 -252
  12. data/lib/google/cloud/pubsub/subscriber/inventory.rb +43 -15
  13. data/lib/google/cloud/pubsub/subscriber/stream.rb +8 -10
  14. data/lib/google/cloud/pubsub/subscriber.rb +86 -15
  15. data/lib/google/cloud/pubsub/subscription/push_config.rb +2 -2
  16. data/lib/google/cloud/pubsub/subscription.rb +297 -7
  17. data/lib/google/cloud/pubsub/topic.rb +65 -2
  18. data/lib/google/cloud/pubsub/version.rb +1 -1
  19. data/lib/google/cloud/pubsub.rb +15 -18
  20. data/lib/google-cloud-pubsub.rb +13 -13
  21. metadata +11 -81
  22. data/lib/google/cloud/pubsub/v1/credentials.rb +0 -41
  23. data/lib/google/cloud/pubsub/v1/doc/google/iam/v1/iam_policy.rb +0 -21
  24. data/lib/google/cloud/pubsub/v1/doc/google/iam/v1/options.rb +0 -21
  25. data/lib/google/cloud/pubsub/v1/doc/google/iam/v1/policy.rb +0 -21
  26. data/lib/google/cloud/pubsub/v1/doc/google/protobuf/duration.rb +0 -91
  27. data/lib/google/cloud/pubsub/v1/doc/google/protobuf/empty.rb +0 -29
  28. data/lib/google/cloud/pubsub/v1/doc/google/protobuf/field_mask.rb +0 -222
  29. data/lib/google/cloud/pubsub/v1/doc/google/protobuf/timestamp.rb +0 -113
  30. data/lib/google/cloud/pubsub/v1/doc/google/pubsub/v1/pubsub.rb +0 -744
  31. data/lib/google/cloud/pubsub/v1/doc/google/type/expr.rb +0 -19
  32. data/lib/google/cloud/pubsub/v1/publisher_client.rb +0 -786
  33. data/lib/google/cloud/pubsub/v1/publisher_client_config.json +0 -105
  34. data/lib/google/cloud/pubsub/v1/subscriber_client.rb +0 -1385
  35. data/lib/google/cloud/pubsub/v1/subscriber_client_config.json +0 -144
  36. data/lib/google/cloud/pubsub/v1.rb +0 -17
  37. data/lib/google/pubsub/v1/pubsub_pb.rb +0 -249
  38. data/lib/google/pubsub/v1/pubsub_services_pb.rb +0 -211
@@ -18,56 +18,43 @@ require "google/cloud/pubsub/credentials"
18
18
  require "google/cloud/pubsub/convert"
19
19
  require "google/cloud/pubsub/version"
20
20
  require "google/cloud/pubsub/v1"
21
- require "google/gax/errors"
21
+ require "securerandom"
22
22
 
23
23
  module Google
24
24
  module Cloud
25
25
  module PubSub
26
26
  ##
27
- # @private Represents the GAX Pub/Sub service, including all the API
28
- # methods.
27
+ # @private Represents the Pub/Sub service API, including IAM mixins.
29
28
  class Service
30
- attr_accessor :project, :credentials, :host, :timeout, :client_config
29
+ attr_accessor :project, :credentials, :host, :timeout
30
+ ###
31
+ # The same client_id is used across all streaming pull connections that are created by this client. This is
32
+ # intentional, as it indicates to the server that any guarantees, such as message ordering, made for a stream
33
+ # that is disconnected will be made for the stream that is created to replace it. The attr_accessor allows the
34
+ # value to be replaced for unit testing.
35
+ attr_accessor :client_id
31
36
 
32
37
  ##
33
38
  # Creates a new Service instance.
34
- def initialize project, credentials, host: nil, timeout: nil,
35
- client_config: nil
39
+ def initialize project, credentials, host: nil, timeout: nil
36
40
  @project = project
37
41
  @credentials = credentials
38
- @host = host || V1::PublisherClient::SERVICE_ADDRESS
42
+ @host = host
39
43
  @timeout = timeout
40
- @client_config = client_config || {}
41
- end
42
-
43
- def channel
44
- require "grpc"
45
- GRPC::Core::Channel.new host, chan_args, chan_creds
46
- end
47
-
48
- def chan_args
49
- { "grpc.max_send_message_length" => -1,
50
- "grpc.max_receive_message_length" => -1,
51
- "grpc.keepalive_time_ms" => 300_000,
52
- "grpc.service_config_disable_resolution" => 1 }
53
- end
54
-
55
- def chan_creds
56
- return credentials if insecure?
57
- require "grpc"
58
- GRPC::Core::ChannelCredentials.new.compose GRPC::Core::CallCredentials.new credentials.client.updater_proc
44
+ @client_id = SecureRandom.uuid.freeze
59
45
  end
60
46
 
61
47
  def subscriber
62
48
  return mocked_subscriber if mocked_subscriber
63
49
  @subscriber ||= begin
64
- V1::SubscriberClient.new(
65
- credentials: channel,
66
- timeout: timeout,
67
- client_config: client_config,
68
- lib_name: "gccl",
69
- lib_version: Google::Cloud::PubSub::VERSION
70
- )
50
+ V1::Subscriber::Client.new do |config|
51
+ config.credentials = credentials if credentials
52
+ config.timeout = timeout if timeout
53
+ config.endpoint = host if host
54
+ config.lib_name = "gccl"
55
+ config.lib_version = Google::Cloud::PubSub::VERSION
56
+ config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}" }
57
+ end
71
58
  end
72
59
  end
73
60
  attr_accessor :mocked_subscriber
@@ -75,20 +62,32 @@ module Google
75
62
  def publisher
76
63
  return mocked_publisher if mocked_publisher
77
64
  @publisher ||= begin
78
- V1::PublisherClient.new(
79
- credentials: channel,
80
- timeout: timeout,
81
- client_config: client_config,
82
- lib_name: "gccl",
83
- lib_version: Google::Cloud::PubSub::VERSION
84
- )
65
+ V1::Publisher::Client.new do |config|
66
+ config.credentials = credentials if credentials
67
+ config.timeout = timeout if timeout
68
+ config.endpoint = host if host
69
+ config.lib_name = "gccl"
70
+ config.lib_version = Google::Cloud::PubSub::VERSION
71
+ config.metadata = { "google-cloud-resource-prefix": "projects/#{@project}" }
72
+ end
85
73
  end
86
74
  end
87
75
  attr_accessor :mocked_publisher
88
76
 
89
- def insecure?
90
- credentials == :this_channel_is_insecure
77
+ def iam
78
+ return mocked_iam if mocked_iam
79
+ @iam ||= begin
80
+ V1::IAMPolicy::Client.new do |config|
81
+ config.credentials = credentials if credentials
82
+ config.timeout = timeout if timeout
83
+ config.endpoint = host if host
84
+ config.lib_name = "gccl"
85
+ config.lib_version = Google::Cloud::PubSub::VERSION
86
+ config.metadata = { "google-cloud-resource-prefix" => "projects/#{@project}" }
87
+ end
88
+ end
91
89
  end
90
+ attr_accessor :mocked_iam
92
91
 
93
92
  ##
94
93
  # Gets the configuration of a topic.
@@ -97,30 +96,17 @@ module Google
97
96
  # If other attributes are added in the future,
98
97
  # they will be returned here.
99
98
  def get_topic topic_name, options = {}
100
- execute do
101
- publisher.get_topic topic_path(topic_name, options),
102
- options: default_options
103
- end
99
+ publisher.get_topic topic: topic_path(topic_name, options)
104
100
  end
105
101
 
106
102
  ##
107
103
  # Lists matching topics.
108
104
  def list_topics options = {}
109
- call_options = default_options
110
- if (token = options[:token])
111
- call_options = Google::Gax::CallOptions.new(
112
- kwargs: default_headers,
113
- page_token: token
114
- )
115
- end
105
+ paged_enum = publisher.list_topics project: project_path(options),
106
+ page_size: options[:max],
107
+ page_token: options[:token]
116
108
 
117
- execute do
118
- paged_enum = publisher.list_topics project_path(options),
119
- page_size: options[:max],
120
- options: call_options
121
-
122
- paged_enum.page.response
123
- end
109
+ paged_enum.response
124
110
  end
125
111
 
126
112
  ##
@@ -132,21 +118,16 @@ module Google
132
118
  }
133
119
  end
134
120
 
135
- execute do
136
- publisher.create_topic \
137
- topic_path(topic_name, options),
138
- labels: labels,
139
- kms_key_name: kms_key_name,
140
- message_storage_policy: message_storage_policy,
141
- options: default_options
142
- end
121
+ publisher.create_topic \
122
+ name: topic_path(topic_name, options),
123
+ labels: labels,
124
+ kms_key_name: kms_key_name,
125
+ message_storage_policy: message_storage_policy
143
126
  end
144
127
 
145
128
  def update_topic topic_obj, *fields
146
129
  mask = Google::Protobuf::FieldMask.new paths: fields.map(&:to_s)
147
- execute do
148
- publisher.update_topic topic_obj, mask, options: default_options
149
- end
130
+ publisher.update_topic topic: topic_obj, update_mask: mask
150
131
  end
151
132
 
152
133
  ##
@@ -155,10 +136,7 @@ module Google
155
136
  # exist. After a topic is deleted, a new topic may be created with the
156
137
  # same name.
157
138
  def delete_topic topic_name
158
- execute do
159
- publisher.delete_topic topic_path(topic_name),
160
- options: default_options
161
- end
139
+ publisher.delete_topic topic: topic_path(topic_name)
162
140
  end
163
141
 
164
142
  ##
@@ -167,142 +145,98 @@ module Google
167
145
  # The messages parameter is an array of arrays.
168
146
  # The first element is the data, second is attributes hash.
169
147
  def publish topic, messages
170
- execute do
171
- publisher.publish topic_path(topic), messages,
172
- options: default_options
173
- end
148
+ publisher.publish topic: topic_path(topic), messages: messages
174
149
  end
175
150
 
176
151
  ##
177
152
  # Gets the details of a subscription.
178
153
  def get_subscription subscription_name, options = {}
179
- subscription = subscription_path subscription_name, options
180
- execute do
181
- subscriber.get_subscription subscription, options: default_options
182
- end
154
+ subscriber.get_subscription subscription: subscription_path(subscription_name, options)
183
155
  end
184
156
 
185
157
  ##
186
158
  # Lists matching subscriptions by project and topic.
187
159
  def list_topics_subscriptions topic, options = {}
188
- call_options = default_options
189
- if (token = options[:token])
190
- call_options = Google::Gax::CallOptions.new(
191
- kwargs: default_headers,
192
- page_token: token
193
- )
194
- end
195
-
196
- execute do
197
- paged_enum = publisher.list_topic_subscriptions \
198
- topic_path(topic, options),
199
- page_size: options[:max],
200
- options: call_options
201
-
202
- paged_enum.page.response
203
- end
160
+ publisher.list_topic_subscriptions topic: topic_path(topic, options),
161
+ page_size: options[:max],
162
+ page_token: options[:token]
204
163
  end
205
164
 
206
165
  ##
207
166
  # Lists matching subscriptions by project.
208
167
  def list_subscriptions options = {}
209
- call_options = default_options
210
- if (token = options[:token])
211
- call_options = Google::Gax::CallOptions.new(
212
- kwargs: default_headers,
213
- page_token: token
214
- )
215
- end
216
-
217
- execute do
218
- paged_enum = subscriber.list_subscriptions project_path(options),
219
- page_size: options[:max],
220
- options: call_options
168
+ paged_enum = subscriber.list_subscriptions project: project_path(options),
169
+ page_size: options[:max],
170
+ page_token: options[:token]
221
171
 
222
- paged_enum.page.response
223
- end
172
+ paged_enum.response
224
173
  end
225
174
 
226
175
  ##
227
176
  # Creates a subscription on a given topic for a given subscriber.
228
177
  def create_subscription topic, subscription_name, options = {}
229
- name = subscription_path subscription_name, options
230
- topic = topic_path topic
231
178
  push_config = if options[:endpoint]
232
179
  Google::Cloud::PubSub::V1::PushConfig.new \
233
180
  push_endpoint: options[:endpoint],
234
181
  attributes: (options[:attributes] || {}).to_h
235
182
  end
236
- deadline = options[:deadline]
237
- retain_acked = options[:retain_acked]
238
- mrd = Convert.number_to_duration options[:retention]
239
- labels = options[:labels]
240
- message_ordering = options[:message_ordering]
241
-
242
- execute do
243
- subscriber.create_subscription \
244
- name, topic,
245
- push_config: push_config,
246
- ack_deadline_seconds: deadline,
247
- retain_acked_messages: retain_acked,
248
- message_retention_duration: mrd,
249
- labels: labels,
250
- enable_message_ordering: message_ordering,
251
- options: default_options
252
- end
183
+ subscriber.create_subscription \
184
+ name: subscription_path(subscription_name, options),
185
+ topic: topic_path(topic),
186
+ push_config: push_config,
187
+ ack_deadline_seconds: options[:deadline],
188
+ retain_acked_messages: options[:retain_acked],
189
+ message_retention_duration: Convert.number_to_duration(options[:retention]),
190
+ labels: options[:labels],
191
+ enable_message_ordering: options[:message_ordering],
192
+ filter: options[:filter],
193
+ dead_letter_policy: dead_letter_policy(options),
194
+ retry_policy: options[:retry_policy]
253
195
  end
254
196
 
255
197
  def update_subscription subscription_obj, *fields
256
198
  mask = Google::Protobuf::FieldMask.new paths: fields.map(&:to_s)
257
- execute do
258
- subscriber.update_subscription subscription_obj, mask, options: default_options
259
- end
199
+ subscriber.update_subscription subscription: subscription_obj, update_mask: mask
260
200
  end
261
201
 
262
202
  ##
263
- # Deletes an existing subscription.
264
- # All pending messages in the subscription are immediately dropped.
203
+ # Deletes an existing subscription. All pending messages in the subscription are immediately dropped.
265
204
  def delete_subscription subscription
266
- execute do
267
- subscriber.delete_subscription subscription_path(subscription),
268
- options: default_options
269
- end
205
+ subscriber.delete_subscription subscription: subscription_path(subscription)
206
+ end
207
+
208
+ ##
209
+ # Detaches a subscription from its topic. All messages retained in the subscription are dropped. Subsequent
210
+ # `Pull` and `StreamingPull` requests will raise `FAILED_PRECONDITION`. If the subscription is a push
211
+ # subscription, pushes to the endpoint will stop.
212
+ def detach_subscription subscription
213
+ publisher.detach_subscription subscription: subscription_path(subscription)
270
214
  end
271
215
 
272
216
  ##
273
217
  # Pulls a single message from the server.
274
218
  def pull subscription, options = {}
275
- subscription = subscription_path subscription, options
276
219
  max_messages = options.fetch(:max, 100).to_i
277
220
  return_immediately = !(!options.fetch(:immediate, true))
278
221
 
279
- execute do
280
- subscriber.pull subscription,
281
- max_messages,
282
- return_immediately: return_immediately,
283
- options: default_options
284
- end
222
+ subscriber.pull subscription: subscription_path(subscription, options),
223
+ max_messages: max_messages,
224
+ return_immediately: return_immediately
285
225
  end
286
226
 
287
227
  def streaming_pull request_enum
288
- execute do
289
- subscriber.streaming_pull request_enum, options: default_options
290
- end
228
+ subscriber.streaming_pull request_enum
291
229
  end
292
230
 
293
231
  ##
294
232
  # Acknowledges receipt of a message.
295
233
  def acknowledge subscription, *ack_ids
296
- execute do
297
- subscriber.acknowledge subscription_path(subscription), ack_ids,
298
- options: default_options
299
- end
234
+ subscriber.acknowledge subscription: subscription_path(subscription), ack_ids: ack_ids
300
235
  end
301
236
 
302
237
  ##
303
238
  # Modifies the PushConfig for a specified subscription.
304
239
  def modify_push_config subscription, endpoint, attributes
305
- subscription = subscription_path subscription
306
240
  # Convert attributes to strings to match the protobuf definition
307
241
  attributes = Hash[attributes.map { |k, v| [String(k), String(v)] }]
308
242
  push_config = Google::Cloud::PubSub::V1::PushConfig.new(
@@ -310,138 +244,83 @@ module Google
310
244
  attributes: attributes
311
245
  )
312
246
 
313
- execute do
314
- subscriber.modify_push_config subscription, push_config,
315
- options: default_options
316
- end
247
+ subscriber.modify_push_config subscription: subscription_path(subscription),
248
+ push_config: push_config
317
249
  end
318
250
 
319
251
  ##
320
252
  # Modifies the ack deadline for a specific message.
321
253
  def modify_ack_deadline subscription, ids, deadline
322
- execute do
323
- subscriber.modify_ack_deadline subscription_path(subscription),
324
- Array(ids),
325
- deadline, options: default_options
326
- end
254
+ subscriber.modify_ack_deadline subscription: subscription_path(subscription),
255
+ ack_ids: Array(ids),
256
+ ack_deadline_seconds: deadline
327
257
  end
328
258
 
329
259
  ##
330
260
  # Lists snapshots by project.
331
261
  def list_snapshots options = {}
332
- call_options = default_options
333
- if (token = options[:token])
334
- call_options = Google::Gax::CallOptions.new(
335
- kwargs: default_headers,
336
- page_token: token
337
- )
338
- end
262
+ paged_enum = subscriber.list_snapshots project: project_path(options),
263
+ page_size: options[:max],
264
+ page_token: options[:token]
339
265
 
340
- execute do
341
- paged_enum = subscriber.list_snapshots project_path(options),
342
- page_size: options[:max],
343
- options: call_options
344
-
345
- paged_enum.page.response
346
- end
266
+ paged_enum.response
347
267
  end
348
268
 
349
269
  ##
350
270
  # Creates a snapshot on a given subscription.
351
271
  def create_snapshot subscription, snapshot_name, labels: nil
352
- name = snapshot_path snapshot_name
353
- execute do
354
- subscriber.create_snapshot name,
355
- subscription_path(subscription),
356
- labels: labels,
357
- options: default_options
358
- end
272
+ subscriber.create_snapshot name: snapshot_path(snapshot_name),
273
+ subscription: subscription_path(subscription),
274
+ labels: labels
359
275
  end
360
276
 
361
277
  def update_snapshot snapshot_obj, *fields
362
278
  mask = Google::Protobuf::FieldMask.new paths: fields.map(&:to_s)
363
- execute do
364
- subscriber.update_snapshot snapshot_obj, mask, options: default_options
365
- end
279
+ subscriber.update_snapshot snapshot: snapshot_obj, update_mask: mask
366
280
  end
367
281
 
368
282
  ##
369
283
  # Deletes an existing snapshot.
370
284
  # All pending messages in the snapshot are immediately dropped.
371
285
  def delete_snapshot snapshot
372
- execute do
373
- subscriber.delete_snapshot snapshot_path(snapshot),
374
- options: default_options
375
- end
286
+ subscriber.delete_snapshot snapshot: snapshot_path(snapshot)
376
287
  end
377
288
 
378
289
  ##
379
290
  # Adjusts the given subscription to a time or snapshot.
380
291
  def seek subscription, time_or_snapshot
381
- subscription = subscription_path subscription
382
- execute do
383
- if a_time? time_or_snapshot
384
- time = Convert.time_to_timestamp time_or_snapshot
385
- subscriber.seek subscription, time: time, options: default_options
386
- else
387
- time_or_snapshot = time_or_snapshot.name if time_or_snapshot.is_a? Snapshot
388
- subscriber.seek subscription,
389
- snapshot: snapshot_path(time_or_snapshot),
390
- options: default_options
391
- end
292
+ if a_time? time_or_snapshot
293
+ time = Convert.time_to_timestamp time_or_snapshot
294
+ subscriber.seek subscription: subscription, time: time
295
+ else
296
+ time_or_snapshot = time_or_snapshot.name if time_or_snapshot.is_a? Snapshot
297
+ subscriber.seek subscription: subscription_path(subscription),
298
+ snapshot: snapshot_path(time_or_snapshot)
392
299
  end
393
300
  end
394
301
 
395
302
  def get_topic_policy topic_name, options = {}
396
- execute do
397
- publisher.get_iam_policy topic_path(topic_name, options),
398
- options: default_options
399
- end
303
+ iam.get_iam_policy resource: topic_path(topic_name, options)
400
304
  end
401
305
 
402
306
  def set_topic_policy topic_name, new_policy, options = {}
403
- resource = topic_path topic_name, options
404
-
405
- execute do
406
- publisher.set_iam_policy resource, new_policy,
407
- options: default_options
408
- end
307
+ iam.set_iam_policy resource: topic_path(topic_name, options), policy: new_policy
409
308
  end
410
309
 
411
310
  def test_topic_permissions topic_name, permissions, options = {}
412
- resource = topic_path topic_name, options
413
-
414
- execute do
415
- publisher.test_iam_permissions resource, permissions,
416
- options: default_options
417
- end
311
+ iam.test_iam_permissions resource: topic_path(topic_name, options), permissions: permissions
418
312
  end
419
313
 
420
314
  def get_subscription_policy subscription_name, options = {}
421
- resource = subscription_path subscription_name, options
422
-
423
- execute do
424
- subscriber.get_iam_policy resource, options: default_options
425
- end
315
+ iam.get_iam_policy resource: subscription_path(subscription_name, options)
426
316
  end
427
317
 
428
318
  def set_subscription_policy subscription_name, new_policy, options = {}
429
- resource = subscription_path subscription_name, options
430
-
431
- execute do
432
- subscriber.set_iam_policy resource, new_policy,
433
- options: default_options
434
- end
319
+ iam.set_iam_policy resource: subscription_path(subscription_name, options), policy: new_policy
435
320
  end
436
321
 
437
- def test_subscription_permissions subscription_name,
438
- permissions, options = {}
439
- resource = subscription_path subscription_name, options
440
-
441
- execute do
442
- subscriber.test_iam_permissions resource, permissions,
443
- options: default_options
444
- end
322
+ def test_subscription_permissions subscription_name, permissions, options = {}
323
+ iam.test_iam_permissions resource: subscription_path(subscription_name, options), permissions: permissions
445
324
  end
446
325
 
447
326
  def project_path options = {}
@@ -477,19 +356,13 @@ module Google
477
356
  true
478
357
  end
479
358
 
480
- def default_headers
481
- { "google-cloud-resource-prefix" => "projects/#{@project}" }
482
- end
483
-
484
- def default_options
485
- Google::Gax::CallOptions.new kwargs: default_headers
486
- end
487
-
488
- def execute
489
- yield
490
- rescue Google::Gax::GaxError => e
491
- # GaxError wraps BadStatus, but exposes it as #cause
492
- raise Google::Cloud::Error.from_error(e.cause)
359
+ def dead_letter_policy options
360
+ return nil unless options[:dead_letter_topic_name]
361
+ policy = Google::Cloud::PubSub::V1::DeadLetterPolicy.new dead_letter_topic: options[:dead_letter_topic_name]
362
+ if options[:dead_letter_max_delivery_attempts]
363
+ policy.max_delivery_attempts = options[:dead_letter_max_delivery_attempts]
364
+ end
365
+ policy
493
366
  end
494
367
  end
495
368
  end
@@ -22,30 +22,40 @@ module Google
22
22
  ##
23
23
  # @private
24
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
+
25
31
  include MonitorMixin
26
32
 
27
- attr_reader :stream, :limit
33
+ attr_reader :stream, :limit, :bytesize, :extension, :max_duration_per_lease_extension
28
34
 
29
- def initialize stream, limit
35
+ def initialize stream, limit:, bytesize:, extension:, max_duration_per_lease_extension:
30
36
  super()
31
-
32
37
  @stream = stream
33
38
  @limit = limit
34
- @_ack_ids = []
39
+ @bytesize = bytesize
40
+ @extension = extension
41
+ @max_duration_per_lease_extension = max_duration_per_lease_extension
42
+ @inventory = {}
35
43
  @wait_cond = new_cond
36
44
  end
37
45
 
38
46
  def ack_ids
39
- @_ack_ids
47
+ @inventory.keys
40
48
  end
41
49
 
42
- def add *ack_ids
43
- ack_ids.flatten!
44
- ack_ids.compact!
45
- return if ack_ids.empty?
50
+ def add *rec_msgs
51
+ rec_msgs.flatten!
52
+ rec_msgs.compact!
53
+ return if rec_msgs.empty?
46
54
 
47
55
  synchronize do
48
- @_ack_ids += ack_ids
56
+ rec_msgs.each do |rec_msg|
57
+ @inventory[rec_msg.ack_id] = InventoryItem.from rec_msg
58
+ end
49
59
  @wait_cond.broadcast
50
60
  end
51
61
  end
@@ -56,20 +66,34 @@ module Google
56
66
  return if ack_ids.empty?
57
67
 
58
68
  synchronize do
59
- @_ack_ids -= ack_ids
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 }
60
78
  @wait_cond.broadcast
61
79
  end
62
80
  end
63
81
 
64
82
  def count
65
83
  synchronize do
66
- @_ack_ids.count
84
+ @inventory.count
85
+ end
86
+ end
87
+
88
+ def total_bytesize
89
+ synchronize do
90
+ @inventory.values.sum(&:bytesize)
67
91
  end
68
92
  end
69
93
 
70
94
  def empty?
71
95
  synchronize do
72
- @_ack_ids.empty?
96
+ @inventory.empty?
73
97
  end
74
98
  end
75
99
 
@@ -93,7 +117,9 @@ module Google
93
117
  end
94
118
 
95
119
  def full?
96
- count >= limit
120
+ synchronize do
121
+ @inventory.count >= limit || @inventory.values.sum(&:bytesize) >= bytesize
122
+ end
97
123
  end
98
124
 
99
125
  protected
@@ -127,7 +153,9 @@ module Google
127
153
  end
128
154
 
129
155
  def calc_delay
130
- (stream.subscriber.deadline - 3) * rand(0.8..0.9)
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
131
159
  end
132
160
  end
133
161
  end