Skip to content

Commit

Permalink
use panic_hook to retrieve message from a failed WASM execution
Browse files Browse the repository at this point in the history
  • Loading branch information
lostman committed Oct 9, 2023
1 parent 8f7b76a commit 5dcc930
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 28 deletions.
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)
}
}
41 changes: 31 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,43 @@ 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

0 comments on commit 5dcc930

Please sign in to comment.