windmill-labs/windmill · error

isolate result channel closed

Error message

isolate result channel closed

What it means

ExecutingIsolate::wait awaits a oneshot channel carrying the execution result from a dedicated (pre-warmed) Deno isolate thread. If the sender is dropped without sending — the isolate side vanished before producing a result — this error is thrown.

Source

Thrown at backend/windmill-runtime-nativets/src/dedicated.rs:25

    ExecuteError, MainArgs, NativeAnnotation,
};

pub struct PrewarmedResult {
    pub result: Result<Box<RawValue>, String>,
    pub logs: String,
}

pub struct ExecutingIsolate {
    result_rx: tokio::sync::oneshot::Receiver<PrewarmedResult>,
    handle: tokio::task::JoinHandle<anyhow::Result<()>>,
}

impl ExecutingIsolate {
    pub async fn wait(self) -> anyhow::Result<PrewarmedResult> {
        let result = self
            .result_rx
            .await
            .map_err(|_| anyhow::anyhow!("isolate result channel closed"))?;
        self.handle
            .await
            .map_err(|e| anyhow::anyhow!("isolate thread panicked: {e}"))??;
        Ok(result)
    }
}

pub struct PrewarmedIsolate {
    args_tx: Option<tokio::sync::oneshot::Sender<String>>,
    result_rx: Option<tokio::sync::oneshot::Receiver<PrewarmedResult>>,
    ready_rx: Option<tokio::sync::oneshot::Receiver<()>>,
    handle: Option<tokio::task::JoinHandle<anyhow::Result<()>>>,
}

/// Parse a JSON args object and reorder into positional args matching `arg_names`.
fn args_to_positional(args_json: &str, arg_names: &[String]) -> Vec<Option<Box<RawValue>>> {
    let map: HashMap<String, Box<RawValue>> = serde_json::from_str(args_json).unwrap_or_default();
    arg_names

View on GitHub (pinned to e474e8803c)

Solutions

  1. Check worker logs immediately before this error for isolate/runtime shutdown or panic messages
  2. Retry the script execution; a transient isolate teardown is often one-off
  3. Check for OOM or memory-limit kills of the worker process (isolate memory limits)
  4. Upgrade windmill-runtime-nativets / backend to the latest version to get isolate lifecycle fixes
  5. If reproducible, file a bug with the script and worker logs

Example fix

// caller-side hardening
match isolate.wait().await {
    Ok(result) => result,
    Err(e) if e.to_string().contains("channel closed") => {
        // recreate a fresh prewarmed isolate and retry once
        let mut iso = PrewarmedIsolate::new(...)?;
        iso.wait_ready().await?;
        iso.start(args).await?.wait().await?
    }
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: retry

Try / catch

match exec.wait().await {
    Err(e) if e.to_string().contains("isolate result channel closed") => {
        // recreate isolate and retry once
        recreate_and_run(args).await
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling .wait() on an ExecutingIsolate when the isolate's task dropped result_tx without sending: the isolate aborted, was dropped early, or the runtime shut the isolate down before completion.

Common situations: Worker shutdown or SIGKILL while a nativets script is executing; an isolate crash path that skips sending a result; bugs in the dedicated-isolate lifecycle under concurrent load.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/a8717c9a07f1a5df. Report an issue: GitHub.