Updated Future FFI
The initial motivation for this was cancellation. PR#1697 made it
so if an async function was cancelled we would eventually release the
resources. However, it would be better if we could immedately release
the resources. In order to implement that, the future FFI needed to
change quite a bit and most of these changes are good.
The new FFI is simpler overall and supports cancel and drop operations.
It's actually quite close to the initial FFI that Hywan proposed. Cancel
ensures that the foreign code will resume and break out of its async
code. Drop ensures that all resources from the wrapped future are
relased.
Note: the new FFI has a cancel method, but no bindings use it yet. For
Python/Kotlin we don't need to because they throw cancellation
exceptions, which means cancellation support falls out from the new API
without any extra work. This cancel method could be used for Swift, but
we still need to think this through in a different PR.
The new code does not use ForeignExecutor anymore, so that code is in a
state of limbo. I hope to repurpose it for foreign dispatch queues
(#1734). If that doesn't work out, we can just delete it.
The FFI calls need some care since we pass a type-erased handle to the
foreign code, while RustFuture is generic over an anonymous Future type:
- The concrete RustFuture type implements the `RustFutureFFI<F>`
trait.
- We give the foreign side a leaked `Box<Arc<dyn RustFutureFFI<F>>>>`.
- We hand-monomorphize, creating a scaffolding function for each
RustFutureFFI method, for each possible FFI type.
Updated proc macros lift arguments in 2 phases. First we call
`try_lift` on all arguments, generating a `Result<LiftedArgsTuple>`.
Then we call the rust function using that tuple or handle the error.
This is needed because the new async code adds a `Send` bound futures.
The `Send` bound is good because futures are going to be moving across
threads as we poll/wake them. However, the lift code won't always be Send
because it may deal with raw pointers. To deal with that, we perform
the non-Send lifting outside of the future, then create a Send future
from the result. This means that the lift phase is executed outside of
the async context, but it should be very fast. This change also allows
futures that return Results to attempt to downcast lift errors.
More changes:
- Updated the futures fixture tests for this to hold on to the mutex
longer in the initial call. This makes it so they will fail unless
the future is dropped while the mutex is still locked. Before they
would only succeed as long as the mutex was dropped once the timeout
expired.
- Updated `RustCallStatus.code` field to be an enum. Added `Cancelled`
as one of the variants. `Cancelled` is only used for async functions.
- Removed the FutureCallback and invoke_future_callback from
`FfiConverter`.
- New syncronization handling code in RustFuture that's hopefully
clearer, more correct, and more understandable than the old stuff.
- Updated `UNIFFI_CONTRACT_VERSION` since this is an ABI change
- Removed the `RustCallStatus` param from async scaffolding functions.
These functions can't fail, so there's no need.
- Added is_async() to the Callable trait.
- Changed `handle_failed_lift` signature. Now, if a `Result<>` is able
to downcast the error, it returns `Self::Err` directly instead of
serializing the error into `RustBuffer`.
Co-authored-by: Ivan Enderlin <ivan@mnt.io>
Co-authored-by: Jonas Platte <jplatte@matrix.org>