sequoia_keystore_backend/
utils.rs

1use std::ops::Deref;
2use std::path::Path;
3use std::path::PathBuf;
4use std::sync::Arc;
5
6use crate::Result;
7
8/// A directory, which is possibly self-destructing.
9///
10/// References a directory.  The directory may be a permanent
11/// directory, or a temporary directory, which is automatically
12/// cleaned up when the data structure is dropped.
13#[derive(Clone, Debug)]
14pub enum Directory {
15    Directory(PathBuf),
16    TempDir(Arc<tempfile::TempDir>),
17}
18
19impl Deref for Directory {
20    type Target = Path;
21
22    fn deref(&self) -> &Path {
23        match self {
24            Directory::Directory(d) => d.as_path(),
25            Directory::TempDir(d) => d.path(),
26        }
27    }
28}
29
30impl From<PathBuf> for Directory {
31    fn from(pathbuf: PathBuf) -> Directory {
32        Directory::Directory(pathbuf)
33    }
34}
35
36impl From<&Path> for Directory {
37    fn from(path: &Path) -> Directory {
38        Directory::Directory(path.to_path_buf())
39    }
40}
41
42impl From<tempfile::TempDir> for Directory {
43    fn from(tempdir: tempfile::TempDir) -> Directory {
44        Directory::TempDir(Arc::new(tempdir))
45    }
46}
47
48impl AsRef<Path> for Directory {
49    fn as_ref(&self) -> &Path {
50        self.deref()
51    }
52}
53
54impl Directory {
55    /// Returns an ephemeral home directory.
56    ///
57    /// An ephmeral home directory is a temporary directory that is
58    /// removed when `self` goes out of scope.
59    pub fn ephemeral() -> Result<Self> {
60        Ok(Directory::from(
61            tempfile::Builder::new().prefix("sq-keystore").tempdir()?))
62    }
63
64    /// Returns the home directory in a `PathBuf`.
65    pub fn to_path_buf(&self) -> PathBuf {
66        self.as_ref().to_path_buf()
67    }
68}
69
70/// Maps a panic of a worker thread to an error.
71///
72/// Unfortunately, there is nothing useful to do with the error, but
73/// returning a generic error is better than panicking.
74fn map_panic(_: Box<dyn std::any::Any + std::marker::Send>) -> anyhow::Error
75{
76    anyhow::anyhow!("worker thread panicked")
77}
78
79/// A helper function to run an asynchronous task in a sync context.
80///
81/// If we are in a sync context, we can't use any async functions
82/// without creating a new async runtime.  Although it is possible to
83/// get a handle to an existing runtime using
84/// [`tokio::runtime::Handle::try_current`](https://docs.rs/tokio/latest/tokio/runtime/struct.Handle.html#method.try_current),
85/// we can't actually use it to run a sync function.  Specifically, if
86/// we do:
87///
88/// ```text
89/// if let Ok(rt) = tokio::runtime::Handle::try_current() {
90///     Ok(rt.block_on(f))
91/// }
92/// ```
93///
94/// then Tokio will panic:
95///
96/// ```text
97/// thread 'tests::verify' panicked at 'Cannot start a runtime from within a runtime. This happens because a function (like `block_on`) attempted to block the current thread while the thread is being used to drive asynchronous tasks.', backend/src/utils.rs:97:15
98/// ```
99///
100/// Instead, we check if there is an async runtime, if *not*, then we
101/// create an async runtime using Tokio, run the async function, and
102/// return.  If there is an async runtime, we start a new thread,
103/// create a new runtime there, and execute the function using that
104/// runtime.
105pub fn run_async<F: std::future::Future + Send>(f: F) -> Result<F::Output>
106where F::Output: Send
107{
108    use tokio::runtime::{Handle, Runtime};
109
110    // See if the current thread is managed by a tokio
111    // runtime.
112    if Handle::try_current().is_err() {
113        // Doesn't seem to be the case, so it is safe
114        // to create a new runtime and block.
115        let rt = Runtime::new()?;
116        Ok(rt.block_on(f))
117    } else {
118        // It is!  We must not create a second runtime
119        // on this thread, but instead we will
120        // delegate this to a new thread.
121        let r: Result<F::Output> = std::thread::scope(|s| {
122            s.spawn(move || -> Result<F::Output> {
123                let rt = Runtime::new()?;
124                Ok(rt.block_on(f))
125            }).join()
126        }).map_err(map_panic)?;
127        r
128    }
129}