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
- Restart or recreate the persistence writer before attempting further saves.
- Check writer task logs for a panic that ended its loop and fix the underlying cause.
- Serialize shutdown so save_and_flush is never called after the writer is stopped (drain saves first).
- 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
- Establish an ownership rule: only the scheduler issues saves and stops the writer, in that order.
- Catch panics in the writer task so a single bad write does not stop it.
- Persist critical snapshots before shutdown begins.
- Alert/log on BrokenPipe so the stopped writer is noticed immediately.
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
- resources persistence writer dropped acknowledgement
- resources persistence writer dropped acknowledgement
- Error: Session ID {session_id} is already in use.
- The change was applied, but Doctor could not verify `{}` in
- process scope already closed; fetch killed{}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/26b20fdc8052382e.
Report an issue: GitHub.