transact-rs/sqlx · critical · io::Error (OutOfMemory)

SQLite is unable to allocate memory to hold the sqlite3 obje

Error message

SQLite is unable to allocate memory to hold the sqlite3 object

What it means

`sqlite3_open_v2` returned success-path memory that is `NULL`, meaning SQLite could not allocate the `sqlite3` handle object. sqlx checks this even when the returned status might otherwise look plausible, and raises an `OutOfMemory` I/O error, closing the (uninitialized) handle path immediately.

Source

Thrown at sqlx-sqlite/src/connection/handle.rs:38

// enabled and [SQLITE_THREADSAFE] was enabled when sqlite was compiled. We refuse to work
// if these conditions are not upheld.
//
// <https://www.sqlite.org/c3ref/threadsafe.html>
// <https://www.sqlite.org/c3ref/c_config_covering_index_scan.html#sqliteconfigmultithread>

unsafe impl Send for ConnectionHandle {}

impl ConnectionHandle {
    pub(crate) fn open(filename: &CStr, flags: c_int) -> Result<Self, Error> {
        let mut handle = ptr::null_mut();

        // <https://www.sqlite.org/c3ref/open.html>
        let status = unsafe { sqlite3_open_v2(filename.as_ptr(), &mut handle, flags, ptr::null()) };

        // SAFETY: the database is still initialized as long as the pointer is not `NULL`.
        // We need to close it even if there's an error.
        let mut handle = Self(NonNull::new(handle).ok_or_else(|| {
            Error::Io(io::Error::new(
                io::ErrorKind::OutOfMemory,
                "SQLite is unable to allocate memory to hold the sqlite3 object",
            ))
        })?);

        if status != SQLITE_OK {
            return Err(Error::Database(Box::new(handle.expect_error())));
        }

        // Enable extended result codes
        // https://www.sqlite.org/c3ref/extended_result_codes.html
        unsafe {
            // This only returns a non-OK code if SQLite is built with `SQLITE_ENABLE_API_ARMOR`
            // and the database pointer is `NULL` or already closed.
            //
            // The invariants of this type guarantee that neither is true.
            sqlite3_extended_result_codes(handle.as_ptr(), 1);
        }

View on GitHub (pinned to 03af8bcc57)

Solutions

  1. Free memory in the process (drop caches/pools, fix leaks) and retry the connection.
  2. Raise the memory limit of the container/process (K8s memory limit, `ulimit -v`, JVM/other runtime heap caps).
  3. Reduce connection pool size (`max_connections`) so concurrent opens fit in available memory.
  4. If persistent, restart the process/host — this indicates the allocator could not satisfy a small allocation.

Example fix

// before
let pool = Pool::connect_with(
    SqliteConnectOptions::new().filename("app.db")
).await?; // OOM under tight memory limits with max pool opens

// after
let pool = Pool::builder(
    SqliteConnectOptions::new().filename("app.db")
)
.max_connections(2)
.build()
.await?;
Defensive patterns

Strategy: retry

Validate before calling

fn can_attempt_db_open() -> bool {
    // cheap heuristic: skip the attempt when the system is under severe memory pressure
    !std::path::Path::new("/proc/meminfo").exists()
        || std::fs::read_to_string("/proc/meminfo")
            .map(|s| !s.contains("MemAvailable:          0 kB"))
            .unwrap_or(true)
}

Type guard

// Rust has no runtime type to narrow for a failed allocation;
// model capacity instead.
fn pool_config_is_sane(max_connections: u32) -> bool {
    max_connections > 0 && max_connections <= 64
}

Try / catch

// no exceptions in Rust; wrap connection setup so OOM is not fatal to the process
let pool = std::panic::catch_unwind(|| {
    tokio::runtime::Handle::current().block_on(
        Pool::connect_with(opts.clone())
    )
});
match pool {
    Ok(Ok(p)) => p,
    _ => { free_resources(); retry_with_backoff(); }
}

Prevention

When it happens

Trigger: Opening any SQLite connection (`SqliteConnection::connect`, pools, `AnyDriver` backed by SQLite) when the process is out of memory or address space, or under extreme resource limits (ulimit, cgroup memory cap, containers with tight memory).

Common situations: Memory-constrained Docker/Kubernetes pods, CI runners with tiny RAM limits, memory leaks elsewhere in a long-lived process exhausting the heap before opening a new connection pool.

Related errors


AI-assisted analysis of transact-rs/sqlx@03af8bcc57 (2026-09-03). Data as JSON: /api/errors/d545ed7df0678594. Report an issue: GitHub.