Michael-F-Bryan/rust-ffi-guide

Asynchronous Tasks

Michael-F-Bryan opened this issue · 0 comments

There's currently an "Asynchronous Operations" chapter which is meant to spin a a job off into the background and will periodically poll it to see if there's a result. We could accomplish this quite easily by using futures and a global CpuPool, with a TaskHandle struct which wraps a future and periodically polls.

The TaskHandle would have to be generic over any generic Task, meaning if we want to use it for FFI we'll want to use some sort of code generation (macro) for generating specialised functions for interacting with the background task.

I've done something like this before at work, so here's a gist to help get started. I was thinking it could use a Task trait similar to the following:

pub trait Task {
  type Output;
  type Error;

  fn run(&self, ctx: Context) -> Result<Self::Output, Self::Error>;
  fn description(&self) -> &'static str;
}

Then the macro which generates FFI bindings for a particular Task implementation might look like this:

struct Foo;
impl Task for Foo { ... } 

generate_task!(Foo; 
  spawn: foo_task_spawn,
  cancel:  foo_task_cancel,
  get_result: foo_task_get_result,
  is_failed: foo_task_is_failed,
  wait: foo_task_wait,
  ...
);

Which might then generate:

/// Spawn the task on a background thread, returning a `TaskHandle`. 
///
/// # Safety
///
/// This **consumes** the task, so it shouldn't be used once the task is spawned.
#[no_mangle]
pub unsafe extern "C" fn foo_task_spawn(task: *mut Foo) -> TaskHandle<Foo> { ... }

/// Cancel a background task.
#[no_mangle]
pub unsafe extern "C" fn foo_task_cancel(handle: *mut TaskHandle<Foo>) { ... } 

/// Wait until the task completes.
///
/// # Safety
///
/// This will consume the task handle and return a `Box<Task::Output>` (that you need
/// to deallocate manually).
#[no_mangle]
pub unsafe extern "C" fn foo_task_wait(handle: *mut TaskHandle<Foo>) -> *const Foo::Output { ... } 

...