Reputation: 1894
I'm using this technique for C code, called from Rust, call a Rust's method back.
pub type OnVpnLog = std::sync::Arc<std::sync::Mutex<dyn Fn(*const libc::c_char) + Send + Sync>>;
pub type OnVpnSomething = std::sync::Arc<std::sync::Mutex<dyn Fn(i32) + Send + Sync>>;
struct OVPNClientInner {
on_vpn_log: Option<OnVpnLog>,
on_vpn_something: Option<OnVpnSomething>,
}
unsafe extern "C" fn on_log_trampoline(
buffer: *const libc::c_char,
user_data: *mut libc::c_void,
) -> libc::c_int {
let ovpn_client_inner = &mut *(user_data as *mut OVPNClientInner);
(ovpn_client_inner.on_vpn_log.as_ref().unwrap().lock().unwrap())(buffer);
0
}
unsafe extern "C" fn on_something_trampoline(
user_data: *mut libc::c_void,
) -> libc::c_int {
let ovpn_client_inner = &mut *(user_data as *mut OVPNClientInner);
(ovpn_client_inner.on_vpn_something.as_ref().unwrap().lock().unwrap())(0);
0
}
In this specific case, I'm calling on_vpn_log
which is behing a Mutex
. However, this is not sufficient. Suppose that I call on_vpn_log
while the function on_vpn_log
is being replaced by another one. This would lead to undefined behaviour.
Also, the 2 trampolines could in the future with a code update try to access something inside OVPNClientInner
. We must ensure that OVPNClientInner
is accessed only once at a time.
One option would be a global static mutex, but that would block one instance of OVPNClientInner
while another was being updated.
Another option would be to put a mutex inside OVPNClientInner
:
struct OVPNClientInner {
on_vpn_log: Option<OnVpnLog>,
on_vpn_something: Option<OnVpnSomething>,
mutex: Mutex
}
But OVPNClientInner
is shared between C and Rust, so no ffi-unsafe types can be present. Also that mutex could be changed while we try to lock it (maybe we could make it immutable, I don't know)
What would be a good solution here to provide good safety?
Upvotes: 0
Views: 287
Reputation: 60597
If on_vpn_log
and on_vpn_something
can be updated and used separately, I'd recommend:
pub type OnVpnLog = dyn Fn(*const libc::c_char) + Send + Sync;
pub type OnVpnSomething = dyn Fn(i32) + Send + Sync;
struct OVPNClientInner {
on_vpn_log: Mutex<Option<Box<OnVpnLog>>>,
on_vpn_something: Mutex<Option<Box<OnVpnSomething>>>,
}
If not, use a single Mutex
when creating and using the user_data
:
struct OVPNClientInner {
on_vpn_log: Option<Box<OnVpnLog>>,
on_vpn_something: Option<Box<OnVpnSomething>>,
}
unsafe extern "C" fn on_log_trampoline(
buffer: *const libc::c_char,
user_data: *mut libc::c_void,
) -> libc::c_int {
let ovpn_client_inner = &*(user_data as *mut Mutex<OVPNClientInner>); // <--------
(ovpn_client_inner.lock().unwrap().on_vpn_log.as_ref().unwrap())(buffer);
0
}
But
OVPNClientInner
is shared between C and Rust, so no ffi-unsafe types can be present.
I'm not familiar with this API, but typically an opaque void*
called "user data" is unused by the framework itself. The ffi boundary being void*
and unused on the C side means there's no concern with using Mutex
in your structure, you can put almost anything actually.
The only thing you should be concerned about is if what you send is Sync
, which means its safe to use from multiple threads. Both the implementations above are safe in that regard.
As an aside, you currently have an Option
that you're assuming is present. You should either remove it or be sure to handle its absence properly.
Upvotes: 1