google-cloud-pubsub 1.1.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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