Skip to content

Commit 49511b5

Browse files
Calgooonclaude
andcommitted
fix: set PRAGMA busy_timeout=500 + SpendingLock on relinquishOutput
Same fix as bsv-wallet-cli main: SQLite contention caused ~11% of relinquishOutput calls to fail with SQLITE_BUSY (error 517). Root cause: busy_timeout=0 (instant failure) + relinquishOutput running without SpendingLock (racing with createAction writes). Fix: busy_timeout=500ms on both connections + SpendingLock guard on relinquishOutput. Verified: 0% failure rate after fix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d1876e3 commit 49511b5

2 files changed

Lines changed: 16 additions & 0 deletions

File tree

src/commands/daemon.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use bsv_sdk::wallet::{ListOutputsArgs, WalletInterface};
44
use bsv_wallet_toolbox::{
55
Chain, Monitor, Services, ServicesOptions, StorageSqlx, Wallet, WalletStorageWriter,
66
};
7+
use sqlx::Executor;
78
use std::sync::Arc;
89

910
use crate::cli::Cli;
@@ -22,6 +23,12 @@ pub async fn run(cli: &Cli) -> Result<()> {
2223

2324
let storage = StorageSqlx::open(&cli.db).await?;
2425
storage.make_available().await?;
26+
// Set busy_timeout so concurrent writes wait instead of returning SQLITE_BUSY.
27+
// Without this, relinquishOutput racing with createAction gets error 517 instantly.
28+
sqlx::query("PRAGMA busy_timeout = 500")
29+
.execute(storage.pool())
30+
.await
31+
.ok();
2532

2633
let make_services = |chain: Chain| -> anyhow::Result<Services> {
2734
let mut opts = match chain {
@@ -51,6 +58,10 @@ pub async fn run(cli: &Cli) -> Result<()> {
5158
// so we need to create a separate storage/services for the wallet)
5259
let storage2 = StorageSqlx::open(&cli.db).await?;
5360
storage2.make_available().await?;
61+
sqlx::query("PRAGMA busy_timeout = 500")
62+
.execute(storage2.pool())
63+
.await
64+
.ok();
5465
let services2 = make_services(chain)?;
5566
let wallet = Wallet::new(Some(root_key), storage2, services2)
5667
.await

src/server/handlers.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,12 +707,17 @@ pub async fn list_outputs(
707707
}
708708

709709
/// POST /relinquishOutput
710+
///
711+
/// Acquires SpendingLock because relinquish modifies UTXO state and can race
712+
/// with createAction on SQLite writes (previously caused SQLITE_BUSY errors).
710713
pub async fn relinquish_output(
711714
State(wallet): State<WalletState>,
715+
axum::Extension(spending_lock): axum::Extension<super::SpendingLock>,
712716
headers: HeaderMap,
713717
Json(args): Json<RelinquishOutputArgs>,
714718
) -> Result<Json<RelinquishOutputResult>, AppError> {
715719
let originator = extract_originator(&headers)?;
720+
let _guard = spending_lock.lock().await;
716721
let result = wallet
717722
.relinquish_output(args, &originator)
718723
.await

0 commit comments

Comments
 (0)