dynamodb_lease/builder.rs
1use crate::Client;
2use std::time::Duration;
3
4/// [`Client`] builder.
5pub struct ClientBuilder {
6 table_name: String,
7 lease_ttl_seconds: u32,
8 extend_period: Option<Duration>,
9 acquire_cooldown: Duration,
10}
11
12impl Default for ClientBuilder {
13 fn default() -> Self {
14 Self {
15 table_name: "leases".into(),
16 lease_ttl_seconds: 60,
17 extend_period: None,
18 acquire_cooldown: Duration::from_secs(1),
19 }
20 }
21}
22
23impl ClientBuilder {
24 /// Sets the lease table name where the lease info will be stored.
25 /// The table must have the correct schema.
26 ///
27 /// Default `"leases"`.
28 pub fn table_name(mut self, table_name: impl Into<String>) -> Self {
29 self.table_name = table_name.into();
30 self
31 }
32
33 /// Sets the time to live for each lease (or lease extension) in seconds.
34 /// **Must be at least 2**.
35 ///
36 /// Note: Time to live is implemented using the native dynamodb feature. As this
37 /// is based on unix timestamps the unit is seconds. This makes extending ttls lower
38 /// than 2s not reliable.
39 ///
40 /// Note: A [`crate::Lease`] will attempt to extend itself in the background until dropped
41 /// and then release itself. However, since db comms failing after acquiring a lease
42 /// is possible, this ttl is the _guaranteed_ lifetime of a lease. As such, and since
43 /// ttl is not in normal operation relied upon to release leases, it may make sense
44 /// to set this higher than the max operation time the lease is wrapping.
45 ///
46 /// So, for example, if the locked task can take 1s to 5m a ttl of 10m should provide
47 /// a decent guarantee that such tasks will never execute concurrently. In normal operation
48 /// each lease will release (be deleted) immediately after dropping, so having a high
49 /// ttl only affects the edge case where the extend/drop db interactions fail.
50 ///
51 /// Default `60`.
52 ///
53 /// # Panics
54 /// Panics if less than 2s.
55 pub fn lease_ttl_seconds(mut self, seconds: u32) -> Self {
56 assert!(
57 seconds >= 2,
58 "must be at least 2s, shorter ttls are not supported"
59 );
60 self.lease_ttl_seconds = seconds;
61 self
62 }
63
64 /// Sets the periodic duration between each background attempt to extend the lease. These
65 /// happen continually while the [`crate::Lease`] is alive.
66 ///
67 /// Each extension renews the lease to the full ttl. This duration must be less
68 /// than the ttl.
69 ///
70 /// Default `lease_ttl_seconds / 2`.
71 ///
72 /// # Panics
73 /// Panics if zero.
74 pub fn extend_every(mut self, extend_period: Duration) -> Self {
75 assert!(extend_period > Duration::ZERO, "must be greater than zero");
76 self.extend_period = Some(extend_period);
77 self
78 }
79
80 /// Sets how long [`Client::acquire`] waits between attempts to acquire a lease.
81 ///
82 /// Default `1s`.
83 pub fn acquire_cooldown(mut self, cooldown: Duration) -> Self {
84 self.acquire_cooldown = cooldown;
85 self
86 }
87
88 /// Builds a [`Client`] and checks the dynamodb table is active with the correct schema.
89 ///
90 /// # Panics
91 /// Panics if `extend_period` is not less than `lease_ttl_seconds`.
92 pub async fn build_and_check_db(
93 self,
94 dynamodb_client: aws_sdk_dynamodb::Client,
95 ) -> anyhow::Result<Client> {
96 let extend_period = self
97 .extend_period
98 .unwrap_or_else(|| Duration::from_secs_f64(self.lease_ttl_seconds as f64 / 2.0));
99 assert!(
100 extend_period < Duration::from_secs(self.lease_ttl_seconds as _),
101 "renew_period must be less than ttl"
102 );
103
104 let client = Client {
105 table_name: self.table_name.into(),
106 client: dynamodb_client,
107 lease_ttl_seconds: self.lease_ttl_seconds,
108 extend_period,
109 acquire_cooldown: self.acquire_cooldown,
110 local_locks: <_>::default(),
111 };
112
113 client.check_schema().await?;
114
115 Ok(client)
116 }
117}