google-cloud-pubsub 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +18 -0
  3. data/AUTHENTICATION.md +177 -0
  4. data/CHANGELOG.md +538 -0
  5. data/CODE_OF_CONDUCT.md +40 -0
  6. data/CONTRIBUTING.md +188 -0
  7. data/EMULATOR.md +37 -0
  8. data/LICENSE +201 -0
  9. data/LOGGING.md +32 -0
  10. data/OVERVIEW.md +557 -0
  11. data/TROUBLESHOOTING.md +31 -0
  12. data/lib/google-cloud-pubsub.rb +139 -0
  13. data/lib/google/cloud/pubsub.rb +173 -0
  14. data/lib/google/cloud/pubsub/async_publisher.rb +399 -0
  15. data/lib/google/cloud/pubsub/async_publisher/batch.rb +309 -0
  16. data/lib/google/cloud/pubsub/batch_publisher.rb +99 -0
  17. data/lib/google/cloud/pubsub/convert.rb +91 -0
  18. data/lib/google/cloud/pubsub/credentials.rb +47 -0
  19. data/lib/google/cloud/pubsub/errors.rb +85 -0
  20. data/lib/google/cloud/pubsub/message.rb +158 -0
  21. data/lib/google/cloud/pubsub/policy.rb +187 -0
  22. data/lib/google/cloud/pubsub/project.rb +393 -0
  23. data/lib/google/cloud/pubsub/publish_result.rb +103 -0
  24. data/lib/google/cloud/pubsub/received_message.rb +297 -0
  25. data/lib/google/cloud/pubsub/retry_policy.rb +90 -0
  26. data/lib/google/cloud/pubsub/service.rb +514 -0
  27. data/lib/google/cloud/pubsub/snapshot.rb +202 -0
  28. data/lib/google/cloud/pubsub/snapshot/list.rb +178 -0
  29. data/lib/google/cloud/pubsub/subscriber.rb +399 -0
  30. data/lib/google/cloud/pubsub/subscriber/enumerator_queue.rb +54 -0
  31. data/lib/google/cloud/pubsub/subscriber/inventory.rb +166 -0
  32. data/lib/google/cloud/pubsub/subscriber/sequencer.rb +115 -0
  33. data/lib/google/cloud/pubsub/subscriber/stream.rb +401 -0
  34. data/lib/google/cloud/pubsub/subscriber/timed_unary_buffer.rb +231 -0
  35. data/lib/google/cloud/pubsub/subscription.rb +1279 -0
  36. data/lib/google/cloud/pubsub/subscription/list.rb +205 -0
  37. data/lib/google/cloud/pubsub/subscription/push_config.rb +244 -0
  38. data/lib/google/cloud/pubsub/topic.rb +934 -0
  39. data/lib/google/cloud/pubsub/topic/list.rb +171 -0
  40. data/lib/google/cloud/pubsub/v1.rb +17 -0
  41. data/lib/google/cloud/pubsub/v1/credentials.rb +41 -0
  42. data/lib/google/cloud/pubsub/v1/doc/google/iam/v1/iam_policy.rb +21 -0
  43. data/lib/google/cloud/pubsub/v1/doc/google/iam/v1/options.rb +21 -0
  44. data/lib/google/cloud/pubsub/v1/doc/google/iam/v1/policy.rb +21 -0
  45. data/lib/google/cloud/pubsub/v1/doc/google/protobuf/duration.rb +91 -0
  46. data/lib/google/cloud/pubsub/v1/doc/google/protobuf/empty.rb +29 -0
  47. data/lib/google/cloud/pubsub/v1/doc/google/protobuf/field_mask.rb +222 -0
  48. data/lib/google/cloud/pubsub/v1/doc/google/protobuf/timestamp.rb +113 -0
  49. data/lib/google/cloud/pubsub/v1/doc/google/pubsub/v1/pubsub.rb +833 -0
  50. data/lib/google/cloud/pubsub/v1/doc/google/type/expr.rb +19 -0
  51. data/lib/google/cloud/pubsub/v1/publisher_client.rb +928 -0
  52. data/lib/google/cloud/pubsub/v1/publisher_client_config.json +120 -0
  53. data/lib/google/cloud/pubsub/v1/subscriber_client.rb +1466 -0
  54. data/lib/google/cloud/pubsub/v1/subscriber_client_config.json +153 -0
  55. data/lib/google/cloud/pubsub/version.rb +24 -0
  56. data/lib/google/pubsub/v1/pubsub_pb.rb +269 -0
  57. data/lib/google/pubsub/v1/pubsub_services_pb.rb +215 -0
  58. metadata +337 -0
@@ -0,0 +1,31 @@
1
+ # Troubleshooting
2
+
3
+ ## Where can I get more help?
4
+
5
+ ### Ask the Community
6
+
7
+ If you have a question about how to use a Google Cloud client library in your
8
+ project or are stuck in the Developer's console and don't know where to turn,
9
+ it's possible your questions have already been addressed by the community.
10
+
11
+ First, check out the appropriate tags on StackOverflow:
12
+ - [`google-cloud-platform+ruby+pubsub`][so-ruby]
13
+
14
+ Next, try searching through the issues on GitHub:
15
+
16
+ - [`api:pubsub` issues][gh-search-ruby]
17
+
18
+ Still nothing?
19
+
20
+ ### Ask the Developers
21
+
22
+ If you're experiencing a bug with the code, or have an idea for how it can be
23
+ improved, *please* create a new issue on GitHub so we can talk about it.
24
+
25
+ - [New issue][gh-ruby]
26
+
27
+ [so-ruby]: http://stackoverflow.com/questions/tagged/google-cloud-platform+ruby+pubsub
28
+
29
+ [gh-search-ruby]: https://github.com/googleapis/google-cloud-ruby/issues?q=label%3A%22api%3A+pubsub%22
30
+
31
+ [gh-ruby]: https://github.com/googleapis/google-cloud-ruby/issues/new
@@ -0,0 +1,139 @@
1
+ # Copyright 2016 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
+ ##
17
+ # This file is here to be autorequired by bundler, so that the
18
+ # Google::Cloud.pubsub and Google::Cloud#pubsub methods can be available, but
19
+ # the library and all dependencies won't be loaded until required and used.
20
+
21
+
22
+ gem "google-cloud-core"
23
+ require "google/cloud" unless defined? Google::Cloud.new
24
+ require "google/cloud/config"
25
+ require "googleauth"
26
+
27
+ module Google
28
+ module Cloud
29
+ ##
30
+ # Creates a new object for connecting to the Pub/Sub service.
31
+ # Each call creates a new connection.
32
+ #
33
+ # For more information on connecting to Google Cloud see the
34
+ # {file:AUTHENTICATION.md Authentication Guide}.
35
+ #
36
+ # @param [String, Array<String>] scope The OAuth 2.0 scopes controlling the
37
+ # set of resources and operations that the connection can access. See
38
+ # [Using OAuth 2.0 to Access Google
39
+ # APIs](https://developers.google.com/identity/protocols/OAuth2).
40
+ #
41
+ # The default scope is:
42
+ #
43
+ # * `https://www.googleapis.com/auth/pubsub`
44
+ # @param [Integer] timeout Default timeout to use in requests. Optional.
45
+ # @param [Hash] client_config A hash of values to override the default
46
+ # behavior of the API client. Optional.
47
+ #
48
+ # @return [Google::Cloud::PubSub::Project]
49
+ #
50
+ # @example
51
+ # require "google/cloud"
52
+ #
53
+ # gcloud = Google::Cloud.new
54
+ # pubsub = gcloud.pubsub
55
+ # topic = pubsub.topic "my-topic"
56
+ # topic.publish "task completed"
57
+ #
58
+ # @example The default scope can be overridden with the `scope` option:
59
+ # require "google/cloud"
60
+ #
61
+ # gcloud = Google::Cloud.new
62
+ # platform_scope = "https://www.googleapis.com/auth/cloud-platform"
63
+ # pubsub = gcloud.pubsub scope: platform_scope
64
+ #
65
+ def pubsub scope: nil, timeout: nil, client_config: nil
66
+ timeout ||= @timeout
67
+ Google::Cloud.pubsub @project, @keyfile, scope: scope, timeout: timeout, client_config: client_config
68
+ end
69
+
70
+ ##
71
+ # Creates a new object for connecting to the Pub/Sub service.
72
+ # Each call creates a new connection.
73
+ #
74
+ # For more information on connecting to Google Cloud see the
75
+ # {file:AUTHENTICATION.md Authentication Guide}.
76
+ #
77
+ # @param [String] project_id Project identifier for the Pub/Sub service you
78
+ # are connecting to. If not present, the default project for the
79
+ # credentials is used.
80
+ # @param [String, Hash, Google::Auth::Credentials] credentials The path to
81
+ # the keyfile as a String, the contents of the keyfile as a Hash, or a
82
+ # Google::Auth::Credentials object.
83
+ # (See {Google::Cloud::PubSub::Credentials})
84
+ # @param [String, Array<String>] scope The OAuth 2.0 scopes controlling the
85
+ # set of resources and operations that the connection can access. See
86
+ # [Using OAuth 2.0 to Access Google
87
+ # APIs](https://developers.google.com/identity/protocols/OAuth2).
88
+ #
89
+ # The default scope is:
90
+ #
91
+ # * `https://www.googleapis.com/auth/pubsub`
92
+ # @param [Integer] timeout Default timeout to use in requests. Optional.
93
+ # @param [Hash] client_config A hash of values to override the default
94
+ # behavior of the API client. Optional.
95
+ #
96
+ # @return [Google::Cloud::PubSub::Project]
97
+ #
98
+ # @example
99
+ # require "google/cloud"
100
+ #
101
+ # pubsub = Google::Cloud.pubsub
102
+ #
103
+ # topic = pubsub.topic "my-topic"
104
+ # topic.publish "task completed"
105
+ #
106
+ def self.pubsub project_id = nil, credentials = nil, scope: nil,
107
+ timeout: nil, client_config: nil
108
+ require "google/cloud/pubsub"
109
+ Google::Cloud::PubSub.new project_id: project_id, credentials: credentials,
110
+ scope: scope, timeout: timeout, client_config: client_config
111
+ end
112
+ end
113
+ end
114
+
115
+ # Set the default pubsub configuration
116
+ Google::Cloud.configure.add_config! :pubsub do |config|
117
+ default_project = Google::Cloud::Config.deferred do
118
+ ENV["PUBSUB_PROJECT"]
119
+ end
120
+ default_creds = Google::Cloud::Config.deferred do
121
+ Google::Cloud::Config.credentials_from_env(
122
+ "PUBSUB_CREDENTIALS", "PUBSUB_CREDENTIALS_JSON", "PUBSUB_KEYFILE", "PUBSUB_KEYFILE_JSON"
123
+ )
124
+ end
125
+ default_emulator = Google::Cloud::Config.deferred do
126
+ ENV["PUBSUB_EMULATOR_HOST"]
127
+ end
128
+
129
+ config.add_field! :project_id, default_project, match: String, allow_nil: true
130
+ config.add_alias! :project, :project_id
131
+ config.add_field! :credentials, default_creds, match: [String, Hash, Google::Auth::Credentials], allow_nil: true
132
+ config.add_alias! :keyfile, :credentials
133
+ config.add_field! :scope, nil, match: [String, Array]
134
+ config.add_field! :timeout, nil, match: Integer
135
+ config.add_field! :client_config, nil, match: Hash
136
+ config.add_field! :emulator_host, default_emulator, match: String, allow_nil: true
137
+ config.add_field! :on_error, nil, match: Proc
138
+ config.add_field! :endpoint, nil, match: String
139
+ end
@@ -0,0 +1,173 @@
1
+ # Copyright 2015 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 "google-cloud-pubsub"
17
+ require "google/cloud/pubsub/project"
18
+ require "google/cloud/config"
19
+ require "google/cloud/env"
20
+
21
+ module Google
22
+ module Cloud
23
+ ##
24
+ # # Google Cloud Pub/Sub
25
+ #
26
+ # Google Cloud Pub/Sub is designed to provide reliable, many-to-many,
27
+ # asynchronous messaging between applications. Publisher applications can
28
+ # send messages to a "topic" and other applications can subscribe to that
29
+ # topic to receive the messages. By decoupling senders and receivers, Google
30
+ # Cloud Pub/Sub allows developers to communicate between independently
31
+ # written applications.
32
+ #
33
+ # See {file:OVERVIEW.md Google Cloud Pub/Sub Overview}.
34
+ #
35
+ module PubSub
36
+ # rubocop:disable Metrics/AbcSize
37
+
38
+ ##
39
+ # Creates a new object for connecting to the Pub/Sub service.
40
+ # Each call creates a new connection.
41
+ #
42
+ # For more information on connecting to Google Cloud see the
43
+ # {file:AUTHENTICATION.md Authentication Guide}.
44
+ #
45
+ # @param [String] project_id Project identifier for the Pub/Sub service
46
+ # you are connecting to. If not present, the default project for the
47
+ # credentials is used.
48
+ # @param [String, Hash, Google::Auth::Credentials] credentials The path to
49
+ # the keyfile as a String, the contents of the keyfile as a Hash, or a
50
+ # Google::Auth::Credentials object. (See {PubSub::Credentials})
51
+ # @param [String, Array<String>] scope The OAuth 2.0 scopes controlling
52
+ # the set of resources and operations that the connection can access.
53
+ # See [Using OAuth 2.0 to Access Google
54
+ # APIs](https://developers.google.com/identity/protocols/OAuth2).
55
+ #
56
+ # The default scope is:
57
+ #
58
+ # * `https://www.googleapis.com/auth/pubsub`
59
+ # @param [Integer] timeout Default timeout to use in requests. Optional.
60
+ # @param [Hash] client_config A hash of values to override the default
61
+ # behavior of the API client. Optional.
62
+ # @param [String] endpoint Override of the endpoint host name. Optional.
63
+ # If the param is nil, uses the default endpoint.
64
+ # @param [String] emulator_host Pub/Sub emulator host. Optional.
65
+ # If the param is nil, uses the value of the `emulator_host` config.
66
+ # @param [String] project Alias for the `project_id` argument. Deprecated.
67
+ # @param [String] keyfile Alias for the `credentials` argument.
68
+ # Deprecated.
69
+ #
70
+ # @return [Google::Cloud::PubSub::Project]
71
+ #
72
+ # @example
73
+ # require "google/cloud/pubsub"
74
+ #
75
+ # pubsub = Google::Cloud::PubSub.new
76
+ #
77
+ # topic = pubsub.topic "my-topic"
78
+ # topic.publish "task completed"
79
+ #
80
+ def self.new project_id: nil, credentials: nil, scope: nil, timeout: nil, client_config: nil, endpoint: nil,
81
+ emulator_host: nil, project: nil, keyfile: nil
82
+ project_id ||= (project || default_project_id)
83
+ scope ||= configure.scope
84
+ timeout ||= configure.timeout
85
+ client_config ||= configure.client_config
86
+ endpoint ||= configure.endpoint
87
+ emulator_host ||= configure.emulator_host
88
+
89
+ if emulator_host
90
+ project_id = project_id.to_s # Always cast to a string
91
+ raise ArgumentError, "project_id is missing" if project_id.empty?
92
+
93
+ return PubSub::Project.new(
94
+ PubSub::Service.new(
95
+ project_id, :this_channel_is_insecure, host: emulator_host, timeout: timeout, client_config: client_config
96
+ )
97
+ )
98
+ end
99
+
100
+ credentials ||= (keyfile || default_credentials(scope: scope))
101
+ unless credentials.is_a? Google::Auth::Credentials
102
+ credentials = PubSub::Credentials.new credentials, scope: scope
103
+ end
104
+
105
+ project_id ||= credentials.project_id if credentials.respond_to? :project_id
106
+ project_id = project_id.to_s # Always cast to a string
107
+ raise ArgumentError, "project_id is missing" if project_id.empty?
108
+
109
+ PubSub::Project.new(
110
+ PubSub::Service.new(
111
+ project_id, credentials, timeout: timeout, host: endpoint, client_config: client_config
112
+ )
113
+ )
114
+ end
115
+
116
+ # rubocop:enable Metrics/AbcSize
117
+
118
+ ##
119
+ # Configure the Google Cloud PubSub library.
120
+ #
121
+ # The following PubSub configuration parameters are supported:
122
+ #
123
+ # * `project_id` - (String) Identifier for a PubSub project. (The
124
+ # parameter `project` is considered deprecated, but may also be used.)
125
+ # * `credentials` - (String, Hash, Google::Auth::Credentials) The path to
126
+ # the keyfile as a String, the contents of the keyfile as a Hash, or a
127
+ # Google::Auth::Credentials object. (See {PubSub::Credentials}) (The
128
+ # parameter `keyfile` is considered deprecated, but may also be used.)
129
+ # * `scope` - (String, Array<String>) The OAuth 2.0 scopes controlling
130
+ # the set of resources and operations that the connection can access.
131
+ # * `retries` - (Integer) Number of times to retry requests on server
132
+ # error.
133
+ # * `timeout` - (Integer) Default timeout to use in requests.
134
+ # * `client_config` - (Hash) A hash of values to override the default
135
+ # behavior of the API client.
136
+ # * `endpoint` - (String) Override of the endpoint host name, or `nil`
137
+ # to use the default endpoint.
138
+ # * `emulator_host` - (String) Host name of the emulator. Defaults to
139
+ # `ENV["PUBSUB_EMULATOR_HOST"]`
140
+ # * `on_error` - (Proc) A Proc to be run when an error is encountered
141
+ # on a background thread. The Proc must take the error object as the
142
+ # single argument. (See {Subscriber.on_error}.)
143
+ #
144
+ # @return [Google::Cloud::Config] The configuration object the
145
+ # Google::Cloud::PubSub library uses.
146
+ #
147
+ def self.configure
148
+ yield Google::Cloud.configure.pubsub if block_given?
149
+
150
+ Google::Cloud.configure.pubsub
151
+ end
152
+
153
+ ##
154
+ # @private Default project.
155
+ def self.default_project_id
156
+ Google::Cloud.configure.pubsub.project_id ||
157
+ Google::Cloud.configure.project_id ||
158
+ Google::Cloud.env.project_id
159
+ end
160
+
161
+ ##
162
+ # @private Default credentials.
163
+ def self.default_credentials scope: nil
164
+ Google::Cloud.configure.pubsub.credentials ||
165
+ Google::Cloud.configure.credentials ||
166
+ PubSub::Credentials.default(scope: scope)
167
+ end
168
+ end
169
+
170
+ ## Legacy namespace
171
+ Pubsub = PubSub unless const_defined? :Pubsub
172
+ end
173
+ end
@@ -0,0 +1,399 @@
1
+ # Copyright 2017 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
+ require "google/cloud/pubsub/errors"
19
+ require "google/cloud/pubsub/async_publisher/batch"
20
+ require "google/cloud/pubsub/publish_result"
21
+ require "google/cloud/pubsub/service"
22
+ require "google/cloud/pubsub/convert"
23
+
24
+ module Google
25
+ module Cloud
26
+ module PubSub
27
+ ##
28
+ # Used to publish multiple messages in batches to a topic. See
29
+ # {Google::Cloud::PubSub::Topic#async_publisher}
30
+ #
31
+ # @example
32
+ # require "google/cloud/pubsub"
33
+ #
34
+ # pubsub = Google::Cloud::PubSub.new
35
+ #
36
+ # topic = pubsub.topic "my-topic"
37
+ # topic.publish_async "task completed" do |result|
38
+ # if result.succeeded?
39
+ # log_publish_success result.data
40
+ # else
41
+ # log_publish_failure result.data, result.error
42
+ # end
43
+ # end
44
+ #
45
+ # topic.async_publisher.stop.wait!
46
+ #
47
+ # @attr_reader [String] topic_name The name of the topic the messages are published to. In the form of
48
+ # "/projects/project-identifier/topics/topic-name".
49
+ # @attr_reader [Integer] max_bytes The maximum size of messages to be collected before the batch is published.
50
+ # Default is 1,000,000 (1MB).
51
+ # @attr_reader [Integer] max_messages The maximum number of messages to be collected before the batch is
52
+ # published. Default is 100.
53
+ # @attr_reader [Numeric] interval The number of seconds to collect messages before the batch is published. Default
54
+ # is 0.01.
55
+ # @attr_reader [Numeric] publish_threads The number of threads used to publish messages. Default is 2.
56
+ # @attr_reader [Numeric] callback_threads The number of threads to handle the published messages' callbacks.
57
+ # Default is 4.
58
+ #
59
+ class AsyncPublisher
60
+ include MonitorMixin
61
+
62
+ attr_reader :topic_name, :max_bytes, :max_messages, :interval,
63
+ :publish_threads, :callback_threads
64
+ ##
65
+ # @private Implementation accessors
66
+ attr_reader :service, :batch, :publish_thread_pool,
67
+ :callback_thread_pool
68
+
69
+ ##
70
+ # @private Create a new instance of the object.
71
+ def initialize topic_name, service, max_bytes: 1_000_000, max_messages: 100, interval: 0.01, threads: {}
72
+ # init MonitorMixin
73
+ super()
74
+ @topic_name = service.topic_path topic_name
75
+ @service = service
76
+
77
+ @max_bytes = max_bytes
78
+ @max_messages = max_messages
79
+ @interval = interval
80
+ @publish_threads = (threads[:publish] || 2).to_i
81
+ @callback_threads = (threads[:callback] || 4).to_i
82
+
83
+ @published_at = nil
84
+ @publish_thread_pool = Concurrent::ThreadPoolExecutor.new max_threads: @publish_threads
85
+ @callback_thread_pool = Concurrent::ThreadPoolExecutor.new max_threads: @callback_threads
86
+
87
+ @ordered = false
88
+ @batches = {}
89
+ @cond = new_cond
90
+
91
+ @thread = Thread.new { run_background }
92
+ end
93
+
94
+ ##
95
+ # Add a message to the async publisher to be published to the topic.
96
+ # Messages will be collected in batches and published together.
97
+ # See {Google::Cloud::PubSub::Topic#publish_async}
98
+ #
99
+ # @param [String, File] data The message payload. This will be converted
100
+ # to bytes encoded as ASCII-8BIT.
101
+ # @param [Hash] attributes Optional attributes for the message.
102
+ # @param [String] ordering_key Identifies related messages for which
103
+ # publish order should be respected.
104
+ # @yield [result] the callback for when the message has been published
105
+ # @yieldparam [PublishResult] result the result of the asynchronous
106
+ # publish
107
+ # @raise [Google::Cloud::PubSub::AsyncPublisherStopped] when the
108
+ # publisher is stopped. (See {#stop} and {#stopped?}.)
109
+ # @raise [Google::Cloud::PubSub::OrderedMessagesDisabled] when
110
+ # publishing a message with an `ordering_key` but ordered messages are
111
+ # not enabled. (See {#message_ordering?} and
112
+ # {#enable_message_ordering!}.)
113
+ # @raise [Google::Cloud::PubSub::OrderingKeyError] when publishing a
114
+ # message with an `ordering_key` that has already failed when
115
+ # publishing. Use {#resume_publish} to allow this `ordering_key` to be
116
+ # published again.
117
+ #
118
+ def publish data = nil, attributes = nil, ordering_key: nil, **extra_attrs, &callback
119
+ msg = Convert.pubsub_message data, attributes, ordering_key, extra_attrs
120
+
121
+ synchronize do
122
+ raise AsyncPublisherStopped if @stopped
123
+ raise OrderedMessagesDisabled if !@ordered && !msg.ordering_key.empty? # default is empty string
124
+
125
+ batch = resolve_batch_for_message msg
126
+ raise OrderingKeyError, batch.ordering_key if batch.canceled?
127
+ batch_action = batch.add msg, callback
128
+ if batch_action == :full
129
+ publish_batches!
130
+ elsif @published_at.nil?
131
+ # Set initial time to now to start the background counter
132
+ @published_at = Time.now
133
+ end
134
+ @cond.signal
135
+ end
136
+
137
+ nil
138
+ end
139
+
140
+ ##
141
+ # Begins the process of stopping the publisher. Messages already in
142
+ # the queue will be published, but no new messages can be added. Use
143
+ # {#wait!} to block until the publisher is fully stopped and all
144
+ # pending messages have been published.
145
+ #
146
+ # @return [AsyncPublisher] returns self so calls can be chained.
147
+ def stop
148
+ synchronize do
149
+ break if @stopped
150
+
151
+ @stopped = true
152
+ publish_batches! stop: true
153
+ @cond.signal
154
+ @publish_thread_pool.shutdown
155
+ end
156
+
157
+ self
158
+ end
159
+
160
+ ##
161
+ # Blocks until the publisher is fully stopped, all pending messages have
162
+ # been published, and all callbacks have completed, or until `timeout`
163
+ # seconds have passed.
164
+ #
165
+ # Does not stop the publisher. To stop the publisher, first call {#stop}
166
+ # and then call {#wait!} to block until the publisher is stopped
167
+ #
168
+ # @param [Number, nil] timeout The number of seconds to block until the
169
+ # publisher is fully stopped. Default will block indefinitely.
170
+ #
171
+ # @return [AsyncPublisher] returns self so calls can be chained.
172
+ def wait! timeout = nil
173
+ synchronize do
174
+ @publish_thread_pool.wait_for_termination timeout
175
+
176
+ @callback_thread_pool.shutdown
177
+ @callback_thread_pool.wait_for_termination timeout
178
+ end
179
+
180
+ self
181
+ end
182
+
183
+ ##
184
+ # Stop this publisher and block until the publisher is fully stopped,
185
+ # all pending messages have been published, and all callbacks have
186
+ # completed, or until `timeout` seconds have passed.
187
+ #
188
+ # The same as calling {#stop} and {#wait!}.
189
+ #
190
+ # @param [Number, nil] timeout The number of seconds to block until the
191
+ # publisher is fully stopped. Default will block indefinitely.
192
+ #
193
+ # @return [AsyncPublisher] returns self so calls can be chained.
194
+ def stop! timeout = nil
195
+ stop
196
+ wait! timeout
197
+ end
198
+
199
+ ##
200
+ # Forces all messages in the current batch to be published
201
+ # immediately.
202
+ #
203
+ # @return [AsyncPublisher] returns self so calls can be chained.
204
+ def flush
205
+ synchronize do
206
+ publish_batches!
207
+ @cond.signal
208
+ end
209
+
210
+ self
211
+ end
212
+
213
+ ##
214
+ # Whether the publisher has been started.
215
+ #
216
+ # @return [boolean] `true` when started, `false` otherwise.
217
+ def started?
218
+ !stopped?
219
+ end
220
+
221
+ ##
222
+ # Whether the publisher has been stopped.
223
+ #
224
+ # @return [boolean] `true` when stopped, `false` otherwise.
225
+ def stopped?
226
+ synchronize { @stopped }
227
+ end
228
+
229
+ ##
230
+ # Enables message ordering for messages with ordering keys. When
231
+ # enabled, messages published with the same `ordering_key` will be
232
+ # delivered in the order they were published.
233
+ #
234
+ # See {#message_ordering?}. See {Topic#publish_async},
235
+ # {Subscription#listen}, and {Message#ordering_key}.
236
+ #
237
+ def enable_message_ordering!
238
+ synchronize { @ordered = true }
239
+ end
240
+
241
+ ##
242
+ # Whether message ordering for messages with ordering keys has been
243
+ # enabled. When enabled, messages published with the same `ordering_key`
244
+ # will be delivered in the order they were published. When disabled,
245
+ # messages may be delivered in any order.
246
+ #
247
+ # See {#enable_message_ordering!}. See {Topic#publish_async},
248
+ # {Subscription#listen}, and {Message#ordering_key}.
249
+ #
250
+ # @return [Boolean]
251
+ #
252
+ def message_ordering?
253
+ synchronize { @ordered }
254
+ end
255
+
256
+ ##
257
+ # Resume publishing ordered messages for the provided ordering key.
258
+ #
259
+ # @param [String] ordering_key Identifies related messages for which
260
+ # publish order should be respected.
261
+ #
262
+ # @return [boolean] `true` when resumed, `false` otherwise.
263
+ #
264
+ def resume_publish ordering_key
265
+ synchronize do
266
+ batch = resolve_batch_for_ordering_key ordering_key
267
+ return if batch.nil?
268
+ batch.resume!
269
+ end
270
+ end
271
+
272
+ protected
273
+
274
+ def run_background
275
+ synchronize do
276
+ until @stopped
277
+ if @published_at.nil?
278
+ @cond.wait
279
+ next
280
+ end
281
+
282
+ time_since_first_publish = Time.now - @published_at
283
+ if time_since_first_publish > @interval
284
+ # interval met, flush the batches...
285
+ publish_batches!
286
+ @cond.wait
287
+ else
288
+ # still waiting for the interval to publish the batch...
289
+ timeout = @interval - time_since_first_publish
290
+ @cond.wait timeout
291
+ end
292
+ end
293
+ end
294
+ end
295
+
296
+ def resolve_batch_for_message msg
297
+ @batches[msg.ordering_key] ||= Batch.new self, msg.ordering_key
298
+ end
299
+
300
+ def resolve_batch_for_ordering_key ordering_key
301
+ @batches[ordering_key]
302
+ end
303
+
304
+ def publish_batches! stop: nil
305
+ @batches.reject! { |_ordering_key, batch| batch.empty? }
306
+ @batches.values.each do |batch|
307
+ ready = batch.publish! stop: stop
308
+ publish_batch_async @topic_name, batch if ready
309
+ end
310
+ # Set published_at to nil to wait indefinitely
311
+ @published_at = nil
312
+ end
313
+
314
+ def publish_batch_async topic_name, batch
315
+ # TODO: raise unless @publish_thread_pool.running?
316
+ return unless @publish_thread_pool.running?
317
+
318
+ Concurrent::Promises.future_on(
319
+ @publish_thread_pool, topic_name, batch
320
+ ) { |t, b| publish_batch_sync t, b }
321
+ end
322
+
323
+ # rubocop:disable Metrics/AbcSize
324
+ # rubocop:disable Metrics/MethodLength
325
+
326
+ def publish_batch_sync topic_name, batch
327
+ # The only batch methods that are safe to call from the loop are
328
+ # rebalance! and reset! because they are the only methods that are
329
+ # synchronized.
330
+ loop do
331
+ items = batch.rebalance!
332
+
333
+ unless items.empty?
334
+ grpc = @service.publish topic_name, items.map(&:msg)
335
+ items.zip Array(grpc.message_ids) do |item, id|
336
+ next unless item.callback
337
+
338
+ item.msg.message_id = id
339
+ publish_result = PublishResult.from_grpc item.msg
340
+ execute_callback_async item.callback, publish_result
341
+ end
342
+ end
343
+
344
+ break unless batch.reset!
345
+ end
346
+ rescue StandardError => e
347
+ items = batch.items
348
+
349
+ unless batch.ordering_key.empty?
350
+ retry if publish_batch_error_retryable? e
351
+ # Cancel the batch if the error is not to be retried.
352
+ begin
353
+ raise OrderingKeyError, batch.ordering_key
354
+ rescue OrderingKeyError => e
355
+ # The existing e variable is not set to OrderingKeyError
356
+ # Get all unsent messages for the callback
357
+ items = batch.cancel!
358
+ end
359
+ end
360
+
361
+ items.each do |item|
362
+ next unless item.callback
363
+
364
+ publish_result = PublishResult.from_error item.msg, e
365
+ execute_callback_async item.callback, publish_result
366
+ end
367
+
368
+ # publish will retry indefinitely, as long as there are unsent items.
369
+ retry if batch.reset!
370
+ end
371
+
372
+ # rubocop:enable Metrics/AbcSize
373
+ # rubocop:enable Metrics/MethodLength
374
+
375
+ PUBLISH_RETRY_ERRORS = [
376
+ GRPC::Cancelled, GRPC::DeadlineExceeded, GRPC::Internal,
377
+ GRPC::ResourceExhausted, GRPC::Unauthenticated, GRPC::Unavailable,
378
+ GRPC::Core::CallError
379
+ ].freeze
380
+
381
+ def publish_batch_error_retryable? error
382
+ PUBLISH_RETRY_ERRORS.any? { |klass| error.is_a? klass }
383
+ end
384
+
385
+ def execute_callback_async callback, publish_result
386
+ return unless @callback_thread_pool.running?
387
+
388
+ Concurrent::Promises.future_on(
389
+ @callback_thread_pool, callback, publish_result
390
+ ) do |cback, p_result|
391
+ cback.call p_result
392
+ end
393
+ end
394
+ end
395
+ end
396
+
397
+ Pubsub = PubSub unless const_defined? :Pubsub
398
+ end
399
+ end