# 2023 January 31 # # The author disclaims copyright to this source code. In place of # a legal notice, here is a blessing: # # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** # set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix pendingrace # This test file tests that a race condition surrounding hot-journal # rollback that once existed has been resolved. The problem was that # if, when attempting to upgrade from a SHARED to EXCLUSIVE lock in # order to roll back a hot journal, a connection failed to take the # lock, the file-descriptor was left holding a PENDING lock for # a very short amount of time. In a multi-threaded deployment, this # could allow a second connection to read the database without rolling # back the hot journal. # testvfs tvfs db close sqlite3 db test.db -vfs tvfs # Create a 20 page database using connection [db]. Connection [db] uses # Tcl VFS wrapper "tvfs", but it is configured to do straight pass-through # for now. # do_execsql_test 1.0 { PRAGMA cache_size = 5; CREATE TABLE t1(a, b); CREATE INDEX i1 ON t1(a, b); WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<10 ) INSERT INTO t1 SELECT hex(randomblob(100)), hex(randomblob(100)) FROM s; } {} do_test 1.1a { set nPg [db one { PRAGMA page_count }] expr ($nPg==20 || $nPg==21) } 1 # Simulate a crash in another process. This leaves the db with a hot-journal. # Without the journal the db is corrupt. # sqlite3 db2 test.db do_execsql_test -db db2 1.1 { PRAGMA cache_size = 5; BEGIN; UPDATE t1 SET b=hex(randomblob(100)); } db_save db2 close proc my_db_restore {} { forcecopy sv_test.db-journal test.db-journal set fd1 [open sv_test.db r] fconfigure $fd1 -encoding binary -translation binary set data [read $fd1] close $fd1 set fd1 [open test.db w] fconfigure $fd1 -encoding binary -translation binary puts -nonewline $fd1 $data close $fd1 } my_db_restore do_test 1.2 { file exists test.db-journal } {1} # Set up connection [db2] to use Tcl VFS wrapper [tvfs2]. Which is configured # so that the first call to xUnlock() fails. And then all VFS calls thereafter # fail as well. # testvfs tvfs2 tvfs2 filter xUnlock tvfs2 script xUnlock set ::seen_unlock 0 proc xUnlock {args} { if {$::seen_unlock==0} { set ::seen_unlock 1 tvfs2 ioerr 1 1 tvfs2 filter {xLock xUnlock} } return "" } sqlite3 db2 test.db -vfs tvfs2 # Configure [tvfs] (used by [db]) so that within the first call to xAccess, # [db2] attempts to read the db. This causes [db2] to fail to upgrade to # EXCLUSIVE, leaving it with a PENDING lock. Which it holds on to, # as the xUnlock() and all subsequent VFS calls fail. # tvfs filter xAccess tvfs script xAccess set ::seen_access 0 proc xAccess {args} { if {$::seen_access==0} { set ::seen_access 1 catch { db2 eval { SELECT count(*)+0 FROM t1 } } breakpoint } return "" } # Run an integrity check using [db]. do_catchsql_test 1.3 { PRAGMA integrity_check } {1 {database is locked}} db close db2 close tvfs delete tvfs2 delete finish_test