diff --git a/CMakeLists.txt b/CMakeLists.txt index ee2befa2d..12cc20e4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,7 +118,7 @@ set(FISH_SRCS src/exec.cpp src/expand.cpp src/fallback.cpp src/fish_indent_common.cpp src/fish_version.cpp src/flog.cpp src/function.cpp src/highlight.cpp src/history.cpp src/history_file.cpp src/input.cpp src/input_common.cpp - src/io.cpp src/iothread.cpp src/kill.cpp + src/io.cpp src/kill.cpp src/null_terminated_array.cpp src/operation_context.cpp src/output.cpp src/pager.cpp src/parse_execution.cpp src/parse_util.cpp src/parser.cpp src/parser_keywords.cpp src/path.cpp src/postfork.cpp diff --git a/fish-rust/src/ffi.rs b/fish-rust/src/ffi.rs index 25f0b3583..afc65e682 100644 --- a/fish-rust/src/ffi.rs +++ b/fish-rust/src/ffi.rs @@ -338,3 +338,21 @@ fn from(value: *const autocxx::c_void) -> Self { Self(value as *const _) } } + +impl core::convert::From for *const u8 { + fn from(value: void_ptr) -> Self { + value.0 as *const _ + } +} + +impl core::convert::From for *const core::ffi::c_void { + fn from(value: void_ptr) -> Self { + value.0 as *const _ + } +} + +impl core::convert::From for *const autocxx::c_void { + fn from(value: void_ptr) -> Self { + value.0 as *const _ + } +} diff --git a/fish-rust/src/threads.rs b/fish-rust/src/threads.rs index 7fa0fed43..be6f45f31 100644 --- a/fish-rust/src/threads.rs +++ b/fish-rust/src/threads.rs @@ -51,6 +51,15 @@ impl FloggableDebug for ThreadId {} #[cxx::bridge] mod ffi { + unsafe extern "C++" { + include!("callback.h"); + + #[rust_name = "CppCallback"] + type callback_t; + fn invoke(&self) -> *const u8; + fn invoke_with_param(&self, param: *const u8) -> *const u8; + } + extern "Rust" { #[cxx_name = "ASSERT_IS_MAIN_THREAD"] fn assert_is_main_thread(); @@ -65,7 +74,7 @@ mod ffi { extern "Rust" { #[cxx_name = "make_detached_pthread"] - fn spawn_ffi(callback: *const u8, param: *const u8) -> bool; + fn spawn_ffi(callback: &SharedPtr) -> bool; } extern "Rust" { @@ -76,9 +85,9 @@ mod ffi { #[cxx_name = "iothread_drain_all"] fn iothread_drain_all_ffi(); #[cxx_name = "iothread_perform"] - fn iothread_perform_ffi(callback: *const u8, param: *const u8); + fn iothread_perform_ffi(callback: &SharedPtr); #[cxx_name = "iothread_perform_cantwait"] - fn iothread_perform_cant_wait_ffi(callback: *const u8, param: *const u8); + fn iothread_perform_cant_wait_ffi(callback: &SharedPtr); } extern "Rust" { @@ -86,14 +95,12 @@ mod ffi { type Debounce; #[cxx_name = "perform"] - fn perform_ffi(&self, callback: *const u8, param: *const u8) -> u64; + fn perform_ffi(&self, callback: &SharedPtr) -> u64; #[cxx_name = "perform_with_completion"] fn perform_with_completion_ffi( &self, - callback: *const u8, - param1: *const u8, - completion: *const u8, - param2: *const u8, + callback: &SharedPtr, + completion: &SharedPtr, ) -> u64; #[cxx_name = "new_debounce_t"] @@ -101,6 +108,9 @@ fn perform_with_completion_ffi( } } +unsafe impl Send for ffi::CppCallback {} +unsafe impl Sync for ffi::CppCallback {} + fn iothread_service_main_with_timeout_ffi(timeout_usec: u64) { iothread_service_main_with_timeout(Duration::from_micros(timeout_usec)) } @@ -109,23 +119,19 @@ fn iothread_drain_all_ffi() { unsafe { iothread_drain_all() } } -fn iothread_perform_ffi(callback: *const u8, param: *const u8) { - type Callback = extern "C" fn(crate::ffi::void_ptr); - let callback: Callback = unsafe { std::mem::transmute(callback) }; - let param = param.into(); +fn iothread_perform_ffi(callback: &cxx::SharedPtr) { + let callback = callback.clone(); iothread_perform(move || { - callback(param); + callback.invoke(); }); } -fn iothread_perform_cant_wait_ffi(callback: *const u8, param: *const u8) { - type Callback = extern "C" fn(crate::ffi::void_ptr); - let callback: Callback = unsafe { std::mem::transmute(callback) }; - let param = param.into(); +fn iothread_perform_cant_wait_ffi(callback: &cxx::SharedPtr) { + let callback = callback.clone(); iothread_perform_cant_wait(move || { - callback(param); + callback.invoke(); }); } @@ -286,13 +292,10 @@ pub fn spawn(callback: F) -> bool { result } -fn spawn_ffi(callback: *const u8, param: *const u8) -> bool { - type Callback = extern "C" fn(crate::ffi::void_ptr); - let callback: Callback = unsafe { std::mem::transmute(callback) }; - let param = param.into(); - +fn spawn_ffi(callback: &cxx::SharedPtr) -> bool { + let callback = callback.clone(); spawn(move || { - callback(param); + callback.invoke(); }) } @@ -645,38 +648,29 @@ pub fn perform(&self, handler: impl FnOnce() + 'static + Send) -> NonZeroU64 { self.perform_inner(h) } - fn perform_with_completion_ffi( - &self, - callback: *const u8, - param1: *const u8, - completion_callback: *const u8, - param2: *const u8, - ) -> u64 { - type Callback = extern "C" fn(crate::ffi::void_ptr) -> crate::ffi::void_ptr; - type CompletionCallback = extern "C" fn(crate::ffi::void_ptr, crate::ffi::void_ptr); + fn perform_ffi(&self, callback: &cxx::SharedPtr) -> u64 { + let callback = callback.clone(); - let callback: Callback = unsafe { std::mem::transmute(callback) }; - let param1 = param1.into(); - let completion_callback: CompletionCallback = - unsafe { std::mem::transmute(completion_callback) }; - let param2 = param2.into(); - - self.perform_with_completion( - move || callback(param1), - move |result| completion_callback(param2, result), - ) + self.perform(move || { + callback.invoke(); + }) .into() } - fn perform_ffi(&self, callback: *const u8, param: *const u8) -> u64 { - type Callback = extern "C" fn(crate::ffi::void_ptr); + fn perform_with_completion_ffi( + &self, + callback: &cxx::SharedPtr, + completion: &cxx::SharedPtr, + ) -> u64 { + let callback = callback.clone(); + let completion = completion.clone(); - let callback: Callback = unsafe { std::mem::transmute(callback) }; - let param = param.into(); - - self.perform(move || { - callback(param); - }) + self.perform_with_completion( + move || -> crate::ffi::void_ptr { callback.invoke().into() }, + move |result| { + completion.invoke_with_param(result.into()); + }, + ) .into() } diff --git a/src/callback.h b/src/callback.h new file mode 100644 index 000000000..6201bea7b --- /dev/null +++ b/src/callback.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include +#include + +/// A RAII callback container that can be used when the rust code needs to (or might need to) free +/// up the resources allocated for a callback (either the type-erased std::function wrapping the +/// lambda itself or the parameter to it.) +struct callback_t { + std::function callback; + std::vector> cleanups; + + /// The default no-op constructor for the callback_t type. + callback_t() { + this->callback = [=](const void *) { return (void *)nullptr; }; + } + + /// Creates a new callback_t instance wrapping the specified type-erased std::function with an + /// optional parameter (defaulting to nullptr). + callback_t(std::function &&callback) { + this->callback = std::move(callback); + } + + /// Executes the wrapped callback with the parameter stored at the time of creation and returns + /// the type-erased (void *) result, but cast to a `const uint8_t *` to please cxx::bridge. + const uint8_t *invoke() const { + const void *result = callback(nullptr); + return (const uint8_t *)result; + } + + /// Executes the wrapped callback with the provided parameter and returns the type-erased + /// (void *) result, but cast to a `const uint8_t *` to please cxx::bridge. + const uint8_t *invoke_with_param(const uint8_t *param) const { + const void *result = callback((const void *)param); + return (const uint8_t *)result; + } + + ~callback_t() { + if (cleanups.size() > 0) { + for (const std::function &dtor : cleanups) { + (dtor)(); + } + cleanups.clear(); + } + } +}; diff --git a/src/iothread.cpp b/src/iothread.cpp deleted file mode 100644 index 0d12dd700..000000000 --- a/src/iothread.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "config.h" // IWYU pragma: keep - -#include "iothread.h" - -extern "C" const void *iothread_trampoline(const void *c) { - iothread_callback_t *callback = (iothread_callback_t *)c; - auto *result = (callback->callback)(callback->param); - delete callback; - return result; -} - -extern "C" const void *iothread_trampoline2(const void *c, const void *p) { - iothread_callback_t *callback = (iothread_callback_t *)c; - auto *result = (callback->callback)(p); - delete callback; - return result; -} diff --git a/src/iothread.h b/src/iothread.h index 2755db112..18dd80c4f 100644 --- a/src/iothread.h +++ b/src/iothread.h @@ -1,123 +1,84 @@ -// Handles IO that may hang. -#ifndef FISH_IOTHREAD_H -#define FISH_IOTHREAD_H -#if INCLUDE_RUST_HEADERS +#pragma once -#include -#include -#include -#include +#include +#include "callback.h" #include "threads.rs.h" -struct iothread_callback_t { - std::function callback; - void *param; - - ~iothread_callback_t() { - if (param) { - free(param); - param = nullptr; - } - } -}; - -extern "C" const void *iothread_trampoline(const void *callback); -extern "C" const void *iothread_trampoline2(const void *callback, const void *param); - // iothread_perform invokes a handler on a background thread. inline void iothread_perform(std::function &&func) { - auto callback = new iothread_callback_t{std::bind([=] { - func(); - return nullptr; - }), - nullptr}; + std::shared_ptr callback = std::make_shared([=](const void *) { + func(); + return nullptr; + }); - iothread_perform((const uint8_t *)&iothread_trampoline, (const uint8_t *)callback); + iothread_perform(callback); } /// Variant of iothread_perform that disrespects the thread limit. /// It does its best to spawn a new thread if all other threads are occupied. /// This is for cases where deferring a new thread might lead to deadlock. inline void iothread_perform_cantwait(std::function &&func) { - auto callback = new iothread_callback_t{std::bind([=] { - func(); - return nullptr; - }), - nullptr}; + std::shared_ptr callback = std::make_shared([=](const void *) { + func(); + return nullptr; + }); - iothread_perform_cantwait((const uint8_t *)&iothread_trampoline, (const uint8_t *)callback); + iothread_perform_cantwait(callback); } inline uint64_t debounce_perform(const debounce_t &debouncer, const std::function &func) { - auto callback = new iothread_callback_t{std::bind([=] { - func(); - return nullptr; - }), - nullptr}; + std::shared_ptr callback = std::make_shared([=](const void *) { + func(); + return nullptr; + }); - return debouncer.perform((const uint8_t *)&iothread_trampoline, (const uint8_t *)callback); + return debouncer.perform(callback); } template inline void debounce_perform_with_completion(const debounce_t &debouncer, std::function &&func, std::function &&completion) { - auto callback1 = new iothread_callback_t{[=](const void *) { - auto *result = new R(func()); - return (void *)result; - }, - nullptr}; + std::shared_ptr callback2 = std::make_shared([=](const void *r) { + assert(r != nullptr && "callback1 result was null!"); + const R *result = (const R *)r; + completion(*result); + return nullptr; + }); - auto callback2 = new iothread_callback_t{ - ([=](const void *r) { - const R *result = (const R *)r; - completion(*result); - delete result; - return nullptr; - }), - nullptr, - }; + std::shared_ptr callback1 = std::make_shared([=](const void *) { + const R *result = new R(func()); + callback2->cleanups.push_back([result]() { delete result; }); + return (void *)result; + }); - debouncer.perform_with_completion( - (const uint8_t *)&iothread_trampoline, (const uint8_t *)callback1, - (const uint8_t *)&iothread_trampoline2, (const uint8_t *)callback2); + debouncer.perform_with_completion(callback1, callback2); } template inline void debounce_perform_with_completion(const debounce_t &debouncer, std::function &&func, std::function &&completion) { - auto callback1 = new iothread_callback_t{[=](const void *) { - auto *result = new R(func()); - return (void *)result; - }, - nullptr}; + std::shared_ptr callback2 = std::make_shared([=](const void *r) { + assert(r != nullptr && "callback1 result was null!"); + const R *result = (const R *)r; + completion(*result); + return nullptr; + }); - auto callback2 = new iothread_callback_t{ - ([=](const void *r) { - const R *result = (const R *)r; - completion(*result); - delete result; - return nullptr; - }), - nullptr, - }; + std::shared_ptr callback1 = std::make_shared([=](const void *) { + const R *result = new R(func()); + callback2->cleanups.push_back([result]() { delete result; }); + return (void *)result; + }); - debouncer.perform_with_completion( - (const uint8_t *)&iothread_trampoline, (const uint8_t *)callback1, - (const uint8_t *)&iothread_trampoline2, (const uint8_t *)callback2); + debouncer.perform_with_completion(callback1, callback2); } inline bool make_detached_pthread(const std::function &func) { - auto callback = new iothread_callback_t{ - [=](const void *) { - func(); - return nullptr; - }, - nullptr, - }; + std::shared_ptr callback = std::make_shared([=](const void *) { + func(); + return nullptr; + }); - return make_detached_pthread((const uint8_t *)&iothread_trampoline, (const uint8_t *)callback); + return make_detached_pthread(callback); } - -#endif -#endif