summaryrefslogtreecommitdiffstats
path: root/test/pendingrace.test
diff options
context:
space:
mode:
Diffstat (limited to 'test/pendingrace.test')
-rw-r--r--test/pendingrace.test123
1 files changed, 123 insertions, 0 deletions
diff --git a/test/pendingrace.test b/test/pendingrace.test
new file mode 100644
index 0000000..ef42578
--- /dev/null
+++ b/test/pendingrace.test
@@ -0,0 +1,123 @@
+# 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