Files
pnpm/pnpr/crates/auth/tests/libsql_connection_close.rs
Zoltan KochanandClaude Opus 5.5 9217e19c2f fix(pnpr): close each libsql connection once (#17366)
Windows Rust CI aborted pnpr-auth libsql tests with 0xc0000005 (four
times recently: registration_cap_is_strict_across_backend_instances
three times, sync_server::bulk_revocation_refreshes_a_replica_whose_tokens_went_elsewhere
once).

libsql's LibsqlConnection::drop calls disconnect() and then the inner
local::Connection's own Drop calls disconnect() again. Both see the
drop_ref Arc as unique, so sqlite3_close_v2 runs a second time on a
handle SQLite already freed. Usually it reads freed memory as "closed"
and reports misuse. When another thread reused that memory, it read
garbage and crashed. The tests that crashed run several connections
concurrently, which makes reuse likely.

Upstream fixed it in tursodatabase/libsql#2261 (released in 0.10.0),
which nulls the handle after the first close. pnpr stays on the pnpm
fork because tursodatabase/libsql#2301 is still unreleased, so the fix
is cherry-picked onto the fork's pnpm-0.9.30 branch.

The new test installs an SQLite log callback, drops one connection and
asserts no misuse was logged. It logged two before the fork bump.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 12:04:53 +01:00

40 lines
1.4 KiB
Rust

//! In a binary of its own: the `SQLite` log callback must be installed before
//! any test in the process opens a database.
#![cfg(feature = "backend-libsql")]
use libsql::ffi;
use std::{
ffi::{c_char, c_int, c_void},
sync::atomic::{AtomicUsize, Ordering},
};
static MISUSES: AtomicUsize = AtomicUsize::new(0);
extern "C" fn count_misuse(_: *mut c_void, code: c_int, _: *const c_char) {
if code & 0xff == ffi::SQLITE_MISUSE {
MISUSES.fetch_add(1, Ordering::SeqCst);
}
}
/// A second `sqlite3_close_v2` on a freed handle reads freed memory, which
/// aborted the libsql tests on Windows with `0xc0000005`. `SQLite` reports a
/// close of an already closed handle as misuse.
#[tokio::test]
async fn a_dropped_connection_is_closed_once() {
let log: extern "C" fn(*mut c_void, c_int, *const c_char) = count_misuse;
// SAFETY: no database is open yet, so `SQLite` is not initialized and
// accepts configuration.
let configured =
unsafe { ffi::sqlite3_config(ffi::SQLITE_CONFIG_LOG, log, std::ptr::null_mut::<c_void>()) };
assert_eq!(configured, ffi::SQLITE_OK);
let directory = tempfile::tempdir().unwrap();
let db = libsql::Builder::new_local(directory.path().join("auth.db")).build().await.unwrap();
let conn = db.connect().unwrap();
conn.execute("CREATE TABLE t (x INTEGER)", ()).await
.unwrap();
drop(conn);
assert_eq!(MISUSES.load(Ordering::SeqCst), 0);
}