fix(duckdb): explicitly close connections to release file locks on Windows

DuckDB opens database files with exclusive locks on Windows. Previously
connections were only released via Drop, which may not release the file
handle synchronously, causing "file already in use" errors when
reconnecting or testing connections in quick succession.
This commit is contained in:
t8y2 2026-05-27 13:52:56 +08:00
parent bcfad319a6
commit a296bd6c3c
3 changed files with 33 additions and 4 deletions

View File

@ -529,7 +529,9 @@ impl AppState {
.cloned()
.collect();
for key in keys_to_remove {
conns.remove(&key);
if let Some(PoolKind::DuckDb(con)) = conns.remove(&key) {
crate::db::duckdb_driver::close_connection(con);
}
}
}

View File

@ -54,6 +54,30 @@ pub fn is_memory_database_path(path: &str) -> bool {
path.trim().eq_ignore_ascii_case(":memory:")
}
/// Closes a DuckDB connection, releasing the file lock.
///
/// Unlike relying on Drop, this calls `duckdb_disconnect` synchronously
/// so the file handle is released before this function returns.
/// On Windows this prevents "file already in use" errors when reconnecting.
pub fn close_connection(con: Arc<Mutex<duckdb::Connection>>) {
match Arc::try_unwrap(con) {
Ok(mutex) => {
match mutex.into_inner() {
Ok(conn) => {
let _ = conn.close();
}
Err(poisoned) => {
let _ = poisoned.into_inner().close();
}
}
}
Err(_) => {
// Arc still referenced elsewhere (e.g. running queries);
// the last holder will drop and close the connection.
}
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -269,8 +269,9 @@ pub async fn test_connection(state: State<'_, Arc<AppState>>, config: Connection
if state.duckdb_existing_pool_is_usable_for_config(&config).await? {
Ok("Connection successful".to_string())
} else {
db::duckdb_driver::connect_path(&expand_tilde(&config.host))
.map(|_| "Connection successful".to_string())
let con = db::duckdb_driver::connect_path(&expand_tilde(&config.host))?;
dbx_core::db::duckdb_driver::close_connection(con);
Ok("Connection successful".to_string())
}
}
DatabaseType::MongoDb => {
@ -474,7 +475,9 @@ pub async fn disconnect_db(state: State<'_, Arc<AppState>>, connection_id: Strin
PoolKind::Postgres(p) => p.close(),
PoolKind::Sqlite(_) => {}
PoolKind::Redis(_) => {}
PoolKind::DuckDb(_) => {}
PoolKind::DuckDb(con) => {
dbx_core::db::duckdb_driver::close_connection(con);
}
PoolKind::MongoDb(_) => {}
PoolKind::ClickHouse(_) => {}
PoolKind::SqlServer(_) => {}