use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use drasi_lib::bootstrap::BootstrapResult;
use drasi_lib::channels::events::{BootstrapEvent, BootstrapEventSender};
use super::payload::consume_bootstrap_event;
use super::vtables::{
FfiBootstrapEvent, FfiBootstrapReceiver, FfiBootstrapResult, FfiBootstrapResultReceiver,
};
pub const BOOTSTRAP_BRIDGE_CAPACITY: usize = 256;
pub const BOOTSTRAP_PROVIDER_CAPACITY: usize = 100;
struct PushCallbackContext {
tx: std::sync::Mutex<Option<std::sync::mpsc::SyncSender<BootstrapEvent>>>,
notify: Arc<tokio::sync::Notify>,
reclaimed: AtomicBool,
}
extern "C" fn bootstrap_push_callback(
ctx: *mut std::ffi::c_void,
event: *mut FfiBootstrapEvent,
) -> bool {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
bootstrap_push_callback_inner(ctx, event)
}))
.unwrap_or(false)
}
fn bootstrap_push_callback_inner(
ctx: *mut std::ffi::c_void,
event: *mut FfiBootstrapEvent,
) -> bool {
if ctx.is_null() {
return false;
}
let context = unsafe { &*(ctx as *const PushCallbackContext) };
if event.is_null() {
if let Ok(mut guard) = context.tx.lock() {
*guard = None;
}
context.notify.notify_one();
if context
.reclaimed
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
unsafe { Arc::from_raw(ctx as *const PushCallbackContext) };
}
return false;
}
let ffi_event = unsafe { &*event };
let decoded = unsafe { consume_bootstrap_event(ffi_event) };
unsafe { drop(Box::from_raw(event)) };
let Some(bootstrap_event) = decoded else {
return true;
};
let Ok(guard) = context.tx.lock() else {
return false;
};
let Some(tx) = guard.as_ref() else {
return false;
};
let ok = tx.send(bootstrap_event).is_ok();
drop(guard);
if ok {
context.notify.notify_one();
true
} else {
false
}
}
struct FfiReceiverState {
drop_fn: extern "C" fn(*mut std::ffi::c_void),
state: *mut std::ffi::c_void,
}
unsafe impl Send for FfiReceiverState {}
unsafe impl Sync for FfiReceiverState {}
impl Drop for FfiReceiverState {
fn drop(&mut self) {
guarded_producer_drop(self.drop_fn, self.state);
}
}
fn guarded_producer_drop(
drop_fn: extern "C" fn(*mut std::ffi::c_void),
state: *mut std::ffi::c_void,
) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| drop_fn(state)));
}
pub fn release_bootstrap_receiver(inner: FfiBootstrapReceiver) {
guarded_producer_drop(inner.drop_fn, inner.state);
}
pub fn release_result_receiver(inner: FfiBootstrapResultReceiver) {
guarded_producer_drop(inner.drop_fn, inner.state);
}
pub struct BootstrapStreamConsumer {
rx: std::sync::mpsc::Receiver<BootstrapEvent>,
notify: Arc<tokio::sync::Notify>,
_callback_ctx: Arc<PushCallbackContext>,
_ffi_state: FfiReceiverState,
}
unsafe impl Send for BootstrapStreamConsumer {}
impl BootstrapStreamConsumer {
pub fn new(inner: FfiBootstrapReceiver) -> Self {
let (tx, rx) = std::sync::mpsc::sync_channel(BOOTSTRAP_BRIDGE_CAPACITY);
let notify = Arc::new(tokio::sync::Notify::new());
let callback_ctx = Arc::new(PushCallbackContext {
tx: std::sync::Mutex::new(Some(tx)),
notify: notify.clone(),
reclaimed: AtomicBool::new(false),
});
let ctx_ptr = Arc::into_raw(callback_ctx.clone()) as *mut std::ffi::c_void;
(inner.start_push_fn)(inner.state, bootstrap_push_callback, ctx_ptr);
Self {
rx,
notify,
_callback_ctx: callback_ctx,
_ffi_state: FfiReceiverState {
drop_fn: inner.drop_fn,
state: inner.state,
},
}
}
pub async fn forward_into(self, tx: &BootstrapEventSender) -> usize {
let mut count = 0usize;
loop {
match self.rx.try_recv() {
Ok(event) => {
if tx.send(event).await.is_err() {
return count;
}
count += 1;
}
Err(std::sync::mpsc::TryRecvError::Empty) => {
self.notify.notified().await;
}
Err(std::sync::mpsc::TryRecvError::Disconnected) => {
return count;
}
}
}
}
}
struct ResultCallbackContext {
tx: std::sync::Mutex<Option<tokio::sync::oneshot::Sender<anyhow::Result<BootstrapResult>>>>,
reclaimed: AtomicBool,
}
pub struct BootstrapResultGuard {
_ctx: Arc<ResultCallbackContext>,
}
extern "C" fn bootstrap_result_callback(
ctx: *mut std::ffi::c_void,
result: *mut FfiBootstrapResult,
) {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
if ctx.is_null() {
return;
}
let context = unsafe { &*(ctx as *const ResultCallbackContext) };
let tx = match context.tx.lock() {
Ok(mut guard) => guard.take(),
Err(_) => None,
};
let outcome: anyhow::Result<BootstrapResult> = if result.is_null() {
Err(anyhow::anyhow!("Bootstrap provider ended without a result"))
} else {
let ffi_result = unsafe { *Box::from_raw(result) };
let error_text = if !ffi_result.error_ptr.is_null() && ffi_result.error_len > 0 {
let bytes = unsafe {
std::slice::from_raw_parts(ffi_result.error_ptr, ffi_result.error_len)
};
let text = String::from_utf8_lossy(bytes).into_owned();
if let Some(drop_fn) = ffi_result.error_drop_fn {
(drop_fn)(ffi_result.error_ptr as *mut u8, ffi_result.error_len);
}
Some(text)
} else {
None
};
if ffi_result.event_count < 0 {
Err(match error_text {
Some(msg) => anyhow::anyhow!("Bootstrap failed: {msg}"),
None => {
anyhow::anyhow!("Bootstrap failed with code {}", ffi_result.event_count)
}
})
} else {
let source_position = if !ffi_result.source_position_ptr.is_null()
&& ffi_result.source_position_len > 0
{
let bytes = unsafe {
std::slice::from_raw_parts(
ffi_result.source_position_ptr,
ffi_result.source_position_len,
)
};
let owned = bytes::Bytes::copy_from_slice(bytes);
if let Some(drop_fn) = ffi_result.source_position_drop_fn {
(drop_fn)(
ffi_result.source_position_ptr as *mut u8,
ffi_result.source_position_len,
);
}
Some(owned)
} else {
None
};
Ok(BootstrapResult {
event_count: ffi_result.event_count as usize,
source_position,
})
}
};
if let Some(tx) = tx {
let _ = tx.send(outcome);
}
if context
.reclaimed
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
unsafe { Arc::from_raw(ctx as *const ResultCallbackContext) };
}
}));
}
pub fn wrap_result_receiver(
inner: FfiBootstrapResultReceiver,
) -> (
tokio::sync::oneshot::Receiver<anyhow::Result<BootstrapResult>>,
BootstrapResultGuard,
) {
let (tx, rx) = tokio::sync::oneshot::channel();
let ctx = Arc::new(ResultCallbackContext {
tx: std::sync::Mutex::new(Some(tx)),
reclaimed: AtomicBool::new(false),
});
let ctx_ptr = Arc::into_raw(ctx.clone()) as *mut std::ffi::c_void;
(inner.start_fn)(inner.state, bootstrap_result_callback, ctx_ptr);
guarded_producer_drop(inner.drop_fn, inner.state);
(rx, BootstrapResultGuard { _ctx: ctx })
}