Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Fix a memory leak that can occur in os_unix.c if an IO error occurs within the xUnlock method. |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
6c5c04eea1f0e8d61883ee8675c249fb |
User & Date: | dan 2010-06-16 10:55:43 |
Context
2010-06-16
| ||
12:30 | Add extra test cases to pager1.test. check-in: ad320957 user: dan tags: trunk | |
10:55 | Fix a memory leak that can occur in os_unix.c if an IO error occurs within the xUnlock method. check-in: 6c5c04ee user: dan tags: trunk | |
2010-06-15
| ||
19:07 | Rationalize a common pattern in tcl test cases into proc do_multiclient_test. check-in: efe44564 user: dan tags: trunk | |
Changes
Changes to src/os_unix.c.
730 730 #endif 731 731 }; 732 732 733 733 /* 734 734 ** A lists of all unixInodeInfo objects. 735 735 */ 736 736 static unixInodeInfo *inodeList = 0; 737 + 738 +/* 739 +** Close all file descriptors accumuated in the unixInodeInfo->pUnused list. 740 +** If all such file descriptors are closed without error, the list is 741 +** cleared and SQLITE_OK returned. 742 +** 743 +** Otherwise, if an error occurs, then successfully closed file descriptor 744 +** entries are removed from the list, and SQLITE_IOERR_CLOSE returned. 745 +** not deleted and SQLITE_IOERR_CLOSE returned. 746 +*/ 747 +static int closePendingFds(unixFile *pFile){ 748 + int rc = SQLITE_OK; 749 + unixInodeInfo *pInode = pFile->pInode; 750 + UnixUnusedFd *pError = 0; 751 + UnixUnusedFd *p; 752 + UnixUnusedFd *pNext; 753 + for(p=pInode->pUnused; p; p=pNext){ 754 + pNext = p->pNext; 755 + if( close(p->fd) ){ 756 + pFile->lastErrno = errno; 757 + rc = SQLITE_IOERR_CLOSE; 758 + p->pNext = pError; 759 + pError = p; 760 + }else{ 761 + sqlite3_free(p); 762 + } 763 + } 764 + pInode->pUnused = pError; 765 + return rc; 766 +} 737 767 738 768 /* 739 769 ** Release a unixInodeInfo structure previously allocated by findInodeInfo(). 740 770 ** 741 771 ** The mutex entered using the unixEnterMutex() function must be held 742 772 ** when this function is called. 743 773 */ 744 -static void releaseInodeInfo(unixInodeInfo *pInode){ 774 +static void releaseInodeInfo(unixFile *pFile){ 775 + unixInodeInfo *pInode = pFile->pInode; 745 776 assert( unixMutexHeld() ); 746 777 if( pInode ){ 747 778 pInode->nRef--; 748 779 if( pInode->nRef==0 ){ 749 780 assert( pInode->pShmNode==0 ); 781 + closePendingFds(pFile); 750 782 if( pInode->pPrev ){ 751 783 assert( pInode->pPrev->pNext==pInode ); 752 784 pInode->pPrev->pNext = pInode->pNext; 753 785 }else{ 754 786 assert( inodeList==pInode ); 755 787 inodeList = pInode->pNext; 756 788 } ................................................................................ 1148 1180 end_lock: 1149 1181 unixLeaveMutex(); 1150 1182 OSTRACE(("LOCK %d %s %s (unix)\n", pFile->h, azFileLock(eFileLock), 1151 1183 rc==SQLITE_OK ? "ok" : "failed")); 1152 1184 return rc; 1153 1185 } 1154 1186 1155 -/* 1156 -** Close all file descriptors accumuated in the unixInodeInfo->pUnused list. 1157 -** If all such file descriptors are closed without error, the list is 1158 -** cleared and SQLITE_OK returned. 1159 -** 1160 -** Otherwise, if an error occurs, then successfully closed file descriptor 1161 -** entries are removed from the list, and SQLITE_IOERR_CLOSE returned. 1162 -** not deleted and SQLITE_IOERR_CLOSE returned. 1163 -*/ 1164 -static int closePendingFds(unixFile *pFile){ 1165 - int rc = SQLITE_OK; 1166 - unixInodeInfo *pInode = pFile->pInode; 1167 - UnixUnusedFd *pError = 0; 1168 - UnixUnusedFd *p; 1169 - UnixUnusedFd *pNext; 1170 - for(p=pInode->pUnused; p; p=pNext){ 1171 - pNext = p->pNext; 1172 - if( close(p->fd) ){ 1173 - pFile->lastErrno = errno; 1174 - rc = SQLITE_IOERR_CLOSE; 1175 - p->pNext = pError; 1176 - pError = p; 1177 - }else{ 1178 - sqlite3_free(p); 1179 - } 1180 - } 1181 - pInode->pUnused = pError; 1182 - return rc; 1183 -} 1184 - 1185 1187 /* 1186 1188 ** Add the file descriptor used by file handle pFile to the corresponding 1187 1189 ** pUnused list. 1188 1190 */ 1189 1191 static void setPendingFd(unixFile *pFile){ 1190 1192 unixInodeInfo *pInode = pFile->pInode; 1191 1193 UnixUnusedFd *p = pFile->pUnused; ................................................................................ 1447 1449 /* If there are outstanding locks, do not actually close the file just 1448 1450 ** yet because that would clear those locks. Instead, add the file 1449 1451 ** descriptor to pInode->pUnused list. It will be automatically closed 1450 1452 ** when the last lock is cleared. 1451 1453 */ 1452 1454 setPendingFd(pFile); 1453 1455 } 1454 - releaseInodeInfo(pFile->pInode); 1456 + releaseInodeInfo(pFile); 1455 1457 rc = closeUnixFile(id); 1456 1458 unixLeaveMutex(); 1457 1459 } 1458 1460 return rc; 1459 1461 } 1460 1462 1461 1463 /************** End of the posix advisory lock implementation ***************** ................................................................................ 2062 2064 */ 2063 2065 static int semClose(sqlite3_file *id) { 2064 2066 if( id ){ 2065 2067 unixFile *pFile = (unixFile*)id; 2066 2068 semUnlock(id, NO_LOCK); 2067 2069 assert( pFile ); 2068 2070 unixEnterMutex(); 2069 - releaseInodeInfo(pFile->pInode); 2071 + releaseInodeInfo(pFile); 2070 2072 unixLeaveMutex(); 2071 2073 closeUnixFile(id); 2072 2074 } 2073 2075 return SQLITE_OK; 2074 2076 } 2075 2077 2076 2078 #endif /* OS_VXWORKS */ ................................................................................ 2529 2531 /* If there are outstanding locks, do not actually close the file just 2530 2532 ** yet because that would clear those locks. Instead, add the file 2531 2533 ** descriptor to pInode->aPending. It will be automatically closed when 2532 2534 ** the last lock is cleared. 2533 2535 */ 2534 2536 setPendingFd(pFile); 2535 2537 } 2536 - releaseInodeInfo(pFile->pInode); 2538 + releaseInodeInfo(pFile); 2537 2539 sqlite3_free(pFile->lockingContext); 2538 2540 rc = closeUnixFile(id); 2539 2541 unixLeaveMutex(); 2540 2542 } 2541 2543 return rc; 2542 2544 } 2543 2545
Changes to src/test_vfs.c.
72 72 #define TESTVFS_SHMLOCK_MASK 0x00000010 73 73 #define TESTVFS_SHMMAP_MASK 0x00000020 74 74 #define TESTVFS_SHMBARRIER_MASK 0x00000040 75 75 #define TESTVFS_SHMCLOSE_MASK 0x00000080 76 76 77 77 #define TESTVFS_OPEN_MASK 0x00000100 78 78 #define TESTVFS_SYNC_MASK 0x00000200 79 -#define TESTVFS_ALL_MASK 0x000003FF 79 +#define TESTVFS_DELETE_MASK 0x00000400 80 +#define TESTVFS_ALL_MASK 0x000007FF 80 81 81 82 82 83 #define TESTVFS_MAX_PAGES 256 83 84 84 85 /* 85 86 ** A shared-memory buffer. There is one of these objects for each shared 86 87 ** memory region opened by clients. If two clients open the same file, ................................................................................ 453 454 454 455 /* 455 456 ** Delete the file located at zPath. If the dirSync argument is true, 456 457 ** ensure the file-system modifications are synced to disk before 457 458 ** returning. 458 459 */ 459 460 static int tvfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ 460 - return sqlite3OsDelete(PARENTVFS(pVfs), zPath, dirSync); 461 + int rc = SQLITE_OK; 462 + Testvfs *p = (Testvfs *)pVfs->pAppData; 463 + 464 + if( p->pScript && p->mask&TESTVFS_DELETE_MASK ){ 465 + tvfsExecTcl(p, "xDelete", 466 + Tcl_NewStringObj(zPath, -1), Tcl_NewIntObj(dirSync), 0 467 + ); 468 + tvfsResultCode(p, &rc); 469 + } 470 + if( rc==SQLITE_OK ){ 471 + rc = sqlite3OsDelete(PARENTVFS(pVfs), zPath, dirSync); 472 + } 473 + return rc; 461 474 } 462 475 463 476 /* 464 477 ** Test for access permissions. Return true if the requested permission 465 478 ** is available, or false otherwise. 466 479 */ 467 480 static int tvfsAccess( ................................................................................ 839 852 } vfsmethod [] = { 840 853 { "xShmOpen", TESTVFS_SHMOPEN_MASK }, 841 854 { "xShmLock", TESTVFS_SHMLOCK_MASK }, 842 855 { "xShmBarrier", TESTVFS_SHMBARRIER_MASK }, 843 856 { "xShmClose", TESTVFS_SHMCLOSE_MASK }, 844 857 { "xShmMap", TESTVFS_SHMMAP_MASK }, 845 858 { "xSync", TESTVFS_SYNC_MASK }, 859 + { "xDelete", TESTVFS_DELETE_MASK }, 846 860 { "xOpen", TESTVFS_OPEN_MASK }, 847 861 }; 848 862 Tcl_Obj **apElem = 0; 849 863 int nElem = 0; 850 864 int i; 851 865 int mask = 0; 852 866 if( objc!=3 ){
Changes to test/malloc_common.tcl.
105 105 eval do_one_faultsim_test "$name-$f" $FAULTSIM($f) $testspec 106 106 } 107 107 } 108 108 109 109 #------------------------------------------------------------------------- 110 110 # Procedures to save and restore the current file-system state: 111 111 # 112 +# faultsim_save 112 113 # faultsim_save_and_close 113 114 # faultsim_restore_and_reopen 115 +# faultsim_delete_and_reopen 114 116 # 117 +proc faultsim_save {} { 118 + foreach f [glob -nocomplain sv_test.db*] { file delete -force $f } 119 + foreach f [glob -nocomplain test.db*] { 120 + set f2 "sv_$f" 121 + file copy -force $f $f2 122 + } 123 +} 115 124 proc faultsim_save_and_close {} { 116 - foreach {a => b} { 117 - test.db => testX.db 118 - test.db-wal => testX.db-wal 119 - test.db-journal => testX.db-journal 120 - } { 121 - if {[file exists $a]} { 122 - file copy -force $a $b 123 - } else { 124 - file delete -force $b 125 - } 126 - } 125 + faultsim_save 127 126 catch { db close } 128 127 return "" 129 128 } 130 129 proc faultsim_restore_and_reopen {} { 131 130 catch { db close } 132 - foreach {a => b} { 133 - testX.db => test.db 134 - testX.db-wal => test.db-wal 135 - testX.db-journal => test.db-journal 136 - } { 137 - if {[file exists $a]} { 138 - file copy -force $a $b 139 - } else { 140 - file delete -force $b 141 - } 131 + foreach f [glob -nocomplain test.db*] { file delete -force $f } 132 + foreach f2 [glob -nocomplain sv_test.db*] { 133 + set f [string range $f2 3 end] 134 + file copy -force $f2 $f 142 135 } 143 136 sqlite3 db test.db 144 137 sqlite3_extended_result_codes db 1 145 138 sqlite3_db_config_lookaside db 0 0 0 146 139 } 147 140 148 141 proc faultsim_integrity_check {{db db}} { 149 142 set ic [$db eval { PRAGMA integrity_check }] 150 143 if {$ic != "ok"} { error "Integrity check: $ic" } 151 144 } 152 145 153 146 proc faultsim_delete_and_reopen {{file test.db}} { 154 147 catch { db close } 155 - file delete -force test.db test.db-wal test.db-journal 148 + foreach f [glob -nocomplain test.db*] { file delete -force $f } 156 149 sqlite3 db test.db 157 150 } 158 151 159 152 160 153 # The following procs are used as [do_one_faultsim_test] callbacks when 161 154 # injecting OOM faults into test cases. 162 155 #
Changes to test/pager1.test.
10 10 #*********************************************************************** 11 11 # 12 12 13 13 set testdir [file dirname $argv0] 14 14 source $testdir/tester.tcl 15 15 source $testdir/lock_common.tcl 16 16 source $testdir/malloc_common.tcl 17 + 18 +# 19 +# pager1-1.*: Test inter-process locking (clients in multiple processes). 20 +# 21 +# pager1-2.*: Test intra-process locking (multiple clients in this process). 22 +# 23 +# pager1-3.*: Savepoint related tests. 24 +# 25 + 26 +proc do_execsql_test {testname sql result} { 27 + uplevel do_test $testname [list "execsql {$sql}"] [list $result] 28 +} 29 +proc do_catchsql_test {testname sql result} { 30 + uplevel do_test $testname [list "catchsql {$sql}"] [list $result] 31 +} 17 32 18 33 do_multiclient_test tn { 19 34 20 35 # Create and populate a database table using connection [db]. Check 21 36 # that connections [db2] and [db3] can see the schema and content. 22 37 # 23 38 do_test pager1-$tn.1 { ................................................................................ 147 162 do_test pager1-$tn.25 { sql1 { UPDATE t1 SET a = a+10 } } {} 148 163 do_test pager1-$tn.26 { sql1 { COMMIT } } {} 149 164 do_test pager1-$tn.27 { sql1 { SELECT * FROM t1 } } {21 one 22 two 23 three} 150 165 do_test pager1-$tn.27 { sql2 { SELECT * FROM t1 } } {21 one 22 two 23 three} 151 166 do_test pager1-$tn.28 { sql3 { SELECT * FROM t1 } } {21 one 22 two 23 three} 152 167 } 153 168 169 +do_test pager1-3.1 { 170 + faultsim_delete_and_reopen 171 + execsql { 172 + CREATE TABLE t1(a PRIMARY KEY, b); 173 + CREATE TABLE counter( 174 + i CHECK (i<5), 175 + u CHECK (u<10) 176 + ); 177 + INSERT INTO counter VALUES(0, 0); 178 + CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN 179 + UPDATE counter SET i = i+1; 180 + END; 181 + CREATE TRIGGER tr2 AFTER UPDATE ON t1 BEGIN 182 + UPDATE counter SET u = u+1; 183 + END; 184 + } 185 + execsql { SELECT * FROM counter } 186 +} {0 0} 187 + 188 +do_execsql_test pager1-3.2 { 189 + BEGIN; 190 + INSERT INTO t1 VALUES(1, randomblob(1500)); 191 + INSERT INTO t1 VALUES(2, randomblob(1500)); 192 + INSERT INTO t1 VALUES(3, randomblob(1500)); 193 + SELECT * FROM counter; 194 +} {3 0} 195 +do_catchsql_test pager1-3.3 { 196 + INSERT INTO t1 SELECT a+3, randomblob(1500) FROM t1 197 +} {1 {constraint failed}} 198 +do_execsql_test pager1-3.4 { SELECT * FROM counter } {3 0} 199 +do_execsql_test pager1-3.5 { SELECT a FROM t1 } {1 2 3} 200 +do_execsql_test pager1-3.6 { COMMIT } {} 201 + 154 202 finish_test 155 203
Added test/pagerfault.test.
1 +# 2010 June 15 2 +# 3 +# The author disclaims copyright to this source code. In place of 4 +# a legal notice, here is a blessing: 5 +# 6 +# May you do good and not evil. 7 +# May you find forgiveness for yourself and forgive others. 8 +# May you share freely, never taking more than you give. 9 +# 10 +#*********************************************************************** 11 +# 12 + 13 +set testdir [file dirname $argv0] 14 +source $testdir/tester.tcl 15 +source $testdir/lock_common.tcl 16 +source $testdir/malloc_common.tcl 17 + 18 +set a_string_counter 1 19 +proc a_string {n} { 20 + global a_string_counter 21 + incr a_string_counter 22 + string range [string repeat "${a_string_counter}." $n] 1 $n 23 +} 24 +db func a_string a_string 25 + 26 +#------------------------------------------------------------------------- 27 +# Test fault-injection while rolling back a hot-journal file. 28 +# 29 +do_test pagerfault-1-pre1 { 30 + execsql { 31 + PRAGMA journal_mode = DELETE; 32 + PRAGMA cache_size = 10; 33 + CREATE TABLE t1(a UNIQUE, b UNIQUE); 34 + INSERT INTO t1 VALUES(a_string(200), a_string(300)); 35 + INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1; 36 + INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1; 37 + BEGIN; 38 + INSERT INTO t1 SELECT a_string(201), a_string(301) FROM t1; 39 + INSERT INTO t1 SELECT a_string(202), a_string(302) FROM t1; 40 + INSERT INTO t1 SELECT a_string(203), a_string(303) FROM t1; 41 + INSERT INTO t1 SELECT a_string(204), a_string(304) FROM t1; 42 + } 43 + faultsim_save_and_close 44 +} {} 45 +do_faultsim_test pagerfault-1 -prep { 46 + faultsim_restore_and_reopen 47 +} -body { 48 + execsql { SELECT count(*) FROM t1 } 49 +} -test { 50 + faultsim_test_result {0 4} 51 + faultsim_integrity_check 52 + if {[db one { SELECT count(*) FROM t1 }] != 4} { 53 + error "Database content appears incorrect" 54 + } 55 +} 56 + 57 +#------------------------------------------------------------------------- 58 +# Test fault-injection while rolling back hot-journals that were created 59 +# as part of a multi-file transaction. 60 +# 61 +do_test pagerfault-2-pre1 { 62 + testvfs tstvfs -default 1 63 + tstvfs filter xDelete 64 + tstvfs script xDeleteCallback 65 + 66 + proc xDeleteCallback {method file args} { 67 + set file [file tail $file] 68 + if { [string match *mj* $file] } { faultsim_save } 69 + } 70 + 71 + faultsim_delete_and_reopen 72 + db func a_string a_string 73 + 74 + execsql { 75 + ATTACH 'test.db2' AS aux; 76 + PRAGMA journal_mode = DELETE; 77 + PRAGMA main.cache_size = 10; 78 + PRAGMA aux.cache_size = 10; 79 + 80 + CREATE TABLE t1(a UNIQUE, b UNIQUE); 81 + CREATE TABLE aux.t2(a UNIQUE, b UNIQUE); 82 + INSERT INTO t1 VALUES(a_string(200), a_string(300)); 83 + INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1; 84 + INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1; 85 + INSERT INTO t2 SELECT * FROM t1; 86 + 87 + BEGIN; 88 + INSERT INTO t1 SELECT a_string(201), a_string(301) FROM t1; 89 + INSERT INTO t1 SELECT a_string(202), a_string(302) FROM t1; 90 + INSERT INTO t1 SELECT a_string(203), a_string(303) FROM t1; 91 + INSERT INTO t1 SELECT a_string(204), a_string(304) FROM t1; 92 + REPLACE INTO t2 SELECT * FROM t1; 93 + COMMIT; 94 + } 95 + 96 + db close 97 + tstvfs delete 98 +} {} 99 +do_faultsim_test pagerfault-2 -faults ioerr-persistent -prep { 100 + faultsim_restore_and_reopen 101 +} -body { 102 + execsql { 103 + ATTACH 'test.db2' AS aux; 104 + SELECT count(*) FROM t2; 105 + SELECT count(*) FROM t1; 106 + } 107 +} -test { 108 + faultsim_test_result {0 {4 4}} {1 {unable to open database: test.db2}} 109 + faultsim_integrity_check 110 + 111 + catchsql { ATTACH 'test.db2' AS aux } 112 + if {[db one { SELECT count(*) FROM t1 }] != 4 113 + || [db one { SELECT count(*) FROM t2 }] != 4 114 + } { 115 + error "Database content appears incorrect" 116 + } 117 +} 118 + 119 +finish_test
Changes to test/permutations.test.
165 165 walfault.test 166 166 } 167 167 168 168 test_suite "coverage-pager" -description { 169 169 Coverage tests for file pager.c. 170 170 } -files { 171 171 pager1.test 172 + pagerfault.test 172 173 } 173 174 174 175 175 176 lappend ::testsuitelist xxx 176 177 #------------------------------------------------------------------------- 177 178 # Define the permutation test suites: 178 179 #