affaan-m/ECC · critical · anyhow::Error

DB writer channel closed

Error message

DB writer channel closed

What it means

The DbRuntime sends database write commands through an mpsc channel to a dedicated DB writer task. If the channel's receiver is dropped — meaning the writer task has exited or panicked — send() fails and the function returns this error. All subsequent DB operations for this session are broken because there is no writer to process them.

Source

Thrown at ecc2/src/session/runtime.rs:71

    }

    async fn append_output_line(&self, stream: OutputStream, line: String) -> Result<()> {
        self.send(|ack| DbMessage::AppendOutputLine { stream, line, ack })
            .await
    }

    async fn touch_heartbeat(&self) -> Result<()> {
        self.send(|ack| DbMessage::TouchHeartbeat { ack }).await
    }

    async fn send<F>(&self, build: F) -> Result<()>
    where
        F: FnOnce(oneshot::Sender<DbAck>) -> DbMessage,
    {
        let (ack_tx, ack_rx) = oneshot::channel();
        self.tx
            .send(build(ack_tx))
            .map_err(|_| anyhow::anyhow!("DB writer channel closed"))?;

        match ack_rx.await {
            Ok(Ok(())) => Ok(()),
            Ok(Err(error)) => Err(anyhow::anyhow!(error)),
            Err(_) => Err(anyhow::anyhow!("DB writer acknowledgement dropped")),
        }
    }
}

fn run_db_writer(db_path: PathBuf, session_id: String, mut rx: mpsc::UnboundedReceiver<DbMessage>) {
    let (opened, open_error) = match StateStore::open(&db_path) {
        Ok(db) => (Some(db), None),
        Err(error) => (None, Some(error.to_string())),
    };

    while let Some(message) = rx.blocking_recv() {
        match message {
            DbMessage::UpdateState { state, ack } => {

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Check logs from run_db_writer for panic or DB open errors
  2. Verify the DB file path is accessible and not locked by another process
  3. Ensure the Tokio runtime is not being shut down prematurely while sessions are active
  4. Restart the session if the writer task has died — the channel cannot be reopened
  5. Monitor DB writer task health and abort sessions whose writer has failed
Defensive patterns

Strategy: try-catch

Try / catch

match db_runtime.touch_heartbeat().await {
    Ok(()) => Ok(()),
    Err(e) if e.to_string().contains("DB writer channel closed") => {
        tracing::error!("DB writer task has died; session DB operations are broken");
        Err(e)
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: The DB writer task (run_db_writer) has exited due to a panic, DB open failure, or runtime shutdown, and a DbRuntime method (touch_heartbeat, etc.) attempts to send a message through the closed channel.

Common situations: DB writer task panicked during a write operation (e.g., SQLite locked, disk full). Tokio runtime was shut down while sessions are still active. DB file was deleted or moved while the writer was running.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/3d99a74b723f2066. Report an issue: GitHub.