Index: src/os_unix.c ================================================================== --- src/os_unix.c +++ src/os_unix.c @@ -732,23 +732,55 @@ /* ** A lists of all unixInodeInfo objects. */ static unixInodeInfo *inodeList = 0; + +/* +** Close all file descriptors accumuated in the unixInodeInfo->pUnused list. +** If all such file descriptors are closed without error, the list is +** cleared and SQLITE_OK returned. +** +** Otherwise, if an error occurs, then successfully closed file descriptor +** entries are removed from the list, and SQLITE_IOERR_CLOSE returned. +** not deleted and SQLITE_IOERR_CLOSE returned. +*/ +static int closePendingFds(unixFile *pFile){ + int rc = SQLITE_OK; + unixInodeInfo *pInode = pFile->pInode; + UnixUnusedFd *pError = 0; + UnixUnusedFd *p; + UnixUnusedFd *pNext; + for(p=pInode->pUnused; p; p=pNext){ + pNext = p->pNext; + if( close(p->fd) ){ + pFile->lastErrno = errno; + rc = SQLITE_IOERR_CLOSE; + p->pNext = pError; + pError = p; + }else{ + sqlite3_free(p); + } + } + pInode->pUnused = pError; + return rc; +} /* ** Release a unixInodeInfo structure previously allocated by findInodeInfo(). ** ** The mutex entered using the unixEnterMutex() function must be held ** when this function is called. */ -static void releaseInodeInfo(unixInodeInfo *pInode){ +static void releaseInodeInfo(unixFile *pFile){ + unixInodeInfo *pInode = pFile->pInode; assert( unixMutexHeld() ); if( pInode ){ pInode->nRef--; if( pInode->nRef==0 ){ assert( pInode->pShmNode==0 ); + closePendingFds(pFile); if( pInode->pPrev ){ assert( pInode->pPrev->pNext==pInode ); pInode->pPrev->pNext = pInode->pNext; }else{ assert( inodeList==pInode ); @@ -1150,40 +1182,10 @@ OSTRACE(("LOCK %d %s %s (unix)\n", pFile->h, azFileLock(eFileLock), rc==SQLITE_OK ? "ok" : "failed")); return rc; } -/* -** Close all file descriptors accumuated in the unixInodeInfo->pUnused list. -** If all such file descriptors are closed without error, the list is -** cleared and SQLITE_OK returned. -** -** Otherwise, if an error occurs, then successfully closed file descriptor -** entries are removed from the list, and SQLITE_IOERR_CLOSE returned. -** not deleted and SQLITE_IOERR_CLOSE returned. -*/ -static int closePendingFds(unixFile *pFile){ - int rc = SQLITE_OK; - unixInodeInfo *pInode = pFile->pInode; - UnixUnusedFd *pError = 0; - UnixUnusedFd *p; - UnixUnusedFd *pNext; - for(p=pInode->pUnused; p; p=pNext){ - pNext = p->pNext; - if( close(p->fd) ){ - pFile->lastErrno = errno; - rc = SQLITE_IOERR_CLOSE; - p->pNext = pError; - pError = p; - }else{ - sqlite3_free(p); - } - } - pInode->pUnused = pError; - return rc; -} - /* ** Add the file descriptor used by file handle pFile to the corresponding ** pUnused list. */ static void setPendingFd(unixFile *pFile){ @@ -1449,11 +1451,11 @@ ** descriptor to pInode->pUnused list. It will be automatically closed ** when the last lock is cleared. */ setPendingFd(pFile); } - releaseInodeInfo(pFile->pInode); + releaseInodeInfo(pFile); rc = closeUnixFile(id); unixLeaveMutex(); } return rc; } @@ -2064,11 +2066,11 @@ if( id ){ unixFile *pFile = (unixFile*)id; semUnlock(id, NO_LOCK); assert( pFile ); unixEnterMutex(); - releaseInodeInfo(pFile->pInode); + releaseInodeInfo(pFile); unixLeaveMutex(); closeUnixFile(id); } return SQLITE_OK; } @@ -2531,11 +2533,11 @@ ** descriptor to pInode->aPending. It will be automatically closed when ** the last lock is cleared. */ setPendingFd(pFile); } - releaseInodeInfo(pFile->pInode); + releaseInodeInfo(pFile); sqlite3_free(pFile->lockingContext); rc = closeUnixFile(id); unixLeaveMutex(); } return rc; Index: src/test_vfs.c ================================================================== --- src/test_vfs.c +++ src/test_vfs.c @@ -74,11 +74,12 @@ #define TESTVFS_SHMBARRIER_MASK 0x00000040 #define TESTVFS_SHMCLOSE_MASK 0x00000080 #define TESTVFS_OPEN_MASK 0x00000100 #define TESTVFS_SYNC_MASK 0x00000200 -#define TESTVFS_ALL_MASK 0x000003FF +#define TESTVFS_DELETE_MASK 0x00000400 +#define TESTVFS_ALL_MASK 0x000007FF #define TESTVFS_MAX_PAGES 256 /* @@ -455,11 +456,23 @@ ** Delete the file located at zPath. If the dirSync argument is true, ** ensure the file-system modifications are synced to disk before ** returning. */ static int tvfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ - return sqlite3OsDelete(PARENTVFS(pVfs), zPath, dirSync); + int rc = SQLITE_OK; + Testvfs *p = (Testvfs *)pVfs->pAppData; + + if( p->pScript && p->mask&TESTVFS_DELETE_MASK ){ + tvfsExecTcl(p, "xDelete", + Tcl_NewStringObj(zPath, -1), Tcl_NewIntObj(dirSync), 0 + ); + tvfsResultCode(p, &rc); + } + if( rc==SQLITE_OK ){ + rc = sqlite3OsDelete(PARENTVFS(pVfs), zPath, dirSync); + } + return rc; } /* ** Test for access permissions. Return true if the requested permission ** is available, or false otherwise. @@ -841,10 +854,11 @@ { "xShmLock", TESTVFS_SHMLOCK_MASK }, { "xShmBarrier", TESTVFS_SHMBARRIER_MASK }, { "xShmClose", TESTVFS_SHMCLOSE_MASK }, { "xShmMap", TESTVFS_SHMMAP_MASK }, { "xSync", TESTVFS_SYNC_MASK }, + { "xDelete", TESTVFS_DELETE_MASK }, { "xOpen", TESTVFS_OPEN_MASK }, }; Tcl_Obj **apElem = 0; int nElem = 0; int i; Index: test/malloc_common.tcl ================================================================== --- test/malloc_common.tcl +++ test/malloc_common.tcl @@ -107,40 +107,33 @@ } #------------------------------------------------------------------------- # Procedures to save and restore the current file-system state: # +# faultsim_save # faultsim_save_and_close # faultsim_restore_and_reopen +# faultsim_delete_and_reopen # +proc faultsim_save {} { + foreach f [glob -nocomplain sv_test.db*] { file delete -force $f } + foreach f [glob -nocomplain test.db*] { + set f2 "sv_$f" + file copy -force $f $f2 + } +} proc faultsim_save_and_close {} { - foreach {a => b} { - test.db => testX.db - test.db-wal => testX.db-wal - test.db-journal => testX.db-journal - } { - if {[file exists $a]} { - file copy -force $a $b - } else { - file delete -force $b - } - } + faultsim_save catch { db close } return "" } proc faultsim_restore_and_reopen {} { catch { db close } - foreach {a => b} { - testX.db => test.db - testX.db-wal => test.db-wal - testX.db-journal => test.db-journal - } { - if {[file exists $a]} { - file copy -force $a $b - } else { - file delete -force $b - } + foreach f [glob -nocomplain test.db*] { file delete -force $f } + foreach f2 [glob -nocomplain sv_test.db*] { + set f [string range $f2 3 end] + file copy -force $f2 $f } sqlite3 db test.db sqlite3_extended_result_codes db 1 sqlite3_db_config_lookaside db 0 0 0 } @@ -150,11 +143,11 @@ if {$ic != "ok"} { error "Integrity check: $ic" } } proc faultsim_delete_and_reopen {{file test.db}} { catch { db close } - file delete -force test.db test.db-wal test.db-journal + foreach f [glob -nocomplain test.db*] { file delete -force $f } sqlite3 db test.db } # The following procs are used as [do_one_faultsim_test] callbacks when Index: test/pager1.test ================================================================== --- test/pager1.test +++ test/pager1.test @@ -12,10 +12,25 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl source $testdir/lock_common.tcl source $testdir/malloc_common.tcl + +# +# pager1-1.*: Test inter-process locking (clients in multiple processes). +# +# pager1-2.*: Test intra-process locking (multiple clients in this process). +# +# pager1-3.*: Savepoint related tests. +# + +proc do_execsql_test {testname sql result} { + uplevel do_test $testname [list "execsql {$sql}"] [list $result] +} +proc do_catchsql_test {testname sql result} { + uplevel do_test $testname [list "catchsql {$sql}"] [list $result] +} do_multiclient_test tn { # Create and populate a database table using connection [db]. Check # that connections [db2] and [db3] can see the schema and content. @@ -149,7 +164,40 @@ do_test pager1-$tn.27 { sql1 { SELECT * FROM t1 } } {21 one 22 two 23 three} do_test pager1-$tn.27 { sql2 { SELECT * FROM t1 } } {21 one 22 two 23 three} do_test pager1-$tn.28 { sql3 { SELECT * FROM t1 } } {21 one 22 two 23 three} } +do_test pager1-3.1 { + faultsim_delete_and_reopen + execsql { + CREATE TABLE t1(a PRIMARY KEY, b); + CREATE TABLE counter( + i CHECK (i<5), + u CHECK (u<10) + ); + INSERT INTO counter VALUES(0, 0); + CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN + UPDATE counter SET i = i+1; + END; + CREATE TRIGGER tr2 AFTER UPDATE ON t1 BEGIN + UPDATE counter SET u = u+1; + END; + } + execsql { SELECT * FROM counter } +} {0 0} + +do_execsql_test pager1-3.2 { + BEGIN; + INSERT INTO t1 VALUES(1, randomblob(1500)); + INSERT INTO t1 VALUES(2, randomblob(1500)); + INSERT INTO t1 VALUES(3, randomblob(1500)); + SELECT * FROM counter; +} {3 0} +do_catchsql_test pager1-3.3 { + INSERT INTO t1 SELECT a+3, randomblob(1500) FROM t1 +} {1 {constraint failed}} +do_execsql_test pager1-3.4 { SELECT * FROM counter } {3 0} +do_execsql_test pager1-3.5 { SELECT a FROM t1 } {1 2 3} +do_execsql_test pager1-3.6 { COMMIT } {} + finish_test ADDED test/pagerfault.test Index: test/pagerfault.test ================================================================== --- /dev/null +++ test/pagerfault.test @@ -0,0 +1,119 @@ +# 2010 June 15 +# +# 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 +source $testdir/lock_common.tcl +source $testdir/malloc_common.tcl + +set a_string_counter 1 +proc a_string {n} { + global a_string_counter + incr a_string_counter + string range [string repeat "${a_string_counter}." $n] 1 $n +} +db func a_string a_string + +#------------------------------------------------------------------------- +# Test fault-injection while rolling back a hot-journal file. +# +do_test pagerfault-1-pre1 { + execsql { + PRAGMA journal_mode = DELETE; + PRAGMA cache_size = 10; + CREATE TABLE t1(a UNIQUE, b UNIQUE); + INSERT INTO t1 VALUES(a_string(200), a_string(300)); + INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1; + INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1; + BEGIN; + INSERT INTO t1 SELECT a_string(201), a_string(301) FROM t1; + INSERT INTO t1 SELECT a_string(202), a_string(302) FROM t1; + INSERT INTO t1 SELECT a_string(203), a_string(303) FROM t1; + INSERT INTO t1 SELECT a_string(204), a_string(304) FROM t1; + } + faultsim_save_and_close +} {} +do_faultsim_test pagerfault-1 -prep { + faultsim_restore_and_reopen +} -body { + execsql { SELECT count(*) FROM t1 } +} -test { + faultsim_test_result {0 4} + faultsim_integrity_check + if {[db one { SELECT count(*) FROM t1 }] != 4} { + error "Database content appears incorrect" + } +} + +#------------------------------------------------------------------------- +# Test fault-injection while rolling back hot-journals that were created +# as part of a multi-file transaction. +# +do_test pagerfault-2-pre1 { + testvfs tstvfs -default 1 + tstvfs filter xDelete + tstvfs script xDeleteCallback + + proc xDeleteCallback {method file args} { + set file [file tail $file] + if { [string match *mj* $file] } { faultsim_save } + } + + faultsim_delete_and_reopen + db func a_string a_string + + execsql { + ATTACH 'test.db2' AS aux; + PRAGMA journal_mode = DELETE; + PRAGMA main.cache_size = 10; + PRAGMA aux.cache_size = 10; + + CREATE TABLE t1(a UNIQUE, b UNIQUE); + CREATE TABLE aux.t2(a UNIQUE, b UNIQUE); + INSERT INTO t1 VALUES(a_string(200), a_string(300)); + INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1; + INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1; + INSERT INTO t2 SELECT * FROM t1; + + BEGIN; + INSERT INTO t1 SELECT a_string(201), a_string(301) FROM t1; + INSERT INTO t1 SELECT a_string(202), a_string(302) FROM t1; + INSERT INTO t1 SELECT a_string(203), a_string(303) FROM t1; + INSERT INTO t1 SELECT a_string(204), a_string(304) FROM t1; + REPLACE INTO t2 SELECT * FROM t1; + COMMIT; + } + + db close + tstvfs delete +} {} +do_faultsim_test pagerfault-2 -faults ioerr-persistent -prep { + faultsim_restore_and_reopen +} -body { + execsql { + ATTACH 'test.db2' AS aux; + SELECT count(*) FROM t2; + SELECT count(*) FROM t1; + } +} -test { + faultsim_test_result {0 {4 4}} {1 {unable to open database: test.db2}} + faultsim_integrity_check + + catchsql { ATTACH 'test.db2' AS aux } + if {[db one { SELECT count(*) FROM t1 }] != 4 + || [db one { SELECT count(*) FROM t2 }] != 4 + } { + error "Database content appears incorrect" + } +} + +finish_test Index: test/permutations.test ================================================================== --- test/permutations.test +++ test/permutations.test @@ -167,10 +167,11 @@ test_suite "coverage-pager" -description { Coverage tests for file pager.c. } -files { pager1.test + pagerfault.test } lappend ::testsuitelist xxx #-------------------------------------------------------------------------