@@ -110,7 +110,7 @@ Mutex SQLiteDatabase::g_sqlite_mutex;
110
110
int SQLiteDatabase::g_sqlite_count = 0 ;
111
111
112
112
SQLiteDatabase::SQLiteDatabase (const fs::path& dir_path, const fs::path& file_path, const DatabaseOptions& options, bool mock)
113
- : WalletDatabase(), m_mock(mock), m_dir_path(fs::PathToString(dir_path)), m_file_path(fs::PathToString(file_path)), m_use_unsafe_sync(options.use_unsafe_sync)
113
+ : WalletDatabase(), m_mock(mock), m_dir_path(fs::PathToString(dir_path)), m_file_path(fs::PathToString(file_path)), m_write_semaphore( 1 ), m_use_unsafe_sync(options.use_unsafe_sync)
114
114
{
115
115
{
116
116
LOCK (g_sqlite_mutex);
@@ -408,7 +408,7 @@ void SQLiteBatch::Close()
408
408
bool force_conn_refresh = false ;
409
409
410
410
// If we began a transaction, and it wasn't committed, abort the transaction in progress
411
- if (m_database. HasActiveTxn () ) {
411
+ if (m_txn ) {
412
412
if (TxnAbort ()) {
413
413
LogPrintf (" SQLiteBatch: Batch closed unexpectedly without the transaction being explicitly committed or aborted\n " );
414
414
} else {
@@ -442,6 +442,8 @@ void SQLiteBatch::Close()
442
442
m_database.Close ();
443
443
try {
444
444
m_database.Open ();
445
+ // If TxnAbort failed and we refreshed the connection, the semaphore was not released, so release it here to avoid deadlocks on future writes.
446
+ m_database.m_write_semaphore .post ();
445
447
} catch (const std::runtime_error&) {
446
448
// If open fails, cleanup this object and rethrow the exception
447
449
m_database.Close ();
@@ -493,13 +495,19 @@ bool SQLiteBatch::WriteKey(DataStream&& key, DataStream&& value, bool overwrite)
493
495
if (!BindBlobToStatement (stmt, 1 , key, " key" )) return false ;
494
496
if (!BindBlobToStatement (stmt, 2 , value, " value" )) return false ;
495
497
498
+ // Acquire semaphore if not previously acquired when creating a transaction.
499
+ if (!m_txn) m_database.m_write_semaphore .wait ();
500
+
496
501
// Execute
497
502
int res = sqlite3_step (stmt);
498
503
sqlite3_clear_bindings (stmt);
499
504
sqlite3_reset (stmt);
500
505
if (res != SQLITE_DONE) {
501
506
LogPrintf (" %s: Unable to execute statement: %s\n " , __func__, sqlite3_errstr (res));
502
507
}
508
+
509
+ if (!m_txn) m_database.m_write_semaphore .post ();
510
+
503
511
return res == SQLITE_DONE;
504
512
}
505
513
@@ -511,13 +519,19 @@ bool SQLiteBatch::ExecStatement(sqlite3_stmt* stmt, Span<const std::byte> blob)
511
519
// Bind: leftmost parameter in statement is index 1
512
520
if (!BindBlobToStatement (stmt, 1 , blob, " key" )) return false ;
513
521
522
+ // Acquire semaphore if not previously acquired when creating a transaction.
523
+ if (!m_txn) m_database.m_write_semaphore .wait ();
524
+
514
525
// Execute
515
526
int res = sqlite3_step (stmt);
516
527
sqlite3_clear_bindings (stmt);
517
528
sqlite3_reset (stmt);
518
529
if (res != SQLITE_DONE) {
519
530
LogPrintf (" %s: Unable to execute statement: %s\n " , __func__, sqlite3_errstr (res));
520
531
}
532
+
533
+ if (!m_txn) m_database.m_write_semaphore .post ();
534
+
521
535
return res == SQLITE_DONE;
522
536
}
523
537
@@ -634,30 +648,43 @@ std::unique_ptr<DatabaseCursor> SQLiteBatch::GetNewPrefixCursor(Span<const std::
634
648
635
649
bool SQLiteBatch::TxnBegin ()
636
650
{
637
- if (!m_database.m_db || m_database.HasActiveTxn ()) return false ;
651
+ if (!m_database.m_db || m_txn) return false ;
652
+ m_database.m_write_semaphore .wait ();
653
+ Assert (!m_database.HasActiveTxn ());
638
654
int res = Assert (m_exec_handler)->Exec (m_database, " BEGIN TRANSACTION" );
639
655
if (res != SQLITE_OK) {
640
656
LogPrintf (" SQLiteBatch: Failed to begin the transaction\n " );
657
+ m_database.m_write_semaphore .post ();
658
+ } else {
659
+ m_txn = true ;
641
660
}
642
661
return res == SQLITE_OK;
643
662
}
644
663
645
664
bool SQLiteBatch::TxnCommit ()
646
665
{
647
- if (!m_database.HasActiveTxn ()) return false ;
666
+ if (!m_database.m_db || !m_txn) return false ;
667
+ Assert (m_database.HasActiveTxn ());
648
668
int res = Assert (m_exec_handler)->Exec (m_database, " COMMIT TRANSACTION" );
649
669
if (res != SQLITE_OK) {
650
670
LogPrintf (" SQLiteBatch: Failed to commit the transaction\n " );
671
+ } else {
672
+ m_txn = false ;
673
+ m_database.m_write_semaphore .post ();
651
674
}
652
675
return res == SQLITE_OK;
653
676
}
654
677
655
678
bool SQLiteBatch::TxnAbort ()
656
679
{
657
- if (!m_database.HasActiveTxn ()) return false ;
680
+ if (!m_database.m_db || !m_txn) return false ;
681
+ Assert (m_database.HasActiveTxn ());
658
682
int res = Assert (m_exec_handler)->Exec (m_database, " ROLLBACK TRANSACTION" );
659
683
if (res != SQLITE_OK) {
660
684
LogPrintf (" SQLiteBatch: Failed to abort the transaction\n " );
685
+ } else {
686
+ m_txn = false ;
687
+ m_database.m_write_semaphore .post ();
661
688
}
662
689
return res == SQLITE_OK;
663
690
}
0 commit comments