Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Foreign BackgroundQueue type #1734

Open
bendk opened this issue Sep 5, 2023 · 4 comments
Open

Foreign BackgroundQueue type #1734

bendk opened this issue Sep 5, 2023 · 4 comments

Comments

@bendk
Copy link
Contributor

bendk commented Sep 5, 2023

A common pattern we use in application-services is to run SQLite queries using a foreign-managed task queue (Dispatchers.IO on Kotlin, DispatchQueue on Swift, etc). Could UniFFI support this as a first-class type?

This would require:

  • Moving it across the FFI (only supporting Foreign -> Rust is probably fine)
  • Adding way to execute Rust synchronous code using the foreign DispatchQueue in two modes:
    • async mode where you await the result
    • fire-and-forget mode where you just want the code to run and don't care about the result.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 8, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

The new code does not use ForeignExecutor and that code is in a state of
limbo for now.  I hope to repurpose it for foreign dispatch queues
(mozilla#1734).  If that doesn't work out, we can just delete it.

It's tricky to implement FFI calls that inputted type-erased
`RustFutureHandle`, since the `F` parameter is an anonymous type that
implements Future. Made a new system that is based to converting an
`Arc<RustFuture<F, T, U>>` into a `Box<Arc<dyn RustFutureFFI>>` before
sending it across the FFI. `Arc<dyn RustFutureFFI>` implements the FFI
and the extra Box converts the wide pointer into a normal pointer.  It's
fairly messy, but I hope mozilla#1730 will improve things.

- 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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 8, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

- 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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 8, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

- 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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 8, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

- 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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 8, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

- 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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 8, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

- 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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 8, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

- 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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 8, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

- 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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 8, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

- 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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 8, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

- 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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 8, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

- 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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 8, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 12, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 12, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 13, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 13, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 18, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 23, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 25, 2023
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, I realized I needed to
change the future FFI quite a bit.

The new FFI is simpler overall and supports cancel and drop operations.
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.

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
(mozilla#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.
  - We give the foreign side a raw Box<Arc<dyn RustFutureFFI>>>.  This
    makes it easy to call the FFI from the scaffolding code.  The extra
    Box turns the wide pointer into a normal sized pointer, which
    simplifies the foreign code.
  - The last bit of complexity is completing a Rust future, since the
    future can output any of the FFI types that we return from sync
    functions.  Handled this with a brute-force monomorphization.  The
    scaffolding code defines a completion function for each FFI type and
    the bindings code has to call the correct one for the future.

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.
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 26, 2023
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 of the new API.
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
(mozilla#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 <[email protected]>
Co-authored-by: Jonas Platte <[email protected]>
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 26, 2023
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 of the new API.
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
(mozilla#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 <[email protected]>
Co-authored-by: Jonas Platte <[email protected]>
bendk added a commit to bendk/uniffi-rs that referenced this issue Sep 26, 2023
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
(mozilla#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 <[email protected]>
Co-authored-by: Jonas Platte <[email protected]>
bendk added a commit to bendk/uniffi-rs that referenced this issue Oct 3, 2023
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
(mozilla#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 <[email protected]>
Co-authored-by: Jonas Platte <[email protected]>
@bendk bendk changed the title Foreign DispatchQueue type Foreign BackgroundQueue type Nov 3, 2023
@bendk
Copy link
Contributor Author

bendk commented Nov 3, 2023

I think BackgroundQueue is a nicer name than DispatchQueue, since the feature is really about running tasks in a background thread. I feel like there could be a better name though, open to any suggestions.

@mhammond
Copy link
Member

mhammond commented Nov 3, 2023

"background thread" implies "low priority thread" to me, which I don't think is the actual intent. The actual use-cases here tend to be more about thread blocking than thread priority. I was going to suggest something like TaskQueue, but depending on the content the Task part of that might itself carry baggage. WorkQueue? But I don't think it matters that much though.

@bendk
Copy link
Contributor Author

bendk commented Nov 3, 2023

You're right, it really is about blocking more than anything. tokio separates their threads into "worker threads" and "blocking threads". What about BlockingQueue or BlockingTaskQueue?

@mhammond
Copy link
Member

mhammond commented Nov 3, 2023

using the same terminology as tokio is certainly appealing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants