xai-org/grok-build · error · io::Error

resources persistence writer stopped

Error message

resources persistence writer stopped

What it means

enqueue_save_and_flush sends a SaveAndFlush command to the resources persistence writer over an mpsc channel; if the send fails because the receiver (writer task) has been dropped/stopped, it maps the SendError to io::ErrorKind::BrokenPipe with the message "resources persistence writer stopped". Called by save_and_flush, it means no persistence request can currently be delivered.

Source

Thrown at crates/codegen/xai-grok-tools/src/persistence.rs:167

    /// Replace pending snapshots, write this snapshot, and acknowledge the result.
    pub fn enqueue_save_and_flush(
        &self,
        snapshot: serde_json::Value,
    ) -> io::Result<tokio::sync::oneshot::Receiver<io::Result<()>>> {
        if self.state_path.is_none() {
            let (respond_to, response) = tokio::sync::oneshot::channel();
            let _ = respond_to.send(Ok(()));
            return Ok(response);
        }
        let (respond_to, response) = tokio::sync::oneshot::channel();
        self.tx
            .send(ResourcesPersistenceCommand::SaveAndFlush {
                snapshot,
                respond_to,
            })
            .map_err(|_| {
                io::Error::new(
                    io::ErrorKind::BrokenPipe,
                    "resources persistence writer stopped",
                )
            })?;
        Ok(response)
    }

    /// Await an acknowledgement returned by [`Self::enqueue_save_and_flush`].
    pub async fn await_save_and_flush(
        response: tokio::sync::oneshot::Receiver<io::Result<()>>,
    ) -> io::Result<()> {
        response.await.map_err(|_| {
            io::Error::new(
                io::ErrorKind::BrokenPipe,
                "resources persistence writer dropped acknowledgement",
            )
        })?
    }

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Restart or recreate the persistence writer before attempting further saves.
  2. Check writer task logs for a panic that ended its loop and fix the underlying cause.
  3. Serialize shutdown so save_and_flush is never called after the writer is stopped (drain saves first).
  4. Handle BrokenPipe gracefully by falling back to a direct (non-actor) write or deferring persistence.

Example fix

// before
writer.save_and_flush(snapshot).await?;
// after
match writer.save_and_flush(snapshot).await {
    Ok(()) => {},
    Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
        tracing::warn!("persistence writer stopped; writing directly");
        write_snapshot_direct(&path, &snapshot).await?;
    }
    Err(e) => return Err(e),
}
Defensive patterns

Strategy: fallback

Validate before calling

if writer.is_stopped() {
    return Err(MyError::PersistenceUnavailable);
}

Try / catch

match persistence.save_and_flush(snapshot).await {
    Ok(()) => {},
    Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
        fallback_direct_write(&path, &snapshot).await?;
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling save_and_flush/enqueue_save_and_flush after the persistence writer task has been shut down or joined (e.g. after a stop()/shutdown of the scheduler, or the writer panicked and its loop exited).

Common situations: Attempting to persist state during application shutdown after the writer was already stopped; a writer panic killed the task; calling the persistence handle from a different part of the app that outlives the scheduler.

Related errors


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/26b20fdc8052382e. Report an issue: GitHub.