Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhancement: use panic_hook to retrieve message from a failed WASM execution #1404

Merged
merged 1 commit into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions packages/fuel-indexer-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub enum WasmIndexerError {
GetStringFailed,
AllocMissing,
AllocFailed,
Panic,
GeneralError,
}

Expand All @@ -70,6 +71,7 @@ impl From<u32> for WasmIndexerError {
10 => Self::GetStringFailed,
11 => Self::AllocMissing,
12 => Self::AllocFailed,
13 => Self::Panic,
_ => Self::GeneralError,
}
}
Expand All @@ -79,10 +81,10 @@ impl std::fmt::Display for WasmIndexerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SerializationError => {
write!(f, "Failed to serialize object.")
write!(f, "Failed to serialize object")
}
Self::DeserializationError => {
write!(f, "Failed to deserialize object.")
write!(f, "Failed to deserialize object")
}
Self::UnableToSaveListType => {
write!(f, "Failed to save list")
Expand All @@ -96,7 +98,7 @@ impl std::fmt::Display for WasmIndexerError {
Self::KillSwitch => {
write!(
f,
"Indexer kill switch has been triggered. Indexer will halt."
"Indexer kill switch has been triggered. Indexer will halt"
)
}
Self::DatabaseError => {
Expand All @@ -106,21 +108,24 @@ impl std::fmt::Display for WasmIndexerError {
write!(f, "Some blocks are missing")
}
Self::InvalidLogLevel => {
write!(f, "Invalid log level.")
write!(f, "Invalid log level")
}
Self::GetObjectIdFailed => {
write!(f, "get_object_id failed.")
write!(f, "get_object_id failed")
}
Self::GetStringFailed => {
write!(f, "get_string failed.")
write!(f, "get_string failed")
}
Self::AllocMissing => {
write!(f, "alloc is missing.")
write!(f, "alloc is missing")
}
Self::AllocFailed => {
write!(f, "alloc failed.")
write!(f, "alloc failed")
}
Self::GeneralError => write!(f, "Some unspecified WASM error occurred."),
Self::Panic => {
write!(f, "Panic")
}
Self::GeneralError => write!(f, "Some unspecified WASM error occurred"),
}
}
}
Expand Down
42 changes: 42 additions & 0 deletions packages/fuel-indexer-macros/src/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ pub fn handler_block_wasm(
) -> proc_macro2::TokenStream {
let wasm_prelude = wasm_prelude();

let panic_hook = panic_hook();

quote! {

#wasm_prelude

#panic_hook

#[no_mangle]
fn handle_events(blob: *mut u8, len: usize) {
register_panic_hook();
use fuel_indexer_utils::plugin::deserialize;
let bytes = unsafe { Vec::from_raw_parts(blob, len, len) };
let blocks: Vec<BlockData> = match deserialize(&bytes) {
Expand All @@ -29,6 +34,43 @@ pub fn handler_block_wasm(
}
}

/// Panic hook for the indexer.
///
/// When a panic occurs, the message is stored in a `static mut` `String` and a
/// `WasmIndexerError::Panic` error code is returned. The message is then
/// retrieved by the indexer service and logged.
fn panic_hook() -> proc_macro2::TokenStream {
quote! {
static mut PANIC_MESSAGE: String = String::new();

#[no_mangle]
fn get_panic_message_ptr() -> *const u8 {
unsafe { PANIC_MESSAGE.as_ptr() }
}

#[no_mangle]
fn get_panic_message_len() -> u32 {
unsafe { PANIC_MESSAGE.len() as u32 }
}

#[no_mangle]
fn register_panic_hook() {
use std::panic;
use std::sync::Once;
static SET_HOOK: Once = Once::new();

SET_HOOK.call_once(|| {
panic::set_hook(Box::new(|info| {
unsafe {
PANIC_MESSAGE = info.to_string();
}
early_exit(WasmIndexerError::Panic);
}));
});
}
}
}

/// Prelude imports for the _indexer_ module.
///
/// These imports are placed below the top-level lib imports, so any
Expand Down
42 changes: 33 additions & 9 deletions packages/fuel-indexer/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,21 @@ pub fn run_executor<T: 'static + Executor + Send + Sync>(

if let Err(e) = result {
if let IndexerError::RuntimeError(ref e) = e {
if let Some(&WasmIndexerError::MissingBlocksError) =
e.downcast_ref::<WasmIndexerError>()
{
return Err(anyhow::format_err!(
"Indexer({indexer_uid}) terminating due to missing blocks."
)
.into());
match e.downcast_ref::<WasmIndexerError>() {
Some(&WasmIndexerError::MissingBlocksError) => {
return Err(anyhow::format_err!(
"Indexer({indexer_uid}) terminating due to missing blocks."
)
.into());
}
Some(&WasmIndexerError::Panic) => {
let message = executor
.get_panic_message()
.await
.unwrap_or("unknown".to_string());
return Err(anyhow::anyhow!("Indexer({indexer_uid}) terminating due to a panic:\n{message}").into());
}
_ => (),
}
}
// Run time metering is deterministic. There is no point in retrying.
Expand Down Expand Up @@ -556,6 +564,8 @@ where
fn manifest(&self) -> &Manifest;

fn kill_switch(&self) -> &Arc<AtomicBool>;

async fn get_panic_message(&self) -> IndexerResult<String>;
}

/// WASM indexer runtime environment responsible for fetching/saving data to and from the database.
Expand Down Expand Up @@ -689,6 +699,13 @@ where
fn manifest(&self) -> &Manifest {
&self.manifest
}

async fn get_panic_message(&self) -> IndexerResult<String> {
return Err(anyhow::anyhow!(
"get_panic_message() not supported in native exetutor."
)
.into());
}
}

/// WASM executors are the primary means of execution.
Expand Down Expand Up @@ -769,10 +786,11 @@ impl WasmIndexExecutor {
// FunctionEnvMut and StoreMut must be scoped because they can't
// be used across await
{
let schema_version_from_wasm = ffi::get_version(&mut store, &instance)?;

let mut env_mut = env.clone().into_mut(&mut store);
let (data_mut, mut store_mut) = env_mut.data_and_store_mut();

let schema_version_from_wasm = ffi::get_version(&mut store_mut, &instance)?;
let (data_mut, store_mut) = env_mut.data_and_store_mut();

if schema_version_from_wasm != schema_version {
return Err(IndexerError::SchemaVersionMismatch(format!(
Expand Down Expand Up @@ -973,4 +991,10 @@ impl Executor for WasmIndexExecutor {
fn manifest(&self) -> &Manifest {
&self.manifest
}

async fn get_panic_message(&self) -> IndexerResult<String> {
let mut store = self.store.lock().await;
let result = ffi::get_panic_message(&mut store, &self.instance)?;
Ok(result)
}
}
45 changes: 35 additions & 10 deletions packages/fuel-indexer/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use fuel_indexer_types::ffi::{
use thiserror::Error;
use tracing::{debug, error, info, trace, warn};
use wasmer::{
ExportError, Exports, Function, FunctionEnvMut, Instance, MemoryView, RuntimeError,
Store, StoreMut, WasmPtr,
AsStoreMut, ExportError, Exports, Function, FunctionEnvMut, Instance, MemoryView,
RuntimeError, Store, WasmPtr,
};
use wasmer_middlewares::metering::{
get_remaining_points, set_remaining_points, MeteringPoints,
Expand All @@ -35,22 +35,47 @@ pub enum FFIError {
None(String),
}

/// Get the version of the indexer schema stored in the WASM instance.
pub fn get_version(store: &mut StoreMut, instance: &Instance) -> FFIResult<String> {
/// Get a string stored in the WASM module.
fn get_string_from_instance(
store: &mut Store,
instance: &Instance,
ptr_fn_name: &str,
len_fn_name: &str,
) -> FFIResult<String> {
let exports = &instance.exports;

let ptr = exports.get_function("get_version_ptr")?.call(store, &[])?[0]
let ptr = exports
.get_function(ptr_fn_name)?
.call(&mut store.as_store_mut(), &[])?[0]
.i32()
.ok_or_else(|| FFIError::None("get_version".to_string()))? as u32;
.ok_or_else(|| FFIError::None(ptr_fn_name.to_string()))? as u32;

let len = exports.get_function("get_version_len")?.call(store, &[])?[0]
let len = exports
.get_function(len_fn_name)?
.call(&mut store.as_store_mut(), &[])?[0]
.i32()
.ok_or_else(|| FFIError::None("get_version".to_string()))? as u32;
.ok_or_else(|| FFIError::None(len_fn_name.to_string()))? as u32;

let memory = exports.get_memory("memory")?.view(store);
let version = get_string(&memory, ptr, len)?;

Ok(version)
let result = get_string(&memory, ptr, len)?;

Ok(result)
}

/// Get the version of the indexer schema stored in the WASM instance.
pub fn get_panic_message(store: &mut Store, instance: &Instance) -> FFIResult<String> {
get_string_from_instance(
store,
instance,
"get_panic_message_ptr",
"get_panic_message_len",
)
}

/// Get the version of the indexer schema stored in the WASM instance.
pub fn get_version(store: &mut Store, instance: &Instance) -> FFIResult<String> {
get_string_from_instance(store, instance, "get_version_ptr", "get_version_len")
}

/// Fetch the string at the given pointer from memory.
Expand Down
Loading