deltachat/
timesmearing.rs

1//! # Time smearing.
2//!
3//! As e-mails typically only use a second-based-resolution for timestamps,
4//! the order of two mails sent within one second is unclear.
5//! This is bad e.g. when forwarding some messages from a chat -
6//! these messages will appear at the recipient easily out of order.
7//!
8//! We work around this issue by not sending out two mails with the same timestamp.
9//! For this purpose, in short, we track the last timestamp used in `last_smeared_timestamp`
10//! when another timestamp is needed in the same second, we use `last_smeared_timestamp+1`
11//! after some moments without messages sent out,
12//! `last_smeared_timestamp` is again in sync with the normal time.
13//!
14//! However, we do not do all this for the far future,
15//! but at max `MAX_SECONDS_TO_LEND_FROM_FUTURE`
16
17use std::cmp::{max, min};
18use std::sync::atomic::{AtomicI64, Ordering};
19
20pub(crate) const MAX_SECONDS_TO_LEND_FROM_FUTURE: i64 = 30;
21
22/// Smeared timestamp generator.
23#[derive(Debug)]
24pub struct SmearedTimestamp {
25    /// Next timestamp available for allocation.
26    smeared_timestamp: AtomicI64,
27}
28
29impl SmearedTimestamp {
30    /// Creates a new smeared timestamp generator.
31    pub fn new() -> Self {
32        Self {
33            smeared_timestamp: AtomicI64::new(0),
34        }
35    }
36
37    /// Allocates `count` unique timestamps.
38    ///
39    /// Returns the first allocated timestamp.
40    #[expect(clippy::arithmetic_side_effects)]
41    pub fn create_n(&self, now: i64, count: i64) -> i64 {
42        let mut prev = self.smeared_timestamp.load(Ordering::Relaxed);
43        loop {
44            // Advance the timestamp if it is in the past,
45            // but keep `count - 1` timestamps from the past if possible.
46            let t = max(prev, now - count + 1);
47
48            // Rewind the time back if there is no room
49            // to allocate `count` timestamps without going too far into the future.
50            // Not going too far into the future
51            // is more important than generating unique timestamps.
52            let first = min(t, now + MAX_SECONDS_TO_LEND_FROM_FUTURE - count + 1);
53
54            // Allocate `count` timestamps by advancing the current timestamp.
55            let next = first + count;
56
57            if let Err(x) = self.smeared_timestamp.compare_exchange_weak(
58                prev,
59                next,
60                Ordering::Relaxed,
61                Ordering::Relaxed,
62            ) {
63                prev = x;
64            } else {
65                return first;
66            }
67        }
68    }
69
70    /// Creates a single timestamp.
71    pub fn create(&self, now: i64) -> i64 {
72        self.create_n(now, 1)
73    }
74
75    /// Returns the current smeared timestamp.
76    pub fn current(&self) -> i64 {
77        self.smeared_timestamp.load(Ordering::Relaxed)
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use crate::test_utils::TestContext;
85    use crate::tools::{
86        SystemTime, create_smeared_timestamp, create_smeared_timestamps, smeared_time, time,
87    };
88
89    #[test]
90    fn test_smeared_timestamp() {
91        let smeared_timestamp = SmearedTimestamp::new();
92        let now = time();
93
94        assert_eq!(smeared_timestamp.current(), 0);
95
96        for i in 0..MAX_SECONDS_TO_LEND_FROM_FUTURE {
97            assert_eq!(smeared_timestamp.create(now), now + i);
98        }
99        assert_eq!(
100            smeared_timestamp.create(now),
101            now + MAX_SECONDS_TO_LEND_FROM_FUTURE
102        );
103        assert_eq!(
104            smeared_timestamp.create(now),
105            now + MAX_SECONDS_TO_LEND_FROM_FUTURE
106        );
107
108        // System time rewinds back by 1000 seconds.
109        let now = now - 1000;
110        assert_eq!(
111            smeared_timestamp.create(now),
112            now + MAX_SECONDS_TO_LEND_FROM_FUTURE
113        );
114        assert_eq!(
115            smeared_timestamp.create(now),
116            now + MAX_SECONDS_TO_LEND_FROM_FUTURE
117        );
118        assert_eq!(
119            smeared_timestamp.create(now + 1),
120            now + MAX_SECONDS_TO_LEND_FROM_FUTURE + 1
121        );
122        assert_eq!(smeared_timestamp.create(now + 100), now + 100);
123        assert_eq!(smeared_timestamp.create(now + 100), now + 101);
124        assert_eq!(smeared_timestamp.create(now + 100), now + 102);
125    }
126
127    #[test]
128    fn test_create_n_smeared_timestamps() {
129        let smeared_timestamp = SmearedTimestamp::new();
130        let now = time();
131
132        // Create a single timestamp to initialize the generator.
133        assert_eq!(smeared_timestamp.create(now), now);
134
135        // Wait a minute.
136        let now = now + 60;
137
138        // Simulate forwarding 7 messages.
139        let forwarded_messages = 7;
140
141        // We have not sent anything for a minute,
142        // so we can take the current timestamp and take 6 timestamps from the past.
143        assert_eq!(smeared_timestamp.create_n(now, forwarded_messages), now - 6);
144
145        assert_eq!(smeared_timestamp.current(), now + 1);
146
147        // Wait 4 seconds.
148        // Now we have 3 free timestamps in the past.
149        let now = now + 4;
150
151        assert_eq!(smeared_timestamp.current(), now - 3);
152
153        // Forward another 7 messages.
154        // We can only lend 3 timestamps from the past.
155        assert_eq!(smeared_timestamp.create_n(now, forwarded_messages), now - 3);
156
157        // We had to borrow 3 timestamps from the future
158        // because there were not enough timestamps in the past.
159        assert_eq!(smeared_timestamp.current(), now + 4);
160
161        // Forward another 32 messages.
162        // We cannot use more than 30 timestamps from the future,
163        // so we use 30 timestamps from the future,
164        // the current timestamp and one timestamp from the past.
165        assert_eq!(smeared_timestamp.create_n(now, 32), now - 1);
166    }
167
168    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
169    async fn test_create_smeared_timestamp() {
170        let t = TestContext::new().await;
171        assert_ne!(create_smeared_timestamp(&t), create_smeared_timestamp(&t));
172        assert!(
173            create_smeared_timestamp(&t)
174                >= SystemTime::now()
175                    .duration_since(SystemTime::UNIX_EPOCH)
176                    .unwrap()
177                    .as_secs() as i64
178        );
179    }
180
181    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
182    async fn test_create_smeared_timestamps() {
183        let t = TestContext::new().await;
184        let count = MAX_SECONDS_TO_LEND_FROM_FUTURE - 1;
185        let start = create_smeared_timestamps(&t, count as usize);
186        let next = smeared_time(&t);
187        assert!((start + count - 1) < next);
188
189        let count = MAX_SECONDS_TO_LEND_FROM_FUTURE + 30;
190        let start = create_smeared_timestamps(&t, count as usize);
191        let next = smeared_time(&t);
192        assert!((start + count - 1) < next);
193    }
194}