google-cloud-pubsub 1.9.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 (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