Skip to content

Commit

Permalink
Fix trap handling, and run trap-test with components
Browse files Browse the repository at this point in the history
  • Loading branch information
elliottt committed Jun 11, 2024
1 parent 81f9d2e commit 9dd8d53
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 37 deletions.
14 changes: 11 additions & 3 deletions cli/tests/trap-test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ pub type Error = Box<dyn std::error::Error + Send + Sync>;
/// Handy alias for the return type of async Tokio tests
pub type TestResult = Result<(), Error>;

#[tokio::test(flavor = "multi_thread")]
async fn fatal_error_traps() -> TestResult {
async fn fatal_error_traps_impl(adapt_core_wasm: bool) -> TestResult {
let module_path = format!("{RUST_FIXTURE_PATH}/response.wasm");
let adapt_core_wasm = false;
let ctx = ExecuteCtx::new(
module_path,
ProfilingStrategy::None,
Expand Down Expand Up @@ -45,3 +43,13 @@ async fn fatal_error_traps() -> TestResult {

Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn fatal_error_traps() -> TestResult {
fatal_error_traps_impl(false).await
}

#[tokio::test(flavor = "multi_thread")]
async fn fatal_error_traps_component() -> TestResult {
fatal_error_traps_impl(true).await
}
3 changes: 2 additions & 1 deletion lib/src/component/http_req.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use {
super::{
fastly::api::{http_req, http_types, types},
headers::write_values,
types::TrappableError,
},
crate::{
config::{Backend, ClientCertInfo},
Expand Down Expand Up @@ -223,7 +224,7 @@ impl http_req::Host for Session {
name: String,
max_len: u64,
cursor: u32,
) -> Result<Option<(Vec<u8>, Option<u32>)>, types::Error> {
) -> Result<Option<(Vec<u8>, Option<u32>)>, TrappableError> {
let headers = &self.request_parts(h.into())?.headers;

let values = headers.get_all(HeaderName::from_str(&name)?);
Expand Down
65 changes: 38 additions & 27 deletions lib/src/component/http_resp.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use {
super::fastly::api::{http_resp, http_types, types},
super::headers::write_values,
super::{headers::write_values, types::TrappableError},
crate::{error::Error, session::Session},
cfg_if::cfg_if,
http::{HeaderName, HeaderValue},
hyper::http::response::Response,
std::str::FromStr,
Expand Down Expand Up @@ -79,6 +80,8 @@ impl http_resp::Host for Session {
max_len: u64,
cursor: u32,
) -> Result<Option<(Vec<u8>, Option<u32>)>, types::Error> {
{}

let headers = &self.response_parts(h.into())?.headers;

let (buf, next) = write_values(
Expand Down Expand Up @@ -134,36 +137,44 @@ impl http_resp::Host for Session {
name: String,
max_len: u64,
cursor: u32,
) -> Result<Option<(Vec<u8>, Option<u32>)>, types::Error> {
if name.len() > MAX_HEADER_NAME_LEN {
return Err(Error::InvalidArgument.into());
}

let headers = &self.response_parts(h.into())?.headers;

let values = headers.get_all(HeaderName::from_str(&name)?);

let (buf, next) = write_values(
values.into_iter(),
b'\0',
usize::try_from(max_len).unwrap(),
cursor,
);

if buf.is_empty() {
if next.is_none() {
return Ok(None);
) -> Result<Option<(Vec<u8>, Option<u32>)>, TrappableError> {
cfg_if! {
if #[cfg(feature = "test-fatalerror-config")] {
// Avoid warnings:
let _ = (h, name, max_len, cursor);
return Err(Error::FatalError("A fatal error occurred in the test-only implementation of header_values_get".to_string()).into());
} else {
// It's an error if we couldn't write even a single value.
return Err(Error::BufferLengthError {
buf: "buf",
len: "buf.len()",
if name.len() > MAX_HEADER_NAME_LEN {
return Err(Error::InvalidArgument.into());
}
.into());

let headers = &self.response_parts(h.into())?.headers;

let values = headers.get_all(HeaderName::from_str(&name)?);

let (buf, next) = write_values(
values.into_iter(),
b'\0',
usize::try_from(max_len).unwrap(),
cursor,
);

if buf.is_empty() {
if next.is_none() {
return Ok(None);
} else {
// It's an error if we couldn't write even a single value.
return Err(Error::BufferLengthError {
buf: "buf",
len: "buf.len()",
}
.into());
}
}

Ok(Some((buf, next)))
}
}

Ok(Some((buf, next)))
}

async fn header_values_set(
Expand Down
8 changes: 8 additions & 0 deletions lib/src/component/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ component::bindgen!({
"wasi:io": wasmtime_wasi::bindings::io,
"wasi:cli": wasmtime_wasi::bindings::cli,
},

trappable_error_type: {
"fastly:api/types/error" => types::TrappableError,
},

trappable_imports: [
"header-values-get"
],
});

pub fn link_host_functions(linker: &mut component::Linker<ComponentCtx>) -> anyhow::Result<()> {
Expand Down
44 changes: 42 additions & 2 deletions lib/src/component/types.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,43 @@
use {super::fastly::api::types, crate::session::Session};
use {
super::fastly::api::types,
crate::{
error::{self, HandleError},
session::Session,
},
http::header::InvalidHeaderName,
};

impl types::Host for Session {}
pub enum TrappableError {
Error(types::Error),
Trap(anyhow::Error),
}

impl types::Host for Session {
fn convert_error(&mut self, err: TrappableError) -> wasmtime::Result<types::Error> {
match err {
TrappableError::Error(err) => Ok(err),
TrappableError::Trap(err) => Err(err),
}
}
}

impl From<HandleError> for TrappableError {
fn from(_: HandleError) -> Self {
Self::Error(types::Error::BadHandle)
}
}

impl From<InvalidHeaderName> for TrappableError {
fn from(_: InvalidHeaderName) -> Self {
Self::Error(types::Error::GenericError)
}
}

impl From<error::Error> for TrappableError {
fn from(e: error::Error) -> Self {
match e {
error::Error::FatalError(_) => Self::Trap(anyhow::anyhow!(e.to_string())),
_ => Self::Error(e.into()),
}
}
}
13 changes: 9 additions & 4 deletions lib/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,15 +465,20 @@ impl ExecuteCtx {
let result = compute
.fastly_api_reactor()
.call_serve(&mut store, req.into(), body.into())
.await
.map_err(ExecutionError::Typechecking)?;
.await;

let outcome = match result {
Ok(()) => Ok(()),
Err(()) => {
Ok(Ok(())) => Ok(()),

Ok(Err(())) => {
event!(Level::ERROR, "WebAssembly exited with an error");
Err(ExecutionError::WasmTrap(anyhow::Error::msg("failed")))
}

Err(e) => {
event!(Level::ERROR, "WebAssembly trapped: {:?}", e);
Err(ExecutionError::WasmTrap(e))
}
};

// Ensure the downstream response channel is closed, whether or not a response was
Expand Down

0 comments on commit 9dd8d53

Please sign in to comment.