Async Internals

This page explains how BoltFFI bridges Rust futures to target language async systems. You don’t need to understand this to use async functions, but it helps when debugging or optimizing async code.

The Polling Model

BoltFFI wraps each Rust future in a RustFuture<T> that exposes a C-compatible interface. The bindings call poll with a continuation callback. If the future is pending, Rust stores that callback and returns; when the future wakes, Rust invokes the callback with MaybeReady, and bindings poll again. When the callback reports Ready, bindings exit the cycle, then run complete and free.

Who does what, and when

  1. Bindings pull: call entry to create a handle.
  2. Bindings pull: call poll(handle, continuation) once.
  3. Rust polls once:
    • if ready, Rust immediately invokes continuation with Ready;
    • if pending, Rust stores continuation and returns.
  4. Rust pushes wake signal: when the underlying future wakes, Rust invokes continuation with MaybeReady.
  5. Bindings pull again: on that callback, bindings call poll again.
  6. Repeat steps 3-5 until callback is Ready.
  7. Bindings finalize: call complete, then free.

There is no busy polling loop in user code. Between polls, bindings wait for Rust to invoke the continuation.

User CodeGenerated BindingsRust Scaffoldingcall async functioncall scaffolding functionreturn RustFuture handlecallback-driven wait/re-poll cyclebindings call RustFuture poll fnRust invokes continuation callbackMaybeReady → bindings poll againReady → exit cyclecall RustFuture complete fnreturn resultcall RustFuture free fnreturn from async function

Generated FFI Functions

For each async function, BoltFFI generates five FFI functions:

  • entry - Creates the RustFuture and returns a handle
  • poll - Polls the future with a continuation callback
  • complete - Extracts the result once the future is ready
  • cancel - Marks the future as cancelled
  • free - Deallocates the future

The bindings use a callback handshake: entry creates the handle, poll registers/waits, callback returns MaybeReady or Ready, MaybeReady triggers another poll, and Ready ends polling before complete/free.

Continuation Callbacks

When bindings call poll, they pass a continuation callback plus callback data. If the future is pending, BoltFFI stores that continuation. When the future wakes (I/O completes, timer fires, etc.), BoltFFI invokes the stored callback with MaybeReady; bindings then poll again. A Ready callback means polling is finished and the call should finalize.

Lock-Free Implementation

Continuation scheduling uses atomic state tags and compare-and-swap transitions. Future execution state and result storage are still guarded by a mutex.

Cancellation

When the target language cancels an async operation, bindings call cancel. BoltFFI marks the future as cancelled and wakes any stored continuation with a Ready signal so waiting bindings can stop polling promptly. Cleanup then runs through free, and cancellation is surfaced by the target runtime’s wrapper.