diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
commit | d8bbc7858622b6d9c278469aab701ca0b609cddf (patch) | |
tree | eff41dc61d9f714852212739e6b3738b82a2af87 /third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs | |
parent | Releasing progress-linux version 125.0.3-1~progress7.99u1. (diff) | |
download | firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip |
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs | 487 |
1 files changed, 0 insertions, 487 deletions
diff --git a/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs b/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs deleted file mode 100644 index 7b1cb9bd80..0000000000 --- a/third_party/rust/uniffi_core/src/ffi/foreignexecutor.rs +++ /dev/null @@ -1,487 +0,0 @@ -/* 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/. */ - -//! Schedule tasks using a foreign executor. - -use std::panic; - -use crate::{ForeignExecutorCallback, ForeignExecutorCallbackCell}; - -/// Opaque handle for a foreign task executor. -/// -/// Foreign code can either use an actual pointer, or use an integer value casted to it. -#[repr(transparent)] -#[derive(Clone, Copy, Debug)] -pub struct ForeignExecutorHandle(pub(crate) *const ()); - -// Implement Send + Sync for `ForeignExecutor`. The foreign bindings code is responsible for -// making the `ForeignExecutorCallback` thread-safe. -unsafe impl Send for ForeignExecutorHandle {} - -unsafe impl Sync for ForeignExecutorHandle {} - -/// Result code returned by `ForeignExecutorCallback` -#[repr(i8)] -#[derive(Debug, PartialEq, Eq)] -pub enum ForeignExecutorCallbackResult { - /// Callback was scheduled successfully - Success = 0, - /// Callback couldn't be scheduled because the foreign executor is canceled/closed. - Cancelled = 1, - /// Callback couldn't be scheduled because of some other error - Error = 2, -} - -impl ForeignExecutorCallbackResult { - /// Check the result code for the foreign executor callback - /// - /// If the result was `ForeignExecutorCallbackResult.Success`, this method returns `true`. - /// - /// If not, this method returns `false`, logging errors for any unexpected return values - pub fn check_result_code(result: i8) -> bool { - match result { - n if n == ForeignExecutorCallbackResult::Success as i8 => true, - n if n == ForeignExecutorCallbackResult::Cancelled as i8 => false, - n if n == ForeignExecutorCallbackResult::Error as i8 => { - log::error!( - "ForeignExecutorCallbackResult::Error returned by foreign executor callback" - ); - false - } - n => { - log::error!("Unknown code ({n}) returned by foreign executor callback"); - false - } - } - } -} - -// Option<RustTaskCallback> should use the null pointer optimization and be represented in C as a -// regular pointer. Let's check that. -static_assertions::assert_eq_size!(usize, Option<RustTaskCallback>); - -/// Callback for a Rust task, this is what the foreign executor invokes -/// -/// The task will be passed the `task_data` passed to `ForeignExecutorCallback` in addition to one -/// of the `RustTaskCallbackCode` values. -pub type RustTaskCallback = extern "C" fn(*const (), RustTaskCallbackCode); - -/// Passed to a `RustTaskCallback` function when the executor invokes them. -/// -/// Every `RustTaskCallback` will be invoked eventually, this code is used to distinguish the times -/// when it's invoked successfully vs times when the callback is being called because the foreign -/// executor has been cancelled / shutdown -#[repr(i8)] -#[derive(Debug, PartialEq, Eq)] -pub enum RustTaskCallbackCode { - /// Successful task callback invocation - Success = 0, - /// The `ForeignExecutor` has been cancelled. - /// - /// This signals that any progress using the executor should be halted. In particular, Futures - /// should not continue to progress. - Cancelled = 1, -} - -static FOREIGN_EXECUTOR_CALLBACK: ForeignExecutorCallbackCell = ForeignExecutorCallbackCell::new(); - -/// Set the global ForeignExecutorCallback. This is called by the foreign bindings, normally -/// during initialization. -pub fn foreign_executor_callback_set(callback: ForeignExecutorCallback) { - FOREIGN_EXECUTOR_CALLBACK.set(callback); -} - -/// Schedule Rust calls using a foreign executor -#[derive(Debug)] -pub struct ForeignExecutor { - pub(crate) handle: ForeignExecutorHandle, -} - -impl ForeignExecutor { - pub fn new(executor: ForeignExecutorHandle) -> Self { - Self { handle: executor } - } - - /// Schedule a closure to be run. - /// - /// This method can be used for "fire-and-forget" style calls, where the calling code doesn't - /// need to await the result. - /// - /// Closure requirements: - /// - Send: since the closure will likely run on a different thread - /// - 'static: since it runs at an arbitrary time, so all references need to be 'static - /// - panic::UnwindSafe: if the closure panics, it should not corrupt any data - pub fn schedule<F: FnOnce() + Send + 'static + panic::UnwindSafe>(&self, delay: u32, task: F) { - let leaked_ptr: *mut F = Box::leak(Box::new(task)); - if !schedule_raw( - self.handle, - delay, - schedule_callback::<F>, - leaked_ptr as *const (), - ) { - // If schedule_raw() failed, drop the leaked box since `schedule_callback()` has not been - // scheduled to run. - unsafe { - drop(Box::<F>::from_raw(leaked_ptr)); - }; - } - } - - /// Schedule a closure to be run and get a Future for the result - /// - /// Closure requirements: - /// - Send: since the closure will likely run on a different thread - /// - 'static: since it runs at an arbitrary time, so all references need to be 'static - /// - panic::UnwindSafe: if the closure panics, it should not corrupt any data - pub async fn run<F, T>(&self, delay: u32, closure: F) -> T - where - F: FnOnce() -> T + Send + 'static + panic::UnwindSafe, - T: Send + 'static, - { - // Create a oneshot channel to handle the future - let (sender, receiver) = oneshot::channel(); - // We can use `AssertUnwindSafe` here because: - // - The closure is unwind safe - // - `Sender` is not marked unwind safe, maybe this is just an oversight in the oneshot - // library. However, calling `send()` and dropping the Sender should certainly be - // unwind safe. `send()` should probably not panic at all and if it does it shouldn't - // do it in a way that breaks the Receiver. - // - Calling `expect` may result in a panic, but this should should not break either the - // Sender or Receiver. - self.schedule( - delay, - panic::AssertUnwindSafe(move || { - sender.send(closure()).expect("Error sending future result") - }), - ); - receiver.await.expect("Error receiving future result") - } -} - -/// Low-level schedule interface -/// -/// When using this function, take care to ensure that the `ForeignExecutor` that holds the -/// `ForeignExecutorHandle` has not been dropped. -/// -/// Returns true if the callback was successfully scheduled -pub(crate) fn schedule_raw( - handle: ForeignExecutorHandle, - delay: u32, - callback: RustTaskCallback, - data: *const (), -) -> bool { - let result_code = (FOREIGN_EXECUTOR_CALLBACK.get())(handle, delay, Some(callback), data); - ForeignExecutorCallbackResult::check_result_code(result_code) -} - -impl Drop for ForeignExecutor { - fn drop(&mut self) { - (FOREIGN_EXECUTOR_CALLBACK.get())(self.handle, 0, None, std::ptr::null()); - } -} - -extern "C" fn schedule_callback<F>(data: *const (), status_code: RustTaskCallbackCode) -where - F: FnOnce() + Send + 'static + panic::UnwindSafe, -{ - // No matter what, we need to call Box::from_raw() to balance the Box::leak() call. - let task = unsafe { Box::from_raw(data as *mut F) }; - // Skip running the task for the `RustTaskCallbackCode::Cancelled` code - if status_code == RustTaskCallbackCode::Success { - run_task(task); - } -} - -/// Run a scheduled task, catching any panics. -/// -/// If there are panics, then we will log a warning and return None. -fn run_task<F: FnOnce() -> T + panic::UnwindSafe, T>(task: F) -> Option<T> { - match panic::catch_unwind(task) { - Ok(v) => Some(v), - Err(cause) => { - let message = if let Some(s) = cause.downcast_ref::<&'static str>() { - (*s).to_string() - } else if let Some(s) = cause.downcast_ref::<String>() { - s.clone() - } else { - "Unknown panic!".to_string() - }; - log::warn!("Error calling UniFFI callback function: {message}"); - None - } - } -} - -#[cfg(test)] -pub use test::MockEventLoop; - -#[cfg(test)] -mod test { - use super::*; - use std::{ - future::Future, - pin::Pin, - sync::{ - atomic::{AtomicU32, Ordering}, - Arc, Mutex, Once, - }, - task::{Context, Poll, Wake, Waker}, - }; - - /// Simulate an event loop / task queue / coroutine scope on the foreign side - /// - /// This simply collects scheduled calls into a Vec for testing purposes. - /// - /// Most of the MockEventLoop methods are `pub` since it's also used by the `rustfuture` tests. - pub struct MockEventLoop { - // Wrap everything in a mutex since we typically share access to MockEventLoop via an Arc - inner: Mutex<MockEventLoopInner>, - } - - pub struct MockEventLoopInner { - // calls that have been scheduled - calls: Vec<(u32, Option<RustTaskCallback>, *const ())>, - // has the event loop been shutdown? - is_shutdown: bool, - } - - unsafe impl Send for MockEventLoopInner {} - - static FOREIGN_EXECUTOR_CALLBACK_INIT: Once = Once::new(); - - impl MockEventLoop { - pub fn new() -> Arc<Self> { - // Make sure we install a foreign executor callback that can deal with mock event loops - FOREIGN_EXECUTOR_CALLBACK_INIT - .call_once(|| foreign_executor_callback_set(mock_executor_callback)); - - Arc::new(Self { - inner: Mutex::new(MockEventLoopInner { - calls: vec![], - is_shutdown: false, - }), - }) - } - - /// Create a new ForeignExecutorHandle - pub fn new_handle(self: &Arc<Self>) -> ForeignExecutorHandle { - // To keep the memory management simple, we simply leak an arc reference for this. We - // only create a handful of these in the tests so there's no need for proper cleanup. - ForeignExecutorHandle(Arc::into_raw(Arc::clone(self)) as *const ()) - } - - pub fn new_executor(self: &Arc<Self>) -> ForeignExecutor { - ForeignExecutor { - handle: self.new_handle(), - } - } - - /// Get the current number of scheduled calls - pub fn call_count(&self) -> usize { - self.inner.lock().unwrap().calls.len() - } - - /// Get the last scheduled call - pub fn last_call(&self) -> (u32, Option<RustTaskCallback>, *const ()) { - self.inner - .lock() - .unwrap() - .calls - .last() - .cloned() - .expect("no calls scheduled") - } - - /// Run all currently scheduled calls - pub fn run_all_calls(&self) { - let mut inner = self.inner.lock().unwrap(); - let is_shutdown = inner.is_shutdown; - for (_delay, callback, data) in inner.calls.drain(..) { - if !is_shutdown { - callback.unwrap()(data, RustTaskCallbackCode::Success); - } else { - callback.unwrap()(data, RustTaskCallbackCode::Cancelled); - } - } - } - - /// Shutdown the eventloop, causing scheduled calls and future calls to be cancelled - pub fn shutdown(&self) { - self.inner.lock().unwrap().is_shutdown = true; - } - } - - // `ForeignExecutorCallback` that we install for testing - extern "C" fn mock_executor_callback( - handle: ForeignExecutorHandle, - delay: u32, - task: Option<RustTaskCallback>, - task_data: *const (), - ) -> i8 { - let eventloop = handle.0 as *const MockEventLoop; - let mut inner = unsafe { (*eventloop).inner.lock().unwrap() }; - if inner.is_shutdown { - ForeignExecutorCallbackResult::Cancelled as i8 - } else { - inner.calls.push((delay, task, task_data)); - ForeignExecutorCallbackResult::Success as i8 - } - } - - #[test] - fn test_schedule_raw() { - extern "C" fn callback(data: *const (), _status_code: RustTaskCallbackCode) { - unsafe { - *(data as *mut u32) += 1; - } - } - - let eventloop = MockEventLoop::new(); - - let value: u32 = 0; - assert_eq!(eventloop.call_count(), 0); - - schedule_raw( - eventloop.new_handle(), - 0, - callback, - &value as *const u32 as *const (), - ); - assert_eq!(eventloop.call_count(), 1); - assert_eq!(value, 0); - - eventloop.run_all_calls(); - assert_eq!(eventloop.call_count(), 0); - assert_eq!(value, 1); - } - - #[test] - fn test_schedule() { - let eventloop = MockEventLoop::new(); - let executor = eventloop.new_executor(); - let value = Arc::new(AtomicU32::new(0)); - assert_eq!(eventloop.call_count(), 0); - - let value2 = value.clone(); - executor.schedule(0, move || { - value2.fetch_add(1, Ordering::Relaxed); - }); - assert_eq!(eventloop.call_count(), 1); - assert_eq!(value.load(Ordering::Relaxed), 0); - - eventloop.run_all_calls(); - assert_eq!(eventloop.call_count(), 0); - assert_eq!(value.load(Ordering::Relaxed), 1); - } - - #[derive(Default)] - struct MockWaker { - wake_count: AtomicU32, - } - - impl Wake for MockWaker { - fn wake(self: Arc<Self>) { - self.wake_count.fetch_add(1, Ordering::Relaxed); - } - } - - #[test] - fn test_run() { - let eventloop = MockEventLoop::new(); - let executor = eventloop.new_executor(); - let mock_waker = Arc::new(MockWaker::default()); - let waker = Waker::from(mock_waker.clone()); - let mut context = Context::from_waker(&waker); - assert_eq!(eventloop.call_count(), 0); - - let mut future = executor.run(0, move || "test-return-value"); - unsafe { - assert_eq!( - Pin::new_unchecked(&mut future).poll(&mut context), - Poll::Pending - ); - } - assert_eq!(eventloop.call_count(), 1); - assert_eq!(mock_waker.wake_count.load(Ordering::Relaxed), 0); - - eventloop.run_all_calls(); - assert_eq!(eventloop.call_count(), 0); - assert_eq!(mock_waker.wake_count.load(Ordering::Relaxed), 1); - unsafe { - assert_eq!( - Pin::new_unchecked(&mut future).poll(&mut context), - Poll::Ready("test-return-value") - ); - } - } - - #[test] - fn test_drop() { - let eventloop = MockEventLoop::new(); - let executor = eventloop.new_executor(); - - drop(executor); - // Calling drop should schedule a call with null task data. - assert_eq!(eventloop.call_count(), 1); - assert_eq!(eventloop.last_call().1, None); - } - - // Test that cancelled calls never run - #[test] - fn test_cancelled_call() { - let eventloop = MockEventLoop::new(); - let executor = eventloop.new_executor(); - // Create a shared counter - let counter = Arc::new(AtomicU32::new(0)); - // schedule increments using both `schedule()` and run()` - let counter_clone = Arc::clone(&counter); - executor.schedule(0, move || { - counter_clone.fetch_add(1, Ordering::Relaxed); - }); - let counter_clone = Arc::clone(&counter); - let future = executor.run(0, move || { - counter_clone.fetch_add(1, Ordering::Relaxed); - }); - // shutdown the eventloop before the scheduled call gets a chance to run. - eventloop.shutdown(); - // `run_all_calls()` will cause the scheduled task callbacks to run, but will pass - // `RustTaskCallbackCode::Cancelled` to it. This drop the scheduled closure without executing - // it. - eventloop.run_all_calls(); - - assert_eq!(counter.load(Ordering::Relaxed), 0); - drop(future); - } - - // Test that when scheduled calls are cancelled, the closures are dropped properly - #[test] - fn test_cancellation_drops_closures() { - let eventloop = MockEventLoop::new(); - let executor = eventloop.new_executor(); - - // Create an Arc<> that we will move into the closures to test if they are dropped or not - let arc = Arc::new(0); - let arc_clone = Arc::clone(&arc); - executor.schedule(0, move || assert_eq!(*arc_clone, 0)); - let arc_clone = Arc::clone(&arc); - let future = executor.run(0, move || assert_eq!(*arc_clone, 0)); - - // shutdown the eventloop and run the (cancelled) scheduled calls. - eventloop.shutdown(); - eventloop.run_all_calls(); - // try to schedule some more calls now that the loop has been shutdown - let arc_clone = Arc::clone(&arc); - executor.schedule(0, move || assert_eq!(*arc_clone, 0)); - let arc_clone = Arc::clone(&arc); - let future2 = executor.run(0, move || assert_eq!(*arc_clone, 0)); - - // Drop the futures so they don't hold on to any references - drop(future); - drop(future2); - - // All of these closures should have been dropped by now, there only remaining arc - // reference should be the original - assert_eq!(Arc::strong_count(&arc), 1); - } -} |