Skip to content

Commit

Permalink
Remove the oneshot dependency (mozilla#1736)
Browse files Browse the repository at this point in the history
Implemented our own oneshot channel using a Mutex.  It's not quite as
efficient as the `oneshot` one, but I think it should be fine for our
purposes.  My gut feeling is that the loss of overhead is neglibable
compared the other existing overhead that UniFFI adds.

The API of the new oneshot is basically the same, except send/recv are
not failable.
  • Loading branch information
bendk committed May 10, 2024
1 parent 326714c commit 91243c0
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 27 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
FFI layer that can simplify the foreign bindings code. It's currently being tested out for the
gecko-js external binding, but other external bindings may also find it useful.

### What's changed?

- Removed the dependency on the `oneshot' crate (https://github.com/mozilla/uniffi-rs/issues/1736)

[All changes in [[UnreleasedUniFFIVersion]]](https://github.com/mozilla/uniffi-rs/compare/v0.27.1...HEAD).

## v0.27.1 (backend crates: v0.27.1) - (_2024-04-03_)
Expand Down
7 changes: 0 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions uniffi_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ bytes = "1.3"
camino = "1.0.8"
log = "0.4"
once_cell = "1.10.0"
# Use the `oneshot-uniffi` crate to get our `oneshot` dependency.
# That crate is a fork of `oneshot` that removes the `loom` target/dependency, which makes it easier to vendor UniFFI into the mozilla-central repository.
# Enable "async" so that receivers implement Future, no need for "std" since we don't block on them.
oneshot = { package = "oneshot-uniffi", version = "0.1.6", features = ["async"] }
# Regular dependencies
paste = "1.0"
static_assertions = "1.1.0"
Expand Down
20 changes: 5 additions & 15 deletions uniffi_core/src/ffi/foreignfuture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

//! This module defines a Rust Future that wraps an async foreign function call.
//!
//! The general idea is to create a [oneshot::Channel], hand the sender to the foreign side, and
//! The general idea is to create a oneshot channel, hand the sender to the foreign side, and
//! await the receiver side on the Rust side.
//!
//! The foreign side should:
Expand All @@ -17,7 +17,7 @@
//! * Wait for the [ForeignFutureHandle::free] function to be called to free the task object.
//! If this is called before the task completes, then the task will be cancelled.
use crate::{LiftReturn, RustCallStatus, UnexpectedUniFFICallbackError};
use crate::{oneshot, LiftReturn, RustCallStatus};

/// Handle for a foreign future
pub type ForeignFutureHandle = u64;
Expand Down Expand Up @@ -69,26 +69,16 @@ where
// The important thing is that the ForeignFuture will be dropped when this Future is.
let _foreign_future =
call_scaffolding_function(foreign_future_complete::<T, UT>, sender.into_raw() as u64);
match receiver.await {
Ok(result) => T::lift_foreign_return(result.return_value, result.call_status),
Err(e) => {
// This shouldn't happen in practice, but we can do our best to recover
T::handle_callback_unexpected_error(UnexpectedUniFFICallbackError::new(format!(
"Error awaiting foreign future: {e}"
)))
}
}
let result = receiver.await;
T::lift_foreign_return(result.return_value, result.call_status)
}

pub extern "C" fn foreign_future_complete<T: LiftReturn<UT>, UT>(
oneshot_handle: u64,
result: ForeignFutureResult<T::ReturnType>,
) {
let channel = unsafe { oneshot::Sender::from_raw(oneshot_handle as *mut ()) };
// Ignore errors in send.
//
// Error means the receiver was already dropped which will happen when the future is cancelled.
let _ = channel.send(result);
channel.send(result);
}

#[cfg(test)]
Expand Down
2 changes: 1 addition & 1 deletion uniffi_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub mod ffi;
mod ffi_converter_impls;
mod ffi_converter_traits;
pub mod metadata;
mod oneshot;

#[cfg(feature = "scaffolding-ffi-buffer-fns")]
pub use ffi::ffiserialize::FfiBufferElement;
Expand All @@ -60,7 +61,6 @@ pub mod deps {
pub use async_compat;
pub use bytes;
pub use log;
pub use oneshot;
pub use static_assertions;
}

Expand Down
77 changes: 77 additions & 0 deletions uniffi_core/src/oneshot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Implements a simple oneshot channel.
//!
//! We used to use the `oneshot` crate for this, but the dependency was hard to manage
//! (https://github.com/mozilla/uniffi-rs/issues/1736)
//!
//! This implementation is less efficient, but the difference is probably negligible for most
//! use-cases involving UniFFI.
use std::{
future::Future,
pin::Pin,
sync::{Arc, Mutex},
task::{Context, Poll, Waker},
};

pub struct Sender<T>(Arc<Mutex<OneshotInner<T>>>);
pub struct Receiver<T>(Arc<Mutex<OneshotInner<T>>>);

struct OneshotInner<T> {
value: Option<T>,
waker: Option<Waker>,
}

pub fn channel<T>() -> (Sender<T>, Receiver<T>) {
let arc = Arc::new(Mutex::new(OneshotInner {
value: None,
waker: None,
}));
(Sender(arc.clone()), Receiver(arc))
}

impl<T> Sender<T> {
/// Send a value to the receiver
pub fn send(self, value: T) {
let mut inner = self.0.lock().unwrap();
inner.value = Some(value);
if let Some(waker) = inner.waker.take() {
waker.wake();
}
}

/// Convert a Sender into a raw pointer
///
/// from_raw must be called with this pointer or else the sender will leak
pub fn into_raw(self) -> *const () {
Arc::into_raw(self.0) as *const ()
}

/// Convert a raw pointer back to a Sender
///
/// # Safety
///
/// `raw_ptr` must have come from into_raw(). Once a pointer is passed into `from_raw` it must
/// not be used again.
pub unsafe fn from_raw(raw_ptr: *const ()) -> Self {
Self(Arc::from_raw(raw_ptr as *const Mutex<OneshotInner<T>>))
}
}

impl<T> Future for Receiver<T> {
type Output = T;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut inner = self.0.lock().unwrap();
match inner.value.take() {
Some(v) => Poll::Ready(v),
None => {
inner.waker = Some(cx.waker().clone());
Poll::Pending
}
}
}
}

0 comments on commit 91243c0

Please sign in to comment.