Skip to content

Commit

Permalink
promise: add tokio_spawn helper
Browse files Browse the repository at this point in the history
This starts up the tokio runtime on a secondary thread
and spawns a future into it.
  • Loading branch information
wez committed Jan 20, 2020
1 parent 7aa0994 commit 1d96787
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 1 deletion.
77 changes: 76 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions promise/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition = "2018"
[dependencies]
async-task = "1.2"
async-std = "1.4"
tokio = {version="0.2", features=["full"]}
anyhow = "1.0"
thiserror = "1.0"
lazy_static = "1.3"
55 changes: 55 additions & 0 deletions promise/src/spawn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,68 @@ fn no_schedule_configured(_: Task<()>) {
lazy_static::lazy_static! {
static ref ON_MAIN_THREAD: Mutex<ScheduleFunc> = Mutex::new(Box::new(no_schedule_configured));
static ref ON_MAIN_THREAD_LOW_PRI: Mutex<ScheduleFunc> = Mutex::new(Box::new(no_schedule_configured));
static ref TOKIO: tokio::runtime::Handle = start_tokio();
}

/// Set callbacks for scheduling normal and low priority futures.
/// Why this and not "just tokio"? In a GUI application there is typically
/// a special GUI processing loop that may need to run on the "main thread",
/// so we can't just run a tokio/mio loop in that context.
/// This particular crate has no real knowledge of how that plumbing works,
/// it just provides the abstraction for scheduling the work.
/// This function allows the embedding application to set that up.
pub fn set_schedulers(main: ScheduleFunc, low_pri: ScheduleFunc) {
*ON_MAIN_THREAD.lock().unwrap() = Box::new(main);
*ON_MAIN_THREAD_LOW_PRI.lock().unwrap() = Box::new(low_pri);
}

/// Spawn the tokio runtime and run it on a secondary thread.
/// We can't run it on the main thread for the reasons mentioned above.
/// This is called implicitly when the TOKIO global is accessed by the
/// `tokio_spawn` function below.
fn start_tokio() -> tokio::runtime::Handle {
let mut runtime = tokio::runtime::Builder::new()
.threaded_scheduler()
.core_threads(1)
.build()
.expect("failed to initialize tokio runtime");
let handle = runtime.handle().clone();

// Run the runtime in another thread.
// I'm not sure that it is strictly needed, or whether we can just
// keep it alive without actively polling anything.
std::thread::spawn(move || {
// A future that never completes
struct Never {}
impl Future for Never {
type Output = ();
fn poll(
self: std::pin::Pin<&mut Self>,
_: &mut std::task::Context,
) -> Poll<Self::Output> {
Poll::Pending
}
}

// manage the runtime forever
runtime.block_on(Never {});
});
handle
}

/// Spawn a future into the tokio runtime, spawning the tokio runtime
/// if it hasn't already been started up. The tokio runtime (in the
/// context of this crate) is intended primarily for scheduling network
/// IO. Most futures should be spawned via the other functions provided
/// by this module.
pub fn tokio_spawn<F>(future: F) -> tokio::task::JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
TOKIO.spawn(future)
}

/// Spawn a new thread to execute the provided function.
/// Returns a JoinHandle that implements the Future trait
/// and that can be used to await and yield the return value
Expand Down

0 comments on commit 1d96787

Please sign in to comment.