A thread-pool executor for async Rust that runs the same code on OS threads natively
and on web workers on wasm32.

This crate is the reference executor for the `some_executor` framework: if you write
code against the `SomeExecutor` trait and want one real executor to run it
everywhere, this is it.
# Why this crate exists
Rust ships no async executor in std, and no trait for executors to implement, so most
async code is written directly against one specific runtime. The `some_executor`
crate fixes this by defining a small interface between the code that *has* futures and
the code that *runs* them: libraries spawn onto "some" executor through a trait, and
applications decide which executor that actually is.
That framework needs a production-quality executor to point at, and this crate is it.
`some_global_executor` implements the `SomeExecutor` trait for `Send + 'static`
futures with a resizable pool of workers, and can install itself as the process-wide
global executor or as a per-thread executor — which is exactly what
`some_executor`'s `current_executor()` discovery hierarchy looks for. Out of the
box, `some_executor` falls back to a built-in toy executor that warns when used;
installing this crate is the intended way to replace it.
# What it does
- **One API, two platforms.** On native targets, tasks run on a pool of OS threads fed
by a `crossbeam-channel` queue. On wasm32, the same public API runs tasks on web
workers, with a custom async channel for distribution (workers can't block). The
platform switch is conditional compilation; your code doesn't change.
- **Task observation.** Spawning returns an observer from the `some_executor`
framework: await it for the result, poll it, detach it, or drop it to request
cooperative cancellation.
- **Graceful shutdown.** `Executor::drain` blocks until every spawned task has
finished; `Executor::drain_async` is the awaitable version for async contexts.
- **Runtime resizing.** `Executor::resize` grows or shrinks the worker pool while
the executor is running.
- **Structured logging.** Internal operations are logged through
[`logwise`](https://github.com/drewcrawford/logwise), with optional diagnostic,
forensic, and performance feature flags. An optional `exfiltrate` feature exposes a
registry of live pools for external debugging tools.
# Quick start
```
use some_global_executor::Executor;
use some_executor::SomeExecutor;
use some_executor::task::{Task, Configuration};
# if cfg!(target_arch = "wasm32") { return; }
// A named pool with 4 worker threads (or web workers on wasm32).
let mut executor = Executor::new("my-executor".to_string(), 4);
let task = Task::without_notifications(
"example-task".to_string(),
Configuration::default(),
async { 42 }
);
let observer = executor.spawn(task);
// Await the observer for the result, or drop it to cancel the task.
// Optionally make this the executor that some_executor's
// current_executor() / global spawning resolves to:
executor.set_as_global_executor();
// Block until all spawned tasks finish.
executor.drain();
```
# Where it fits in the ecosystem
The `some_executor` family divides the work like this:
- `some_executor` — the trait layer. Defines `Task`,
observers, cancellation, priorities, task-locals, and the global/thread-local
executor registry. No real scheduling of its own beyond a fallback.
- **`some_global_executor`** (this crate) — the reference executor for `Send` tasks:
a thread pool natively, web workers on wasm32.
- [`some_local_executor`](https://github.com/drewcrawford/some_local_executor) — runs
non-`Send` tasks on the current thread, and can receive tasks from other threads.
- [`test_executors`](https://github.com/drewcrawford/test_executors) — toy executors
for unit tests.
So: libraries depend on `some_executor` and stay executor-agnostic; applications
depend on this crate (or another implementation) and install it once at startup.
# Alternatives
If you aren't committed to the `some_executor` interface, the field looks like this:
| [tokio](https://crates.io/crates/tokio) | Full runtime: executor plus async I/O, timers, sync primitives, and a huge ecosystem | The default choice if you need its I/O stack — but your code becomes tokio-specific, and there is no multithreaded wasm32 story. This crate is executor-only and trait-first. |
| [async-executor](https://crates.io/crates/async-executor) (smol) | Small, composable executor; bring your own reactor | Closest in spirit (executor without a bundled runtime), but no common spawning trait for libraries, no observer/cancellation model, and no web-worker pool on wasm32. |
| [futures-executor](https://crates.io/crates/futures-executor) | The minimal `ThreadPool`/`LocalPool` in the futures crate | Fine for gluing a future into sync code; no task metadata, draining, resizing, or wasm parallelism. |
| [wasm-bindgen-futures](https://crates.io/crates/wasm-bindgen-futures) | `spawn_local` onto the browser event loop | The standard wasm answer, but single-threaded and wasm-only. This crate gives you actual parallelism via web workers and the same API natively. |
| [rayon](https://crates.io/crates/rayon) | Data-parallelism for synchronous code | Not an async executor at all; complementary rather than competing. |
| async-std | Formerly a full alternative runtime | Discontinued (deprecated in 2025); its maintainers point to smol. |
| `some_executor`'s built-in fallback | Zero-config executor included in the trait crate | Always available so libraries work with no setup, but deliberately not production quality — it exists to be replaced by this crate. |
Pick this crate when you want executor-agnostic code (or already have it via
`some_executor`), need the same spawning code to work natively and on wasm32 with
real parallelism, and don't need a bundled I/O reactor. Pick tokio when you're
building on its networking stack and wasm isn't a target.
# More examples
## Observing task progress
Tasks can be observed to monitor their execution state:
```
use some_global_executor::Executor;
use some_executor::SomeExecutor;
use some_executor::task::{Task, Configuration};
use some_executor::observer::{Observer, Observation};
# if cfg!(target_arch = "wasm32") { return; }
let mut executor = Executor::new("observer-example".to_string(), 2);
let task = Task::without_notifications(
"monitored-task".to_string(),
Configuration::default(),
async { "result" }
);
let observer = executor.spawn(task);
// Poll the observer to check task state
loop {
match observer.observe() {
Observation::Ready(value) => {
println!("Task completed with: {}", value);
break;
}
Observation::Pending => {
// Task still running
std::thread::yield_now();
}
_ => break,
}
}
executor.drain();
```
## Global executor pattern
Set an executor as the global default for the application:
```
use some_global_executor::Executor;
# if cfg!(target_arch = "wasm32") { return; }
// Create and configure the global executor
let executor = Executor::new("global".to_string(), num_cpus::get());
executor.set_as_global_executor();
// Now tasks can be spawned using the global executor from anywhere
// in the application without passing executor references
# executor.drain();
```
## Dynamic thread pool management
Adjust executor capacity based on workload:
```
use some_global_executor::Executor;
let mut executor = Executor::new("dynamic".to_string(), 2);
// Scale up for heavy workload
executor.resize(8);
// Scale down during idle periods
executor.resize(2);
executor.drain();
```
# Performance considerations
- Thread pool sizing: Default to `num_cpus::get()` for CPU-bound work
- For I/O-bound tasks, consider using more threads than CPU cores
- WASM targets have platform-specific limitations on parallelism
- Use `drain_async()` in async contexts to avoid blocking
# Logging
This crate uses the `logwise` framework for structured logging. Internal operations
are logged at various levels for debugging and monitoring:
```
# use some_global_executor::Executor;
// Executor creation and operations are automatically logged
let executor = Executor::new("logged-executor".to_string(), 4);
// Logs: "Creating executor with name logged-executor and 4 threads"
# executor.drain();
```
# Requirements
Rust 1.95+ (edition 2024). Native targets need only stable Rust; running the wasm32
test suite requires nightly.