-
Notifications
You must be signed in to change notification settings - Fork 150
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: introduce try_executor to detect whether in scope of smol
This way third parties can decide how to spawn in runtime time but not coding time. It is similar to `tokio::runtime::Handle::try_current`.
- Loading branch information
Showing
4 changed files
with
134 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
use std::cell::Cell; | ||
use std::future::Future; | ||
use std::panic::catch_unwind; | ||
use std::thread; | ||
|
||
use async_executor::{Executor, Task}; | ||
use async_lock::OnceCell; | ||
use futures_lite::future; | ||
|
||
thread_local! { | ||
static SMOL: Cell<bool> = const { Cell::new(false) }; | ||
} | ||
|
||
pub(crate) struct EnterScope { | ||
first: bool, | ||
} | ||
|
||
impl Drop for EnterScope { | ||
fn drop(&mut self) { | ||
if self.first { | ||
SMOL.set(false); | ||
} | ||
} | ||
} | ||
|
||
pub(crate) fn enter() -> EnterScope { | ||
let smol = SMOL.get(); | ||
SMOL.set(true); | ||
EnterScope { first: !smol } | ||
} | ||
|
||
/// Gets executor if runs in control of smol, that is [block_on], [unblock] and tasks spawnned from | ||
/// [crate::spawn()]. | ||
pub fn try_executor() -> Option<&'static Executor<'static>> { | ||
if SMOL.get() { | ||
Some(global()) | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
/// Same as [async_io::block_on] expect it setup thread context executor for [try_executor]. | ||
pub fn block_on<T>(future: impl Future<Output = T>) -> T { | ||
let _scope = enter(); | ||
async_io::block_on(future) | ||
} | ||
|
||
/// Same as [blocking::unblock] expect it setup thread context executor for [try_executor]. | ||
pub fn unblock<T, F>(f: F) -> Task<T> | ||
where | ||
F: FnOnce() -> T + Send + 'static, | ||
T: Send + 'static, | ||
{ | ||
blocking::unblock(move || { | ||
let _scope = enter(); | ||
f() | ||
}) | ||
} | ||
|
||
pub(crate) fn global() -> &'static Executor<'static> { | ||
static GLOBAL: OnceCell<Executor<'_>> = OnceCell::new(); | ||
GLOBAL.get_or_init_blocking(|| { | ||
let num_threads = { | ||
// Parse SMOL_THREADS or default to 1. | ||
std::env::var("SMOL_THREADS") | ||
.ok() | ||
.and_then(|s| s.parse().ok()) | ||
.unwrap_or(1) | ||
}; | ||
|
||
for n in 1..=num_threads { | ||
thread::Builder::new() | ||
.name(format!("smol-{}", n)) | ||
.spawn(|| loop { | ||
catch_unwind(|| block_on(global().run(future::pending::<()>()))).ok(); | ||
}) | ||
.expect("cannot spawn executor thread"); | ||
} | ||
|
||
// Prevent spawning another thread by running the process driver on this thread. | ||
let ex = Executor::new(); | ||
#[cfg(not(target_os = "espidf"))] | ||
ex.spawn(async_process::driver()).detach(); | ||
ex | ||
}) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use futures_lite::future; | ||
|
||
#[test] | ||
fn try_executor_no() { | ||
assert!(try_executor().is_none()); | ||
} | ||
|
||
#[test] | ||
fn try_executor_block_on() { | ||
let executor = block_on(async { try_executor().unwrap() }); | ||
assert!(std::ptr::eq(executor, global())); | ||
} | ||
|
||
#[test] | ||
fn try_executor_block_on_recursively() { | ||
let executor = block_on(async { block_on(async { try_executor().unwrap() }) }); | ||
assert!(std::ptr::eq(executor, global())); | ||
} | ||
|
||
#[test] | ||
fn try_executor_unblock() { | ||
let executor = future::block_on(unblock(|| try_executor().unwrap())); | ||
assert!(std::ptr::eq(executor, global())); | ||
} | ||
|
||
#[test] | ||
fn try_executor_spawn() { | ||
for _ in 0..100 { | ||
let executor = future::block_on(crate::spawn(async { try_executor().unwrap() })); | ||
assert!(std::ptr::eq(executor, global())); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters