-C Convert\smany\ssqlite3_realloc()\scalls\sto\ssqlite3_realloc64().
-D 2026-03-02T17:11:44.720
+C Avoid\san\sobscure\srace\scondition\sbetween\sa\scheckpointer\sand\sa\swriter\swrapping\saround\sto\sthe\sstart\sof\sthe\swal\sfile.
+D 2026-03-03T17:34:01.231
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F src/vdbevtab.c fc46b9cbd759dc013f0b3724549cc0d71379183c667df3a5988f7e2f1bd485f3
F src/vtab.c 5437ce986db2f70e639ce8a3fe68dcdfe64b0f1abb14eaebecdabd5e0766cc68
F src/vxworks.h 9d18819c5235b49c2340a8a4d48195ec5d5afb637b152406de95a9436beeaeab
-F src/wal.c 505a98fbc599a971d92cb90371cf54546c404cd61e04fd093e7b0c8ff978f9b6
+F src/wal.c 47e0d493ee7e5a9942467bf992f156d407e432af2b80bf0759a0b38d65a6505c
F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452
F src/walker.c d5006d6b005e4ea7302ad390957a8d41ed83faa177e412f89bc5600a7462a014
F src/where.c 9f09ee7b260010138d5f9fb5f195b98051119eae3096a99d72ff16c83230f4af
F test/walpersist.test 8d78a1ec91299163451417b451a2bac3481f8eb9f455b1ca507a6625c927ca6e
F test/walprotocol.test 1b3f922125e341703f6e946d77fdc564d38fb3e07a9385cfdc6c99cac1ecf878
F test/walprotocol2.test 7d3b6b4bf0b12f8007121b1e6ef714bc99101fb3b48e46371df1db868eebc131
+F test/walrestart.test e5caacd7fc0a13055a5f567f11afd99724ab827d8c0bd0670958ffedb6325b8f
F test/walro.test 78a84bc0fdae1385c06b017215c426b6845734d6a5a3ac75c918dd9b801b1b9d
F test/walro2.test 33955a6fd874dd9724005e17f77fef89d334b3171454a1256fe4941a96766cdc
F test/walrofault.test c70cb6e308c443867701856cce92ad8288cd99488fa52afab77cca6cfd51af68
F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c
-P 204f25c882832058c3291d4d6a0a6ac2711c081cb91b19feb2cf94a25ed61ecf a391f5646926787fd9a004225ea406b61d20f282c852c0282fd26cada644b601
-R fd09c97895a4cac92ff79c9e4e8d0ece
-T +closed a391f5646926787fd9a004225ea406b61d20f282c852c0282fd26cada644b601
-U drh
-Z f2554e3cff169f1dfd8e4679b455ef58
+P 88dce64242552e7443d9fb496f6f3ad71dc5b4a882ce21b7ef1d5ea4e26f1e61
+R 7123c2f78c4a83a2b9a28313122c346f
+T *branch * wal-restart-fix
+T *sym-wal-restart-fix *
+T -sym-trunk *
+U dan
+Z f02d11aee2845419e316ead57cf24834
# Remove this line to create a well-formed Fossil manifest.
&& (rc = walBusyLock(pWal,xBusy,pBusyArg,WAL_READ_LOCK(0),1))==SQLITE_OK
){
u32 nBackfill = pInfo->nBackfill;
- pInfo->nBackfillAttempted = mxSafeFrame; SEH_INJECT_FAULT;
-
- /* Sync the WAL to disk */
- rc = sqlite3OsSync(pWal->pWalFd, CKPT_SYNC_FLAGS(sync_flags));
-
- /* If the database may grow as a result of this checkpoint, hint
- ** about the eventual size of the db file to the VFS layer.
- */
- if( rc==SQLITE_OK ){
- i64 nReq = ((i64)mxPage * szPage);
- i64 nSize; /* Current size of database file */
- sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_CKPT_START, 0);
- rc = sqlite3OsFileSize(pWal->pDbFd, &nSize);
- if( rc==SQLITE_OK && nSize<nReq ){
- if( (nSize+65536+(i64)pWal->hdr.mxFrame*szPage)<nReq ){
- /* If the size of the final database is larger than the current
- ** database plus the amount of data in the wal file, plus the
- ** maximum size of the pending-byte page (65536 bytes), then
- ** must be corruption somewhere. */
- rc = SQLITE_CORRUPT_BKPT;
- }else{
- sqlite3OsFileControlHint(pWal->pDbFd, SQLITE_FCNTL_SIZE_HINT,&nReq);
+ WalIndexHdr *pLive = (WalIndexHdr*)walIndexHdr(pWal);
+
+ /* Now that read-lock slot 0 is locked, check that the wal has not been
+ ** wrapped since the header was read for this checkpoint. If it was, then
+ ** there was no work to do anyway. In this case the
+ ** (pInfo->nBackfill<pWal->hdr.mxFrame) test above only passed because
+ ** pInfo->nBackfill had already been set to 0 by the writer that wrapped
+ ** the wal file. It would also be dangerous to proceed, as there may be
+ ** fewer than pWal->hdr.mxFrame valid frames in the wal file. */
+ if( 0==memcmp(pLive->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt)) ){
+
+ pInfo->nBackfillAttempted = mxSafeFrame; SEH_INJECT_FAULT;
+
+ /* Sync the WAL to disk */
+ rc = sqlite3OsSync(pWal->pWalFd, CKPT_SYNC_FLAGS(sync_flags));
+
+ /* If the database may grow as a result of this checkpoint, hint
+ ** about the eventual size of the db file to the VFS layer.
+ */
+ if( rc==SQLITE_OK ){
+ i64 nReq = ((i64)mxPage * szPage);
+ i64 nSize; /* Current size of database file */
+ sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_CKPT_START, 0);
+ rc = sqlite3OsFileSize(pWal->pDbFd, &nSize);
+ if( rc==SQLITE_OK && nSize<nReq ){
+ if( (nSize+65536+(i64)pWal->hdr.mxFrame*szPage)<nReq ){
+ /* If the size of the final database is larger than the current
+ ** database plus the amount of data in the wal file, plus the
+ ** maximum size of the pending-byte page (65536 bytes), then
+ ** must be corruption somewhere. */
+ rc = SQLITE_CORRUPT_BKPT;
+ }else{
+ sqlite3OsFileControlHint(
+ pWal->pDbFd, SQLITE_FCNTL_SIZE_HINT, &nReq);
+ }
}
+
}
-
- }
-
- /* Iterate through the contents of the WAL, copying data to the db file */
- while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){
- i64 iOffset;
- assert( walFramePgno(pWal, iFrame)==iDbpage );
- SEH_INJECT_FAULT;
- if( AtomicLoad(&db->u1.isInterrupted) ){
- rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_INTERRUPT;
- break;
- }
- if( iFrame<=nBackfill || iFrame>mxSafeFrame || iDbpage>mxPage ){
- continue;
- }
- iOffset = walFrameOffset(iFrame, szPage) + WAL_FRAME_HDRSIZE;
- /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL file */
- rc = sqlite3OsRead(pWal->pWalFd, zBuf, szPage, iOffset);
- if( rc!=SQLITE_OK ) break;
- iOffset = (iDbpage-1)*(i64)szPage;
- testcase( IS_BIG_INT(iOffset) );
- rc = sqlite3OsWrite(pWal->pDbFd, zBuf, szPage, iOffset);
- if( rc!=SQLITE_OK ) break;
- }
- sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_CKPT_DONE, 0);
-
- /* If work was actually accomplished... */
- if( rc==SQLITE_OK ){
- if( mxSafeFrame==walIndexHdr(pWal)->mxFrame ){
- i64 szDb = pWal->hdr.nPage*(i64)szPage;
- testcase( IS_BIG_INT(szDb) );
- rc = sqlite3OsTruncate(pWal->pDbFd, szDb);
- if( rc==SQLITE_OK ){
- rc = sqlite3OsSync(pWal->pDbFd, CKPT_SYNC_FLAGS(sync_flags));
+
+ /* Iterate through the contents of the WAL, copying data to the
+ ** db file */
+ while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){
+ i64 iOffset;
+ assert( walFramePgno(pWal, iFrame)==iDbpage );
+ SEH_INJECT_FAULT;
+ if( AtomicLoad(&db->u1.isInterrupted) ){
+ rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_INTERRUPT;
+ break;
}
+ if( iFrame<=nBackfill || iFrame>mxSafeFrame || iDbpage>mxPage ){
+ continue;
+ }
+ iOffset = walFrameOffset(iFrame, szPage) + WAL_FRAME_HDRSIZE;
+ /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL file */
+ rc = sqlite3OsRead(pWal->pWalFd, zBuf, szPage, iOffset);
+ if( rc!=SQLITE_OK ) break;
+ iOffset = (iDbpage-1)*(i64)szPage;
+ testcase( IS_BIG_INT(iOffset) );
+ rc = sqlite3OsWrite(pWal->pDbFd, zBuf, szPage, iOffset);
+ if( rc!=SQLITE_OK ) break;
}
+ sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_CKPT_DONE, 0);
+
+ /* If work was actually accomplished... */
if( rc==SQLITE_OK ){
- AtomicStore(&pInfo->nBackfill, mxSafeFrame); SEH_INJECT_FAULT;
+ if( mxSafeFrame==walIndexHdr(pWal)->mxFrame ){
+ i64 szDb = pWal->hdr.nPage*(i64)szPage;
+ testcase( IS_BIG_INT(szDb) );
+ rc = sqlite3OsTruncate(pWal->pDbFd, szDb);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3OsSync(pWal->pDbFd, CKPT_SYNC_FLAGS(sync_flags));
+ }
+ }
+ if( rc==SQLITE_OK ){
+ AtomicStore(&pInfo->nBackfill, mxSafeFrame); SEH_INJECT_FAULT;
+ }
}
}
sqlite3OsUnfetch(pWal->pDbFd, 0, 0);
}
}
-
+
/* Copy data from the log to the database file. */
if( rc==SQLITE_OK ){
+ sqlite3FaultSim(660);
if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){
rc = SQLITE_CORRUPT_BKPT;
}else if( eMode2!=SQLITE_CHECKPOINT_NOOP ){
--- /dev/null
+# 2026-03-03
+#
+# 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.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this file is testing a race condition in WAL restart.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+if {$::tcl_platform(platform) ne "unix"} {
+ # This test only works on unix
+ finish_test
+ return
+}
+set testprefix walrestart
+
+db close
+sqlite3_shutdown
+
+proc faultsim {n} { return 0 }
+sqlite3_test_control_fault_install faultsim
+
+# Populate database. Create a large wal file and checkpoint it.
+#
+reset_db
+do_execsql_test 1.0 {
+ PRAGMA auto_vacuum = 0;
+ PRAGMA journal_mode = wal;
+ CREATE TABLE t1(a INTEGER PRIMARY KEY, b);
+ WITH s(i) AS (
+ SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<20
+ )
+ INSERT INTO t1 SELECT NULL, randomblob(600) FROM s;
+ CREATE INDEX i1 ON t1(b);
+ PRAGMA wal_checkpoint;
+} {wal 0 49 49}
+do_execsql_test 1.1 {
+ UPDATE t1 SET b=randomblob(600);
+ PRAGMA wal_checkpoint;
+} {0 45 45}
+
+# We have a completely checkpointed wal file on disk. mxFrame=$large,
+# nBackfill=$large. Do another checkpoint with [db]. This time, after [db]
+# reads mxFrame but before it reads nBackfill, write to the db such
+# that 0 < mxFrame < large.
+#
+proc faultsim {n} {
+ if {$n==660} {
+ db2 eval { UPDATE t1 SET b=randomblob(600) WHERE a<5 }
+ }
+ return 0
+}
+sqlite3 db2 test.db
+do_execsql_test 1.3 {
+ PRAGMA wal_checkpoint;
+} {0 45 0}
+
+# Now write another big update to the wal file and checkpoint it.
+#
+do_execsql_test -db db2 1.3 {
+ UPDATE t1 SET b=randomblob(600);
+}
+proc faultsim {n} { return 0 }
+do_execsql_test 1.4 {
+ PRAGMA wal_checkpoint;
+} {0 58 58}
+
+do_catchsql_test 1.5 {
+ PRAGMA integrity_check
+} {0 ok}
+
+finish_test