const abortController = new AbortController();
setTimeout(() => abortController.abort(), 1000); // Abort the task below after 1 second.
try {
await cancellableDelayPromise(24 * 60 * 60 * 1000, abortController.signal);
throw new Error("You're too late! It's 24 hours afterwards");
} catch (e: any) {
assertTrue(e instanceof Error && e.name === "AbortError");
console.log("Phew, you didn't wait all that time");
}
Cancelling Rust tasks
Uniffi's machinery provides a cancelFunc. As of v0.28.0, this causes the Future to be dropped. The Rust should be written in such a way as to do any cleanup for the task when this happens.
This cancelFunc can be called indirectly by passing an { signal: AbortSignal; } when calling any async function.
There is no way for uniffi to know which Futures can be cancelled, so all async functions have an optional argument of asyncOpts_?: { signal: AbortSignal; } appended to their argument list.
Thus:
await fetchUser(userId);
may also be called with an AbortSignal.
await fetchUser(userId, { signal });
Since these are optional arguments, it is up to the Typescript caller whether or not to include them.
Cancelling async Javascript callbacks
The futures_util crate provides structures similar to AbortController and AbortSignal. In this example, obj is a JS callback interface.
async fn cancel_delay_using_trait(obj: Arc<dyn AsyncParser>, delay_ms: i32) {
let (abort_handle, abort_registration) = AbortHandle::new_pair();
thread::spawn(move || {
// Simulate a different thread aborting the process
thread::sleep(Duration::from_millis(1));
abort_handle.abort();
});
let future = Abortable::new(obj.delay(delay_ms), abort_registration);
assert_eq!(future.await, Err(Aborted));
}
The obj.delay(delay_ms) call translates to a call to a Javascript function.
When abort_handle.abort() is called, the Abortable future is dropped. The AbortSignal in Javascript is told to abort when it is being cleaned up, and hasn't yet settled.
Because uniffi can't tell which Javascript callbacks support an AbortSignal, all async functions have an optional argument of asyncOpts_?: { signal: AbortSignal; } appended to their argument list.
Since these are optional arguments, it is up to the Typescript implementer whether or not to include them.
Caveat emptor
Because of the different APIs across languages and the co-operative nature of task cancellation in Rust, there is a diversity of API support for task cancellation across the various backend languages that uniffi supports. This PR brings uniffi-bindgen-react-native to parity with the Mozilla supported languages.
However, the uniffi docs currently suggest more modest support:
We don't directly support cancellation in UniFFI even when the underlying platforms do.
You should build your cancellation in a separate, library specific channel; for example, exposing a `cancel()` method that sets a flag that the library checks periodically.
Cancellation can then be exposed in the API and be mapped to one of the error variants, or None/empty-vec/whatever makes sense.
There's no builtin way to cancel a future, nor to cause/raise a platform native async cancellation error (eg, a swift `CancellationError`).
According to The Big O of Code Reviews, this is a O(n) change.
Fixes #73 Fixes #74
Background
Task cancellation is cooperative in both Rust and Javascript. i.e. they rely on cooperation of the task itself to perform cancellation.
In Javascript, there is API for the canceller: the
AbortController
. TheAbortController
has anAbortSignal
object which can be given to the task.For example:
This might be used like so:
Cancelling Rust tasks
Uniffi's machinery provides a
cancelFunc
. As ofv0.28.0
, this causes theFuture
to be dropped. The Rust should be written in such a way as to do any cleanup for the task when this happens.This
cancelFunc
can be called indirectly by passing an{ signal: AbortSignal; }
when calling any async function.There is no way for uniffi to know which Futures can be cancelled, so all async functions have an optional argument of
asyncOpts_?: { signal: AbortSignal; }
appended to their argument list.Thus:
may also be called with an
AbortSignal
.Since these are optional arguments, it is up to the Typescript caller whether or not to include them.
Cancelling async Javascript callbacks
The
futures_util
crate provides structures similar toAbortController
andAbortSignal
. In this example,obj
is a JS callback interface.The
obj.delay(delay_ms)
call translates to a call to a Javascript function.When
abort_handle.abort()
is called, the Abortablefuture
is dropped. TheAbortSignal
in Javascript is told to abort when it is being cleaned up, and hasn't yet settled.Because uniffi can't tell which Javascript callbacks support an
AbortSignal
, all async functions have an optional argument ofasyncOpts_?: { signal: AbortSignal; }
appended to their argument list.Since these are optional arguments, it is up to the Typescript implementer whether or not to include them.
Caveat emptor
Because of the different APIs across languages and the co-operative nature of task cancellation in Rust, there is a diversity of API support for task cancellation across the various backend languages that uniffi supports. This PR brings uniffi-bindgen-react-native to parity with the Mozilla supported languages.
However, the uniffi docs currently suggest more modest support:
I would expect this to change over time.