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    pub fn create_n(&self, now: i64, count: i64) -> i64 {
41        let mut prev = self.smeared_timestamp.load(Ordering::Relaxed);
42        loop {
43            // Advance the timestamp if it is in the past,
44            // but keep `count - 1` timestamps from the past if possible.
45            let t = max(prev, now - count + 1);
46
47            // Rewind the time back if there is no room
48            // to allocate `count` timestamps without going too far into the future.
49            // Not going too far into the future
50            // is more important than generating unique timestamps.
51            let first = min(t, now + MAX_SECONDS_TO_LEND_FROM_FUTURE - count + 1);
52
53            // Allocate `count` timestamps by advancing the current timestamp.
54            let next = first + count;
55
56            if let Err(x) = self.smeared_timestamp.compare_exchange_weak(
57                prev,
58                next,
59                Ordering::Relaxed,
60                Ordering::Relaxed,
61            ) {
62                prev = x;
63            } else {
64                return first;
65            }
66        }
67    }
68
69    /// Creates a single timestamp.
70    pub fn create(&self, now: i64) -> i64 {
71        self.create_n(now, 1)
72    }
73
74    /// Returns the current smeared timestamp.
75    pub fn current(&self) -> i64 {
76        self.smeared_timestamp.load(Ordering::Relaxed)
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use crate::test_utils::TestContext;
84    use crate::tools::{
85        create_smeared_timestamp, create_smeared_timestamps, smeared_time, time, SystemTime,
86    };
87
88    #[test]
89    fn test_smeared_timestamp() {
90        let smeared_timestamp = SmearedTimestamp::new();
91        let now = time();
92
93        assert_eq!(smeared_timestamp.current(), 0);
94
95        for i in 0..MAX_SECONDS_TO_LEND_FROM_FUTURE {
96            assert_eq!(smeared_timestamp.create(now), now + i);
97        }
98        assert_eq!(
99            smeared_timestamp.create(now),
100            now + MAX_SECONDS_TO_LEND_FROM_FUTURE
101        );
102        assert_eq!(
103            smeared_timestamp.create(now),
104            now + MAX_SECONDS_TO_LEND_FROM_FUTURE
105        );
106
107        // System time rewinds back by 1000 seconds.
108        let now = now - 1000;
109        assert_eq!(
110            smeared_timestamp.create(now),
111            now + MAX_SECONDS_TO_LEND_FROM_FUTURE
112        );
113        assert_eq!(
114            smeared_timestamp.create(now),
115            now + MAX_SECONDS_TO_LEND_FROM_FUTURE
116        );
117        assert_eq!(
118            smeared_timestamp.create(now + 1),
119            now + MAX_SECONDS_TO_LEND_FROM_FUTURE + 1
120        );
121        assert_eq!(smeared_timestamp.create(now + 100), now + 100);
122        assert_eq!(smeared_timestamp.create(now + 100), now + 101);
123        assert_eq!(smeared_timestamp.create(now + 100), now + 102);
124    }
125
126    #[test]
127    fn test_create_n_smeared_timestamps() {
128        let smeared_timestamp = SmearedTimestamp::new();
129        let now = time();
130
131        // Create a single timestamp to initialize the generator.
132        assert_eq!(smeared_timestamp.create(now), now);
133
134        // Wait a minute.
135        let now = now + 60;
136
137        // Simulate forwarding 7 messages.
138        let forwarded_messages = 7;
139
140        // We have not sent anything for a minute,
141        // so we can take the current timestamp and take 6 timestamps from the past.
142        assert_eq!(smeared_timestamp.create_n(now, forwarded_messages), now - 6);
143
144        assert_eq!(smeared_timestamp.current(), now + 1);
145
146        // Wait 4 seconds.
147        // Now we have 3 free timestamps in the past.
148        let now = now + 4;
149
150        assert_eq!(smeared_timestamp.current(), now - 3);
151
152        // Forward another 7 messages.
153        // We can only lend 3 timestamps from the past.
154        assert_eq!(smeared_timestamp.create_n(now, forwarded_messages), now - 3);
155
156        // We had to borrow 3 timestamps from the future
157        // because there were not enough timestamps in the past.
158        assert_eq!(smeared_timestamp.current(), now + 4);
159
160        // Forward another 32 messages.
161        // We cannot use more than 30 timestamps from the future,
162        // so we use 30 timestamps from the future,
163        // the current timestamp and one timestamp from the past.
164        assert_eq!(smeared_timestamp.create_n(now, 32), now - 1);
165    }
166
167    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
168    async fn test_create_smeared_timestamp() {
169        let t = TestContext::new().await;
170        assert_ne!(create_smeared_timestamp(&t), create_smeared_timestamp(&t));
171        assert!(
172            create_smeared_timestamp(&t)
173                >= SystemTime::now()
174                    .duration_since(SystemTime::UNIX_EPOCH)
175                    .unwrap()
176                    .as_secs() as i64
177        );
178    }
179
180    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
181    async fn test_create_smeared_timestamps() {
182        let t = TestContext::new().await;
183        let count = MAX_SECONDS_TO_LEND_FROM_FUTURE - 1;
184        let start = create_smeared_timestamps(&t, count as usize);
185        let next = smeared_time(&t);
186        assert!((start + count - 1) < next);
187
188        let count = MAX_SECONDS_TO_LEND_FROM_FUTURE + 30;
189        let start = create_smeared_timestamps(&t, count as usize);
190        let next = smeared_time(&t);
191        assert!((start + count - 1) < next);
192    }
193}