diff --git a/lez/indexer/ffi/indexer_ffi.h b/lez/indexer/ffi/indexer_ffi.h index 569a26a3..8347ad3c 100644 --- a/lez/indexer/ffi/indexer_ffi.h +++ b/lez/indexer/ffi/indexer_ffi.h @@ -24,35 +24,6 @@ typedef enum FfiBedrockStatus { Finalized, } FfiBedrockStatus; -/** - * FFI-owned indexer. - * - * Has three fields behind `c_void` (so that cbindgen never needs to see their Rust layout): - * - An [`IndexerCore`] used to answer queries - * - The background task [`JoinHandle`] that drives ingestion (consuming the block stream so the - * store stays populated) - * - The [`Runtime`] they run on. It owns the underlying tokio runtime when we created it (and - * drops it on teardown) and merely borrows it when the caller supplied one. - */ -typedef struct IndexerServiceFFI { - void *core; - void *ingest_handle; - void *runtime; -} IndexerServiceFFI; - -/** - * Simple wrapper around a pointer to a value or an error. - * - * Pointer is not guaranteed. You should check the error field before - * dereferencing the pointer. - */ -typedef struct PointerResult_IndexerServiceFFI__OperationStatus { - struct IndexerServiceFFI *value; - enum OperationStatus error; -} PointerResult_IndexerServiceFFI__OperationStatus; - -typedef struct PointerResult_IndexerServiceFFI__OperationStatus InitializedIndexerServiceFFIResult; - typedef enum PointerKind_Tag { Owned, Borrowed, @@ -82,6 +53,34 @@ typedef struct Runtime { struct Pointer_Runtime inner; } Runtime; +/** + * FFI-owned indexer. + * + * - An [`IndexerCore`] used to answer queries + * - The background task [`JoinHandle`] that drives ingestion (consuming the block stream so the + * store stays populated) + * - The [`Runtime`] used to run async queries against the store (either owned or borrowed), + * already FFI-safe. + */ +typedef struct IndexerServiceFFI { + void *core; + void *ingest_handle; + struct Runtime runtime; +} IndexerServiceFFI; + +/** + * Simple wrapper around a pointer to a value or an error. + * + * Pointer is not guaranteed. You should check the error field before + * dereferencing the pointer. + */ +typedef struct PointerResult_IndexerServiceFFI__OperationStatus { + struct IndexerServiceFFI *value; + enum OperationStatus error; +} PointerResult_IndexerServiceFFI__OperationStatus; + +typedef struct PointerResult_IndexerServiceFFI__OperationStatus InitializedIndexerServiceFFIResult; + /** * Result of [`query_last_block`], returned **inline** (no heap allocation, so * there is no corresponding `free_*` to call). diff --git a/lez/indexer/ffi/src/indexer.rs b/lez/indexer/ffi/src/indexer.rs index 619373e6..0b6b874a 100644 --- a/lez/indexer/ffi/src/indexer.rs +++ b/lez/indexer/ffi/src/indexer.rs @@ -7,17 +7,16 @@ use crate::Runtime; /// FFI-owned indexer. /// -/// Has three fields behind `c_void` (so that cbindgen never needs to see their Rust layout): /// - An [`IndexerCore`] used to answer queries /// - The background task [`JoinHandle`] that drives ingestion (consuming the block stream so the /// store stays populated) -/// - The [`Runtime`] they run on. It owns the underlying tokio runtime when we created it (and -/// drops it on teardown) and merely borrows it when the caller supplied one. +/// - The [`Runtime`] used to run async queries against the store (either owned or borrowed), +/// already FFI-safe. #[repr(C)] pub struct IndexerServiceFFI { core: *mut c_void, ingest_handle: *mut c_void, - runtime: *mut c_void, + runtime: Runtime, } impl IndexerServiceFFI { @@ -26,7 +25,7 @@ impl IndexerServiceFFI { Self { core: Box::into_raw(Box::new(core)).cast::(), ingest_handle: Box::into_raw(Box::new(ingest_handle)).cast::(), - runtime: Box::into_raw(Box::new(runtime)).cast::(), + runtime, } } @@ -44,38 +43,24 @@ impl IndexerServiceFFI { /// Borrow the runtime to `block_on` an async store query. #[must_use] pub const fn runtime(&self) -> &Runtime { - unsafe { - self.runtime - .cast::() - .as_ref() - .expect("Runtime must be a non-null pointer") - } + &self.runtime } } -// Implement Drop to stop ingestion and free the boxed resources. impl Drop for IndexerServiceFFI { fn drop(&mut self) { - let Self { - core, - ingest_handle, - runtime, - } = self; - - if !ingest_handle.is_null() { - // Stop the background ingestion task before tearing down the core. - let handle = unsafe { Box::from_raw(ingest_handle.cast::>()) }; + if !self.ingest_handle.is_null() { + let handle = unsafe { Box::from_raw(self.ingest_handle.cast::>()) }; + // stop the background ingestion task before tearing down the core. handle.abort(); drop(handle); } - if !core.is_null() { - drop(unsafe { Box::from_raw(core.cast::()) }); - } - // Dropping the `Runtime` shuts down the tokio runtime only if we own it - // (a borrowed one is left for its external owner). Done last, and from - // the consumer thread, so it never drops from within a runtime worker. - if !runtime.is_null() { - drop(unsafe { Box::from_raw(runtime.cast::()) }); + if !self.core.is_null() { + drop(unsafe { Box::from_raw(self.core.cast::()) }); } + + // `runtime` field is dropped automatically on return here: + // - if runtime was owned, it is shutdown at this point + // - if it was borrowed, it continues to live within the external owner } }