google-cloud-pubsub 1.0.2 → 1.1.0

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