google-cloud-pubsub 1.0.2 → 1.1.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.
@@ -100,6 +100,48 @@ module Google
100
100
  end
101
101
  alias publish_time published_at
102
102
 
103
+ ##
104
+ # Identifies related messages for which publish order should be
105
+ # respected.
106
+ #
107
+ # Google Cloud Pub/Sub ordering keys provide the ability to ensure
108
+ # related messages are sent to subscribers in the order in which they
109
+ # were published. Messages can be tagged with an ordering key, a string
110
+ # that identifies related messages for which publish order should be
111
+ # respected. The service guarantees that, for a given ordering key and
112
+ # publisher, messages are sent to subscribers in the order in which they
113
+ # were published. Ordering does not require sacrificing high throughput
114
+ # or scalability, as the service automatically distributes messages for
115
+ # different ordering keys across subscribers.
116
+ #
117
+ # See {Topic#publish_async} and {Subscription#listen}.
118
+ #
119
+ # @return [String]
120
+ #
121
+ def ordering_key
122
+ @grpc.ordering_key
123
+ end
124
+
125
+ # @private
126
+ def hash
127
+ @grpc.hash
128
+ end
129
+
130
+ # @private
131
+ def eql? other
132
+ return false unless other.is_a? self.class
133
+ @grpc.hash == other.hash
134
+ end
135
+ # @private
136
+ alias == eql?
137
+
138
+ # @private
139
+ def <=> other
140
+ return nil unless other.is_a? self.class
141
+ other_grpc = other.instance_variable_get :@grpc
142
+ @grpc <=> other_grpc
143
+ end
144
+
103
145
  ##
104
146
  # @private New Message from a Google::Cloud::PubSub::V1::PubsubMessage
105
147
  # object.
@@ -205,8 +205,7 @@ module Google
205
205
  # pubsub = Google::Cloud::PubSub.new
206
206
  # topic = pubsub.create_topic "my-topic"
207
207
  #
208
- def create_topic topic_name, labels: nil, kms_key: nil,
209
- persistence_regions: nil, async: nil
208
+ def create_topic topic_name, labels: nil, kms_key: nil, persistence_regions: nil, async: nil
210
209
  ensure_service!
211
210
  grpc = service.create_topic topic_name,
212
211
  labels: labels,
@@ -291,9 +290,7 @@ module Google
291
290
  def subscription subscription_name, project: nil, skip_lookup: nil
292
291
  ensure_service!
293
292
  options = { project: project }
294
- if skip_lookup
295
- return Subscription.from_name subscription_name, service, options
296
- end
293
+ return Subscription.from_name subscription_name, service, options if skip_lookup
297
294
  grpc = service.get_subscription subscription_name
298
295
  Subscription.from_grpc grpc, service
299
296
  rescue Google::Cloud::NotFoundError
@@ -17,7 +17,12 @@ module Google
17
17
  module Cloud
18
18
  module PubSub
19
19
  ##
20
- # # PublishResult
20
+ # The result of a publish operation. The message object is available on
21
+ # {#message} and will have {#message_id} assigned by the API.
22
+ #
23
+ # When the publish operation was successful the result will be marked
24
+ # {#succeeded?}. Otherwise, the result will be marked {#failed?} and the
25
+ # error raised will be availabe on {#error}.
21
26
  #
22
27
  class PublishResult
23
28
  ##
@@ -91,6 +91,28 @@ module Google
91
91
  end
92
92
  alias msg_id message_id
93
93
 
94
+ ##
95
+ # Identifies related messages for which publish order should be
96
+ # respected.
97
+ #
98
+ # Google Cloud Pub/Sub ordering keys provide the ability to ensure
99
+ # related messages are sent to subscribers in the order in which they
100
+ # were published. Messages can be tagged with an ordering key, a string
101
+ # that identifies related messages for which publish order should be
102
+ # respected. The service guarantees that, for a given ordering key and
103
+ # publisher, messages are sent to subscribers in the order in which they
104
+ # were published. Ordering does not require sacrificing high throughput
105
+ # or scalability, as the service automatically distributes messages for
106
+ # different ordering keys across subscribers.
107
+ #
108
+ # See {Topic#publish_async} and {Subscription#listen}.
109
+ #
110
+ # @return [String]
111
+ #
112
+ def ordering_key
113
+ message.ordering_key
114
+ end
115
+
94
116
  ##
95
117
  # The time at which the message was published.
96
118
  def published_at
@@ -192,6 +214,26 @@ module Google
192
214
  alias nack! reject!
193
215
  alias ignore! reject!
194
216
 
217
+ # @private
218
+ def hash
219
+ @grpc.hash
220
+ end
221
+
222
+ # @private
223
+ def eql? other
224
+ return false unless other.is_a? self.class
225
+ @grpc.hash == other.hash
226
+ end
227
+ # @private
228
+ alias == eql?
229
+
230
+ # @private
231
+ def <=> other
232
+ return nil unless other.is_a? self.class
233
+ other_grpc = other.instance_variable_get :@grpc
234
+ @grpc <=> other_grpc
235
+ end
236
+
195
237
  ##
196
238
  # @private New ReceivedMessage from a
197
239
  # Google::Cloud::PubSub::V1::ReceivedMessage object.
@@ -48,15 +48,14 @@ module Google
48
48
  def chan_args
49
49
  { "grpc.max_send_message_length" => -1,
50
50
  "grpc.max_receive_message_length" => -1,
51
- "grpc.keepalive_time_ms" => 300000,
51
+ "grpc.keepalive_time_ms" => 300_000,
52
52
  "grpc.service_config_disable_resolution" => 1 }
53
53
  end
54
54
 
55
55
  def chan_creds
56
56
  return credentials if insecure?
57
57
  require "grpc"
58
- GRPC::Core::ChannelCredentials.new.compose \
59
- GRPC::Core::CallCredentials.new credentials.client.updater_proc
58
+ GRPC::Core::ChannelCredentials.new.compose GRPC::Core::CallCredentials.new credentials.client.updater_proc
60
59
  end
61
60
 
62
61
  def subscriber
@@ -126,8 +125,7 @@ module Google
126
125
 
127
126
  ##
128
127
  # Creates the given topic with the given name.
129
- def create_topic topic_name, labels: nil, kms_key_name: nil,
130
- persistence_regions: nil, options: {}
128
+ def create_topic topic_name, labels: nil, kms_key_name: nil, persistence_regions: nil, options: {}
131
129
  if persistence_regions
132
130
  message_storage_policy = {
133
131
  allowed_persistence_regions: Array(persistence_regions)
@@ -147,8 +145,7 @@ module Google
147
145
  def update_topic topic_obj, *fields
148
146
  mask = Google::Protobuf::FieldMask.new paths: fields.map(&:to_s)
149
147
  execute do
150
- publisher.update_topic \
151
- topic_obj, mask, options: default_options
148
+ publisher.update_topic topic_obj, mask, options: default_options
152
149
  end
153
150
  end
154
151
 
@@ -240,6 +237,7 @@ module Google
240
237
  retain_acked = options[:retain_acked]
241
238
  mrd = Convert.number_to_duration options[:retention]
242
239
  labels = options[:labels]
240
+ message_ordering = options[:message_ordering]
243
241
 
244
242
  execute do
245
243
  subscriber.create_subscription \
@@ -249,6 +247,7 @@ module Google
249
247
  retain_acked_messages: retain_acked,
250
248
  message_retention_duration: mrd,
251
249
  labels: labels,
250
+ enable_message_ordering: message_ordering,
252
251
  options: default_options
253
252
  end
254
253
  end
@@ -256,8 +255,7 @@ module Google
256
255
  def update_subscription subscription_obj, *fields
257
256
  mask = Google::Protobuf::FieldMask.new paths: fields.map(&:to_s)
258
257
  execute do
259
- subscriber.update_subscription \
260
- subscription_obj, mask, options: default_options
258
+ subscriber.update_subscription subscription_obj, mask, options: default_options
261
259
  end
262
260
  end
263
261
 
@@ -363,8 +361,7 @@ module Google
363
361
  def update_snapshot snapshot_obj, *fields
364
362
  mask = Google::Protobuf::FieldMask.new paths: fields.map(&:to_s)
365
363
  execute do
366
- subscriber.update_snapshot \
367
- snapshot_obj, mask, options: default_options
364
+ subscriber.update_snapshot snapshot_obj, mask, options: default_options
368
365
  end
369
366
  end
370
367
 
@@ -387,9 +384,7 @@ module Google
387
384
  time = Convert.time_to_timestamp time_or_snapshot
388
385
  subscriber.seek subscription, time: time, options: default_options
389
386
  else
390
- if time_or_snapshot.is_a? Snapshot
391
- time_or_snapshot = time_or_snapshot.name
392
- end
387
+ time_or_snapshot = time_or_snapshot.name if time_or_snapshot.is_a? Snapshot
393
388
  subscriber.seek subscription,
394
389
  snapshot: snapshot_path(time_or_snapshot),
395
390
  options: default_options
@@ -465,14 +460,12 @@ module Google
465
460
  end
466
461
 
467
462
  def snapshot_path snapshot_name, options = {}
468
- if snapshot_name.nil? || snapshot_name.to_s.include?("/")
469
- return snapshot_name
470
- end
463
+ return snapshot_name if snapshot_name.nil? || snapshot_name.to_s.include?("/")
471
464
  "#{project_path options}/snapshots/#{snapshot_name}"
472
465
  end
473
466
 
474
467
  def inspect
475
- "#<#{self.class.name} #{@project}>"
468
+ "#<#{self.class.name} (#{@project})>"
476
469
  end
477
470
 
478
471
  protected
@@ -127,15 +127,13 @@ module Google
127
127
  #
128
128
  def all request_limit: nil
129
129
  request_limit = request_limit.to_i if request_limit
130
- unless block_given?
131
- return enum_for :all, request_limit: request_limit
132
- end
130
+ return enum_for :all, request_limit: request_limit unless block_given?
133
131
  results = self
134
132
  loop do
135
133
  results.each { |r| yield r }
136
134
  if request_limit
137
135
  request_limit -= 1
138
- break if request_limit < 0
136
+ break if request_limit.negative?
139
137
  end
140
138
  break unless results.next?
141
139
  results = results.next
@@ -50,6 +50,8 @@ module Google
50
50
  # @attr_reader [Numeric] deadline The default number of seconds the stream
51
51
  # will hold received messages before modifying the message's ack
52
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.
53
55
  # @attr_reader [Integer] streams The number of concurrent streams to open
54
56
  # to pull messages from the subscription. Default is 4.
55
57
  # @attr_reader [Integer] inventory The number of received messages to be
@@ -65,7 +67,8 @@ module Google
65
67
  include MonitorMixin
66
68
 
67
69
  attr_reader :subscription_name, :callback, :deadline, :streams,
68
- :inventory, :callback_threads, :push_threads
70
+ :inventory, :message_ordering, :callback_threads,
71
+ :push_threads
69
72
 
70
73
  ##
71
74
  # @private Implementation attributes.
@@ -74,14 +77,16 @@ module Google
74
77
 
75
78
  ##
76
79
  # @private Create an empty {Subscriber} object.
77
- def initialize subscription_name, callback, deadline: nil, streams: nil,
78
- inventory: nil, threads: {}, service: nil
80
+ def initialize subscription_name, callback, deadline: nil,
81
+ message_ordering: nil, streams: nil, inventory: nil,
82
+ threads: {}, service: nil
79
83
  @callback = callback
80
84
  @error_callbacks = []
81
85
  @subscription_name = subscription_name
82
86
  @deadline = deadline || 60
83
87
  @streams = streams || 4
84
88
  @inventory = inventory || 1000
89
+ @message_ordering = message_ordering
85
90
  @callback_threads = (threads[:callback] || 8).to_i
86
91
  @push_threads = (threads[:push] || 4).to_i
87
92
 
@@ -292,8 +297,7 @@ module Google
292
297
  ##
293
298
  # @private
294
299
  def to_s
295
- "(subscription: #{subscription_name}, " \
296
- "streams: [#{stream_pool.map(&:to_s).join(', ')}])"
300
+ "(subscription: #{subscription_name}, streams: [#{stream_pool.map(&:to_s).join(', ')}])"
297
301
  end
298
302
 
299
303
  ##
@@ -101,26 +101,24 @@ module Google
101
101
  def background_run
102
102
  delay_target = nil
103
103
 
104
- synchronize do
105
- until @stopped
106
- if @_ack_ids.empty?
107
- delay_target = nil
108
-
109
- @wait_cond.wait # wait until broadcast
110
- next
111
- end
104
+ until stopped?
105
+ if empty?
106
+ delay_target = nil
112
107
 
113
- delay_target ||= calc_target
114
- delay_gap = delay_target - Time.now
108
+ synchronize { @wait_cond.wait } # wait until broadcast
109
+ next
110
+ end
115
111
 
116
- unless delay_gap.positive?
117
- delay_target = calc_target
118
- stream.renew_lease!
119
- next
120
- end
112
+ delay_target ||= calc_target
113
+ delay_gap = delay_target - Time.now
121
114
 
122
- @wait_cond.wait delay_gap
115
+ unless delay_gap.positive?
116
+ delay_target = calc_target
117
+ stream.renew_lease!
118
+ next
123
119
  end
120
+
121
+ synchronize { @wait_cond.wait delay_gap }
124
122
  end
125
123
  end
126
124
 
@@ -0,0 +1,115 @@
1
+ # Copyright 2019 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
+
18
+ module Google
19
+ module Cloud
20
+ module PubSub
21
+ class Subscriber
22
+ ##
23
+ # @private The sequencer's job is simple, keep track of all the
24
+ # streams's recieved message and deliver the messages with an
25
+ # ordering_key in the order they were recieved. The sequencer ensures
26
+ # only one callback can be performed at a time per ordering_key.
27
+ class Sequencer
28
+ include MonitorMixin
29
+
30
+ ##
31
+ # @private Create an empty Subscriber::Sequencer object.
32
+ def initialize &block
33
+ raise ArgumentError if block.nil?
34
+
35
+ @seq_hash = Hash.new { |hash, key| hash[key] = [] }
36
+ @process_callback = block
37
+
38
+ super() # to init MonitorMixin
39
+ end
40
+
41
+ ##
42
+ # @private Add a ReceivedMessage to the sequencer.
43
+ def add message
44
+ # Messages without ordering_key are not managed by the sequencer
45
+ if message.ordering_key.empty?
46
+ @process_callback.call message
47
+ return
48
+ end
49
+
50
+ perform_callback = synchronize do
51
+ # The purpose of this block is to add the message to the
52
+ # sequencer, and to return whether the message should be processed
53
+ # immediately, or whether it will be processed later by #next. We
54
+ # want to ensure that these operations happen atomically.
55
+
56
+ @seq_hash[message.ordering_key].push message
57
+ @seq_hash[message.ordering_key].count == 1
58
+ end
59
+
60
+ @process_callback.call message if perform_callback
61
+ end
62
+
63
+ ##
64
+ # @private Indicate a ReceivedMessage was processed, and the next in
65
+ # the queue can now be processed.
66
+ def next message
67
+ # Messages without ordering_key are not managed by the sequencer
68
+ return if message.ordering_key.empty?
69
+
70
+ next_message = synchronize do
71
+ # The purpose of this block is to remove the message that was
72
+ # processed from the sequencer, and to return the next message to
73
+ # be processed. We want to ensure that these operations happen
74
+ # atomically.
75
+
76
+ # The message should be at index 0, so this should be a very quick
77
+ # operation.
78
+ if @seq_hash[message.ordering_key].first != message
79
+ # Raising this error will stop the other messages with this
80
+ # ordering key from being processed by the callback (delivered).
81
+ raise OrderedMessageDeliveryError, message
82
+ end
83
+
84
+ # Remove the message
85
+ @seq_hash[message.ordering_key].shift
86
+
87
+ # Retrieve the next message to be processed, or nil if empty
88
+ next_msg = @seq_hash[message.ordering_key].first
89
+
90
+ # Remove the ordering_key from hash when empty
91
+ @seq_hash.delete message.ordering_key if next_msg.nil?
92
+
93
+ # Return the next message to be processed, or nil if empty
94
+ next_msg
95
+ end
96
+
97
+ @process_callback.call next_message unless next_message.nil?
98
+ end
99
+
100
+ # @private
101
+ def to_s
102
+ "#{@seq_hash.count}/#{@seq_hash.values.sum(&:count)}"
103
+ end
104
+
105
+ # @private
106
+ def inspect
107
+ "#<#{self.class.name} (#{self})>"
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ Pubsub = PubSub unless const_defined? :Pubsub
114
+ end
115
+ end