rust_query/
token.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
use std::{any::Any, cell::Cell, rc::Rc};

use rusqlite::Connection;

/// [ThreadToken] is used to separate transactions in time for each thread.
///
/// Only one [ThreadToken] can exist in each thread and transactions need to mutably borrow a thread token.
/// Furthermore, neither [ThreadToken] nor any of the transaction types can be moved between threads.
/// This makes it impossible to have access to two transactions from one thread.
pub struct ThreadToken {
    _p: std::marker::PhantomData<*const ()>,
    pub(crate) stuff: Rc<dyn Any>,
    pub(crate) conn: Option<Connection>,
}

thread_local! {
    static EXISTS: Cell<bool> = const { Cell::new(true) };
}

impl ThreadToken {
    fn new() -> Self {
        ThreadToken {
            _p: std::marker::PhantomData,
            stuff: Rc::new(()),
            conn: None,
        }
    }

    /// Create a [ThreadToken] if it was created not created yet on this thread.
    ///
    /// Async tasks often share their thread and can thus not use this method.
    /// Instead you should use your equivalent of `spawn_blocking` or `block_in_place`.
    /// These functions guarantee that you have a unique thread and thus allow [ThreadToken::try_new].
    ///
    /// Note that using `spawn_blocking` for sqlite is actually a good practice.
    /// Sqlite queries can be expensive, it might need to read from disk which is slow.
    /// Doing so on all async runtime threads would prevent other tasks from executing.
    pub fn try_new() -> Option<Self> {
        EXISTS.replace(false).then(ThreadToken::new)
    }
}

impl Drop for ThreadToken {
    /// Dropping a [ThreadToken] allows retrieving it with [ThreadToken::try_new] again.
    fn drop(&mut self) {
        EXISTS.set(true)
    }
}