[][src]Crate async_executors

async_executors

standard-readme compliant Build Status Docs crates.io

Abstract over different executors.

The aim of async_executors is to provide a uniform interface to the main async executors available in Rust. We provide wrapper types that always implement the Spawn and/or LocalSpawn traits from futures, making it easy to pass any supported executor to an API which requires exec: impl Spawn or exec: impl LocalSpawn.

A problem with these traits is that they do not provide an API for getting a JoinHandle to await completion of the task. The current trend in async is to favor joining tasks and thus certain executors now return JoinHandles from their spawn function. It's also a fundamental building block for structured concurrency. async_executors provides an executor agnostic JoinHandle that wraps the framework native JoinHandle types.

SpawnHandle traits are also provided so API's can express you need to pass them an executor which allows spawning with a handle. Note that this was already provided by the futures in SpawnExt::spawn_with_handle, but this uses RemoteHandle which incurs a performance overhead. By wrapping the native JoinHandle types, we can avoid some of that overhead while still being executor agnostic. These traits are feature gated behind the spawn_handle feature.

The traits provided by this crate are also implemented for the Instrumented and WithDispatch wrappers from tracing-futures when the tracing feature is enabled. So you can pass an instrumented executor to an API requiring exec: impl SpawnHandle.

The currently supported executors are (let me know if you want to see others supported):

All executors are behind feature flags: async_std, tokio_ct, tokio_tp, bindgen, localpool, threadpool.

Table of Contents

Install

With cargo add: cargo add async_executors

With cargo yaml:

dependencies:

   async_executors: ^0.1

With Cargo.toml

[dependencies]

    async_executors = "^0.1"

Upgrade

Please check out the changelog when upgrading.

Dependencies

This crate has few dependencies. Cargo will automatically handle it's dependencies for you.

The only dependency is futures-task, the rest are the optional dependencies to turn on support for each executor.

Security

There is one use of unsafe to make it possible to spawn !Send futures on the tokio Runtime with the basic_scheduler. Review is welcome.

There is one line of unsafe and our dependencies use unsafe.

Performance

Most wrappers are very thin but the Spawn and LocalSpawn traits do imply boxing the future. With executors boxing futures to put them in a queue you probably get 2 heap allocations per spawn.

JoinHandle uses the native JoinHandle types from tokio and async-std to avoid the overhead from RemoteHandle, but wrap the future in Abortable to create consistent behavior across all executors.

SpawnHandle and LocalSpawnHandle are thin wrappers, but not object safe. The object safe versions do imply boxing the future twice, just like Spawn and LocalSpawn.

Existing benchmarks for all executors can be found in executor_benchmarks.

Usage

For API providers

When writing a library that needs to spawn, you probably shouldn't lock your clients into one framework or another. It's usually not appropriate to setup your own thread pool for spawning futures. It belongs to the application developer to decide where futures are spawned and it might not be appreciated if libraries bring in extra dependencies on a framework.

In order to get round this you can take an executor in as a parameter from client code and spawn your futures on the provided executor. Currently the only to traits that are kind of widely available for this are Spawn and LocalSpawn from the futures library. Unfortunately, other executor providers do not implement these traits. So by publishing an API that relies on these traits, you would have been restricting the clients to use the executors from futures, or start implementing their own wrappers that implement the traits.

async_executors has wrappers providing impls on various executors, namely tokio, async_std, wasm_bindgen. As such you can just use the trait bounds and refer your users to this crate if they want to use any of the supported executors.

All wrappers also implement Clone, Debug and the zero sized ones also Copy. You can express you will need to clone in your API: impl Spawn + Clone.

Spawning with handles

You can use the SpawnHandle and LocalSpawnHandle traits as bounds for obtaining join handles. They can't be stored however as they aren't object safe. So either you make the data structure that needs to store them generic, or you need to revert to the object safe versions (SpawnHandleOs and LocalSpawnHandleOs) which imply you have to specify the output type and futures will have to be boxed an extra time.

Example
use
{
  async_executors::*,
  std::sync::Arc,
  futures::future::FutureExt,
  futures::executor::{ ThreadPool, block_on },
};


fn needs_exec( exec: impl SpawnHandle )
{
   let handle = exec.spawn_handle( async {} );
}


struct SomeObj{ exec: Arc< dyn SpawnHandleOs<u8> > }


impl SomeObj
{
   pub fn new( exec: Arc< dyn SpawnHandleOs<u8> > ) -> SomeObj
   {
      SomeObj{ exec }
   }

   fn run( &self ) -> JoinHandle<u8>
   {
      let task   = async{ 5 }.boxed();

      self.exec.spawn_handle_os( task ).expect( "spawn" )
   }
}

fn main()
{
  let exec = ThreadPool::new().expect( "build threadpool" );
  let obj  = SomeObj::new( Arc::new(exec) );

  let x = block_on( obj.run() );

  assert_eq!( x, 5 );
}

For API consumers

You can basically pass the wrapper types provided in async_executors to API's that take any of the following. Traits are also implemented for Rc, Arc, &, &mut, Box and Instrumented and WithDispatch from tracing-futures wrappers:

  • impl Spawn
  • impl LocalSpawn
  • impl SpawnHandle
  • impl LocalSpawnHandle
  • impl SpawnHandleOs<T>
  • impl LocalSpawnHandleOs<T>

All wrappers also implement Clone, Debug and the zero sized ones also Copy.

Some executors are a bit special, so make sure to check the API docs for the one you intend to use. Some also provide extra methods like block_on which will call a framework specific block_on rather than the one from futures.

Example

use
{
  async_executors::*,
  std::convert::TryFrom,
};

fn needs_exec( exec: impl SpawnHandle ){};

needs_exec( AsyncStd::default() );

let tp = TokioTp::try_from( &mut tokio::runtime::Builder::new() ).expect( "build threadpool" );

needs_exec( tp );

API

API documentation can be found on docs.rs.

Contributing

This repository accepts contributions. Ideas, questions, feature requests and bug reports can be filed through Github issues.

Pull Requests are welcome on Github. By committing pull requests, you accept that your code might be modified and reformatted to fit the project coding style or to improve the implementation. Contributed code is considered licensed under the Unlicence unless explicitly agreed otherwise.

Please discuss what you want to see modified before filing a pull request if you don't want to be doing work that might be rejected. Please file PR's against the dev branch, don't forget to update the changelog and the documentation.

Testing

Run ./ci.bash to run all tests.

Code of conduct

Any of the behaviors described in point 4 "Unacceptable Behavior" of the Citizens Code of Conduct are not welcome here and might get you banned. If anyone including maintainers and moderators of the project fail to respect these/your limits, you are entitled to call them out.

License

Unlicence

Structs

AsyncStdfeature="async_std"

An executor that spawns tasks on async-std. In contrast to the other executors, this one is not self contained, because async-std does not provide an API that allows that, so the threadpool is global.

Bindgenfeature="bindgen"

A type that implements Spawn and LocalSpawn and spawns on the wasm-bingen-futures executor. The executor is global, eg. not self contained.

JoinHandlefeature="spawn_handle"

A framework agnostic JoinHandle type. Cancels the future on dropping the handle. You can call detach to leave the future running when dropping the handle.

TokioCtfeature="tokio_ct"

An executor that uses a tokio::runtime::Runtime with the basic scheduler. Can spawn !Send futures.

TokioHandle

A handle to a TokioCt or TokioTp executor. It implements Spawn, SpawnHandle and SpawnHandleOs traits.

TokioTpfeature="tokio_tp"

An executor that uses tokio::runtime::Runtime.

Traits

LocalSpawnHandlefeature="spawn_handle"

Let's you spawn and get a JoinHandle to await the output of a future.

LocalSpawnHandleOsfeature="spawn_handle"

Object safe version of LocalSpawnHandle. This allows you to take it as a param and store it. It incurs some overhead, since the future needs to be boxed and executors will box it again to queue it.

SpawnHandlefeature="spawn_handle"

Let's you spawn and get a JoinHandle to await the output of a future.

SpawnHandleOsfeature="spawn_handle"

Object safe version of SpawnHandle. This allows you to take it as a param and store it. It incurs some overhead, since the future needs to be boxed and executors will box it again to queue it.