diesel-rs/diesel · error

Error closing SQLite blob

Error message

Error closing SQLite blob: {error_message}

What it means

When an SqliteReadOnlyBlob (an incremental-blob-I/O handle) is dropped, diesel calls sqlite3_blob_close via close_inner; on failure it prints (while panicking) or panics with "Error closing SQLite blob: {message}". It means the blob handle could not be released, typically because the underlying connection or row is in a bad state.

Solutions

  1. Scope the blob tightly: read and finish with the blob before any transaction ends or the table is modified.
  2. Drop the blob before the connection (declare the connection binding after blob usage or use explicit blocks).
  3. Avoid writing to the same row/table while a read blob handle is open; re-open the blob after the write instead.
  4. If seen during a panic unwind, fix the originating panic — the blob message is a secondary cleanup diagnostic.

Example fix

// before
let conn = SqliteConnection::establish(url)?;
let blob = SqliteReadOnlyBlob::new(&conn, "t", "data", rowid, 0)?;
// ... table rewritten here -> close fails on drop

// after
let conn = SqliteConnection::establish(url)?;
{
    let blob = SqliteReadOnlyBlob::new(&conn, "t", "data", rowid, 0)?;
    // finish reading before any writes to the row
}
Defensive patterns

Strategy: type-guard

Validate before calling

let blob = SqliteReadOnlyBlob::new(&conn, "t", "data", rowid, 0)?;
// read immediately, then drop(blob) before commit/write

Type guard

fn blob_scope_safe<T>(conn: &SqliteConnection, f: impl FnOnce() -> T) -> T {
    // run all blob work inside one scope; blob cannot outlive this call
    f()
}

Try / catch

std::panic::catch_unwind(AssertUnwindSafe(|| drop(blob))) // on cleanup paths where a second panic must be tolerated

Prevention

When it happens

Trigger: Dropping an SqliteReadOnlyBlob whose close_inner() returns Err — e.g. the underlying connection was invalidated, the row holding the blob was freed, or the blob was already closed/failed mid-read; during normal drop it panics, during unwinding it logs to stderr.

Common situations: Reading a blob from a row while another statement modifies/rewrites the table (incremental blob handles point into the row); holding blob handles across transaction commit/rollback; dropping connection and blob in the wrong order.

Related errors


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

Appendix: source

Thrown at diesel/src/sqlite/connection/sqlite_blob.rs:37

#[cfg_attr(not(feature = "std"), expect(dead_code))]
pub struct SqliteReadOnlyBlob<'conn> {
    pub(crate) blob: core::ptr::NonNull<ffi::sqlite3_blob>,
    pub(crate) read_index: usize,

    pub(crate) blob_size: usize,
    pub(crate) _pd: core::marker::PhantomData<&'conn mut ffi::sqlite3_blob>,
}

impl Drop for SqliteReadOnlyBlob<'_> {
    fn drop(&mut self) {
        use crate::util::std_compat::panicking;

        if let Err(error_message) = self.close_inner() {
            if panicking() {
                #[cfg(feature = "std")]
                eprintln!("Error closing SQLite blob: {error_message}");
            } else {
                panic!("Error closing SQLite blob: {error_message}");
            }
        }
    }
}

impl SqliteReadOnlyBlob<'_> {
    /// Is the blob storage empty
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// The size of the underlying blob in bytes
    pub fn len(&self) -> usize {
        self.blob_size
    }

    /// Close the handle
    ///

View on GitHub (pinned to 6fa6ed01b2)