Skip to main content

google_cloud_pubsub/subscriber/
builder.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::MessageStream;
16use super::ShutdownBehavior;
17use super::transport::Transport;
18use std::sync::Arc;
19use std::time::Duration;
20
21const MIB: i64 = 1024 * 1024;
22// Clamp the max lease to 100 years to avoid overflow errors.
23const MAX_LEASE: Duration = Duration::from_secs(100 * 365 * 24 * 60 * 60);
24
25pub use super::client_builder::ClientBuilder;
26
27/// Builder for the [`client::Subscriber::subscribe`][crate::client::Subscriber::subscribe] method.
28pub struct Subscribe {
29    pub(super) inner: Arc<Transport>,
30    pub(super) subscription: String,
31    pub(super) client_id: String,
32    pub(super) grpc_subchannel_count: usize,
33    pub(super) ack_deadline_seconds: i32,
34    pub(super) max_lease: Duration,
35    pub(super) max_outstanding_messages: i64,
36    pub(super) max_outstanding_bytes: i64,
37    pub(super) shutdown_behavior: ShutdownBehavior,
38}
39
40impl Subscribe {
41    pub(super) fn new(
42        inner: Arc<Transport>,
43        subscription: String,
44        client_id: String,
45        grpc_subchannel_count: usize,
46    ) -> Self {
47        Self {
48            inner,
49            subscription,
50            client_id,
51            grpc_subchannel_count,
52            ack_deadline_seconds: 60,
53            max_lease: Duration::from_secs(60 * 60),
54            max_outstanding_messages: 1000,
55            max_outstanding_bytes: 100 * MIB,
56            shutdown_behavior: ShutdownBehavior::WaitForProcessing,
57        }
58    }
59
60    /// Creates a new stream to receive messages from the subscription.
61    ///
62    /// # Example
63    /// ```
64    /// # use google_cloud_pubsub::client::Subscriber;
65    /// # async fn sample(client: Subscriber) -> anyhow::Result<()> {
66    /// let mut stream = client
67    ///     .subscribe("projects/my-project/subscriptions/my-subscription")
68    ///     .build();
69    /// while let Some((m, h)) = stream.next().await.transpose()? {
70    ///     println!("Received message m={m:?}");
71    ///     h.ack();
72    /// }
73    /// # Ok(()) }
74    /// ```
75    ///
76    /// Note that the underlying connection with the server is lazy-initialized.
77    /// It is not established until [`MessageStream::next()`] is called.
78    pub fn build(self) -> MessageStream {
79        MessageStream::new(self)
80    }
81
82    /// Sets the maximum lease deadline for a message.
83    ///
84    /// # Example
85    /// ```
86    /// # use google_cloud_pubsub::client::Subscriber;
87    /// # use std::time::Duration;
88    /// # async fn sample() -> anyhow::Result<()> {
89    /// # let client = Subscriber::builder().build().await?;
90    /// let stream = client.subscribe("projects/my-project/subscriptions/my-subscription")
91    ///     .set_max_lease(Duration::from_secs(3600))
92    ///     .build();
93    /// # Ok(()) }
94    /// ```
95    ///
96    /// The client holds a message for at most this amount. After a message has
97    /// been held for this long, the client will stop extending its lease.
98    ///
99    /// The default value is 60 minutes. If it takes your application longer
100    /// than 60 minutes to process a message, you should increase this value.
101    pub fn set_max_lease<T: Into<Duration>>(mut self, v: T) -> Self {
102        self.max_lease = v.into().min(MAX_LEASE);
103        self
104    }
105
106    /// Sets the maximum duration to extend lease deadlines by.
107    ///
108    /// # Example
109    /// ```
110    /// # use google_cloud_pubsub::client::Subscriber;
111    /// # use std::time::Duration;
112    /// # async fn sample() -> anyhow::Result<()> {
113    /// # let client = Subscriber::builder().build().await?;
114    /// let stream = client.subscribe("projects/my-project/subscriptions/my-subscription")
115    ///     .set_max_lease_extension(Duration::from_secs(20))
116    ///     .build();
117    /// # Ok(()) }
118    /// ```
119    ///
120    /// The client extends lease deadlines by at most this amount.
121    ///
122    /// If the server does not hear back from the client within this deadline
123    /// (e.g. if an application crashes), it will resend any unacknowledged
124    /// messages to another subscriber.
125    ///
126    /// Note that this value is independent of the ack deadline configured on
127    /// the subscription.
128    ///
129    /// The minimum deadline you can specify is 10 seconds. The maximum deadline
130    /// you can specify is 10 minutes. The client clamps the supplied value to
131    /// this range.
132    ///
133    /// The default value is 60 seconds.
134    pub fn set_max_lease_extension<T: Into<Duration>>(mut self, v: T) -> Self {
135        self.ack_deadline_seconds = v.into().as_secs().clamp(10, 600) as i32;
136        self
137    }
138
139    /// Flow control settings for the maximum number of outstanding messages.
140    ///
141    /// # Example
142    /// ```
143    /// # use google_cloud_pubsub::client::Subscriber;
144    /// # async fn sample() -> anyhow::Result<()> {
145    /// # let client = Subscriber::builder().build().await?;
146    /// let stream = client.subscribe("projects/my-project/subscriptions/my-subscription")
147    ///     .set_max_outstanding_messages(2000)
148    ///     .build();
149    /// # Ok(()) }
150    /// ```
151    ///
152    /// The server will stop sending messages to a client when this many
153    /// messages are outstanding (i.e. that have not been acked). The server
154    /// resumes sending messages when the outstanding message count drops below
155    /// this value.
156    ///
157    /// The limit applies per-stream. It is not a global limit.
158    ///
159    /// Use a value <= 0 to set no limit on the number of outstanding messages.
160    ///
161    /// The default value is 1000 messages.
162    pub fn set_max_outstanding_messages<T: Into<i64>>(mut self, v: T) -> Self {
163        self.max_outstanding_messages = v.into();
164        self
165    }
166
167    /// Flow control settings for the maximum number of outstanding bytes.
168    ///
169    /// # Example
170    /// ```
171    /// # use google_cloud_pubsub::client::Subscriber;
172    /// # async fn sample() -> anyhow::Result<()> {
173    /// # let client = Subscriber::builder().build().await?;
174    /// const MIB: i64 = 1024 * 1024;
175    /// let stream = client.subscribe("projects/my-project/subscriptions/my-subscription")
176    ///     .set_max_outstanding_bytes(200 * MIB)
177    ///     .build();
178    /// # Ok(()) }
179    /// ```
180    ///
181    /// The server will stop sending messages to a client when this many bytes
182    /// of messages are outstanding (i.e. that have not been acked). The server
183    /// resumes sending messages when the outstanding byte count drops below
184    /// this value.
185    ///
186    /// The limit applies per-stream. It is not a global limit.
187    ///
188    /// Use a value <= 0 to set no limit on the number of outstanding bytes.
189    ///
190    /// The default value is 100 MiB.
191    pub fn set_max_outstanding_bytes<T: Into<i64>>(mut self, v: T) -> Self {
192        self.max_outstanding_bytes = v.into();
193        self
194    }
195
196    /// Sets the shutdown behavior for the stream.
197    ///
198    /// # Example
199    /// ```
200    /// # use google_cloud_pubsub::client::Subscriber;
201    /// # async fn sample() -> anyhow::Result<()> {
202    /// # let client = Subscriber::builder().build().await?;
203    /// use google_cloud_pubsub::subscriber::ShutdownBehavior::NackImmediately;
204    /// let stream = client.subscribe("projects/my-project/subscriptions/my-subscription")
205    ///     .set_shutdown_behavior(NackImmediately)
206    ///     .build();
207    /// # Ok(()) }
208    /// ```
209    ///
210    /// The default behavior is [`WaitForProcessing`][wait].
211    ///
212    /// [wait]: crate::subscriber::ShutdownBehavior::WaitForProcessing
213    pub fn set_shutdown_behavior(mut self, v: ShutdownBehavior) -> Self {
214        self.shutdown_behavior = v;
215        self
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222    use gaxi::options::ClientConfig;
223    use google_cloud_auth::credentials::anonymous::Builder as Anonymous;
224    use test_case::test_case;
225
226    const KIB: i64 = 1024;
227
228    async fn test_inner() -> anyhow::Result<Arc<Transport>> {
229        let mut config = ClientConfig::default();
230        config.cred = Some(Anonymous::new().build());
231        let transport = Transport::new(config).await?;
232        Ok(Arc::new(transport))
233    }
234
235    #[tokio::test]
236    async fn reasonable_defaults() -> anyhow::Result<()> {
237        let builder = Subscribe::new(
238            test_inner().await?,
239            "projects/my-project/subscriptions/my-subscription".to_string(),
240            "client-id".to_string(),
241            1_usize,
242        );
243        assert_eq!(
244            builder.subscription,
245            "projects/my-project/subscriptions/my-subscription"
246        );
247        assert_eq!(builder.grpc_subchannel_count, 1);
248        assert_eq!(builder.ack_deadline_seconds, 60);
249        assert!(
250            builder.max_lease >= Duration::from_secs(300),
251            "max_lease={:?}",
252            builder.max_lease
253        );
254        assert!(
255            100_000 > builder.max_outstanding_messages && builder.max_outstanding_messages > 100,
256            "max_outstanding_messages={}",
257            builder.max_outstanding_messages
258        );
259        assert!(
260            builder.max_outstanding_bytes > 100 * KIB,
261            "max_outstanding_bytes={}",
262            builder.max_outstanding_bytes
263        );
264        assert_eq!(
265            builder.shutdown_behavior,
266            ShutdownBehavior::WaitForProcessing
267        );
268
269        Ok(())
270    }
271
272    #[tokio::test]
273    async fn options() -> anyhow::Result<()> {
274        let builder = Subscribe::new(
275            test_inner().await?,
276            "projects/my-project/subscriptions/my-subscription".to_string(),
277            "client-id".to_string(),
278            2_usize,
279        )
280        .set_max_lease(Duration::from_secs(3600))
281        .set_max_lease_extension(Duration::from_secs(20))
282        .set_max_outstanding_messages(12345)
283        .set_max_outstanding_bytes(6789 * KIB)
284        .set_shutdown_behavior(ShutdownBehavior::NackImmediately);
285        assert_eq!(
286            builder.subscription,
287            "projects/my-project/subscriptions/my-subscription"
288        );
289        assert_eq!(builder.grpc_subchannel_count, 2);
290        assert_eq!(builder.max_lease, Duration::from_secs(3600));
291        assert_eq!(builder.ack_deadline_seconds, 20);
292        assert_eq!(builder.max_outstanding_messages, 12345);
293        assert_eq!(builder.max_outstanding_bytes, 6789 * KIB);
294        assert_eq!(builder.shutdown_behavior, ShutdownBehavior::NackImmediately);
295
296        Ok(())
297    }
298
299    #[test_case(Duration::ZERO, 10)]
300    #[test_case(Duration::from_secs(42), 42)]
301    #[test_case(Duration::from_secs(4200), 600)]
302    #[tokio::test]
303    async fn clamp_ack_deadline(v: Duration, want: i32) -> anyhow::Result<()> {
304        let builder = Subscribe::new(
305            test_inner().await?,
306            "projects/my-project/subscriptions/my-subscription".to_string(),
307            "client-id".to_string(),
308            1_usize,
309        )
310        .set_max_lease_extension(v);
311        assert_eq!(builder.ack_deadline_seconds, want);
312
313        Ok(())
314    }
315
316    #[tokio::test]
317    async fn clamp_max_lease() -> anyhow::Result<()> {
318        let builder = Subscribe::new(
319            test_inner().await?,
320            "projects/my-project/subscriptions/my-subscription".to_string(),
321            "client-id".to_string(),
322            1_usize,
323        )
324        .set_max_lease(Duration::MAX);
325        assert_eq!(builder.max_lease, MAX_LEASE);
326
327        Ok(())
328    }
329}