rwf2/Rocket · error · io::Error

BrokenPipe

BrokenPipe

Error message

spawn_block

What it means

Runtime io error from TempFile::persist_to: the blocking call to NamedTempFile::persist was moved onto task::spawn_blocking, and awaiting the JoinHandle returned a JoinError (the blocking task panicked or the runtime cancelled it). Rocket maps that JoinError to io::ErrorKind::BrokenPipe with message 'spawn_block', surfacing it from persist_to before the persist result is even inspected.

Source

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

    ///     file.persist_to(&some_path).await?;
    ///     assert_eq!(file.path(), Some(&*some_path));
    ///
    ///     Ok(())
    /// }
    /// # let file = TempFile::Buffered { content: "hi".as_bytes() };
    /// # rocket::async_test(handle(file)).unwrap();
    /// ```
    pub async fn persist_to<P>(&mut self, path: P) -> io::Result<()>
        where P: AsRef<Path>
    {
        let new_path = path.as_ref().to_path_buf();
        match self {
            TempFile::File { path: either, .. } => {
                let path = mem::replace(either, Either::Right(new_path.clone()));
                match path {
                    Either::Left(temp) => {
                        let result = task::spawn_blocking(move || temp.persist(new_path)).await
                            .map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "spawn_block"))?;

                        if let Err(e) = result {
                            *either = Either::Left(e.path);
                            return Err(e.error);
                        }
                    },
                    Either::Right(prev) => {
                        if let Err(e) = fs::rename(&prev, new_path).await {
                            *either = Either::Right(prev);
                            return Err(e);
                        }
                    }
                }
            }
            TempFile::Buffered { content } => {
                fs::write(&new_path, &content).await?;
                *self = TempFile::File {
                    file_name: None,

View on GitHub (pinned to 3a54d079ae)

Solutions

  1. Make temp_dir and the persist target live on the same filesystem/device (set temp_dir in Rocket.toml to a dir on the target volume)
  2. Upgrade rocket (and thus tempfile) so cross-filesystem persist is handled by copy+delete instead of panicking
  3. Avoid issuing persist_to after initiating shutdown; drain in-flight uploads before dropping the runtime
  4. Inspect the original error: io error kinds here usually wrap a panic — check logs for the panic message above this error

Example fix

# before (temp on tmpfs, target on disk → cross-device panic)
# Rocket.toml defaults: temp_dir = /tmp
file.persist_to("/var/data/uploads/x.png").await?;

# after
# Rocket.toml
[default]
temp_dir = "/var/data/tmp"

file.persist_to("/var/data/uploads/x.png").await?;
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure target dir exists and is on the same device as temp_dir before persisting
fn can_persist(temp_dir: &Path, target: &Path) -> io::Result<()> {
    std::fs::create_dir_all(target.parent().unwrap())?;
    let a = temp_dir.metadata()?.dev(); // unix: use std::os::unix::fs::MetadataExt
    let b = target.parent().unwrap().metadata()?.dev();
    if a != b { return Err(io::Error::new(io::ErrorKind::CrossesDevices, "temp and target on different filesystems")); }
    Ok(())
}

Try / catch

match file.persist_to(&dest).await {
    Ok(()) => {}
    Err(e) if e.kind() == io::ErrorKind::BrokenPipe => {
        error_!("persist task died (shutdown or panic): {e}");
        return Status::InternalServerError;
    }
    Err(e) => { error_!("persist failed: {e}"); return Status::InternalServerError; }
}

Prevention

When it happens

Trigger: Calling temp_file.persist_to("path").await when the blocking task panics — most commonly because the tempfile crate panics on a persist across filesystems/devices or an invalid target — or when the tokio runtime is shutting down and cancels the blocking task mid-await.

Common situations: Persisting to a path on a different mount than temp_dir (e.g. temp on tmpfs, target on disk) with a tempfile version lacking cross-device handling; calling persist_to during server shutdown; panics inside blocking threads triggered by extreme conditions (out of file descriptors, deleted temp dir).

Related errors


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