diff --git a/lez/indexer/ffi/indexer_ffi.h b/lez/indexer/ffi/indexer_ffi.h index 04d33b33..4bd2d549 100644 --- a/lez/indexer/ffi/indexer_ffi.h +++ b/lez/indexer/ffi/indexer_ffi.h @@ -31,12 +31,13 @@ typedef enum FfiBedrockStatus { * - An [`IndexerCore`] used to answer queries * - The background task [`JoinHandle`] that drives ingestion (consuming the block stream so the * store stays populated) - * - A [`Handle`] to the runtime they live on. + * - 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_handle; + void *runtime; } IndexerServiceFFI; /** @@ -81,17 +82,6 @@ typedef struct Runtime { struct Pointer_Runtime inner; } Runtime; -/** - * 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_Runtime__OperationStatus { - struct Runtime *value; - enum OperationStatus error; -} PointerResult_Runtime__OperationStatus; - /** * Result of [`query_last_block`], returned **inline** (no heap allocation, so * there is no corresponding `free_*` to call). @@ -423,6 +413,8 @@ typedef struct PointerResult_FfiVec_FfiTransaction_____OperationStatus { * * # Arguments * + * - `runtime`: A runtime for the indexer to run on, or null to have the indexer create and own + * one. * - `config_path`: A pointer to a string representing the path to the configuration file. * * # Returns @@ -432,17 +424,12 @@ typedef struct PointerResult_FfiVec_FfiTransaction_____OperationStatus { * * # Safety * The caller must ensure that: - * - `runtime` is a valid pointer to a `tokio::runtime::Runtime` instance. + * - `runtime` is either null or a valid pointer to a [`Runtime`] that outlives the indexer. * - `config_path` is a valid pointer to a null-terminated C string. */ InitializedIndexerServiceFFIResult start_indexer(const struct Runtime *runtime, const char *config_path); -/** - * Creates a new [`tokio::runtime::Runtime`]. - */ -struct PointerResult_Runtime__OperationStatus new_runtime(void); - /** * Stops and frees the resources associated with the given indexer service. * diff --git a/lez/indexer/ffi/src/api/lifecycle.rs b/lez/indexer/ffi/src/api/lifecycle.rs index bedebb2b..07b2a9be 100644 --- a/lez/indexer/ffi/src/api/lifecycle.rs +++ b/lez/indexer/ffi/src/api/lifecycle.rs @@ -12,6 +12,8 @@ pub type InitializedIndexerServiceFFIResult = PointerResult PointerResult { - Runtime::new().map_or_else( - |_e| PointerResult::from_error(OperationStatus::InitializationError), - PointerResult::from_value, - ) -} - /// Initializes and starts an indexer based on the provided /// configuration file path. /// /// # Arguments /// +/// - `runtime`: A runtime for the indexer to run on, or null to create and own one. /// - `config_path`: A pointer to a string representing the path to the configuration file. /// /// # Returns @@ -58,7 +52,7 @@ pub extern "C" fn new_runtime() -> PointerResult { /// /// # Safety /// The caller must ensure that: -/// - `runtime` is a valid pointer to a `tokio::runtime::Runtime` instance. +/// - `runtime` is either null or a valid pointer to a [`Runtime`] that outlives the indexer. /// - `config_path` is a valid pointer to a null-terminated C string. unsafe fn setup_indexer( runtime: *const Runtime, @@ -77,9 +71,19 @@ unsafe fn setup_indexer( OperationStatus::InitializationError })?; - // SAFETY: The caller must ensure that `runtime` is a valid pointer to a - // `tokio::runtime::Runtime` instance. - let runtime = unsafe { &*runtime }; + // Use the caller's runtime if one was supplied, otherwise create (and own) + // our own. The `Runtime` wrapper drops the underlying tokio runtime only + // when we own it; a borrowed one is left to its external owner. + let runtime = if runtime.is_null() { + Runtime::new().map_err(|e| { + log::error!("Could not create tokio runtime: {e}"); + OperationStatus::InitializationError + })? + } else { + // SAFETY: the caller guarantees `runtime` is valid and outlives the indexer. + let caller = unsafe { &*runtime }; + unsafe { Runtime::from_borrowed(caller.as_ref()) } + }; let core = IndexerCore::new(config).map_err(|e| { log::error!("Could not initialize indexer core: {e}"); @@ -100,11 +104,7 @@ unsafe fn setup_indexer( log::warn!("Indexer block stream ended"); }); - Ok(IndexerServiceFFI::new( - core, - ingest_handle, - runtime.handle().clone(), - )) + Ok(IndexerServiceFFI::new(core, ingest_handle, runtime)) } /// Stops and frees the resources associated with the given indexer service. diff --git a/lez/indexer/ffi/src/api/logging.rs b/lez/indexer/ffi/src/api/logging.rs index 34bc5f76..06c41688 100644 --- a/lez/indexer/ffi/src/api/logging.rs +++ b/lez/indexer/ffi/src/api/logging.rs @@ -24,10 +24,9 @@ pub unsafe extern "C" fn init_logger(level: *const c_char) { .unwrap_or(LevelFilter::Info) }; - env_logger::Builder::new() + let _dontcare = env_logger::Builder::new() .filter_level(LevelFilter::Off) .filter_module("indexer_ffi", level) .filter_module("indexer_core", level) - .try_init() - .ok(); + .try_init(); } diff --git a/lez/indexer/ffi/src/api/query.rs b/lez/indexer/ffi/src/api/query.rs index 548deabc..23233a76 100644 --- a/lez/indexer/ffi/src/api/query.rs +++ b/lez/indexer/ffi/src/api/query.rs @@ -209,7 +209,7 @@ pub unsafe extern "C" fn query_account( value: account_id.data, }; indexer - .runtime_handle() + .runtime() .block_on( indexer .core() diff --git a/lez/indexer/ffi/src/indexer.rs b/lez/indexer/ffi/src/indexer.rs index 327773b3..619373e6 100644 --- a/lez/indexer/ffi/src/indexer.rs +++ b/lez/indexer/ffi/src/indexer.rs @@ -1,7 +1,9 @@ use std::ffi::c_void; use indexer_core::IndexerCore; -use tokio::{runtime::Handle, task::JoinHandle}; +use tokio::task::JoinHandle; + +use crate::Runtime; /// FFI-owned indexer. /// @@ -9,21 +11,22 @@ use tokio::{runtime::Handle, task::JoinHandle}; /// - An [`IndexerCore`] used to answer queries /// - The background task [`JoinHandle`] that drives ingestion (consuming the block stream so the /// store stays populated) -/// - A [`Handle`] to the runtime they live on. +/// - 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. #[repr(C)] pub struct IndexerServiceFFI { core: *mut c_void, ingest_handle: *mut c_void, - runtime_handle: *mut c_void, + runtime: *mut c_void, } impl IndexerServiceFFI { #[must_use] - pub fn new(core: IndexerCore, ingest_handle: JoinHandle<()>, runtime_handle: Handle) -> Self { + pub fn new(core: IndexerCore, ingest_handle: JoinHandle<()>, runtime: Runtime) -> Self { Self { core: Box::into_raw(Box::new(core)).cast::(), ingest_handle: Box::into_raw(Box::new(ingest_handle)).cast::(), - runtime_handle: Box::into_raw(Box::new(runtime_handle)).cast::(), + runtime: Box::into_raw(Box::new(runtime)).cast::(), } } @@ -38,14 +41,14 @@ impl IndexerServiceFFI { } } - /// Borrow the runtime handle to `block_on` an async store query. + /// Borrow the runtime to `block_on` an async store query. #[must_use] - pub const fn runtime_handle(&self) -> &Handle { + pub const fn runtime(&self) -> &Runtime { unsafe { - self.runtime_handle - .cast::() + self.runtime + .cast::() .as_ref() - .expect("Runtime handle must be a non-null pointer") + .expect("Runtime must be a non-null pointer") } } } @@ -56,7 +59,7 @@ impl Drop for IndexerServiceFFI { let Self { core, ingest_handle, - runtime_handle, + runtime, } = self; if !ingest_handle.is_null() { @@ -68,8 +71,11 @@ impl Drop for IndexerServiceFFI { if !core.is_null() { drop(unsafe { Box::from_raw(core.cast::()) }); } - if !runtime_handle.is_null() { - drop(unsafe { Box::from_raw(runtime_handle.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::()) }); } } }