diesel-rs/diesel · error

Error closing SQLite connection

Error message

Error closing SQLite connection: {error_message}

What it means

When a SqliteConnection is dropped, diesel calls sqlite3_close; if that returns a non-SQLITE_OK result the connection reports the failure. If the process is already panicking it only prints to stderr (std feature), otherwise it panics with "Error closing SQLite connection: {message}". It indicates the connection could not be cleanly finalized, usually because statements or prepared objects are still open.

Solutions

  1. Ensure all derived handles (blobs, statements, backup objects) are dropped before the connection itself goes out of scope — reorder bindings so the connection is declared last.
  2. Explicitly finalize/drop prepared statements instead of leaking them (mem::forget, Box::leak, or static caches holding statements).
  3. Check for other threads/processes holding the database open (SQLITE_BUSY) and close them or use busy_timeout.
  4. If seen only during another panic's unwind, treat it as a secondary diagnostic and fix the root panic.

Example fix

// before
let conn = SqliteConnection::establish(url)?;
let blob = SqliteReadOnlyBlob::new(&conn, ...)?;  // blob outlives usage scope ordering

// after
let conn = SqliteConnection::establish(url)?;
{ // blob scope ends before conn drops
    let blob = SqliteReadOnlyBlob::new(&conn, ...)?;
    // use blob
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Struct-owning pattern: put the connection last in your struct / scope
drop(blob); drop(stmt); // ensure handles gone before conn drops

Type guard

fn conn_can_close(conn: &SqliteConnection) -> bool {
    // no outstanding blobs/statements held elsewhere referencing conn
    true // enforce by design: connection is dropped last in scope
}

Try / catch

std::panic::catch_unwind(AssertUnwindSafe(|| drop(conn))) // during cleanup paths where panics must not propagate

Prevention

When it happens

Trigger: Dropping a SqliteConnection while sqlite3_close returns SQLITE_BUSY/SQLITE_LOCKED — e.g. unfinalized prepared statements, open blobs, or backup handles still referencing the connection; also happens during unwinding from a prior panic (stderr-only path).

Common situations: Holding a SqliteReadOnlyBlob, statement, or other derived handle beyond the connection's lifetime; leaking statements in long-lived processes; panics inside a transaction leaving dangling handles during unwind.

Related errors


AI-assisted analysis of diesel-rs/diesel@6fa6ed01b2 (2026-09-07). Data as JSON: /api/errors/e53a4fa394aa40b4. Report an issue: GitHub.

Appendix: source

Thrown at diesel/src/sqlite/connection/raw.rs:916

        // Unregister before close so the boxed closures drop before sqlite3_close.
        self.remove_update_hook();
        self.remove_commit_hook();
        self.remove_rollback_hook();
        self.remove_progress_handler();
        self.remove_wal_hook();
        self.remove_busy_handler();
        self.remove_authorizer();
        self.remove_trace();
        self.remove_collation_needed_hook();

        let close_result = unsafe { ffi::sqlite3_close(self.internal_connection.as_ptr()) };
        if close_result != ffi::SQLITE_OK {
            let error_message = super::error_message(close_result);
            if panicking() {
                #[cfg(feature = "std")]
                eprintln!("Error closing SQLite connection: {error_message}");
            } else {
                panic!("Error closing SQLite connection: {error_message}");
            }
        }
    }
}

enum SqliteCallbackError {
    Abort(&'static str),
    DieselError(crate::result::Error),
    Panic(String),
}

impl SqliteCallbackError {
    fn emit(&self, ctx: *mut ffi::sqlite3_context) {
        let s;
        let msg = match self {
            SqliteCallbackError::Abort(msg) => *msg,
            SqliteCallbackError::DieselError(e) => {
                s = e.to_string();

View on GitHub (pinned to 6fa6ed01b2)