fix(DBDriver): resolve lock-order inversion between dbSafeAccess and trashes mutexes (#1775)

* fix(DBDriver): resolve lock-order inversion between dbSafeAccess and trashes mutexes

emptyTrashes() acquired _dbSafeAccessMutex while holding _trashesMutex
(M1->M0), whereas the load() path acquires _dbSafeAccessMutex and then,
inside loadQuery()->getLastWordId(), acquires _trashesMutex (M0->M1).
This opposite nesting forms a lock-order cycle that ThreadSanitizer flags
as a potential deadlock.

Acquire _dbSafeAccessMutex only after releasing _trashesMutex, matching
the sequential 'look in trash, then database' pattern used by every other
DBDriver accessor (getLastWordId, getLastMapId, getInvertedIndexNi, ...).

Refs: #1765

* Fxing the actual deadlock

* Added doc

---------

Co-authored-by: matlabbe <[email protected]>
This commit is contained in:
Golitsin Vyacheslav
2026-09-27 22:43:06 -07:00
committed by GitHub
co-authored by matlabbe
parent 5c9cfa98fe
commit c59e0d7c35
2 changed files with 11 additions and 2 deletions
+2 -2
View File
@@ -3624,8 +3624,8 @@ void DBDriverSqlite3::loadQuery(VWDictionary & dictionary, bool lastStateOnly, b
rc = sqlite3_finalize(ppStmt);
UASSERT_MSG(rc == SQLITE_OK, uFormat("DB error (%s): %s", _version.c_str(), sqlite3_errmsg(_ppDb)).c_str());
// Get Last word id
getLastWordId(id);
// Get Last word id (query directly: _dbSafeAccessMutex is already locked by DBDriver::load())
getLastIdQuery("Word", id);
dictionary.setLastWordId(id);
if(!idsOnly && uStrNumCmp(_version, "0.23.0") >= 0) {