-C Handle\scorrupt\sjournal\sfile\sheaders\scorrectly.\s(CVS\s1674)
-D 2004-06-23T01:05:27
+C Test\scases\sto\sverify\srecovery\safter\sa\scrash.\s(CVS\s1675)
+D 2004-06-23T10:43:10
F Makefile.in 0a3d7aaefa50717bd550b0cf568a51072c4c103c
F Makefile.linux-gcc a9e5a0d309fa7c38e7c14d3ecf7690879d3a5457
F README f1de682fbbd94899d50aca13d387d1b3fd3be2dd
F src/os_common.h ba1b7306e16e2091718f2c48db0fe6c1d7a31bb8
F src/os_mac.c 3d31e26be1411acfb7961033098631b4f3486fdf
F src/os_mac.h 51d2445f47e182ed32d3bd6937f81070c6fd9bd4
-F src/os_test.c db4df491bad874c095b1a9d4db346990cfd56ae0
-F src/os_test.h acacfe7e7bb78dd99865f16cfa822426b177d2ab
+F src/os_test.c ab55524911b66395c39ff8f465dfee0ba1ba06e4
+F src/os_test.h 6a26a4978492e4bbdbf385554958418ff02db162
F src/os_unix.c 39e73ed02fc992a6bfc52200ea26704633412cc0
F src/os_unix.h 00c1f82b526ab2fb7ee5ddd555ea4ed68363c93a
F src/os_win.c 84549f6cc815237533c5d0eb3697352b03478d96
F src/os_win.h babd4e912967c6b09088cfe38a45e8005a07ba44
-F src/pager.c 42297421e9e7646f99b332c69f3f8085c1d765bf
+F src/pager.c ec34fbae1a23228cb3743cf7cd8eba1af8e4cd5c
F src/pager.h bc58d32a9dee464f7268fb68652c130a4216e438
F src/parse.y 097438674976355a10cf177bd97326c548820b86
F src/pragma.c 0750e1c360647dbe0a991f16133b0fe5e42e5039
F src/sqliteInt.h dd796b6abc6d50505fe33c54f0143d7000681a41
F src/table.c af14284fa36c8d41f6829e3f2819dce07d3e2de2
F src/tclsqlite.c 8d093146332b2f0cbf2a8ebe8597d481619308a3
-F src/test1.c 7ecde02fdecff651250f11d96da73d484a4ff764
+F src/test1.c a7e559240e677671224d2d13b4d1dab284e23c20
F src/test2.c dafd8bd314a554bf376c6d3a8c83fd69219f5a40
F src/test3.c 7247090d15a5a43823079b6fd8dad1ed3cccdedf
F src/test4.c a921a69821fd30209589228e64f94e9f715b6fe2
F test/collate5.test 1dd5f0f508c46667f9d4606c7950c414b0bdc0d5
F test/collate6.test 2a45768914f04c1447a69d1358bbede376552675
F test/conflict.test c5b849b01cfbe0a4f63a90cba6f68e2fe3a75f87
-F test/crash.test fa7c6ef4d1ac1aa2d14d8afd1583cef8f8e2a0e4
+F test/crash.test 01b4a1cf195678138810f973ec9e2e6cef731d3e
F test/crashtest1.c 09c1c7d728ccf4feb9e481671e29dda5669bbcc2
F test/date.test aed5030482ebc02bd8d386c6c86a29f694ab068d
F test/delete.test 4f0c86e2bebdc822d179c80697b1ceabe6bbcd07
F www/vdbe.tcl 59288db1ac5c0616296b26dce071c36cb611dfe9
F www/version3.tcl 563ba3ac02f64da27ab17f3edbe8e56bfd0293fb
F www/whentouse.tcl a8335bce47cc2fddb07f19052cb0cb4d9129a8e4
-P e2f7f182987fbfe8611ead8bd1f12b2e8b47f6dc
-R 8314e42b760c1a8a452b6f1accd15858
+P 46107da7eddbdda8b582e2ece2dc41222a70330a
+R 2d06dadbd77bf8c7d6cc2f4d941e75d8
U danielk1977
-Z 0fc9a3cf7f51cb64d701f7be6ce96ff8
+Z eb0225a30546e9d6f49bf0fdd5127daf
-46107da7eddbdda8b582e2ece2dc41222a70330a
\ No newline at end of file
+41868d79ac5b3c496c4d87ca6b4ee7c17ef38965
\ No newline at end of file
/*
-** The crash-seed. Accessed via functions crashseed() and
-** sqlite3SetCrashseed().
+** The following variables control when a simulated crash occurs.
+**
+** If iCrashDelay is non-zero, then zCrashFile contains (full path) name of
+** a file that SQLite will call sqlite3OsSync() on. Each time this happens
+** iCrashDelay is decremented. If iCrashDelay is zero after being
+** decremented, a "crash" occurs during the sync() operation.
+**
+** In other words, a crash occurs the iCrashDelay'th time zCrashFile is
+** synced.
*/
-static int crashseed_var = 0;
+static int iCrashDelay = 0;
+char zCrashFile[256];
/*
-** This function is used to set the value of the 'crash-seed' integer.
-**
-** If the crash-seed is 0, the default value, then whenever sqlite3OsSync()
-** or sqlite3OsClose() is called, the write cache is written to disk before
-** the os_unix.c Sync() or Close() function is called.
-**
-** If the crash-seed is non-zero, then it is used to determine a subset of
-** the write-cache to actually write to disk before calling Sync() or
-** Close() in os_unix.c. The actual subset of writes selected is not
-** significant, except that it is constant for a given value of the
-** crash-seed and cache contents. Before returning, exit(-1) is invoked.
+** Set the value of the two crash parameters.
*/
-void sqlite3SetCrashseed(int seed){
+void sqlite3SetCrashParams(int iDelay, char const *zFile){
sqlite3OsEnterMutex();
- crashseed_var = seed;
+ assert( strlen(zFile)<256 );
+ strcpy(zCrashFile, zFile);
+ iCrashDelay = iDelay;
sqlite3OsLeaveMutex();
}
/*
-** Retrieve the current value of the crash-seed.
+** File zPath is being sync()ed. Return non-zero if this should
+** cause a crash.
*/
-static int crashseed(){
- int i;
+static int crashRequired(char const *zPath){
+ int r;
+ int n;
sqlite3OsEnterMutex();
- i = crashseed_var;
+ n = strlen(zCrashFile);
+ if( zCrashFile[n-1]=='*' ){
+ n--;
+ }else if( strlen(zPath)>n ){
+ n = strlen(zPath);
+ }
+ r = (
+ iCrashDelay>0 &&
+ !strncmp(zPath, zCrashFile, n) &&
+ --iCrashDelay==0
+ )?1:0;
sqlite3OsLeaveMutex();
- return i;
+ return r;
}
+
static OsTestFile *pAllFiles = 0;
/*
return SQLITE_OK;
}
+/* #define TRACE_WRITECACHE */
+
/*
** Write the cache of pFile to disk. If crash is non-zero, randomly
** skip blocks when writing. The cache is deleted before returning.
sqlite3Randomness(1, &random);
if( random & 0x01 ){
skip = 1;
-/*
- printf("Not writing block %d of %s\n", i, pFile->zName);
-*/
+#ifdef TRACE_WRITECACHE
+printf("Not writing block %d of %s\n", i, pFile->zName);
}else{
-/*
- printf("Writing block %d of %s\n", i, pFile->zName);
-*/
+printf("Writing block %d of %s\n", i, pFile->zName);
+#endif
}
}
-
if( rc==SQLITE_OK ){
rc = sqlite3RealSeek(&pFile->fd, BLOCK_OFFSET(i));
}
** Write the cache to disk.
*/
static int writeCache(OsTestFile *pFile){
- int cs = crashseed();
- if( cs==1 ){
- /* FIX ME: writeCache2() should be called on all open files here. */
- OsTestFile *pFile;
- for(pFile=pAllFiles; pFile; pFile=pFile->pNext){
- writeCache2(pFile, 1);
+ if( pFile->apBlk ){
+ int c = crashRequired(pFile->zName);
+ if( c ){
+ OsTestFile *p;
+#ifdef TRACE_WRITECACHE
+ printf("Crash during sync of %s\n", pFile->zName);
+#endif
+ for(p=pAllFiles; p; p=p->pNext){
+ writeCache2(p, 1);
+ }
+ exit(-1);
+ }else{
+ return writeCache2(pFile, 0);
}
- exit(-1);
- }else{
- if( cs>0 ) sqlite3SetCrashseed(cs-1);
- return writeCache2(pFile, 0);
}
+ return SQLITE_OK;
}
/*
OsTestFile *pNext;
};
-void sqlite3SetCrashseed(int seed);
+void sqlite3SetCrashParams(int iDelay, char const *zFile);
#endif /* _SQLITE_OS_UNIX_H_ */
** file simultaneously, or one process from reading the database while
** another is writing.
**
-** @(#) $Id: pager.c,v 1.136 2004/06/23 01:05:27 danielk1977 Exp $
+** @(#) $Id: pager.c,v 1.137 2004/06/23 10:43:10 danielk1977 Exp $
*/
#include "os.h" /* Must be first to enable large file support */
#include "sqliteInt.h"
pPg->dirty = 0;
pPg->needSync = 0;
}
+ pPager->dirtyCache = 0;
+ pPager->nMaster = 0;
+ pPager->nRec = 0;
}else{
assert( pPager->dirtyCache==0 || pPager->useJournal==0 );
}
sqlite3OsUnlock(&pPager->fd, SHARED_LOCK);
pPager->state = PAGER_SHARED;
+ pPager->origDbSize = 0;
return SQLITE_OK;
}
** chance of failing the checksum and thus detecting the problem.
*/
static u32 pager_cksum(Pager *pPager, Pgno pgno, const char *aData){
- u32 cksum = pPager->cksumInit + pgno;
+ u32 cksum = pPager->cksumInit;
+ int i = pPager->pageSize-200;
+ while( i>0 ){
+ cksum += aData[i];
+ i -= 200;
+ }
return cksum;
}
/* (2) Read the number of pages stored in the journal. */
rc = read32bits(&pPager->jfd, (u32*)&nRec);
if( rc ) goto end_playback;
- if( nRec==0xffffffff || useJournalSize ){
+ if( nRec==0xffffffff || useJournalSize ||
+ nRec>(szJ - JOURNAL_HDR_SZ(pPager))/JOURNAL_PG_SZ(pPager)
+ ){
nRec = (szJ - JOURNAL_HDR_SZ(pPager))/JOURNAL_PG_SZ(pPager);
}
}
if( mxPage>10 ){
pPager->mxPage = mxPage;
+ }else{
+ pPager->mxPage = 10;
}
}
return rc;
}
assert( pPager->journalOpen );
-#if 0
- rc = syncJournal(pPager, 0);
- if( rc!=SQLITE_OK ){
- goto commit_abort;
- }
- pPg = pager_get_all_dirty_pages(pPager);
- if( pPg ){
- rc = pager_write_pagelist(pPg);
- if( rc || (!pPager->noSync && sqlite3OsSync(&pPager->fd)!=SQLITE_OK) ){
- goto commit_abort;
- }
- }
-#endif
rc = sqlite3pager_sync(pPager, 0);
if( rc!=SQLITE_OK ){
goto commit_abort;
** is not included in the SQLite library. It is used for automated
** testing of the SQLite library.
**
-** $Id: test1.c,v 1.83 2004/06/22 13:12:52 danielk1977 Exp $
+** $Id: test1.c,v 1.84 2004/06/23 10:43:11 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "tcl.h"
return TCL_ERROR;
}
-static int sqlite3_crashseed(
+static int sqlite3_crashparams(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
#ifdef OS_TEST
- int seed;
- if( objc!=2 ) goto bad_args;
- if( Tcl_GetIntFromObj(interp, objv[1], &seed) ) return TCL_ERROR;
- sqlite3SetCrashseed(seed);
+ int delay;
+ if( objc!=3 ) goto bad_args;
+ if( Tcl_GetIntFromObj(interp, objv[1], &delay) ) return TCL_ERROR;
+ sqlite3SetCrashParams(delay, Tcl_GetString(objv[2]));
#endif
return TCL_OK;
#ifdef OS_TEST
bad_args:
Tcl_AppendResult(interp, "wrong # args: should be \"",
- Tcl_GetStringFromObj(objv[0], 0), "<seed>", 0);
+ Tcl_GetStringFromObj(objv[0], 0), "<delay> <filename>", 0);
return TCL_ERROR;
#endif
}
{ "sqlite3OsLock", test_sqlite3OsLock, 0 },
{ "sqlite3OsUnlock", test_sqlite3OsUnlock, 0 },
{ "add_test_collate", test_collate, 0 },
- { "sqlite3_crashseed", sqlite3_crashseed, 0 },
+ { "sqlite3_crashparams", sqlite3_crashparams, 0 },
};
int i;
#***********************************************************************
# This file implements regression tests for SQLite library.
#
-# $Id: crash.test,v 1.2 2004/06/23 01:05:27 danielk1977 Exp $
+# $Id: crash.test,v 1.3 2004/06/23 10:43:15 danielk1977 Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
+set repeats 100
+# set repeats 5
+
# This proc execs a seperate process that crashes midway through executing
# the SQL script $sql on database test.db.
#
-# Argument $crashdelay indicates the number of file closes or syncs to wait
-# before crashing. When a crash occurs a random subset of unsynced writes
-# are written into any open files.
-proc crashsql {crashdelay sql} {
+# The crash occurs during a sync() of file $crashfile. When the crash
+# occurs a random subset of all unsynced writes made by the process are
+# written into the files on disk. Argument $crashdelay indicates the
+# number of file syncs to wait before crashing.
+#
+# The return value is a list of two elements. The first element is a
+# boolean, indicating whether or not the process actually crashed or
+# reported some other error. The second element in the returned list is the
+# error message. This is "child process exited abnormally" if the crash
+# occured.
+proc crashsql {crashdelay crashfile sql} {
+ set cfile [file join [pwd] $crashfile]
set f [open crash.tcl w]
- puts $f "sqlite3_crashseed $crashdelay"
+ puts $f "sqlite3_crashparams $crashdelay $cfile"
puts $f "sqlite3 db test.db"
+ puts $f "db eval {pragma full_synchronous = 1}"
puts $f "db eval {"
puts $f "$sql"
puts $f "}"
close $f
- exec [file join . crashtest] crash.tcl
+ set r [catch {
+ exec [file join . crashtest] crash.tcl
+ } msg]
+ lappend r $msg
+}
+
+# The following procedure computes a "signature" for table "abc". If
+# abc changes in any way, the signature should change.
+proc signature {} {
+ return [db eval {SELECT count(*), md5sum(a), md5sum(b), md5sum(c) FROM abc}]
+}
+proc signature2 {} {
+ return [db eval {SELECT count(*), md5sum(a), md5sum(b), md5sum(c) FROM abc2}]
}
+# Use a small pager-cache for these tests.
+do_test crash-0.1 {
+ execsql { pragma default_cache_size = 10 }
+} {}
+
+#--------------------------------------------------------------------------
# Simple crash test:
#
# crash-1.1: Create a database with a table with two rows.
# crash-1.2: Run a 'DELETE FROM abc WHERE a = 1' that crashes during
-# journal-sync
+# the first journal-sync.
# crash-1.3: Ensure the database is in the same state as after crash-1.1.
# crash-1.4: Run a 'DELETE FROM abc WHERE a = 1' that crashes during
-# database-sync
+# the first database-sync.
# crash-1.5: Ensure the database is in the same state as after crash-1.1.
#
+# Tests 1.6 through 1.9 are the same as 1.2 through 1.5, except the crash
+# is requested on the second sync of each file. This doesn't happen in
+# such a small test case, so these tests are just to verify that the
+# test infrastructure operates as expected.
+#
do_test crash-1.1 {
execsql {
CREATE TABLE abc(a, b, c);
INSERT INTO abc VALUES(1, 2, 3);
INSERT INTO abc VALUES(4, 5, 6);
}
-} {}
+ set ::sig [signature]
+ expr 0
+} {0}
do_test crash-1.2 {
- catch {
- crashsql 1 {
- DELETE FROM abc WHERE a = 1;
- }
- } msg
- set msg
-} {child process exited abnormally}
+ crashsql 1 test.db-journal {
+ DELETE FROM abc WHERE a = 1;
+ }
+} {1 {child process exited abnormally}}
+
+# exit
+
do_test crash-1.3 {
+ signature
+} $::sig
+do_test crash-1.4 {
+ crashsql 1 test.db {
+ DELETE FROM abc WHERE a = 1;
+ }
+} {1 {child process exited abnormally}}
+do_test crash-1.5 {
+ signature
+} $::sig
+do_test crash-1.6 {
+ crashsql 2 test.db-journal {
+ DELETE FROM abc WHERE a = 1;
+ }
+} {0 {}}
+do_test crash-1.7 {
catchsql {
SELECT * FROM abc;
}
-} {0 {1 2 3 4 5 6}}
-do_test crash-1.4 {
- catch {
- crashsql 1 {
- DELETE FROM abc WHERE a = 1;
- }
- } msg
- set msg
-} {child process exited abnormally}
-do_test crash-1.5 {
+} {0 {4 5 6}}
+do_test crash-1.8 {
+ crashsql 2 test.db {
+ DELETE FROM abc WHERE a = 4;
+ }
+} {0 {}}
+do_test crash-1.9 {
catchsql {
SELECT * FROM abc;
}
-} {0 {1 2 3 4 5 6}}
+} {0 {}}
-finish_test
+#--------------------------------------------------------------------------
+# The following tests test recovery when both the database file and the the
+# journal file contain corrupt data. This can happen after pages are
+# written to the database file before a transaction is committed due to
+# cache-pressure.
+#
+# crash-2.1: Insert 18 pages of data into the database.
+# crash-2.2: Check the database file size looks ok.
+# crash-2.3: Delete 15 or so pages (with a 10 page page-cache), then crash.
+# crash-2.4: Ensure the database is in the same state as after crash-2.1.
+#
+# Test cases crash-2.5 and crash-2.6 check that the database is OK if the
+# crash occurs during the main database file sync. But this isn't really
+# different from the crash-1.* cases.
+#
+do_test crash-2.1 {
+ execsql { BEGIN }
+ for {set n 0} {$n < 1000} {incr n} {
+ execsql "INSERT INTO abc VALUES($n, [expr 2*$n], [expr 3*$n])"
+ }
+ execsql { COMMIT }
+ set ::sig [signature]
+ execsql { SELECT sum(a), sum(b), sum(c) from abc }
+} {499500 999000 1498500}
+do_test crash-2.2 {
+ expr [file size test.db] / 1024
+} {19}
+do_test crash-2.3 {
+ crashsql 2 test.db-journal {
+ DELETE FROM abc WHERE a < 800;
+ }
+} {1 {child process exited abnormally}}
+do_test crash-2.4 {
+ signature
+} $sig
+do_test crash-2.5 {
+ crashsql 1 test.db {
+ DELETE FROM abc WHERE a<800;
+ }
+} {1 {child process exited abnormally}}
+do_test crash-2.6 {
+ signature
+} $sig
+
+#--------------------------------------------------------------------------
+# The crash-3.* test cases are essentially the same test as test case
+# crash-2.*, but with a more complicated data set.
+#
+# The test is repeated a few times with different seeds for the random
+# number generator in the crashing executable. Because there is no way to
+# seed the random number generator directly, some SQL is added to the test
+# case to 'use up' a different quantity random numbers before the test SQL
+# is executed.
+#
+
+# Make sure the file is much bigger than the pager-cache (10 pages). This
+# ensures that cache-spills happen regularly.
+do_test crash-3.0 {
+ execsql {
+ INSERT INTO abc SELECT * FROM abc;
+ INSERT INTO abc SELECT * FROM abc;
+ INSERT INTO abc SELECT * FROM abc;
+ INSERT INTO abc SELECT * FROM abc;
+ INSERT INTO abc SELECT * FROM abc;
+ }
+ expr [file size test.db] / 1024
+} {554}
+for {set i 1} {$i < $repeats} {incr i} {
+ set sig [signature]
+ do_test crash-3.$i.1 {
+ crashsql [expr $i%5 + 1] test.db-journal "
+ BEGIN;
+ SELECT random() FROM abc LIMIT $i;
+ INSERT INTO abc VALUES(randstr(10,10), 0, 0);
+ DELETE FROM abc WHERE random()%10!=0;
+ COMMIT;
+ "
+ } {1 {child process exited abnormally}}
+ do_test crash-3.$i.2 {
+ signature
+ } $sig
+}
+
+#--------------------------------------------------------------------------
+# The following test cases - crash-4.* - test the correct recovery of the
+# database when a crash occurs during a multi-file transaction.
+#
+# crash-4.1.*: Test recovery when crash occurs during sync() of the
+# main database journal file.
+# crash-4.2.*: Test recovery when crash occurs during sync() of an
+# attached database journal file.
+# crash-4.3.*: Test recovery when crash occurs during sync() of the master
+# journal file.
+#
+do_test crash-4.0 {
+ file delete -force test2.db
+ file delete -force test2.db-journal
+ sqlite3 db2 test2.db
+ execsql {pragma default_cache_size = 10} db2
+ db2 close
+ execsql {
+ ATTACH 'test2.db' AS aux;
+ CREATE TABLE aux.abc2 AS SELECT 2*a as a, 2*b as b, 2*c as c FROM abc;
+ }
+ expr [file size test2.db] / 1024
+} {559}
+for {set i 1} {$i < $repeats} {incr i} {
+ set sig [signature]
+ set sig2 [signature2]
+ do_test crash-4.1.$i.1 {
+ crashsql [expr $i%5 + 1] test.db-journal "
+ ATTACH 'test2.db' AS aux;
+ BEGIN;
+ SELECT random() FROM abc LIMIT $i;
+ INSERT INTO abc VALUES(randstr(10,10), 0, 0);
+ DELETE FROM abc WHERE random()%10!=0;
+ INSERT INTO abc2 VALUES(randstr(10,10), 0, 0);
+ DELETE FROM abc2 WHERE random()%10!=0;
+ COMMIT;
+ "
+ } {1 {child process exited abnormally}}
+ do_test crash-4.1.$i.2 {
+ signature
+ } $sig
+ do_test crash-4.1.$i.3 {
+ signature2
+ } $sig2
+}
+for {set i 1} {$i < $repeats} {incr i} {
+ set sig [signature]
+ set sig2 [signature2]
+ do_test crash-4.2.$i.1 {
+ crashsql [expr $i%5 + 1] test2.db-journal "
+ ATTACH 'test2.db' AS aux;
+ BEGIN;
+ SELECT random() FROM abc LIMIT $i;
+ INSERT INTO abc VALUES(randstr(10,10), 0, 0);
+ DELETE FROM abc WHERE random()%10!=0;
+ INSERT INTO abc2 VALUES(randstr(10,10), 0, 0);
+ DELETE FROM abc2 WHERE random()%10!=0;
+ COMMIT;
+ "
+ } {1 {child process exited abnormally}}
+ do_test crash-4.2.$i.2 {
+ signature
+ } $sig
+ do_test crash-4.2.$i.3 {
+ signature2
+ } $sig2
+}
+for {set i 1} {$i < 5} {incr i} {
+ set sig [signature]
+ set sig2 [signature2]
+ do_test crash-4.3.$i.1 {
+ crashsql 1 test.db-mj* "
+ ATTACH 'test2.db' AS aux;
+ BEGIN;
+ SELECT random() FROM abc LIMIT $i;
+ INSERT INTO abc VALUES(randstr(10,10), 0, 0);
+ DELETE FROM abc WHERE random()%10!=0;
+ INSERT INTO abc2 VALUES(randstr(10,10), 0, 0);
+ DELETE FROM abc2 WHERE random()%10!=0;
+ COMMIT;
+ "
+ } {1 {child process exited abnormally}}
+ do_test crash-4.3.$i.2 {
+ signature
+ } $sig
+ do_test crash-4.3.$i.3 {
+ signature2
+ } $sig2
+}
+
+finish_test