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

feat: Add support for async/Future #1409

Merged
merged 132 commits into from
Feb 27, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
132 commits
Select commit Hold shift + click to select a range
bf53393
feat: `FnMetadata` and `MethodMetadata` have an `is_async` attribute.
Hywan Nov 3, 2022
4c450ce
feat: `Function` and `Method` have an `is_async` attribute.
Hywan Nov 3, 2022
efb19ff
feat: Welcome `FfiFuture`.
Hywan Nov 3, 2022
1bf1e84
test: Let's introduce the `future` fixture.
Hywan Nov 3, 2022
505c253
feat: Let's experiment with Python and asyncio.
Hywan Nov 7, 2022
47eb221
feat: Implement our own `Future` API in Python, and continue the Rust…
Hywan Nov 10, 2022
f2ad621
feat: Add scaffolding and ComponentInterface and FFI support for Future.
Hywan Nov 10, 2022
9125e52
feat: Define a new `FfiType::FutureWaker` “type”.
Hywan Nov 10, 2022
ffab20d
feat: Passing `RustFuture` over FFI to be polled.
Hywan Nov 11, 2022
88c12d6
feat: Generate the last glue part.
Hywan Nov 11, 2022
85f010e
chore: Rename an FFI function.
Hywan Nov 11, 2022
2354be5
!debug
Hywan Nov 14, 2022
e3e19d2
!debug
Hywan Nov 14, 2022
18f759a
!cleanup Need a real commit message
Hywan Nov 14, 2022
50ba8c7
feat: Simplify `RustFuture` “padding” in Python.
Hywan Nov 14, 2022
34d87c0
feat: Call Python event_loop with `call_soon_threadsafe.
Hywan Nov 16, 2022
e1f654e
feat: `async` function returns a `Box<RustFuture>`.
Hywan Nov 16, 2022
56c7a0b
feat: Drop the Rust Future from Python.
Hywan Nov 16, 2022
14e36ef
chore: Clean up.
Hywan Nov 17, 2022
ba60a91
feat: Update `RustFuture` to be generic over the `Future`'s `Output`.
Hywan Nov 17, 2022
665cac7
!temp
Hywan Nov 21, 2022
763a9c6
feat: Allow `RustFuture::poll` to return the actual result.
Hywan Nov 21, 2022
b22e3f1
chore: Remove a useless unsafe impl of `Send` for `RustBuffer`.
Hywan Nov 22, 2022
26fd256
feat: Lift the result of a reay future in Python.
Hywan Nov 22, 2022
2ad07c9
feaft: Implement an async waker in Python so that Rust has room to dr…
Hywan Nov 22, 2022
c54212d
chore: `FFIType::(Pointer|Future|FutureWaker)` are no longer necessary.
Hywan Nov 22, 2022
147820f
doc: Write missing documentation.
Hywan Nov 22, 2022
497da07
feat: Use `Arc::increment_strong_count` to replace `ManuallyDrop` + `…
Hywan Nov 22, 2022
92c34bc
doc: Write missing documentation.
Hywan Nov 22, 2022
136af12
feat: The `Future` output does not need to be `Send`
Hywan Nov 23, 2022
3d5c49f
fix: `int` cannot cast a `ctypes.c_byte`, so lets just rely on dynami…
Hywan Nov 23, 2022
b19d621
chore: Remove useless newlines.
Hywan Nov 23, 2022
8c4be6e
test: Adding more async functions!
Hywan Nov 23, 2022
0111cf3
test: Add tests for Python.
Hywan Nov 23, 2022
42163ef
doc: Add examples.
Hywan Nov 23, 2022
e9933d4
test: Add a test for a future that is already ready.
Hywan Nov 23, 2022
0274a58
chore: Rename the examples directory for Python.
Hywan Nov 23, 2022
26f4850
chore: Remove an artifact.
Hywan Nov 23, 2022
4aab503
feat: Make `RustFuture.poll` private.
Hywan Nov 23, 2022
8e56077
feat: Simplify code a little bit.
Hywan Nov 23, 2022
a611ed4
feat: `RustFuture` on Python size is an opaque type.
Hywan Nov 23, 2022
630bc32
feat: Add `async` support for object methods.
Hywan Nov 23, 2022
270e923
test: Test `async` methods.
Hywan Nov 23, 2022
7d7469a
chore: Simplify code by removing a closure.
Hywan Nov 24, 2022
0415db7
feat: The `waker` can receive an environment.
Hywan Nov 24, 2022
e190fdf
feat: Add `async` API in `.h` for Swift.
Hywan Nov 24, 2022
8ddd0f1
!debug
Hywan Nov 30, 2022
3da52d0
feat: First valid Swift implementation that seems to work \o/
Hywan Dec 5, 2022
6b37cf8
feat: Add `async` support object methods for Swift.
Hywan Dec 7, 2022
214f766
chore: Ignore build artifact.
Hywan Dec 7, 2022
c7079c8
chore: Format code.
Hywan Dec 7, 2022
86c83f1
doc: Write proper example for Swift and async.
Hywan Dec 7, 2022
ae85b55
test: Test `async` in Swift.
Hywan Dec 7, 2022
888359b
fix: Add missing nullability type specifier for `…_poll` functions.
Hywan Dec 7, 2022
bf97f58
fix: FFI functions for async code returns an `Option<Box<RustFuture<T…
Hywan Dec 7, 2022
33a6ef3
doc: Improve documentation.
Hywan Dec 7, 2022
2e80dff
doc: Improve documentation of `RustFuture` and other types.
Hywan Dec 12, 2022
96ad445
test: Make the code safer and test it!
Hywan Dec 12, 2022
73798c9
doc: Fix a typo.
Hywan Dec 12, 2022
dad3950
doc: Clarifies some part of the doc.
Hywan Dec 12, 2022
d983837
doc: Clarifies some part of the doc.
Hywan Dec 12, 2022
7ef5ff0
doc: Add comments.
Hywan Dec 12, 2022
35765ab
feat: Simplify the code a little bit.
Hywan Dec 12, 2022
a71df05
feat: First (incomplete) support for async in Kotlin.
Hywan Dec 15, 2022
e7b8c3a
feat: Complete support for async (func only) in Kotlin.
Hywan Dec 16, 2022
c6175ab
doc: Add a better demo.
Hywan Dec 16, 2022
df12c3b
chore: Add missing EOL newline.
Hywan Dec 16, 2022
3d7e723
doc: Mimic the other foreign languages demo.
Hywan Dec 19, 2022
67d7875
feat: Use the current coroutine scope instead of `GlobalScope` to lau…
Hywan Dec 19, 2022
67361b1
chore: Address feedbacks.
Hywan Dec 19, 2022
1eeb899
feat: `RustFutureWakerEnvironment` in Kotlin is now a class.
Hywan Dec 19, 2022
0b44d1f
feat: Support async methods in Kotlin.
Hywan Dec 19, 2022
48ed0bd
test: Install `kotlinx-coroutines-core-jvm.jar`.
Hywan Dec 19, 2022
c5ccd97
test: Add tests for async in Kotlin.
Hywan Dec 21, 2022
9f3d4a7
chore: Simplify an example.
Hywan Dec 21, 2022
c8c20e0
Merge branch 'main' into feat-async-with-fallible
Hywan Dec 21, 2022
212a34d
feat: `RustFuture` can return `()`.
Hywan Dec 21, 2022
39c33b7
feat: `RustFuture` which returns `()` works in Python.
Hywan Dec 21, 2022
8cb8c81
feat: `RustFuture` which returns `()` works in Python.
Hywan Dec 21, 2022
b8b0985
feat: Async function returning `()` works in Swift.
Hywan Dec 21, 2022
b5942dd
test: Async function can return `()` in Swift.
Hywan Dec 21, 2022
248b473
feat: Async method returning `()` works in Swift.
Hywan Dec 22, 2022
d966c84
feat: Async functions returning `()` work in Kotlin.
Hywan Dec 22, 2022
c2a285e
feat: Async methods returning `()` work in Kotlin.
Hywan Dec 22, 2022
215307f
chore: Clean up.
Hywan Dec 22, 2022
cf69bd2
proc-macro: Generalize parsing helpers
jplatte Dec 22, 2022
a163678
proc-macro: Add support for running async fn's in tokio's runtime
jplatte Dec 22, 2022
170c8fe
proc-macro: Stop wrapping &mut Pin<_> in another layer of Pin
jplatte Dec 22, 2022
f982269
doc: Fix intra-link.
Hywan Dec 22, 2022
3f7dd89
feat: Remove `mem::transmute` in `RustFuture`s waker.
Hywan Dec 22, 2022
1cb13f2
Merge pull request #1 from matrix-org/jplatte/async-tokio
Hywan Dec 22, 2022
cafbad4
doc: Fix a comment.
Hywan Dec 22, 2022
082bd68
proc-macro: Silence UnwindSafe error without Mutex
jplatte Dec 22, 2022
f791a89
Merge pull request #2 from matrix-org/jplatte/assert-unwind-safe
Hywan Dec 22, 2022
8e33c07
test: Test an async function that uses Tokio as the async runtime.
Hywan Jan 9, 2023
60fabef
feat: `RustFuture` now always holds a `Result<T, E>` to support to th…
Hywan Jan 11, 2023
ae68a98
feat: Swift support async _`throws`_ function!
Hywan Jan 12, 2023
bc3db63
chore: Oops.
Hywan Jan 12, 2023
2f4308e
feat: Swift supports async _`throws`_ methods!
Hywan Jan 12, 2023
7dfc984
feat: Kotlin supports async _`throws`_ functions and methods!
Hywan Jan 12, 2023
1525f5f
feat: Python supports async _`throws`_ functions and methods!
Hywan Jan 12, 2023
d9a71e4
Merge branch 'main' into feat-async
Hywan Jan 16, 2023
4e07f23
chore: Finalize the merge with `main`.
Hywan Jan 16, 2023
59c13db
chore: Finalize the merge (bis).
Hywan Jan 16, 2023
5c89db3
test: New test case for an async function returning a record.
Hywan Jan 16, 2023
0b15785
doc: Update documentation.
Hywan Jan 16, 2023
1602eef
chore: Add missing EOL newline.
Hywan Jan 16, 2023
71f8760
chore: Add missing EOL newline.
Hywan Jan 16, 2023
1963aca
feat: Resolve the async waker-re-entering problem in Kotlin.
Hywan Jan 19, 2023
50759a0
fix: Use a `Semaphore` to avoid `wake` re-entry.
Hywan Jan 23, 2023
0ab0345
doc: Improve the `README.md` of `fixtures/futures/`.
Hywan Jan 23, 2023
497e335
test: Changes `secs` to `ms` in futures tests.
Hywan Jan 23, 2023
e23ae1c
test: Changes `secs` to `ms` in futures tests.
Hywan Jan 23, 2023
ef5cd68
chore: Clean up empty lines.
Hywan Jan 23, 2023
44118aa
chore: Create the `async_func` and `async_meth` macros in Kotlin.
Hywan Jan 23, 2023
8c9327d
doc: Improve documentation of `RustFuture`.
Hywan Jan 23, 2023
0837d3a
feat: Add generic `FfiDefault` implementation for `Option<T>`.
Hywan Jan 23, 2023
fafbfd8
feat: Remove the `RustFutureForeignWakerEnvironment` type alias.
Hywan Jan 23, 2023
57f8fa0
Merge branch 'main' into feat-async
Hywan Feb 13, 2023
802aeac
chore(fixtures): Use our own bindgen bin.
Hywan Feb 13, 2023
e42febf
doc(uniffi): Use the correct function name.
Hywan Feb 13, 2023
926135a
doc(uniffi-bindgen): Improve a comment.
Hywan Feb 13, 2023
828a6bd
fix: Fix a merge issue.
Hywan Feb 13, 2023
2ab5c10
test: Diable Kotlin future test suite for now.
Hywan Feb 13, 2023
a12768e
Revert "test: Diable Kotlin future test suite for now."
Hywan Feb 14, 2023
0d4e3a6
Only import async-related functionality if there are async founctions
bendk Feb 16, 2023
1701412
fix(docker) Remove a double `-o` in a `curl` command.
Hywan Feb 20, 2023
8d30f0b
test: Disable Kotlin tests for `fixtures/futures/`.
Hywan Feb 20, 2023
53af6b3
chore: Fix code formatting.
Hywan Feb 20, 2023
4d6cd03
test: Disable Swift tests for `fixtures/futures/.
Hywan Feb 20, 2023
cca6274
test: Update Swift to 5.5.
Hywan Feb 20, 2023
1ad12f0
Merge branch 'main' into feat-async
Hywan Feb 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: Implement our own Future API in Python, and continue the Rust…
… side.
  • Loading branch information
Hywan committed Nov 10, 2022
commit 47eb22154ce57e02492dc70588bdcf6aa4ba9d81
50 changes: 50 additions & 0 deletions uniffi/src/ffi/ffifuture.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use super::FfiDefault;
use std::{
future::{self, Future},
mem::{self, ManuallyDrop},
pin::Pin,
sync::Arc,
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
};

#[repr(transparent)]
Expand All @@ -19,6 +22,16 @@ where
{
Self(Box::pin(future))
}

pub fn poll(&mut self, waker_pointer: usize) -> Option<T> {
let waker: Waker = { unimplemented!() };
let context: Context = Context::from_waker(&waker);

match Pin::new(&mut self.0).poll(&mut context) {
Poll::Ready(result) => Some(result),
Poll::Pending => None,
}
}
}

impl<T> FfiDefault for FfiFuture<T>
Expand All @@ -29,3 +42,40 @@ where
Self::new(future::ready(T::ffi_default()))
}
}

struct FfiTaskWakerBuilder<F>(F)
where
F: Fn() + Send + Sync + 'static;

impl<F> FfiTaskWakerBuilder<F>
where
F: Fn() + Send + Sync + 'static,
{
const VTABLE: RawWakerVTable = RawWakerVTable::new(
Self::clone_waker,
Self::wake,
Self::wake_by_ref,
Self::drop_waker,
);

unsafe fn clone_waker(ptr: *const ()) -> RawWaker {
let arc = ManuallyDrop::new(Arc::from_raw(ptr as *const F));
mem::forget(arc.clone());

RawWaker::new(ptr, &Self::VTABLE)
}

unsafe fn wake(ptr: *const ()) {
let arc = Arc::from_raw(ptr as *const F);
(arc)();
}

unsafe fn wake_by_ref(ptr: *const ()) {
let arc = ManuallyDrop::new(Arc::from_raw(ptr as *const F));
(arc)();
}

unsafe fn drop_waker(ptr: *const ()) {
drop(Arc::from_raw(ptr as *const F));
}
}
93 changes: 93 additions & 0 deletions uniffi_bindgen/src/bindings/python/templates/FutureTemplate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
class FfiFuture(ctypes.Structure):
_fields_ = [
("waker", ctypes.c_uint32),
]

class FuturePoll(enum.Enum):
PENDING = 0
DONE = 1

class Future:
def __init__(self, future: any):
self._asyncio_future_blocking = False
self._loop = asyncio.get_event_loop()
self._state = FuturePoll.PENDING
self._result = None
self._future = future
self._waker = None
self._callbacks = []

def poll():
state, self._result = (self._future)()

if state == FuturePoll.DONE:
self.set_result(self._result)
self._state = state

self._waker = poll

# Poll it once.
(self._future_waker())()

def _future_waker(self) -> any:
return self._waker

def done(self) -> bool:
return self._state == FuturePoll.DONE

def result(self) -> any:
if self._state != FuturePoll.DONE:
raise RuntimeError('Result is not ready')

return self._result

def set_result(self, result: any):
if self._state != FuturePoll.PENDING:
raise RuntimeError('This future has already been resolved')

self._result = result
self._state = FuturePoll.DONE
self.__schedule_callbacks()

def __schedule_callbacks(self):
callbacks = self._callbacks[:]

if not callbacks:
return

self._callbacks[:] = []

for callback, context in callbacks:
self._loop.call_soon(callback, self, context=context)

def add_done_callback(self, fn, *, context=None):
if self._state != FuturePoll.PENDING:
self._loop.call_soon(fn, self, context=context)
else:
if context is None:
context = contextvars.copy_context()

self._callbacks.append((fn, context))

def remove_done_callback(self, fn):
filtered_callbacks = [(callback, context)
for (callback, context) in self._callbacks
if callback != fn]
removed_count = len(self._callbacks) - len(filtered_callbacks)

if removed_count:
self._callbacks[:] = filtered_callbacks

return removed_count

def __await__(self):
if not self.done():
self._asyncio_future_blocking = True
yield self

if not self.done():
raise RuntimeError('await wasn\'t used with future')

return self.result()

__iter__ = __await__
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,20 @@ def loadIndirect():

_UniFFILib = loadIndirect()
{%- for func in ci.iter_ffi_function_definitions() %}
{%- if func.is_async() %}

# ASYNC
_UniFFILib.{{ func.name() }}.argtypes = (
{%- call py::arg_list_ffi_decl(func) -%}
)
_UniFFILib.{{ func.name() }}.restype = {% match func.return_type() %}{% when Some with (type_) %}FfiFuture{% when None %}None{% endmatch %}

{%- else %}

_UniFFILib.{{ func.name() }}.argtypes = (
{%- call py::arg_list_ffi_decl(func) -%}
)
_UniFFILib.{{ func.name() }}.restype = {% match func.return_type() %}{% when Some with (type_) %}{{ type_|ffi_type_name }}{% when None %}None{% endmatch %}

{%- endif %}
{%- endfor %}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@

async def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}):
{%- call py::setup_args(func) %}
return {{ return_type|lift_fn }}({% call py::to_ffi_call(func) %})
def inner() -> (FuturePoll, any):
# return {{ return_type|lift_fn }}({% call py::to_ffi_call(func) %})
pass

future = Future(inner)
# waker = future._future_waker()

await future

{%- else %}

Expand Down
2 changes: 2 additions & 0 deletions uniffi_bindgen/src/bindings/python/templates/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import contextlib
import datetime
import asyncio
import enum
{%- for module_name in self.imports() %}
import {{ module_name }}
{%- endfor %}
Expand All @@ -35,6 +36,7 @@
{% include "RustBufferTemplate.py" %}
{% include "Helpers.py" %}
{% include "RustBufferHelper.py" %}
{% include "FutureTemplate.py" %}

# Contains loading, initialization code,
# and the FFI Function declarations in a com.sun.jna.Library.
Expand Down
7 changes: 7 additions & 0 deletions uniffi_bindgen/src/interface/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub enum FFIType {
#[derive(Debug, Default, Clone)]
pub struct FFIFunction {
pub(super) name: String,
pub(super) is_async: bool,
pub(super) arguments: Vec<FFIArgument>,
pub(super) return_type: Option<FFIType>,
}
Expand All @@ -67,9 +68,15 @@ impl FFIFunction {
pub fn name(&self) -> &str {
&self.name
}

pub fn is_async(&self) -> bool {
self.is_async
}

pub fn arguments(&self) -> Vec<&FFIArgument> {
self.arguments.iter().collect()
}

pub fn return_type(&self) -> Option<&FFIType> {
self.return_type.as_ref()
}
Expand Down
1 change: 1 addition & 0 deletions uniffi_bindgen/src/interface/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ impl From<uniffi_meta::FnMetadata> for Function {

let ffi_func = FFIFunction {
name: ffi_name,
is_async,
..FFIFunction::default()
};

Expand Down
5 changes: 4 additions & 1 deletion uniffi_bindgen/src/interface/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ use uniffi_meta::{MethodMetadata, ObjectMetadata};

/// The main public interface for this module, representing the complete details of an interface exposed
/// by a rust component and the details of consuming it via an extern-C FFI layer.
///
#[derive(Debug, Default)]
pub struct ComponentInterface {
/// Every ComponentInterface gets tagged with the version of uniffi used to create it.
Expand Down Expand Up @@ -356,6 +355,7 @@ impl ComponentInterface {
pub fn ffi_rustbuffer_alloc(&self) -> FFIFunction {
FFIFunction {
name: format!("ffi_{}_rustbuffer_alloc", self.ffi_namespace()),
is_async: false,
arguments: vec![FFIArgument {
name: "size".to_string(),
type_: FFIType::Int32,
Expand All @@ -370,6 +370,7 @@ impl ComponentInterface {
pub fn ffi_rustbuffer_from_bytes(&self) -> FFIFunction {
FFIFunction {
name: format!("ffi_{}_rustbuffer_from_bytes", self.ffi_namespace()),
is_async: false,
arguments: vec![FFIArgument {
name: "bytes".to_string(),
type_: FFIType::ForeignBytes,
Expand All @@ -384,6 +385,7 @@ impl ComponentInterface {
pub fn ffi_rustbuffer_free(&self) -> FFIFunction {
FFIFunction {
name: format!("ffi_{}_rustbuffer_free", self.ffi_namespace()),
is_async: false,
arguments: vec![FFIArgument {
name: "buf".to_string(),
type_: FFIType::RustBuffer,
Expand All @@ -398,6 +400,7 @@ impl ComponentInterface {
pub fn ffi_rustbuffer_reserve(&self) -> FFIFunction {
FFIFunction {
name: format!("ffi_{}_rustbuffer_reserve", self.ffi_namespace()),
is_async: false,
arguments: vec![
FFIArgument {
name: "buf".to_string(),
Expand Down