refactor: better runtime handling + logger lint fix

This commit is contained in:
erhant 2026-06-19 14:25:45 +03:00
parent 4a3fa1d4be
commit 0db82d2344
5 changed files with 47 additions and 55 deletions

View File

@ -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.
*

View File

@ -12,6 +12,8 @@ pub type InitializedIndexerServiceFFIResult = PointerResult<IndexerServiceFFI, O
///
/// # 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
@ -21,7 +23,7 @@ pub type InitializedIndexerServiceFFIResult = PointerResult<IndexerServiceFFI, O
///
/// # 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(no_mangle)]
pub unsafe extern "C" fn start_indexer(
@ -35,20 +37,12 @@ pub unsafe extern "C" fn start_indexer(
)
}
/// Creates a new [`tokio::runtime::Runtime`].
#[unsafe(no_mangle)]
pub extern "C" fn new_runtime() -> PointerResult<Runtime, OperationStatus> {
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<Runtime, 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.
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.

View File

@ -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();
}

View File

@ -209,7 +209,7 @@ pub unsafe extern "C" fn query_account(
value: account_id.data,
};
indexer
.runtime_handle()
.runtime()
.block_on(
indexer
.core()

View File

@ -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::<c_void>(),
ingest_handle: Box::into_raw(Box::new(ingest_handle)).cast::<c_void>(),
runtime_handle: Box::into_raw(Box::new(runtime_handle)).cast::<c_void>(),
runtime: Box::into_raw(Box::new(runtime)).cast::<c_void>(),
}
}
@ -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::<Handle>()
self.runtime
.cast::<Runtime>()
.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::<IndexerCore>()) });
}
if !runtime_handle.is_null() {
drop(unsafe { Box::from_raw(runtime_handle.cast::<Handle>()) });
// 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::<Runtime>()) });
}
}
}