From 6dd9f9c34f59e7b26899fc077c45f5ae29c9ac9f Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Tue, 22 Sep 2026 01:27:43 -0300 Subject: [PATCH] feat(workers): report a failed worker creation separately from a failed module InvalidWorkerCreation covered three unrelated outcomes: the pool dropped the sender, the worker did not come up before the pool gave up, and the module threw while being evaluated. Only the last is the caller's own code failing, and an embedder had no way to tell them apart. The pool now sends a typed WorkerCreationTimeout instead of a message built from a string, so the two cases where no worker was produced can be matched on and reported as WorkerUnavailable. InvalidWorkerCreation keeps its name and now means only that evaluating the module failed. The request path lists the new variant explicitly rather than widening its match to a wildcard, so a future variant still has to be decided there. Two integration tests asserted the old creation-timeout message and now expect the new name. --- crates/base/src/worker/pool.rs | 2 +- crates/base/tests/integration_tests.rs | 6 +++--- ext/workers/errors.rs | 3 +++ ext/workers/lib.rs | 21 +++++++++++++++++---- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/crates/base/src/worker/pool.rs b/crates/base/src/worker/pool.rs index 88fca9f9e..8dce10fe1 100644 --- a/crates/base/src/worker/pool.rs +++ b/crates/base/src/worker/pool.rs @@ -408,7 +408,7 @@ impl WorkerPool { () = &mut wait_timeout => { if tx - .send(Err(anyhow!("worker did not respond in time"))) + .send(Err(anyhow!(WorkerError::WorkerCreationTimeout))) .is_err() { error!("main worker receiver dropped"); diff --git a/crates/base/tests/integration_tests.rs b/crates/base/tests/integration_tests.rs index 0cc559a6c..730a2db9f 100644 --- a/crates/base/tests/integration_tests.rs +++ b/crates/base/tests/integration_tests.rs @@ -1003,8 +1003,8 @@ async fn req_failure_case_timeout() { if !found_timeout { let buf = to_bytes(res.body_mut()).await.unwrap(); let status_500 = res.status() == StatusCode::INTERNAL_SERVER_ERROR; - let valid_output = - buf == "{\"msg\":\"InvalidWorkerCreation: worker did not respond in time\"}"; + let valid_output = buf + == "{\"msg\":\"WorkerUnavailable: worker did not respond in time\"}"; found_timeout = status_500 && valid_output; } @@ -1181,7 +1181,7 @@ async fn req_failure_case_wall_clock_reached_less_than_100ms() { assert!( buf == "{\"msg\":\"InvalidWorkerResponse: user worker failed to respond\"}" - || buf == "{\"msg\":\"InvalidWorkerCreation: worker did not respond in time\"}" + || buf == "{\"msg\":\"WorkerUnavailable: worker did not respond in time\"}" || buf == "{\"msg\":\"WorkerRequestCancelled: request has been cancelled by supervisor\"}" ); diff --git a/ext/workers/errors.rs b/ext/workers/errors.rs index 53952ca67..4fb18a691 100644 --- a/ext/workers/errors.rs +++ b/ext/workers/errors.rs @@ -15,4 +15,7 @@ pub enum WorkerError { WorkerAlreadyRetired, #[error("request timed out")] RequestIdleTimeout, + /// The worker did not come up before the pool gave up waiting for it. + #[error("worker did not respond in time")] + WorkerCreationTimeout, } diff --git a/ext/workers/lib.rs b/ext/workers/lib.rs index 15e61849b..3650e3317 100644 --- a/ext/workers/lib.rs +++ b/ext/workers/lib.rs @@ -302,17 +302,24 @@ pub async fn op_user_worker_create( }; match result_rx.await { + // The pool dropped the sender, so no worker was produced and the module was + // never evaluated. Reported separately from a module that failed to + // evaluate, because only the latter is the caller's own code failing. Err(err) => Err(custom_error( - "InvalidWorkerCreation", + "WorkerUnavailable", format!( "{:#}", AnyError::from(err).context("failed to create worker") ), )), - Ok(Err(err)) => { - Err(custom_error("InvalidWorkerCreation", format!("{err:#}"))) - } + Ok(Err(err)) => Err(match err.downcast_ref::() { + Some(WorkerError::WorkerCreationTimeout) => { + custom_error("WorkerUnavailable", format!("{err:#}")) + } + // Anything else reaching here came out of evaluating the module. + _ => custom_error("InvalidWorkerCreation", format!("{err:#}")), + }), Ok(Ok(v)) => Ok((v.key.to_string(), v.reused)), } } @@ -646,6 +653,12 @@ pub async fn op_user_worker_fetch_send( err.to_string(), )); } + // Raised while creating a worker, so it cannot reach a request that + // already has one. Listed rather than folded into a wildcard so a new + // variant still has to be decided here. + Some(err @ WorkerError::WorkerCreationTimeout) => { + return Err(custom_error("InvalidWorkerResponse", err.to_string())); + } None => { return Err(custom_error("InvalidWorkerResponse", err.to_string()));