Skip to main content

google_cloud_pubsub/publisher/
client.rs

1// Copyright 2025 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
15use super::options::{BatchingOptions, HedgingOptions};
16use crate::publisher::actor::BundledMessage;
17use crate::publisher::actor::ToDispatcher;
18use crate::publisher::builder::PublisherBuilder;
19
20use tokio::sync::mpsc::UnboundedSender;
21use tokio::sync::oneshot;
22
23pub use super::base_publisher::BasePublisher;
24
25/// A Publisher client for the [Cloud Pub/Sub] API.
26///
27/// A `Publisher` sends messages to a specific topic. It manages message batching
28/// and sending in a background task.
29///
30/// ```
31/// # async fn sample() -> anyhow::Result<()> {
32/// # use google_cloud_pubsub::*;
33/// # use google_cloud_pubsub::client::Publisher;
34/// # use model::Message;
35/// let publisher = Publisher::builder("projects/my-project/topics/my-topic").build().await?;
36/// let message_id_future = publisher.publish(Message::new().set_data("Hello, World"));
37/// # Ok(()) }
38/// ```
39///
40/// # Configuration
41///
42/// To configure `Publisher` use the `with_*` methods in the type returned
43/// by [builder()][Publisher::builder]. The default configuration should
44/// work for most applications. Common configuration changes include
45///
46/// * [with_endpoint()]: by default this client uses the global default endpoint
47///   (`https://pubsub.googleapis.com`). Applications using regional
48///   endpoints or running in restricted networks (e.g. a network configured
49///   with [Private Google Access with VPC Service Controls]) may want to
50///   override this default.
51/// * [with_credentials()]: by default this client uses
52///   [Application Default Credentials]. Applications using custom
53///   authentication may need to override this default.
54///
55/// # Pooling and Cloning
56///
57/// `Publisher` holds a connection pool internally, it is advised to
58/// create one and then reuse it. You do not need to wrap `Publisher` in
59/// an [Rc](std::rc::Rc) or [Arc](std::sync::Arc) to reuse it.
60///
61/// [cloud pub/sub]: https://docs.cloud.google.com/pubsub/docs/overview
62/// [with_endpoint()]: crate::builder::publisher::PublisherBuilder::with_endpoint
63/// [with_credentials()]: crate::builder::publisher::PublisherBuilder::with_credentials
64/// [Private Google Access with VPC Service Controls]: https://cloud.google.com/vpc-service-controls/docs/private-connectivity
65/// [Application Default Credentials]: https://cloud.google.com/docs/authentication#adc
66#[derive(Debug, Clone)]
67pub struct Publisher {
68    // A copy of the batching options are stored in the Publisher for testing
69    // purposes and also to include in the Debug output.
70    #[cfg_attr(not(test), expect(dead_code))]
71    pub(crate) batching_options: BatchingOptions,
72    // A copy of the hedging options are stored in the Publisher for testing
73    // purposes and also to include in the Debug output.
74    #[cfg_attr(not(test), expect(dead_code))]
75    pub(crate) hedging_options: Option<HedgingOptions>,
76    pub(crate) tx: UnboundedSender<ToDispatcher>,
77}
78
79impl Publisher {
80    /// Returns a builder for [Publisher].
81    ///
82    /// # Example
83    ///
84    /// ```
85    /// # async fn sample() -> anyhow::Result<()> {
86    /// # use google_cloud_pubsub::*;
87    /// # use google_cloud_pubsub::client::Publisher;
88    /// let publisher = Publisher::builder("projects/my-project/topics/topic").build().await?;
89    /// # Ok(()) }
90    /// ```
91    pub fn builder<T: Into<String>>(topic: T) -> PublisherBuilder {
92        PublisherBuilder::new(topic.into())
93    }
94
95    /// Publishes a message to the topic.
96    ///
97    /// When this method encounters a non-recoverable error publishing for an ordering key,
98    /// it will pause publishing on all new messages on that ordering key. Any outstanding
99    /// messages that have not yet been published may return an error.
100    ///
101    /// ```
102    /// # use google_cloud_pubsub::client::Publisher;
103    /// # async fn sample(publisher: Publisher) -> anyhow::Result<()> {
104    /// # use google_cloud_pubsub::model::Message;
105    /// let message_id = publisher.publish(Message::new().set_data("Hello, World")).await?;
106    /// # Ok(()) }
107    /// ```
108    #[must_use = "ignoring the publish result may lead to undetected delivery failures"]
109    pub fn publish(&self, msg: crate::model::Message) -> crate::publisher::PublishFuture {
110        let (tx, rx) = tokio::sync::oneshot::channel();
111
112        // If this fails, the Dispatcher is gone, which indicates it has been dropped,
113        // possibly due to the background task being stopped by the runtime.
114        // The PublishFuture will automatically receive an error when `tx` is dropped.
115        if self
116            .tx
117            .send(ToDispatcher::Publish(BundledMessage { msg, tx }))
118            .is_err()
119        {
120            // `tx` is dropped here if the send errors.
121        }
122        crate::publisher::PublishFuture { rx }
123    }
124
125    /// Flushes all buffered messages across all ordering keys, sending them immediately.
126    ///
127    /// ```
128    /// # use google_cloud_pubsub::model::Message;
129    /// # async fn sample(publisher: google_cloud_pubsub::client::Publisher) -> anyhow::Result<()> {
130    /// let _handle = publisher.publish(Message::new().set_data("event"));
131    /// // Ensures the message above is sent without needing to track its future.
132    /// publisher.flush().await;
133    /// # Ok(())
134    /// # }
135    /// ```
136    ///
137    /// This method bypasses configured batching delays and returns only after all
138    /// messages buffered at the time of the call have reached a terminal state
139    /// (success or permanent failure).
140    ///
141    /// ### Recommendations
142    ///
143    /// *   For most use cases, we recommend you `.await`
144    ///     the [`PublishFuture`][crate::publisher::PublishFuture] returned by
145    ///     [`publish`][Self::publish] to retrieve message IDs and handle
146    ///     specific errors.
147    /// *   Use `flush()` as a convenience during application shutdown to
148    ///     ensure the client attempts to send all outstanding data.
149    pub async fn flush(&self) {
150        let (tx, rx) = oneshot::channel();
151        if self.tx.send(ToDispatcher::Flush(tx)).is_ok() {
152            let _ = rx.await;
153        }
154    }
155
156    /// Resume accepting publish for a paused ordering key.
157    ///
158    /// Publishing using an ordering key might be paused if an error is encountered while publishing, to prevent messages from being published out of order.
159    /// If the ordering key is not currently paused, this function is a no-op.
160    ///
161    /// # Example
162    ///
163    /// ```
164    /// # use google_cloud_pubsub::model::Message;
165    /// # async fn sample(publisher: google_cloud_pubsub::client::Publisher) -> anyhow::Result<()> {
166    /// if let Err(_) = publisher.publish(Message::new().set_data("foo").set_ordering_key("bar")).await {
167    ///     // Error handling code can go here.
168    ///     publisher.resume_publish("bar");
169    /// }
170    /// # Ok(())
171    /// # }
172    /// ```
173    pub fn resume_publish<T: std::convert::Into<std::string::String>>(&self, ordering_key: T) {
174        let _ = self
175            .tx
176            .send(ToDispatcher::ResumePublish(ordering_key.into()));
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183    use crate::publisher::builder::PublisherPartialBuilder;
184    use crate::publisher::client::BasePublisher;
185    use crate::publisher::constants::*;
186    use crate::publisher::options::BatchingOptions;
187    use crate::{
188        generated::gapic_dataplane::client::Publisher as GapicPublisher,
189        model::{Message, PublishResponse},
190    };
191    use google_cloud_test_macros::tokio_test_no_panics;
192    use mockall::Sequence;
193    use rand::{RngExt, distr::Alphanumeric};
194    use std::error::Error;
195    use std::time::Duration;
196
197    static TOPIC: &str = "my-topic";
198
199    mockall::mock! {
200        #[derive(Debug)]
201        GapicPublisher {}
202        impl crate::generated::gapic_dataplane::stub::Publisher for GapicPublisher {
203            async fn publish(&self, req: crate::model::PublishRequest, _options: crate::RequestOptions) -> crate::Result<crate::Response<crate::model::PublishResponse>>;
204        }
205    }
206
207    // Similar to GapicPublisher but returns impl Future instead.
208    // This is useful for mocking a response with delays/timeouts.
209    // See https://github.com/asomers/mockall/issues/189 for more
210    // detail on why this is needed.
211    // While this can used inplace of GapicPublisher, it makes the
212    // normal usage without async closure much more cumbersome.
213    mockall::mock! {
214        #[derive(Debug)]
215        GapicPublisherWithFuture {}
216        impl crate::generated::gapic_dataplane::stub::Publisher for GapicPublisherWithFuture {
217            fn publish(&self, req: crate::model::PublishRequest, _options: crate::RequestOptions) -> impl Future<Output=crate::Result<crate::Response<crate::model::PublishResponse>>> + Send;
218        }
219    }
220
221    fn publish_ok(
222        req: crate::model::PublishRequest,
223        _options: crate::RequestOptions,
224    ) -> crate::Result<crate::Response<crate::model::PublishResponse>> {
225        let ids = req
226            .messages
227            .iter()
228            .map(|m| String::from_utf8(m.data.to_vec()).unwrap());
229        Ok(crate::Response::from(
230            PublishResponse::new().set_message_ids(ids),
231        ))
232    }
233
234    fn publish_err(
235        _req: crate::model::PublishRequest,
236        _options: crate::RequestOptions,
237    ) -> crate::Result<crate::Response<crate::model::PublishResponse>> {
238        Err(crate::Error::service(
239            google_cloud_gax::error::rpc::Status::default()
240                .set_code(google_cloud_gax::error::rpc::Code::Unknown)
241                .set_message("unknown error has occurred"),
242        ))
243    }
244
245    #[track_caller]
246    fn assert_publish_err(got_err: crate::error::PublishError) {
247        assert!(
248            matches!(got_err, crate::error::PublishError::Rpc(_)),
249            "{got_err:?}"
250        );
251        let source = got_err
252            .source()
253            .and_then(|e| e.downcast_ref::<std::sync::Arc<crate::Error>>())
254            .expect("send error should contain a source");
255        assert!(source.status().is_some(), "{got_err:?}");
256        assert_eq!(
257            source.status().unwrap().code,
258            google_cloud_gax::error::rpc::Code::Unknown,
259            "{got_err:?}"
260        );
261    }
262
263    fn generate_random_data() -> String {
264        rand::rng()
265            .sample_iter(&Alphanumeric)
266            .take(16)
267            .map(char::from)
268            .collect()
269    }
270
271    macro_rules! assert_publishing_is_ok {
272        ($publisher:ident, $($ordering_key:expr),+) => {
273            $(
274                let msg = generate_random_data();
275                let got = $publisher
276                    .publish(
277                        Message::new()
278                            .set_ordering_key($ordering_key)
279                            .set_data(msg.clone()),
280                    )
281                    .await;
282                assert_eq!(got?, msg);
283            )+
284        };
285    }
286
287    macro_rules! assert_publishing_is_paused {
288        ($publisher:ident, $($ordering_key:expr),+) => {
289            $(
290                let got_err = $publisher
291                    .publish(
292                        Message::new()
293                            .set_ordering_key($ordering_key)
294                            .set_data(generate_random_data()),
295                    )
296                    .await;
297                assert!(
298                    matches!(got_err, Err(crate::error::PublishError::OrderingKeyPaused)),
299                    "{got_err:?}"
300                );
301            )+
302        };
303    }
304
305    #[tokio_test_no_panics]
306    async fn publisher_publish_successfully() -> anyhow::Result<()> {
307        let mut mock = MockGapicPublisher::new();
308        mock.expect_publish()
309            .times(2)
310            .withf(|req, _o| req.topic == TOPIC)
311            .returning(publish_ok);
312
313        let client = GapicPublisher::from_stub(mock);
314        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string())
315            .set_message_count_threshold(1_u32)
316            .build();
317
318        let messages = [
319            Message::new().set_data("hello"),
320            Message::new().set_data("world"),
321        ];
322        let mut handles = Vec::new();
323        for msg in messages {
324            let handle = publisher.publish(msg.clone());
325            handles.push((msg, handle));
326        }
327
328        for (id, rx) in handles.into_iter() {
329            let got = rx.await?;
330            let id = String::from_utf8(id.data.to_vec())?;
331            assert_eq!(got, id);
332        }
333
334        Ok(())
335    }
336
337    #[tokio_test_no_panics]
338    async fn publisher_publish_successfully_with_arc() -> anyhow::Result<()> {
339        let mut mock = MockGapicPublisher::new();
340        mock.expect_publish()
341            .times(2)
342            .withf(|req, _o| req.topic == TOPIC)
343            .returning(publish_ok);
344
345        let mock_arc = std::sync::Arc::new(mock);
346        let client = GapicPublisher::from_stub::<MockGapicPublisher>(mock_arc);
347        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string())
348            .set_message_count_threshold(1_u32)
349            .build();
350
351        let messages = [
352            Message::new().set_data("hello"),
353            Message::new().set_data("world"),
354        ];
355        let mut handles = Vec::new();
356        for msg in messages {
357            let handle = publisher.publish(msg.clone());
358            handles.push((msg, handle));
359        }
360
361        for (id, rx) in handles.into_iter() {
362            let got = rx.await?;
363            let id = String::from_utf8(id.data.to_vec())?;
364            assert_eq!(got, id);
365        }
366
367        Ok(())
368    }
369
370    #[tokio::test]
371    async fn publisher_publish_large_message() -> anyhow::Result<()> {
372        let mut mock = MockGapicPublisher::new();
373        mock.expect_publish()
374            .withf(|req, _o| req.topic == TOPIC)
375            .returning(publish_ok);
376
377        let client = GapicPublisher::from_stub(mock);
378        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string())
379            .set_byte_threshold(1_u32)
380            .build();
381        assert_publishing_is_ok!(publisher, "");
382        assert_publishing_is_ok!(publisher, "key");
383
384        Ok(())
385    }
386
387    #[tokio::test(start_paused = true)]
388    async fn worker_handles_forced_shutdown_gracefully() -> anyhow::Result<()> {
389        let mock = MockGapicPublisher::new();
390
391        let client = GapicPublisher::from_stub(mock);
392        let (publisher, background_task_handle) =
393            PublisherPartialBuilder::new(client, TOPIC.to_string())
394                .set_message_count_threshold(100_u32)
395                .build_return_handle();
396
397        let messages = [
398            Message::new().set_data("hello"),
399            Message::new().set_data("world"),
400        ];
401        let mut handles = Vec::new();
402        for msg in messages {
403            let handle = publisher.publish(msg);
404            handles.push(handle);
405        }
406
407        background_task_handle.abort();
408
409        for rx in handles.into_iter() {
410            rx.await
411                .expect_err("expected error when background task canceled");
412        }
413
414        Ok(())
415    }
416
417    #[tokio_test_no_panics(start_paused = true)]
418    async fn dropping_publisher_flushes_pending_messages() -> anyhow::Result<()> {
419        // If we hold on to the handles returned from the publisher, it should
420        // be safe to drop the publisher and .await on the handles.
421        let mut mock = MockGapicPublisher::new();
422        mock.expect_publish()
423            .withf(|req, _o| req.topic == TOPIC)
424            .times(2)
425            .returning(publish_ok);
426
427        let client = GapicPublisher::from_stub(mock);
428        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string())
429            .set_message_count_threshold(1000_u32)
430            .set_delay_threshold(Duration::from_secs(60))
431            .build();
432
433        let start = tokio::time::Instant::now();
434        let messages = [
435            Message::new().set_data("hello"),
436            Message::new().set_data("world"),
437            Message::new().set_data("hello").set_ordering_key("key"),
438            Message::new().set_data("world").set_ordering_key("key"),
439        ];
440        let mut handles = Vec::new();
441        for msg in messages {
442            let handle = publisher.publish(msg.clone());
443            handles.push((msg, handle));
444        }
445        drop(publisher); // This should trigger the publisher to send all pending messages.
446
447        for (id, rx) in handles.into_iter() {
448            let got = rx.await?;
449            let id = String::from_utf8(id.data.to_vec())?;
450            assert_eq!(got, id);
451            assert_eq!(start.elapsed(), Duration::ZERO);
452        }
453
454        Ok(())
455    }
456
457    #[tokio_test_no_panics]
458    async fn publisher_handles_publish_errors() -> anyhow::Result<()> {
459        let mut mock = MockGapicPublisher::new();
460        mock.expect_publish()
461            .times(2)
462            .withf(|req, _o| req.topic == TOPIC)
463            .returning(publish_err);
464
465        let client = GapicPublisher::from_stub(mock);
466        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string())
467            .set_message_count_threshold(1_u32)
468            .build();
469
470        let messages = [
471            Message::new().set_data("hello"),
472            Message::new().set_data("world"),
473        ];
474
475        let mut handles = Vec::new();
476        for msg in messages {
477            let handle = publisher.publish(msg.clone());
478            handles.push(handle);
479        }
480
481        for rx in handles.into_iter() {
482            let got = rx.await;
483            assert!(got.is_err(), "{got:?}");
484        }
485
486        Ok(())
487    }
488
489    #[tokio_test_no_panics(start_paused = true)]
490    async fn flush_sends_pending_messages_immediately() -> anyhow::Result<()> {
491        let mut mock = MockGapicPublisher::new();
492        mock.expect_publish()
493            .withf(|req, _o| req.topic == TOPIC)
494            .returning(publish_ok);
495
496        let client = GapicPublisher::from_stub(mock);
497        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string())
498            // Set a long delay.
499            .set_message_count_threshold(1000_u32)
500            .set_delay_threshold(Duration::from_secs(60))
501            .build();
502
503        let start = tokio::time::Instant::now();
504        let messages = [
505            Message::new().set_data("hello"),
506            Message::new().set_data("world"),
507        ];
508        let mut handles = Vec::new();
509        for msg in messages {
510            let handle = publisher.publish(msg.clone());
511            handles.push((msg, handle));
512        }
513
514        publisher.flush().await;
515        assert_eq!(start.elapsed(), Duration::ZERO);
516
517        let post = publisher.publish(Message::new().set_data("after"));
518        for (id, rx) in handles.into_iter() {
519            let got = rx.await?;
520            let id = String::from_utf8(id.data.to_vec())?;
521            assert_eq!(got, id);
522            assert_eq!(start.elapsed(), Duration::ZERO);
523        }
524
525        // Validate that the post message is only sent after the next timeout.
526        // I.e., the Publisher does not continuously flush new messages.
527        let got = post.await?;
528        assert_eq!(got, "after");
529        assert_eq!(start.elapsed(), Duration::from_secs(60));
530
531        Ok(())
532    }
533
534    #[tokio_test_no_panics(start_paused = true)]
535    // Users should be able to drop handles and the messages will still send.
536    async fn dropping_handles_does_not_prevent_publishing() -> anyhow::Result<()> {
537        let mut mock = MockGapicPublisher::new();
538        mock.expect_publish()
539            .withf(|r, _| {
540                r.messages.len() == 2
541                    && r.messages[0].data == "hello"
542                    && r.messages[1].data == "world"
543            })
544            .return_once(publish_ok);
545
546        let client = GapicPublisher::from_stub(mock);
547        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string())
548            // Set a long delay.
549            .set_message_count_threshold(1000_u32)
550            .set_delay_threshold(Duration::from_secs(60))
551            .build();
552
553        let start = tokio::time::Instant::now();
554        let messages = [
555            Message::new().set_data("hello"),
556            Message::new().set_data("world"),
557        ];
558        for msg in messages {
559            let handle = publisher.publish(msg.clone());
560            drop(handle);
561        }
562
563        publisher.flush().await;
564        assert_eq!(start.elapsed(), Duration::ZERO);
565
566        Ok(())
567    }
568
569    #[tokio::test(start_paused = true)]
570    async fn flush_with_no_messages_is_noop() -> anyhow::Result<()> {
571        let mock = MockGapicPublisher::new();
572
573        let client = GapicPublisher::from_stub(mock);
574        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string()).build();
575
576        let start = tokio::time::Instant::now();
577        publisher.flush().await;
578        assert_eq!(start.elapsed(), Duration::ZERO);
579
580        Ok(())
581    }
582
583    #[tokio_test_no_panics]
584    async fn batch_sends_on_message_count_threshold_success() -> anyhow::Result<()> {
585        // Make sure all messages in a batch receive the correct message ID.
586        let mut mock = MockGapicPublisher::new();
587        mock.expect_publish()
588            .withf(|r, _| r.messages.len() == 2)
589            .return_once(publish_ok);
590
591        let client = GapicPublisher::from_stub(mock);
592        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string())
593            .set_message_count_threshold(2_u32)
594            .set_byte_threshold(MAX_BYTES)
595            .set_delay_threshold(std::time::Duration::MAX)
596            .build();
597
598        let messages = [
599            Message::new().set_data("hello"),
600            Message::new().set_data("world"),
601        ];
602        let mut handles = Vec::new();
603        for msg in messages {
604            let handle = publisher.publish(msg.clone());
605            handles.push((msg, handle));
606        }
607
608        for (id, rx) in handles.into_iter() {
609            let got = rx.await?;
610            let id = String::from_utf8(id.data.to_vec())?;
611            assert_eq!(got, id);
612        }
613
614        Ok(())
615    }
616
617    #[tokio_test_no_panics]
618    async fn batch_sends_on_message_count_threshold_error() -> anyhow::Result<()> {
619        // Make sure all messages in a batch receive an error.
620        let mut mock = MockGapicPublisher::new();
621        mock.expect_publish()
622            .withf(|r, _| r.messages.len() == 2)
623            .return_once(publish_err);
624
625        let client = GapicPublisher::from_stub(mock);
626        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string())
627            .set_message_count_threshold(2_u32)
628            .set_byte_threshold(MAX_BYTES)
629            .set_delay_threshold(std::time::Duration::MAX)
630            .build();
631
632        let messages = [
633            Message::new().set_data("hello"),
634            Message::new().set_data("world"),
635        ];
636        let mut handles = Vec::new();
637        for msg in messages {
638            let handle = publisher.publish(msg.clone());
639            handles.push(handle);
640        }
641
642        for rx in handles.into_iter() {
643            let got = rx.await;
644            assert!(got.is_err(), "{got:?}");
645        }
646
647        Ok(())
648    }
649
650    #[tokio_test_no_panics(start_paused = true)]
651    async fn batch_sends_on_byte_threshold() -> anyhow::Result<()> {
652        // Make sure all messages in a batch receive the correct message ID.
653        let mut mock = MockGapicPublisher::new();
654        mock.expect_publish()
655            .withf(|r, _| r.messages.len() == 1)
656            .times(2)
657            .returning(publish_ok);
658
659        let client = GapicPublisher::from_stub(mock);
660        // Ensure that the first message does not pass the threshold.
661        let byte_threshold = TOPIC.len() + "hello".len() + "key".len() + 1;
662        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string())
663            .set_message_count_threshold(MAX_MESSAGES)
664            .set_byte_threshold(byte_threshold as u32)
665            .set_delay_threshold(std::time::Duration::MAX)
666            .build();
667
668        // Validate without ordering key.
669        let handle = publisher.publish(Message::new().set_data("hello"));
670        // Publish a second message to trigger send on threshold.
671        let _handle = publisher.publish(Message::new().set_data("world"));
672        assert_eq!(handle.await?, "hello");
673
674        // Validate with ordering key.
675        let handle = publisher.publish(Message::new().set_data("hello").set_ordering_key("key"));
676        // Publish a second message to trigger send on threshold.
677        let _handle = publisher.publish(Message::new().set_data("world").set_ordering_key("key"));
678        assert_eq!(handle.await?, "hello");
679
680        Ok(())
681    }
682
683    #[tokio_test_no_panics(start_paused = true)]
684    async fn batch_sends_on_delay_threshold() -> anyhow::Result<()> {
685        let mut mock = MockGapicPublisher::new();
686        mock.expect_publish()
687            .withf(|req, _| req.topic == TOPIC)
688            .returning(publish_ok);
689
690        let client = GapicPublisher::from_stub(mock);
691        let delay = std::time::Duration::from_millis(10);
692        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string())
693            .set_message_count_threshold(u32::MAX)
694            .set_byte_threshold(MAX_BYTES)
695            .set_delay_threshold(delay)
696            .build();
697
698        // Test that messages send after delay.
699        for _ in 0..3 {
700            let start = tokio::time::Instant::now();
701            let messages = [
702                Message::new().set_data("hello 0"),
703                Message::new().set_data("hello 1"),
704                Message::new()
705                    .set_data("hello 2")
706                    .set_ordering_key("ordering key 1"),
707                Message::new()
708                    .set_data("hello 3")
709                    .set_ordering_key("ordering key 2"),
710            ];
711            let mut handles = Vec::new();
712            for msg in messages {
713                let handle = publisher.publish(msg.clone());
714                handles.push((msg, handle));
715            }
716
717            for (id, rx) in handles.into_iter() {
718                let got = rx.await?;
719                let id = String::from_utf8(id.data.to_vec())?;
720                assert_eq!(got, id);
721                assert_eq!(
722                    start.elapsed(),
723                    delay,
724                    "batch of messages should have sent after {:?}",
725                    delay
726                )
727            }
728        }
729
730        Ok(())
731    }
732
733    #[tokio::test(start_paused = true)]
734    #[allow(clippy::get_first)]
735    async fn batching_separates_by_ordering_key() -> anyhow::Result<()> {
736        // Publish messages with different ordering key and validate that they are in different batches.
737        let mut mock = MockGapicPublisher::new();
738        mock.expect_publish()
739            .withf(|r, _| {
740                r.messages.len() == 2 && r.messages[0].ordering_key == r.messages[1].ordering_key
741            })
742            .returning(publish_ok);
743
744        let client = GapicPublisher::from_stub(mock);
745        // Use a low message count to trigger batch sends.
746        let message_count_threshold = 2_u32;
747        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string())
748            .set_message_count_threshold(message_count_threshold)
749            .set_byte_threshold(MAX_BYTES)
750            .set_delay_threshold(std::time::Duration::MAX)
751            .build();
752
753        let num_ordering_keys = 3;
754        let mut messages = Vec::new();
755        // We want the number of messages to be a multiple of num_ordering_keys
756        // and message_count_threshold. Otherwise, the final batch of each
757        // ordering key may fail the message len assertion.
758        for i in 0..(2 * message_count_threshold * num_ordering_keys) {
759            messages.push(
760                Message::new()
761                    .set_data(format!("test message {}", i))
762                    .set_ordering_key(format!("ordering key: {}", i % num_ordering_keys)),
763            );
764        }
765        let mut handles = Vec::new();
766        for msg in messages {
767            let handle = publisher.publish(msg.clone());
768            handles.push((msg, handle));
769        }
770
771        for (id, rx) in handles.into_iter() {
772            let got = rx.await?;
773            let id = String::from_utf8(id.data.to_vec())?;
774            assert_eq!(got, id);
775        }
776
777        Ok(())
778    }
779
780    #[tokio_test_no_panics(start_paused = true)]
781    #[allow(clippy::get_first)]
782    async fn batching_handles_empty_ordering_key() -> anyhow::Result<()> {
783        // Publish messages with different ordering key and validate that they are in different batches.
784        let mut mock = MockGapicPublisher::new();
785        mock.expect_publish()
786            .withf(|r, _| {
787                r.messages.len() == 2 && r.messages[0].ordering_key == r.messages[1].ordering_key
788            })
789            .returning(publish_ok);
790
791        let client = GapicPublisher::from_stub(mock);
792        // Use a low message count to trigger batch sends.
793        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string())
794            .set_message_count_threshold(2_u32)
795            .set_byte_threshold(MAX_BYTES)
796            .set_delay_threshold(std::time::Duration::MAX)
797            .build();
798
799        let messages = [
800            Message::new().set_data("hello 1"),
801            Message::new().set_data("hello 2").set_ordering_key(""),
802            Message::new()
803                .set_data("hello 3")
804                .set_ordering_key("ordering key :1"),
805            Message::new()
806                .set_data("hello 4")
807                .set_ordering_key("ordering key :1"),
808        ];
809
810        let mut handles = Vec::new();
811        for msg in messages {
812            let handle = publisher.publish(msg.clone());
813            handles.push((msg, handle));
814        }
815
816        for (id, rx) in handles.into_iter() {
817            let got = rx.await?;
818            let id = String::from_utf8(id.data.to_vec())?;
819            assert_eq!(got, id);
820        }
821
822        Ok(())
823    }
824
825    #[tokio_test_no_panics(start_paused = true)]
826    #[allow(clippy::get_first)]
827    async fn ordering_key_limits_to_one_outstanding_batch() -> anyhow::Result<()> {
828        // Verify that Publisher must only have 1 outstanding batch inflight at a time.
829        // This is done by validating that the 2 expected publish calls are done in sequence
830        // with a sleep delay in the first Publish reply.
831        let mut seq = Sequence::new();
832        let mut mock = MockGapicPublisherWithFuture::new();
833        mock.expect_publish()
834            .times(1)
835            .in_sequence(&mut seq)
836            .withf(|r, _| r.messages.len() == 1)
837            .returning({
838                |r, o| {
839                    Box::pin(async move {
840                        tokio::time::sleep(Duration::from_millis(10)).await;
841                        publish_ok(r, o)
842                    })
843                }
844            });
845
846        mock.expect_publish()
847            .times(1)
848            .in_sequence(&mut seq)
849            .withf(|r, _| r.messages.len() == 1)
850            .returning(|r, o| Box::pin(async move { publish_ok(r, o) }));
851
852        let client = GapicPublisher::from_stub(mock);
853        // Use a low message count to trigger batch sends.
854        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string())
855            .set_message_count_threshold(1_u32)
856            .set_byte_threshold(MAX_BYTES)
857            .set_delay_threshold(std::time::Duration::MAX)
858            .build();
859
860        let messages = [
861            Message::new()
862                .set_data("hello 1")
863                .set_ordering_key("ordering key"),
864            Message::new()
865                .set_data("hello 2")
866                .set_ordering_key("ordering key"),
867        ];
868
869        let start = tokio::time::Instant::now();
870        let msg1_handle = publisher.publish(messages.get(0).unwrap().clone());
871        let msg2_handle = publisher.publish(messages.get(1).unwrap().clone());
872        assert_eq!(msg2_handle.await?, "hello 2");
873        assert_eq!(
874            start.elapsed(),
875            Duration::from_millis(10),
876            "the second batch of messages should have sent after the first which is has been delayed by {:?}",
877            Duration::from_millis(10)
878        );
879        // Also validate the content of the first publish.
880        assert_eq!(msg1_handle.await?, "hello 1");
881
882        Ok(())
883    }
884
885    #[tokio_test_no_panics(start_paused = true)]
886    #[allow(clippy::get_first)]
887    async fn empty_ordering_key_allows_concurrent_batches() -> anyhow::Result<()> {
888        // Verify that for empty ordering key, the Publisher will send multiple batches without
889        // awaiting for the results.
890        // This is done by adding a delay in the first Publish reply and validating that
891        // the second batch does not await for the first batch.
892        let mut seq = Sequence::new();
893        let mut mock = MockGapicPublisherWithFuture::new();
894        mock.expect_publish()
895            .times(1)
896            .in_sequence(&mut seq)
897            .withf(|r, _| r.messages.len() == 1)
898            .returning(|r, o| {
899                Box::pin(async move {
900                    tokio::time::sleep(Duration::from_millis(10)).await;
901                    publish_ok(r, o)
902                })
903            });
904
905        mock.expect_publish()
906            .times(1)
907            .in_sequence(&mut seq)
908            .withf(|r, _| r.topic == TOPIC && r.messages.len() == 1)
909            .returning(|r, o| Box::pin(async move { publish_ok(r, o) }));
910
911        let client = GapicPublisher::from_stub(mock);
912        // Use a low message count to trigger batch sends.
913        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string())
914            .set_message_count_threshold(1_u32)
915            .set_byte_threshold(MAX_BYTES)
916            .set_delay_threshold(std::time::Duration::MAX)
917            .build();
918
919        let messages = [
920            Message::new().set_data("hello 1").set_ordering_key(""),
921            Message::new().set_data("hello 2").set_ordering_key(""),
922        ];
923
924        let start = tokio::time::Instant::now();
925        let msg1_handle = publisher.publish(messages.get(0).unwrap().clone());
926        let msg2_handle = publisher.publish(messages.get(1).unwrap().clone());
927        assert_eq!(msg2_handle.await?, "hello 2");
928        assert_eq!(
929            start.elapsed(),
930            Duration::from_millis(0),
931            "the second batch of messages should have sent without any delay"
932        );
933        // Also validate the content of the first publish.
934        assert_eq!(msg1_handle.await?, "hello 1");
935
936        Ok(())
937    }
938
939    #[tokio_test_no_panics(start_paused = true)]
940    async fn ordering_key_error_pauses_publisher() -> anyhow::Result<()> {
941        // Verify that a Publish send error will pause the publisher for an ordering key.
942        let mut seq = Sequence::new();
943        let mut mock = MockGapicPublisher::new();
944        mock.expect_publish()
945            .withf(|req, _o| req.topic == TOPIC)
946            .times(1)
947            .in_sequence(&mut seq)
948            .returning(publish_err);
949
950        mock.expect_publish()
951            .withf(|req, _o| req.topic == TOPIC)
952            .times(2)
953            .in_sequence(&mut seq)
954            .returning(publish_ok);
955
956        let client = GapicPublisher::from_stub(mock);
957        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string())
958            .set_message_count_threshold(1_u32)
959            .build();
960
961        let key = "ordering_key";
962        let msg_0_handle =
963            publisher.publish(Message::new().set_ordering_key(key).set_data("msg 0"));
964        // Publish an additional message so that there are pending messages.
965        let msg_1_handle =
966            publisher.publish(Message::new().set_ordering_key(key).set_data("msg 1"));
967
968        // Assert the error is caused by the Publish send operation.
969        let mut got_err = msg_0_handle.await.unwrap_err();
970        assert_publish_err(got_err);
971
972        // Assert that the pending message error is caused by the Publisher being paused.
973        got_err = msg_1_handle.await.unwrap_err();
974        assert!(
975            matches!(got_err, crate::error::PublishError::OrderingKeyPaused),
976            "{got_err:?}"
977        );
978
979        // Assert that new publish messages return errors because the Publisher is paused.
980        for _ in 0..3 {
981            assert_publishing_is_paused!(publisher, key);
982        }
983
984        // Verify that the other ordering keys are not paused.
985        assert_publishing_is_ok!(publisher, "", "without_error");
986
987        Ok(())
988    }
989
990    #[tokio_test_no_panics(start_paused = true)]
991    async fn batch_error_pauses_ordering_key() -> anyhow::Result<()> {
992        // Verify that all messages in the same batch receives the Send error for that batch.
993        let mut mock = MockGapicPublisher::new();
994        mock.expect_publish()
995            .times(1)
996            .withf(|r, _| r.topic == TOPIC && r.messages.len() == 2)
997            .returning(publish_err);
998
999        let client = GapicPublisher::from_stub(mock);
1000        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string())
1001            .set_message_count_threshold(2_u32)
1002            .build();
1003
1004        let key = "ordering_key";
1005        // Publish 2 messages so they are in the same batch.
1006        let msg_0_handle =
1007            publisher.publish(Message::new().set_ordering_key(key).set_data("msg 0"));
1008        let msg_1_handle =
1009            publisher.publish(Message::new().set_ordering_key(key).set_data("msg 1"));
1010
1011        // Validate that they both receives the Send error.
1012        let mut got_err = msg_0_handle.await.unwrap_err();
1013        assert_publish_err(got_err);
1014        got_err = msg_1_handle.await.unwrap_err();
1015        assert_publish_err(got_err);
1016
1017        // Assert that new publish messages returns an error because the Publisher is paused.
1018        assert_publishing_is_paused!(publisher, key);
1019
1020        Ok(())
1021    }
1022
1023    #[tokio_test_no_panics(start_paused = true)]
1024    async fn flush_on_paused_ordering_key_returns_error() -> anyhow::Result<()> {
1025        // Verify that Flush on a paused ordering key returns an error.
1026        let mut seq = Sequence::new();
1027        let mut mock = MockGapicPublisher::new();
1028        mock.expect_publish()
1029            .withf(|req, _o| req.topic == TOPIC)
1030            .times(1)
1031            .in_sequence(&mut seq)
1032            .returning(publish_err);
1033
1034        mock.expect_publish()
1035            .withf(|req, _o| req.topic == TOPIC)
1036            .times(2)
1037            .in_sequence(&mut seq)
1038            .returning(publish_ok);
1039
1040        let client = GapicPublisher::from_stub(mock);
1041        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string()).build();
1042
1043        let key = "ordering_key";
1044        // Cause an ordering key to be paused.
1045        let handle = publisher.publish(Message::new().set_ordering_key(key).set_data("msg 0"));
1046        publisher.flush().await;
1047        // Assert the error is caused by the Publish send operation.
1048        let got_err = handle.await.unwrap_err();
1049        assert_publish_err(got_err);
1050
1051        // Validate that new Publish on the paused ordering key will result in an error.
1052        assert_publishing_is_paused!(publisher, key);
1053
1054        // Verify that the other ordering keys are not paused.
1055        assert_publishing_is_ok!(publisher, "", "without_error");
1056
1057        Ok(())
1058    }
1059
1060    #[tokio_test_no_panics(start_paused = true)]
1061    async fn resuming_non_paused_ordering_key_is_noop() -> anyhow::Result<()> {
1062        let mut mock = MockGapicPublisher::new();
1063        mock.expect_publish()
1064            .withf(|req, _o| req.topic == TOPIC)
1065            .times(4)
1066            .returning(publish_ok);
1067
1068        let client = GapicPublisher::from_stub(mock);
1069        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string()).build();
1070
1071        // Test resume and publish for empty ordering key.
1072        publisher.resume_publish("");
1073        assert_publishing_is_ok!(publisher, "");
1074
1075        // Test resume and publish after the BatchActor has been created for the empty ordering key.
1076        publisher.resume_publish("");
1077        assert_publishing_is_ok!(publisher, "");
1078
1079        // Test resume and publish before the BatchActor has been created.
1080        let key = "without_error";
1081        publisher.resume_publish(key);
1082        assert_publishing_is_ok!(publisher, key);
1083
1084        // Test resume and publish after the BatchActor has been created.
1085        publisher.resume_publish(key);
1086        assert_publishing_is_ok!(publisher, key);
1087
1088        Ok(())
1089    }
1090
1091    #[tokio_test_no_panics(start_paused = true)]
1092    async fn resuming_paused_ordering_key_allows_publishing() -> anyhow::Result<()> {
1093        let mut seq = Sequence::new();
1094        let mut mock = MockGapicPublisher::new();
1095        mock.expect_publish()
1096            .withf(|req, _o| req.topic == TOPIC)
1097            .times(1)
1098            .in_sequence(&mut seq)
1099            .returning(publish_err);
1100
1101        mock.expect_publish()
1102            .withf(|req, _o| req.topic == TOPIC)
1103            .times(3)
1104            .in_sequence(&mut seq)
1105            .returning(publish_ok);
1106
1107        let client = GapicPublisher::from_stub(mock);
1108        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string()).build();
1109
1110        let key = "ordering_key";
1111        // Cause an ordering key to be paused.
1112        let handle = publisher.publish(Message::new().set_ordering_key(key).set_data("msg 0"));
1113        // Assert the error is caused by the Publish send operation.
1114        let got_err = handle.await.unwrap_err();
1115        assert_publish_err(got_err);
1116
1117        // Validate that new Publish on the paused ordering key will result in an error.
1118        assert_publishing_is_paused!(publisher, key);
1119
1120        // Resume and validate the key is no longer paused.
1121        publisher.resume_publish(key);
1122        assert_publishing_is_ok!(publisher, key);
1123
1124        // Verify that the other ordering keys continue to work as expected.
1125        assert_publishing_is_ok!(publisher, "", "without_error");
1126
1127        Ok(())
1128    }
1129
1130    #[tokio_test_no_panics(start_paused = true)]
1131    async fn resuming_ordering_key_twice_is_safe() -> anyhow::Result<()> {
1132        // Validate that resuming twice sequentially does not have bad side effects.
1133        let mut seq = Sequence::new();
1134        let mut mock = MockGapicPublisher::new();
1135        mock.expect_publish()
1136            .withf(|req, _o| req.topic == TOPIC)
1137            .in_sequence(&mut seq)
1138            .times(1)
1139            .returning(publish_err);
1140
1141        mock.expect_publish()
1142            .withf(|req, _o| req.topic == TOPIC)
1143            .in_sequence(&mut seq)
1144            .return_once(publish_ok);
1145
1146        let client = GapicPublisher::from_stub(mock);
1147        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string()).build();
1148
1149        let key = "ordering_key";
1150        // Cause an ordering key to be paused.
1151        let handle = publisher.publish(Message::new().set_ordering_key(key).set_data("msg 0"));
1152        publisher.flush().await;
1153        // Assert the error is caused by the Publish send operation.
1154        let got_err = handle.await.unwrap_err();
1155        assert_publish_err(got_err);
1156
1157        // Validate that new Publish on the paused ordering key will result in an error.
1158        assert_publishing_is_paused!(publisher, key);
1159
1160        // Resume twice on the paused ordering key.
1161        publisher.resume_publish(key);
1162        publisher.resume_publish(key);
1163        assert_publishing_is_ok!(publisher, key);
1164
1165        Ok(())
1166    }
1167
1168    #[tokio_test_no_panics(start_paused = true)]
1169    async fn resuming_one_ordering_key_does_not_resume_others() -> anyhow::Result<()> {
1170        // Validate that resume_publish only resumes the paused ordering key .
1171        let mut seq = Sequence::new();
1172        let mut mock = MockGapicPublisher::new();
1173        mock.expect_publish()
1174            .withf(|req, _o| req.topic == TOPIC)
1175            .times(2)
1176            .in_sequence(&mut seq)
1177            .returning(publish_err);
1178
1179        mock.expect_publish()
1180            .withf(|req, _o| req.topic == TOPIC)
1181            .times(1)
1182            .in_sequence(&mut seq)
1183            .returning(publish_ok);
1184
1185        let client = GapicPublisher::from_stub(mock);
1186        let publisher = PublisherPartialBuilder::new(client, TOPIC.to_string()).build();
1187
1188        let key_0 = "ordering_key_0";
1189        let key_1 = "ordering_key_1";
1190        // Cause both ordering keys to pause.
1191        let handle_0 = publisher.publish(Message::new().set_ordering_key(key_0).set_data("msg 0"));
1192        let handle_1 = publisher.publish(Message::new().set_ordering_key(key_1).set_data("msg 1"));
1193        publisher.flush().await;
1194        let mut got_err = handle_0.await.unwrap_err();
1195        assert_publish_err(got_err);
1196        got_err = handle_1.await.unwrap_err();
1197        assert_publish_err(got_err);
1198
1199        // Assert that both ordering keys are paused.
1200        assert_publishing_is_paused!(publisher, key_0, key_1);
1201
1202        // Resume on one of the ordering key.
1203        publisher.resume_publish(key_0);
1204
1205        // Validate that only the correct ordering key is resumed.
1206        assert_publishing_is_ok!(publisher, key_0);
1207
1208        // Validate the other ordering key is still paused.
1209        assert_publishing_is_paused!(publisher, key_1);
1210
1211        Ok(())
1212    }
1213
1214    #[tokio::test]
1215    async fn publisher_builder_clamps_batching_options() -> anyhow::Result<()> {
1216        // Test values that are too high and should be clamped.
1217        let oversized_options = BatchingOptions::new()
1218            .set_delay_threshold(MAX_DELAY + Duration::from_secs(1))
1219            .set_message_count_threshold(MAX_MESSAGES + 1)
1220            .set_byte_threshold(MAX_BYTES + 1);
1221
1222        let publishers = vec![
1223            BasePublisher::builder()
1224                .build()
1225                .await?
1226                .publisher("projects/my-project/topics/my-topic")
1227                .set_delay_threshold(oversized_options.delay_threshold)
1228                .set_message_count_threshold(oversized_options.message_count_threshold)
1229                .set_byte_threshold(oversized_options.byte_threshold)
1230                .build(),
1231            Publisher::builder("projects/my-project/topics/my-topic".to_string())
1232                .set_delay_threshold(oversized_options.delay_threshold)
1233                .set_message_count_threshold(oversized_options.message_count_threshold)
1234                .set_byte_threshold(oversized_options.byte_threshold)
1235                .build()
1236                .await?,
1237        ];
1238
1239        for publisher in publishers {
1240            let got = publisher.batching_options;
1241            assert_eq!(got.delay_threshold, MAX_DELAY);
1242            assert_eq!(got.message_count_threshold, MAX_MESSAGES);
1243            assert_eq!(got.byte_threshold, MAX_BYTES);
1244        }
1245
1246        // Test values that are within limits and should not be changed.
1247        let normal_options = BatchingOptions::new()
1248            .set_delay_threshold(Duration::from_secs(10))
1249            .set_message_count_threshold(10_u32)
1250            .set_byte_threshold(100_u32);
1251
1252        let publishers = vec![
1253            BasePublisher::builder()
1254                .build()
1255                .await?
1256                .publisher("projects/my-project/topics/my-topic")
1257                .set_delay_threshold(normal_options.delay_threshold)
1258                .set_message_count_threshold(normal_options.message_count_threshold)
1259                .set_byte_threshold(normal_options.byte_threshold)
1260                .build(),
1261            Publisher::builder("projects/my-project/topics/my-topic".to_string())
1262                .set_delay_threshold(normal_options.delay_threshold)
1263                .set_message_count_threshold(normal_options.message_count_threshold)
1264                .set_byte_threshold(normal_options.byte_threshold)
1265                .build()
1266                .await?,
1267        ];
1268
1269        for publisher in publishers {
1270            let got = publisher.batching_options;
1271
1272            assert_eq!(got.delay_threshold, normal_options.delay_threshold);
1273            assert_eq!(
1274                got.message_count_threshold,
1275                normal_options.message_count_threshold
1276            );
1277            assert_eq!(got.byte_threshold, normal_options.byte_threshold);
1278        }
1279        Ok(())
1280    }
1281}