mirror of
https://github.com/fish-shell/fish-shell.git
synced 2025-02-22 02:02:11 +08:00
Add and use type-erased RAII callback wrapper for ffi
This allows the rust code to free up C++ resources allocated for a callback even when the callback isn't executed (as opposed to requiring the callback to run and at the end of the callback cleaning up all allocated resources). Also add type-erased destructor registration to callback_t. This allows for freeing variables allocated by the callback for debounce_t's perform_with_callback() that don't end up having their completion called due to a timeout.
This commit is contained in:
parent
6cd2d0ffed
commit
ecf1676601
@ -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/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/flog.cpp src/function.cpp src/highlight.cpp
|
||||||
src/history.cpp src/history_file.cpp src/input.cpp src/input_common.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/null_terminated_array.cpp src/operation_context.cpp src/output.cpp
|
||||||
src/pager.cpp src/parse_execution.cpp src/parse_util.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
|
src/parser.cpp src/parser_keywords.cpp src/path.cpp src/postfork.cpp
|
||||||
|
@ -338,3 +338,21 @@ impl core::convert::From<*const autocxx::c_void> for void_ptr {
|
|||||||
Self(value as *const _)
|
Self(value as *const _)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl core::convert::From<void_ptr> for *const u8 {
|
||||||
|
fn from(value: void_ptr) -> Self {
|
||||||
|
value.0 as *const _
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::convert::From<void_ptr> for *const core::ffi::c_void {
|
||||||
|
fn from(value: void_ptr) -> Self {
|
||||||
|
value.0 as *const _
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::convert::From<void_ptr> for *const autocxx::c_void {
|
||||||
|
fn from(value: void_ptr) -> Self {
|
||||||
|
value.0 as *const _
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -51,6 +51,15 @@ static NOTIFY_SIGNALLER: once_cell::sync::Lazy<&'static crate::fd_monitor::FdEve
|
|||||||
|
|
||||||
#[cxx::bridge]
|
#[cxx::bridge]
|
||||||
mod ffi {
|
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" {
|
extern "Rust" {
|
||||||
#[cxx_name = "ASSERT_IS_MAIN_THREAD"]
|
#[cxx_name = "ASSERT_IS_MAIN_THREAD"]
|
||||||
fn assert_is_main_thread();
|
fn assert_is_main_thread();
|
||||||
@ -65,7 +74,7 @@ mod ffi {
|
|||||||
|
|
||||||
extern "Rust" {
|
extern "Rust" {
|
||||||
#[cxx_name = "make_detached_pthread"]
|
#[cxx_name = "make_detached_pthread"]
|
||||||
fn spawn_ffi(callback: *const u8, param: *const u8) -> bool;
|
fn spawn_ffi(callback: &SharedPtr<CppCallback>) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "Rust" {
|
extern "Rust" {
|
||||||
@ -76,9 +85,9 @@ mod ffi {
|
|||||||
#[cxx_name = "iothread_drain_all"]
|
#[cxx_name = "iothread_drain_all"]
|
||||||
fn iothread_drain_all_ffi();
|
fn iothread_drain_all_ffi();
|
||||||
#[cxx_name = "iothread_perform"]
|
#[cxx_name = "iothread_perform"]
|
||||||
fn iothread_perform_ffi(callback: *const u8, param: *const u8);
|
fn iothread_perform_ffi(callback: &SharedPtr<CppCallback>);
|
||||||
#[cxx_name = "iothread_perform_cantwait"]
|
#[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<CppCallback>);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "Rust" {
|
extern "Rust" {
|
||||||
@ -86,14 +95,12 @@ mod ffi {
|
|||||||
type Debounce;
|
type Debounce;
|
||||||
|
|
||||||
#[cxx_name = "perform"]
|
#[cxx_name = "perform"]
|
||||||
fn perform_ffi(&self, callback: *const u8, param: *const u8) -> u64;
|
fn perform_ffi(&self, callback: &SharedPtr<CppCallback>) -> u64;
|
||||||
#[cxx_name = "perform_with_completion"]
|
#[cxx_name = "perform_with_completion"]
|
||||||
fn perform_with_completion_ffi(
|
fn perform_with_completion_ffi(
|
||||||
&self,
|
&self,
|
||||||
callback: *const u8,
|
callback: &SharedPtr<CppCallback>,
|
||||||
param1: *const u8,
|
completion: &SharedPtr<CppCallback>,
|
||||||
completion: *const u8,
|
|
||||||
param2: *const u8,
|
|
||||||
) -> u64;
|
) -> u64;
|
||||||
|
|
||||||
#[cxx_name = "new_debounce_t"]
|
#[cxx_name = "new_debounce_t"]
|
||||||
@ -101,6 +108,9 @@ mod ffi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for ffi::CppCallback {}
|
||||||
|
unsafe impl Sync for ffi::CppCallback {}
|
||||||
|
|
||||||
fn iothread_service_main_with_timeout_ffi(timeout_usec: u64) {
|
fn iothread_service_main_with_timeout_ffi(timeout_usec: u64) {
|
||||||
iothread_service_main_with_timeout(Duration::from_micros(timeout_usec))
|
iothread_service_main_with_timeout(Duration::from_micros(timeout_usec))
|
||||||
}
|
}
|
||||||
@ -109,23 +119,19 @@ fn iothread_drain_all_ffi() {
|
|||||||
unsafe { iothread_drain_all() }
|
unsafe { iothread_drain_all() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn iothread_perform_ffi(callback: *const u8, param: *const u8) {
|
fn iothread_perform_ffi(callback: &cxx::SharedPtr<ffi::CppCallback>) {
|
||||||
type Callback = extern "C" fn(crate::ffi::void_ptr);
|
let callback = callback.clone();
|
||||||
let callback: Callback = unsafe { std::mem::transmute(callback) };
|
|
||||||
let param = param.into();
|
|
||||||
|
|
||||||
iothread_perform(move || {
|
iothread_perform(move || {
|
||||||
callback(param);
|
callback.invoke();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn iothread_perform_cant_wait_ffi(callback: *const u8, param: *const u8) {
|
fn iothread_perform_cant_wait_ffi(callback: &cxx::SharedPtr<ffi::CppCallback>) {
|
||||||
type Callback = extern "C" fn(crate::ffi::void_ptr);
|
let callback = callback.clone();
|
||||||
let callback: Callback = unsafe { std::mem::transmute(callback) };
|
|
||||||
let param = param.into();
|
|
||||||
|
|
||||||
iothread_perform_cant_wait(move || {
|
iothread_perform_cant_wait(move || {
|
||||||
callback(param);
|
callback.invoke();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,13 +292,10 @@ pub fn spawn<F: FnOnce() + Send + 'static>(callback: F) -> bool {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_ffi(callback: *const u8, param: *const u8) -> bool {
|
fn spawn_ffi(callback: &cxx::SharedPtr<ffi::CppCallback>) -> bool {
|
||||||
type Callback = extern "C" fn(crate::ffi::void_ptr);
|
let callback = callback.clone();
|
||||||
let callback: Callback = unsafe { std::mem::transmute(callback) };
|
|
||||||
let param = param.into();
|
|
||||||
|
|
||||||
spawn(move || {
|
spawn(move || {
|
||||||
callback(param);
|
callback.invoke();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -645,38 +648,29 @@ impl Debounce {
|
|||||||
self.perform_inner(h)
|
self.perform_inner(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_with_completion_ffi(
|
fn perform_ffi(&self, callback: &cxx::SharedPtr<ffi::CppCallback>) -> u64 {
|
||||||
&self,
|
let callback = callback.clone();
|
||||||
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);
|
|
||||||
|
|
||||||
let callback: Callback = unsafe { std::mem::transmute(callback) };
|
self.perform(move || {
|
||||||
let param1 = param1.into();
|
callback.invoke();
|
||||||
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),
|
|
||||||
)
|
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_ffi(&self, callback: *const u8, param: *const u8) -> u64 {
|
fn perform_with_completion_ffi(
|
||||||
type Callback = extern "C" fn(crate::ffi::void_ptr);
|
&self,
|
||||||
|
callback: &cxx::SharedPtr<ffi::CppCallback>,
|
||||||
|
completion: &cxx::SharedPtr<ffi::CppCallback>,
|
||||||
|
) -> u64 {
|
||||||
|
let callback = callback.clone();
|
||||||
|
let completion = completion.clone();
|
||||||
|
|
||||||
let callback: Callback = unsafe { std::mem::transmute(callback) };
|
self.perform_with_completion(
|
||||||
let param = param.into();
|
move || -> crate::ffi::void_ptr { callback.invoke().into() },
|
||||||
|
move |result| {
|
||||||
self.perform(move || {
|
completion.invoke_with_param(result.into());
|
||||||
callback(param);
|
},
|
||||||
})
|
)
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
49
src/callback.h
Normal file
49
src/callback.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
/// 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<void *(const void *param)> callback;
|
||||||
|
std::vector<std::function<void()>> 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<void *(const void *param)> &&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<void()> &dtor : cleanups) {
|
||||||
|
(dtor)();
|
||||||
|
}
|
||||||
|
cleanups.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -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;
|
|
||||||
}
|
|
133
src/iothread.h
133
src/iothread.h
@ -1,123 +1,84 @@
|
|||||||
// Handles IO that may hang.
|
#pragma once
|
||||||
#ifndef FISH_IOTHREAD_H
|
|
||||||
#define FISH_IOTHREAD_H
|
|
||||||
#if INCLUDE_RUST_HEADERS
|
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cassert>
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
|
#include "callback.h"
|
||||||
#include "threads.rs.h"
|
#include "threads.rs.h"
|
||||||
|
|
||||||
struct iothread_callback_t {
|
|
||||||
std::function<void *(const void *param)> 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.
|
// iothread_perform invokes a handler on a background thread.
|
||||||
inline void iothread_perform(std::function<void()> &&func) {
|
inline void iothread_perform(std::function<void()> &&func) {
|
||||||
auto callback = new iothread_callback_t{std::bind([=] {
|
std::shared_ptr<callback_t> callback = std::make_shared<callback_t>([=](const void *) {
|
||||||
func();
|
func();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}),
|
});
|
||||||
nullptr};
|
|
||||||
|
|
||||||
iothread_perform((const uint8_t *)&iothread_trampoline, (const uint8_t *)callback);
|
iothread_perform(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Variant of iothread_perform that disrespects the thread limit.
|
/// Variant of iothread_perform that disrespects the thread limit.
|
||||||
/// It does its best to spawn a new thread if all other threads are occupied.
|
/// 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.
|
/// This is for cases where deferring a new thread might lead to deadlock.
|
||||||
inline void iothread_perform_cantwait(std::function<void()> &&func) {
|
inline void iothread_perform_cantwait(std::function<void()> &&func) {
|
||||||
auto callback = new iothread_callback_t{std::bind([=] {
|
std::shared_ptr<callback_t> callback = std::make_shared<callback_t>([=](const void *) {
|
||||||
func();
|
func();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}),
|
});
|
||||||
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<void()> &func) {
|
inline uint64_t debounce_perform(const debounce_t &debouncer, const std::function<void()> &func) {
|
||||||
auto callback = new iothread_callback_t{std::bind([=] {
|
std::shared_ptr<callback_t> callback = std::make_shared<callback_t>([=](const void *) {
|
||||||
func();
|
func();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}),
|
});
|
||||||
nullptr};
|
|
||||||
|
|
||||||
return debouncer.perform((const uint8_t *)&iothread_trampoline, (const uint8_t *)callback);
|
return debouncer.perform(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename R>
|
template <typename R>
|
||||||
inline void debounce_perform_with_completion(const debounce_t &debouncer, std::function<R()> &&func,
|
inline void debounce_perform_with_completion(const debounce_t &debouncer, std::function<R()> &&func,
|
||||||
std::function<void(R)> &&completion) {
|
std::function<void(R)> &&completion) {
|
||||||
auto callback1 = new iothread_callback_t{[=](const void *) {
|
std::shared_ptr<callback_t> callback2 = std::make_shared<callback_t>([=](const void *r) {
|
||||||
auto *result = new R(func());
|
assert(r != nullptr && "callback1 result was null!");
|
||||||
return (void *)result;
|
const R *result = (const R *)r;
|
||||||
},
|
completion(*result);
|
||||||
nullptr};
|
return nullptr;
|
||||||
|
});
|
||||||
|
|
||||||
auto callback2 = new iothread_callback_t{
|
std::shared_ptr<callback_t> callback1 = std::make_shared<callback_t>([=](const void *) {
|
||||||
([=](const void *r) {
|
const R *result = new R(func());
|
||||||
const R *result = (const R *)r;
|
callback2->cleanups.push_back([result]() { delete result; });
|
||||||
completion(*result);
|
return (void *)result;
|
||||||
delete result;
|
});
|
||||||
return nullptr;
|
|
||||||
}),
|
|
||||||
nullptr,
|
|
||||||
};
|
|
||||||
|
|
||||||
debouncer.perform_with_completion(
|
debouncer.perform_with_completion(callback1, callback2);
|
||||||
(const uint8_t *)&iothread_trampoline, (const uint8_t *)callback1,
|
|
||||||
(const uint8_t *)&iothread_trampoline2, (const uint8_t *)callback2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename R>
|
template <typename R>
|
||||||
inline void debounce_perform_with_completion(const debounce_t &debouncer, std::function<R()> &&func,
|
inline void debounce_perform_with_completion(const debounce_t &debouncer, std::function<R()> &&func,
|
||||||
std::function<void(const R &)> &&completion) {
|
std::function<void(const R &)> &&completion) {
|
||||||
auto callback1 = new iothread_callback_t{[=](const void *) {
|
std::shared_ptr<callback_t> callback2 = std::make_shared<callback_t>([=](const void *r) {
|
||||||
auto *result = new R(func());
|
assert(r != nullptr && "callback1 result was null!");
|
||||||
return (void *)result;
|
const R *result = (const R *)r;
|
||||||
},
|
completion(*result);
|
||||||
nullptr};
|
return nullptr;
|
||||||
|
});
|
||||||
|
|
||||||
auto callback2 = new iothread_callback_t{
|
std::shared_ptr<callback_t> callback1 = std::make_shared<callback_t>([=](const void *) {
|
||||||
([=](const void *r) {
|
const R *result = new R(func());
|
||||||
const R *result = (const R *)r;
|
callback2->cleanups.push_back([result]() { delete result; });
|
||||||
completion(*result);
|
return (void *)result;
|
||||||
delete result;
|
});
|
||||||
return nullptr;
|
|
||||||
}),
|
|
||||||
nullptr,
|
|
||||||
};
|
|
||||||
|
|
||||||
debouncer.perform_with_completion(
|
debouncer.perform_with_completion(callback1, callback2);
|
||||||
(const uint8_t *)&iothread_trampoline, (const uint8_t *)callback1,
|
|
||||||
(const uint8_t *)&iothread_trampoline2, (const uint8_t *)callback2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool make_detached_pthread(const std::function<void()> &func) {
|
inline bool make_detached_pthread(const std::function<void()> &func) {
|
||||||
auto callback = new iothread_callback_t{
|
std::shared_ptr<callback_t> callback = std::make_shared<callback_t>([=](const void *) {
|
||||||
[=](const void *) {
|
func();
|
||||||
func();
|
return nullptr;
|
||||||
return nullptr;
|
});
|
||||||
},
|
|
||||||
nullptr,
|
|
||||||
};
|
|
||||||
|
|
||||||
return make_detached_pthread((const uint8_t *)&iothread_trampoline, (const uint8_t *)callback);
|
return make_detached_pthread(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user