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}