rwf2/Rocket · error · io::Error

Other

Other

Error message

spawn_block panic

What it means

Runtime io error while materializing an incoming multipart file part (TempFile::internal_from): creating the NamedTempFile happens inside task::spawn_blocking, and the JoinHandle returned a JoinError. Rocket maps it to io::ErrorKind::Other with message 'spawn_block panic'. In practice this is almost always the blocking task being cancelled at runtime shutdown rather than a literal panic in tempfile creation.

Source

Thrown at core/lib/src/fs/temp_file.rs:521

        }
    }

    async fn from<'a>(
        req: &Request<'_>,
        data: Data<'_>,
        file_name: Option<&'a FileName>,
        content_type: Option<ContentType>,
    ) -> io::Result<Capped<TempFile<'a>>> {
        let limit = content_type.as_ref()
            .and_then(|ct| ct.extension())
            .and_then(|ext| req.limits().find(["file", ext.as_str()]))
            .or_else(|| req.limits().get("file"))
            .unwrap_or(Limits::FILE);

        let temp_dir = req.rocket().config().temp_dir.relative();
        let file = task::spawn_blocking(move || NamedTempFile::new_in(temp_dir));
        let file = file.await;
        let file = file.map_err(|_| io::Error::new(io::ErrorKind::Other, "spawn_block panic"))??;
        let (file, temp_path) = file.into_parts();

        let mut file = File::from_std(file);
        let fut = data.open(limit).stream_to(tokio::io::BufWriter::new(&mut file));
        let n = fut.await;
        let n = n?;
        let temp_file = TempFile::File {
            content_type, file_name,
            path: Either::Left(temp_path),
            len: n.written,
        };

        Ok(Capped::new(temp_file, n))
    }
}

#[crate::async_trait]
impl<'v> FromFormField<'v> for Capped<TempFile<'v>> {

View on GitHub (pinned to 3a54d079ae)

Solutions

  1. Raise shutdown grace so uploads finish: Rocket.toml [default.shutdown] grace = 30, timeout is separate
  2. Drain/stop accepting uploads before shutdown (e.g. readiness gate in orchestrators)
  3. Check dmesg/logs for resource exhaustion if shutdown is not the cause
  4. Retry the upload client-side on network error — this failure manifests to the client as a dropped connection

Example fix

# before
# Rocket.toml (default grace = 5s, too short for slow uploads)

# after
# Rocket.toml
[default.shutdown]
grace = 30
ctrlc = true
signals = ["term"]
Defensive patterns

Strategy: try-catch

Validate before calling

// fail fast at startup if temp_dir is unusable, instead of per-request
# use std::path::Path;
fn temp_dir_ok(temp_dir: &Path) -> io::Result<()> {
    std::fs::create_dir_all(temp_dir)?;
    let probe = tempfile::Builder::new().prefix("probe").tempfile_in(temp_dir)?;
    drop(probe);
    Ok(())
}

Try / catch

// in the handler; internal_from errors surface as a failed TempFile guard
// register a catcher that distinguishes shutdown noise from real I/O failure
#[catch(500)]
fn internal(r: &Request) -> &'static str {
    error_!("temp file creation failed (spawn_block panic?): {e:?}", e = r.guard::<&std::io::Error>());
    "internal error"
}

Prevention

When it happens

Trigger: A multipart upload with a file part arrives exactly as the server shuts down (grace period expires and the runtime cancels blocking tasks), so the spawn_blocking future is dropped/JoinError; also possible if the process is critically out of resources and the task panics.

Common situations: Deployments that recycle workers mid-upload (Kubernetes rolling updates, systemd restarts); load tests that shut servers down while uploads are in flight; very long uploads with a short shutdown.grace.

Related errors


AI-assisted analysis of rwf2/Rocket@3a54d079ae (2026-08-16). Data as JSON: /api/errors/3ff6e4b39204ea67. Report an issue: GitHub.