Skip to content

Commit 9f14ec7

Browse files
committed
fix(async): add send-safe wrapper for raw pointers
1 parent e1ce080 commit 9f14ec7

7 files changed

Lines changed: 203 additions & 46 deletions

File tree

src/callback.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ impl Drop for CallbackContext {
142142
}
143143
}
144144

145+
unsafe impl Send for CallbackContext {}
146+
unsafe impl Sync for CallbackContext {}
147+
145148
pub struct CallbackFuture {
146149
pub(crate) context: Arc<CallbackContext>,
147150
}
@@ -221,6 +224,7 @@ impl std::future::Future for CallbackFuture {
221224
}
222225

223226
unsafe impl Send for CallbackFuture {}
227+
unsafe impl Sync for CallbackFuture {}
224228

225229
pub fn with_libstorage_lock<F, R>(f: F) -> R
226230
where

src/download/stream.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::callback::{c_callback, with_libstorage_lock, CallbackFuture};
88
use crate::download::session::download_init_sync;
99
use crate::download::types::{DownloadOptions, DownloadResult, DownloadStreamOptions};
1010
use crate::error::{Result, StorageError};
11-
use crate::ffi::{free_c_string, storage_download_stream, string_to_c_string};
11+
use crate::ffi::{free_c_string, storage_download_stream, string_to_c_string, SendSafePtr};
1212
use crate::node::lifecycle::StorageNode;
1313
use libc::c_void;
1414
use std::io::Write;
@@ -114,7 +114,7 @@ pub async fn download_stream(
114114

115115
download_init_sync(node, cid, &download_options)?;
116116

117-
let context_ptr = future.context_ptr() as *mut c_void;
117+
let context_ptr = unsafe { SendSafePtr::new(future.context_ptr() as *mut c_void) };
118118
let filepath_str = options
119119
.filepath
120120
.as_ref()
@@ -133,7 +133,7 @@ pub async fn download_stream(
133133
options.local,
134134
c_filepath,
135135
Some(c_callback),
136-
context_ptr,
136+
context_ptr.as_ptr(),
137137
);
138138

139139
free_c_string(c_cid);

src/ffi/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ mod generated {
1313
// Re-export all the generated bindings
1414
pub use generated::*;
1515

16+
// Send-safe wrapper for raw pointers
17+
pub mod send_safe;
18+
pub use send_safe::SendSafePtr;
19+
1620
use libc::c_char;
1721
use std::ffi::CStr;
1822
use std::str::Utf8Error;

src/ffi/send_safe.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
//! Send-safe wrapper for raw pointers
2+
//!
3+
//! This module provides a wrapper type that allows raw pointers to be sent
4+
//! across thread boundaries safely. This is necessary because raw pointers in
5+
//! Rust are not `Send` by default, but we need to use them in async functions
6+
//! that must be `Send` for Tauri compatibility.
7+
8+
/// A Send-safe wrapper for raw pointers
9+
///
10+
/// This wrapper allows raw pointers to be sent across thread boundaries safely.
11+
/// It is safe because:
12+
/// - The underlying data is protected by `with_libstorage_lock()` mutex
13+
/// - The FFI library is thread-safe
14+
/// - Pointers are only passed to FFI functions, never dereferenced in Rust
15+
///
16+
/// # Safety
17+
///
18+
/// The caller must ensure that:
19+
/// - The pointer is valid for the duration of its use
20+
/// - The underlying FFI library is thread-safe
21+
/// - Access to the pointer is protected by appropriate synchronization (e.g., `with_libstorage_lock()`)
22+
/// - The pointer is not dereferenced in Rust code
23+
///
24+
/// # Example
25+
///
26+
/// ```no_run
27+
/// use storage_bindings::ffi::SendSafePtr;
28+
///
29+
/// // Create a Send-safe pointer
30+
/// let ptr = unsafe { SendSafePtr::new(std::ptr::null_mut::<i32>()) };
31+
///
32+
/// // Use it in a thread-safe context
33+
/// let raw_ptr = unsafe { ptr.as_ptr() };
34+
/// ```
35+
pub struct SendSafePtr<T>(*mut T);
36+
37+
impl<T> SendSafePtr<T> {
38+
/// Create a new Send-safe pointer wrapper
39+
///
40+
/// # Safety
41+
///
42+
/// The caller must ensure that:
43+
/// - The pointer is valid for the duration of its use
44+
/// - The underlying FFI library is thread-safe
45+
/// - Access to the pointer is protected by appropriate synchronization
46+
pub unsafe fn new(ptr: *mut T) -> Self {
47+
Self(ptr)
48+
}
49+
50+
/// Get the raw pointer
51+
///
52+
/// # Safety
53+
///
54+
/// The caller must ensure that:
55+
/// - The pointer is only used within appropriate synchronization (e.g., `with_libstorage_lock()`)
56+
/// - The pointer is not dereferenced in Rust code
57+
/// - The pointer is only passed to FFI functions
58+
pub unsafe fn as_ptr(&self) -> *mut T {
59+
self.0
60+
}
61+
62+
/// Get the raw pointer as a const pointer
63+
///
64+
/// # Safety
65+
///
66+
/// The caller must ensure that:
67+
/// - The pointer is only used within appropriate synchronization (e.g., `with_libstorage_lock()`)
68+
/// - The pointer is not dereferenced in Rust code
69+
/// - The pointer is only passed to FFI functions
70+
pub unsafe fn as_const_ptr(&self) -> *const T {
71+
self.0 as *const T
72+
}
73+
}
74+
75+
// SAFETY: This is safe because:
76+
// 1. The pointer is only used within `with_libstorage_lock()` which provides mutual exclusion
77+
// 2. The FFI library is thread-safe
78+
// 3. The pointer is never dereferenced in Rust, only passed to FFI functions
79+
// 4. The underlying CallbackFuture already implements Send, confirming thread safety
80+
unsafe impl<T> Send for SendSafePtr<T> {}
81+
82+
// SAFETY: This is safe because:
83+
// 1. The pointer is only used within `with_libstorage_lock()` which provides mutual exclusion
84+
// 2. The FFI library is thread-safe
85+
// 3. The pointer is never dereferenced in Rust, only passed to FFI functions
86+
// 4. The underlying CallbackFuture already implements Send, confirming thread safety
87+
unsafe impl<T> Sync for SendSafePtr<T> {}
88+
89+
#[cfg(test)]
90+
mod tests {
91+
use super::*;
92+
use libc::c_void;
93+
use std::thread;
94+
95+
#[test]
96+
fn test_send_safe_ptr_is_send() {
97+
let ptr = unsafe { SendSafePtr::new(std::ptr::null_mut::<i32>()) };
98+
99+
// This should compile because SendSafePtr is Send
100+
thread::spawn(move || {
101+
let _ = ptr;
102+
})
103+
.join()
104+
.unwrap();
105+
}
106+
107+
#[test]
108+
fn test_send_safe_ptr_is_sync() {
109+
let ptr = unsafe { SendSafePtr::new(std::ptr::null_mut::<i32>()) };
110+
111+
// This should compile because SendSafePtr is Sync
112+
let handle = thread::spawn(|| {
113+
let _ = ptr;
114+
});
115+
116+
// Can still use ptr in main thread
117+
let _ = ptr;
118+
119+
handle.join().unwrap();
120+
}
121+
122+
#[test]
123+
fn test_send_safe_ptr_as_ptr() {
124+
let raw_ptr: *mut i32 = std::ptr::null_mut();
125+
let ptr = unsafe { SendSafePtr::new(raw_ptr) };
126+
127+
let retrieved_ptr = unsafe { ptr.as_ptr() };
128+
assert_eq!(raw_ptr, retrieved_ptr);
129+
}
130+
131+
#[test]
132+
fn test_send_safe_ptr_as_const_ptr() {
133+
let raw_ptr: *mut i32 = std::ptr::null_mut();
134+
let ptr = unsafe { SendSafePtr::new(raw_ptr) };
135+
136+
let const_ptr = unsafe { ptr.as_const_ptr() };
137+
assert_eq!(raw_ptr as *const i32, const_ptr);
138+
}
139+
140+
#[test]
141+
fn test_send_safe_ptr_with_c_void() {
142+
let raw_ptr: *mut c_void = std::ptr::null_mut();
143+
let ptr = unsafe { SendSafePtr::new(raw_ptr) };
144+
145+
let retrieved_ptr = unsafe { ptr.as_ptr() };
146+
assert_eq!(raw_ptr, retrieved_ptr);
147+
}
148+
}

src/node/lifecycle.rs

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::error::{Result, StorageError};
33
use crate::ffi::{
44
free_c_string, storage_close, storage_destroy, storage_new, storage_peer_id, storage_repo,
55
storage_revision, storage_spr, storage_start, storage_stop, storage_version,
6-
string_to_c_string,
6+
string_to_c_string, SendSafePtr,
77
};
88
use crate::node::config::StorageConfig;
99
use libc::c_void;
@@ -55,13 +55,10 @@ impl StorageNode {
5555
let c_json_config = string_to_c_string(&json_config);
5656

5757
let future = CallbackFuture::new();
58+
let context_ptr = unsafe { SendSafePtr::new(future.context_ptr() as *mut c_void) };
5859

5960
let node_ctx = with_libstorage_lock(|| unsafe {
60-
let node_ctx = storage_new(
61-
c_json_config,
62-
Some(c_callback),
63-
future.context_ptr() as *mut c_void,
64-
);
61+
let node_ctx = storage_new(c_json_config, Some(c_callback), context_ptr.as_ptr());
6562

6663
free_c_string(c_json_config);
6764

@@ -120,14 +117,14 @@ impl StorageNode {
120117
}
121118

122119
let future = CallbackFuture::new();
120+
let context_ptr = unsafe { SendSafePtr::new(future.context_ptr() as *mut c_void) };
123121

124122
let ctx = {
125123
let inner = node.inner.lock().unwrap();
126124
inner.ctx as *mut _
127125
};
128126

129-
let result =
130-
unsafe { storage_start(ctx, Some(c_callback), future.context_ptr() as *mut c_void) };
127+
let result = unsafe { storage_start(ctx, Some(c_callback), context_ptr.as_ptr()) };
131128

132129
if result != 0 {
133130
return Err(StorageError::node_error("start", "Failed to start node"));
@@ -163,14 +160,14 @@ impl StorageNode {
163160
}
164161

165162
let future = CallbackFuture::new();
163+
let context_ptr = unsafe { SendSafePtr::new(future.context_ptr() as *mut c_void) };
166164

167165
let ctx = {
168166
let inner = node.inner.lock().unwrap();
169167
inner.ctx as *mut _
170168
};
171169

172-
let result =
173-
unsafe { storage_stop(ctx, Some(c_callback), future.context_ptr() as *mut c_void) };
170+
let result = unsafe { storage_stop(ctx, Some(c_callback), context_ptr.as_ptr()) };
174171

175172
if result != 0 {
176173
return Err(StorageError::node_error("stop", "Failed to stop node"));
@@ -212,14 +209,14 @@ impl StorageNode {
212209
}
213210

214211
let future = CallbackFuture::new();
212+
let context_ptr = unsafe { SendSafePtr::new(future.context_ptr() as *mut c_void) };
215213

216214
let ctx = {
217215
let inner = node.inner.lock().unwrap();
218216
inner.ctx as *mut _
219217
};
220218

221-
let result =
222-
unsafe { storage_close(ctx, Some(c_callback), future.context_ptr() as *mut c_void) };
219+
let result = unsafe { storage_close(ctx, Some(c_callback), context_ptr.as_ptr()) };
223220

224221
if result != 0 {
225222
return Err(StorageError::node_error("close", "Failed to close node"));
@@ -259,14 +256,14 @@ impl StorageNode {
259256
}
260257

261258
let future = CallbackFuture::new();
259+
let context_ptr = unsafe { SendSafePtr::new(future.context_ptr() as *mut c_void) };
262260

263261
let ctx = {
264262
let inner = self.inner.lock().unwrap();
265263
inner.ctx as *mut _
266264
};
267265

268-
let result =
269-
unsafe { storage_close(ctx, Some(c_callback), future.context_ptr() as *mut c_void) };
266+
let result = unsafe { storage_close(ctx, Some(c_callback), context_ptr.as_ptr()) };
270267

271268
if result != 0 {
272269
return Err(StorageError::node_error("destroy", "Failed to close node"));
@@ -314,14 +311,14 @@ impl StorageNode {
314311
pub async fn version(&self) -> Result<String> {
315312
let node = self.clone();
316313
let future = CallbackFuture::new();
314+
let context_ptr = unsafe { SendSafePtr::new(future.context_ptr() as *mut c_void) };
317315

318316
let ctx = {
319317
let inner = node.inner.lock().unwrap();
320318
inner.ctx as *mut _
321319
};
322320

323-
let result =
324-
unsafe { storage_version(ctx, Some(c_callback), future.context_ptr() as *mut c_void) };
321+
let result = unsafe { storage_version(ctx, Some(c_callback), context_ptr.as_ptr()) };
325322

326323
if result != 0 {
327324
return Err(StorageError::node_error("version", "Failed to get version"));
@@ -334,14 +331,14 @@ impl StorageNode {
334331
pub async fn revision(&self) -> Result<String> {
335332
let node = self.clone();
336333
let future = CallbackFuture::new();
334+
let context_ptr = unsafe { SendSafePtr::new(future.context_ptr() as *mut c_void) };
337335

338336
let ctx = {
339337
let inner = node.inner.lock().unwrap();
340338
inner.ctx as *mut _
341339
};
342340

343-
let result =
344-
unsafe { storage_revision(ctx, Some(c_callback), future.context_ptr() as *mut c_void) };
341+
let result = unsafe { storage_revision(ctx, Some(c_callback), context_ptr.as_ptr()) };
345342

346343
if result != 0 {
347344
return Err(StorageError::node_error(
@@ -357,14 +354,14 @@ impl StorageNode {
357354
pub async fn repo(&self) -> Result<String> {
358355
let node = self.clone();
359356
let future = CallbackFuture::new();
357+
let context_ptr = unsafe { SendSafePtr::new(future.context_ptr() as *mut c_void) };
360358

361359
let ctx = {
362360
let inner = node.inner.lock().unwrap();
363361
inner.ctx as *mut _
364362
};
365363

366-
let result =
367-
unsafe { storage_repo(ctx, Some(c_callback), future.context_ptr() as *mut c_void) };
364+
let result = unsafe { storage_repo(ctx, Some(c_callback), context_ptr.as_ptr()) };
368365

369366
if result != 0 {
370367
return Err(StorageError::node_error("repo", "Failed to get repo path"));
@@ -377,14 +374,14 @@ impl StorageNode {
377374
pub async fn spr(&self) -> Result<String> {
378375
let node = self.clone();
379376
let future = CallbackFuture::new();
377+
let context_ptr = unsafe { SendSafePtr::new(future.context_ptr() as *mut c_void) };
380378

381379
let ctx = {
382380
let inner = node.inner.lock().unwrap();
383381
inner.ctx as *mut _
384382
};
385383

386-
let result =
387-
unsafe { storage_spr(ctx, Some(c_callback), future.context_ptr() as *mut c_void) };
384+
let result = unsafe { storage_spr(ctx, Some(c_callback), context_ptr.as_ptr()) };
388385

389386
if result != 0 {
390387
return Err(StorageError::node_error("spr", "Failed to get SPR"));
@@ -419,14 +416,14 @@ impl StorageNode {
419416
pub async fn peer_id(&self) -> Result<String> {
420417
let node = self.clone();
421418
let future = CallbackFuture::new();
419+
let context_ptr = unsafe { SendSafePtr::new(future.context_ptr() as *mut c_void) };
422420

423421
let ctx = {
424422
let inner = node.inner.lock().unwrap();
425423
inner.ctx as *mut _
426424
};
427425

428-
let result =
429-
unsafe { storage_peer_id(ctx, Some(c_callback), future.context_ptr() as *mut c_void) };
426+
let result = unsafe { storage_peer_id(ctx, Some(c_callback), context_ptr.as_ptr()) };
430427

431428
if result != 0 {
432429
return Err(StorageError::node_error("peer_id", "Failed to get peer ID"));

0 commit comments

Comments
 (0)