Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Fix a problem whereby following an IO error in CommitPhaseTwo() of a multi-file transaction the b-tree layer could be left in TRANS_WRITE state, causing problems later on. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
dbe569a099c2855480e35c0cc4d93328 |
User & Date: | dan 2011-03-29 15:40:55.407 |
Context
2011-03-29
| ||
18:28 | Add tests to syscall.test and sysfault.test. (check-in: 3d2de01181 user: dan tags: trunk) | |
15:40 | Fix a problem whereby following an IO error in CommitPhaseTwo() of a multi-file transaction the b-tree layer could be left in TRANS_WRITE state, causing problems later on. (check-in: dbe569a099 user: dan tags: trunk) | |
10:04 | Fix a problem in the unix VFS implementation of xNextSystemCall(). Also some typos that prevent compilation when HAVE_POSIX_FALLOCATE is defined. (check-in: bc6cce8156 user: dan tags: trunk) | |
Changes
install-sh became executable.
︙ | ︙ |
Changes to src/backup.c.
︙ | ︙ | |||
484 485 486 487 488 489 490 | } }else{ rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 0); } /* Finish committing the transaction to the destination database. */ if( SQLITE_OK==rc | | | | 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 | } }else{ rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 0); } /* Finish committing the transaction to the destination database. */ if( SQLITE_OK==rc && SQLITE_OK==(rc = sqlite3BtreeCommitPhaseTwo(p->pDest, 0)) ){ rc = SQLITE_DONE; } } /* If bCloseTrans is true, then this function opened a read transaction ** on the source database. Close the read transaction here. There is ** no need to check the return values of the btree methods here, as ** "committing" a read-only transaction cannot fail. */ if( bCloseTrans ){ TESTONLY( int rc2 ); TESTONLY( rc2 = ) sqlite3BtreeCommitPhaseOne(p->pSrc, 0); TESTONLY( rc2 |= ) sqlite3BtreeCommitPhaseTwo(p->pSrc, 0); assert( rc2==SQLITE_OK ); } if( rc==SQLITE_IOERR_NOMEM ){ rc = SQLITE_NOMEM; } p->rc = rc; |
︙ | ︙ |
Changes to src/btree.c.
︙ | ︙ | |||
3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 | ** sqlite3BtreeCommitPhaseOne() routine does the first phase and should ** be invoked prior to calling this routine. The sqlite3BtreeCommitPhaseOne() ** routine did all the work of writing information out to disk and flushing the ** contents so that they are written onto the disk platter. All this ** routine has to do is delete or truncate or zero the header in the ** the rollback journal (which causes the transaction to commit) and ** drop locks. ** ** This will release the write lock on the database file. If there ** are no active cursors, it also releases the read lock. */ | > > > > > > > > > > > | | | | 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 | ** sqlite3BtreeCommitPhaseOne() routine does the first phase and should ** be invoked prior to calling this routine. The sqlite3BtreeCommitPhaseOne() ** routine did all the work of writing information out to disk and flushing the ** contents so that they are written onto the disk platter. All this ** routine has to do is delete or truncate or zero the header in the ** the rollback journal (which causes the transaction to commit) and ** drop locks. ** ** Normally, if an error occurs while the pager layer is attempting to ** finalize the underlying journal file, this function returns an error and ** the upper layer will attempt a rollback. However, if the second argument ** is non-zero then this b-tree transaction is part of a multi-file ** transaction. In this case, the transaction has already been committed ** (by deleting a master journal file) and the caller will ignore this ** functions return code. So, even if an error occurs in the pager layer, ** reset the b-tree objects internal state to indicate that the write ** transaction has been closed. This is quite safe, as the pager will have ** transitioned to the error state. ** ** This will release the write lock on the database file. If there ** are no active cursors, it also releases the read lock. */ int sqlite3BtreeCommitPhaseTwo(Btree *p, int bCleanup){ if( p->inTrans==TRANS_NONE ) return SQLITE_OK; sqlite3BtreeEnter(p); btreeIntegrity(p); /* If the handle has a write-transaction open, commit the shared-btrees ** transaction and set the shared state to TRANS_READ. */ if( p->inTrans==TRANS_WRITE ){ int rc; BtShared *pBt = p->pBt; assert( pBt->inTransaction==TRANS_WRITE ); assert( pBt->nTransaction>0 ); rc = sqlite3PagerCommitPhaseTwo(pBt->pPager); if( rc!=SQLITE_OK && bCleanup==0 ){ sqlite3BtreeLeave(p); return rc; } pBt->inTransaction = TRANS_READ; } btreeEndTransaction(p); sqlite3BtreeLeave(p); return SQLITE_OK; } /* ** Do both phases of a commit. */ int sqlite3BtreeCommit(Btree *p){ int rc; sqlite3BtreeEnter(p); rc = sqlite3BtreeCommitPhaseOne(p, 0); if( rc==SQLITE_OK ){ rc = sqlite3BtreeCommitPhaseTwo(p, 0); } sqlite3BtreeLeave(p); return rc; } #ifndef NDEBUG /* |
︙ | ︙ |
Changes to src/btree.h.
︙ | ︙ | |||
83 84 85 86 87 88 89 | u32 sqlite3BtreeLastPage(Btree*); int sqlite3BtreeSecureDelete(Btree*,int); int sqlite3BtreeGetReserve(Btree*); int sqlite3BtreeSetAutoVacuum(Btree *, int); int sqlite3BtreeGetAutoVacuum(Btree *); int sqlite3BtreeBeginTrans(Btree*,int); int sqlite3BtreeCommitPhaseOne(Btree*, const char *zMaster); | | | 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | u32 sqlite3BtreeLastPage(Btree*); int sqlite3BtreeSecureDelete(Btree*,int); int sqlite3BtreeGetReserve(Btree*); int sqlite3BtreeSetAutoVacuum(Btree *, int); int sqlite3BtreeGetAutoVacuum(Btree *); int sqlite3BtreeBeginTrans(Btree*,int); int sqlite3BtreeCommitPhaseOne(Btree*, const char *zMaster); int sqlite3BtreeCommitPhaseTwo(Btree*, int); int sqlite3BtreeCommit(Btree*); int sqlite3BtreeRollback(Btree*); int sqlite3BtreeBeginStmt(Btree*,int); int sqlite3BtreeCreateTable(Btree*, int*, int flags); int sqlite3BtreeIsInTrans(Btree*); int sqlite3BtreeIsInReadTrans(Btree*); int sqlite3BtreeIsInBackup(Btree*); |
︙ | ︙ |
Changes to src/test_syscall.c.
︙ | ︙ | |||
116 117 118 119 120 121 122 | } aSyscall[] = { /* 0 */ { "open", (sqlite3_syscall_ptr)ts_open, 0, EACCES, 0 }, /* 1 */ { "close", (sqlite3_syscall_ptr)ts_close, 0, 0, 0 }, /* 2 */ { "access", (sqlite3_syscall_ptr)ts_access, 0, 0, 0 }, /* 3 */ { "getcwd", (sqlite3_syscall_ptr)ts_getcwd, 0, 0, 0 }, /* 4 */ { "stat", (sqlite3_syscall_ptr)ts_stat, 0, 0, 0 }, /* 5 */ { "fstat", (sqlite3_syscall_ptr)ts_fstat, 0, 0, 0 }, | | | 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | } aSyscall[] = { /* 0 */ { "open", (sqlite3_syscall_ptr)ts_open, 0, EACCES, 0 }, /* 1 */ { "close", (sqlite3_syscall_ptr)ts_close, 0, 0, 0 }, /* 2 */ { "access", (sqlite3_syscall_ptr)ts_access, 0, 0, 0 }, /* 3 */ { "getcwd", (sqlite3_syscall_ptr)ts_getcwd, 0, 0, 0 }, /* 4 */ { "stat", (sqlite3_syscall_ptr)ts_stat, 0, 0, 0 }, /* 5 */ { "fstat", (sqlite3_syscall_ptr)ts_fstat, 0, 0, 0 }, /* 6 */ { "ftruncate", (sqlite3_syscall_ptr)ts_ftruncate, 0, EIO, 0 }, /* 7 */ { "fcntl", (sqlite3_syscall_ptr)ts_fcntl, 0, 0, 0 }, /* 8 */ { "read", (sqlite3_syscall_ptr)ts_read, 0, 0, 0 }, /* 9 */ { "pread", (sqlite3_syscall_ptr)ts_pread, 0, 0, 0 }, /* 10 */ { "pread64", (sqlite3_syscall_ptr)ts_pread64, 0, 0, 0 }, /* 11 */ { "write", (sqlite3_syscall_ptr)ts_write, 0, 0, 0 }, /* 12 */ { "pwrite", (sqlite3_syscall_ptr)ts_pwrite, 0, 0, 0 }, /* 13 */ { "pwrite64", (sqlite3_syscall_ptr)ts_pwrite64, 0, 0, 0 }, |
︙ | ︙ | |||
147 148 149 150 151 152 153 | #define orig_write ((ssize_t(*)(int,const void*,size_t))aSyscall[11].xOrig) #define orig_pwrite ((ssize_t(*)(int,const void*,size_t,off_t))\ aSyscall[12].xOrig) #define orig_pwrite64 ((ssize_t(*)(int,const void*,size_t,off_t))\ aSyscall[13].xOrig) #define orig_fchmod ((int(*)(int,mode_t))aSyscall[14].xOrig) #define orig_fallocate ((int(*)(int,off_t,off_t))aSyscall[15].xOrig) | < | 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | #define orig_write ((ssize_t(*)(int,const void*,size_t))aSyscall[11].xOrig) #define orig_pwrite ((ssize_t(*)(int,const void*,size_t,off_t))\ aSyscall[12].xOrig) #define orig_pwrite64 ((ssize_t(*)(int,const void*,size_t,off_t))\ aSyscall[13].xOrig) #define orig_fchmod ((int(*)(int,mode_t))aSyscall[14].xOrig) #define orig_fallocate ((int(*)(int,off_t,off_t))aSyscall[15].xOrig) /* ** This function is called exactly once from within each invocation of a ** system call wrapper in this file. It returns 1 if the function should ** fail, or 0 if it should succeed. */ static int tsIsFail(void){ |
︙ | ︙ | |||
260 261 262 263 264 265 266 | return orig_fstat(fd, p); } /* ** A wrapper around ftruncate(). */ static int ts_ftruncate(int fd, off_t n){ | | | 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 | return orig_fstat(fd, p); } /* ** A wrapper around ftruncate(). */ static int ts_ftruncate(int fd, off_t n){ if( tsIsFailErrno("ftruncate") ){ return -1; } return orig_ftruncate(fd, n); } /* ** A wrapper around fcntl(). |
︙ | ︙ | |||
529 530 531 532 533 534 535 536 537 538 539 540 541 542 | int rc; struct Errno { const char *z; int i; } aErrno[] = { { "EACCES", EACCES }, { 0, 0 } }; if( objc!=4 ){ Tcl_WrongNumArgs(interp, 2, objv, "SYSCALL ERRNO"); return TCL_ERROR; } | > > | 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 | int rc; struct Errno { const char *z; int i; } aErrno[] = { { "EACCES", EACCES }, { "EINTR", EINTR }, { "EIO", EIO }, { 0, 0 } }; if( objc!=4 ){ Tcl_WrongNumArgs(interp, 2, objv, "SYSCALL ERRNO"); return TCL_ERROR; } |
︙ | ︙ |
Changes to src/vdbeaux.c.
︙ | ︙ | |||
1699 1700 1701 1702 1703 1704 1705 | ** If one of the BtreeCommitPhaseOne() calls fails, this indicates an ** IO error while deleting or truncating a journal file. It is unlikely, ** but could happen. In this case abandon processing and return the error. */ for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ Btree *pBt = db->aDb[i].pBt; if( pBt ){ | | | 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 | ** If one of the BtreeCommitPhaseOne() calls fails, this indicates an ** IO error while deleting or truncating a journal file. It is unlikely, ** but could happen. In this case abandon processing and return the error. */ for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ Btree *pBt = db->aDb[i].pBt; if( pBt ){ rc = sqlite3BtreeCommitPhaseTwo(pBt, 0); } } if( rc==SQLITE_OK ){ sqlite3VtabCommit(db); } } |
︙ | ︙ | |||
1831 1832 1833 1834 1835 1836 1837 | ** may be lying around. Returning an error code won't help matters. */ disable_simulated_io_errors(); sqlite3BeginBenignMalloc(); for(i=0; i<db->nDb; i++){ Btree *pBt = db->aDb[i].pBt; if( pBt ){ | | | 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 | ** may be lying around. Returning an error code won't help matters. */ disable_simulated_io_errors(); sqlite3BeginBenignMalloc(); for(i=0; i<db->nDb; i++){ Btree *pBt = db->aDb[i].pBt; if( pBt ){ sqlite3BtreeCommitPhaseTwo(pBt, 1); } } sqlite3EndBenignMalloc(); enable_simulated_io_errors(); sqlite3VtabCommit(db); } |
︙ | ︙ |
Changes to test/fts3fault2.test.
︙ | ︙ | |||
9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # #*********************************************************************** # set testdir [file dirname $argv0] source $testdir/tester.tcl set ::testprefix fts3fault2 do_test 1.0 { execsql { CREATE VIRTUAL TABLE t1 USING fts4(x); INSERT INTO t1 VALUES('a b c'); INSERT INTO t1 VALUES('c d e'); CREATE VIRTUAL TABLE terms USING fts4aux(t1); | > > > | 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | # #*********************************************************************** # set testdir [file dirname $argv0] source $testdir/tester.tcl set ::testprefix fts3fault2 # If SQLITE_ENABLE_FTS3 is not defined, omit this file. ifcapable !fts3 { finish_test ; return } do_test 1.0 { execsql { CREATE VIRTUAL TABLE t1 USING fts4(x); INSERT INTO t1 VALUES('a b c'); INSERT INTO t1 VALUES('c d e'); CREATE VIRTUAL TABLE terms USING fts4aux(t1); |
︙ | ︙ |
test/progress.test became a regular file.
︙ | ︙ |
Changes to test/syscall.test.
︙ | ︙ | |||
54 55 56 57 58 59 60 61 62 | # set syscall_list [list \ open close access getcwd stat fstat ftruncate \ fcntl read pread write pwrite fchmod \ ] if {[test_syscall exists fallocate]} {lappend syscall_list fallocate} do_test 3.1 { test_syscall list } $syscall_list finish_test | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | # set syscall_list [list \ open close access getcwd stat fstat ftruncate \ fcntl read pread write pwrite fchmod \ ] if {[test_syscall exists fallocate]} {lappend syscall_list fallocate} do_test 3.1 { test_syscall list } $syscall_list #------------------------------------------------------------------------- # This test verifies that if a call to open() fails and errno is set to # EINTR, the call is retried. If it succeeds, execution continues as if # nothing happened. # test_syscall reset forcedelete test.db2 do_execsql_test 4.1 { CREATE TABLE t1(x, y); INSERT INTO t1 VALUES(1, 2); ATTACH 'test.db2' AS aux; CREATE TABLE aux.t2(x, y); INSERT INTO t2 VALUES(3, 4); } db_save_and_close test_syscall install open foreach jrnl [list wal delete] { for {set i 1} {$i < 20} {incr i} { db_restore_and_reopen test_syscall fault $i 0 test_syscall errno open EINTR do_test 4.2.$jrnl.$i { sqlite3 db test.db execsql { ATTACH 'test.db2' AS aux } execsql "PRAGMA main.journal_mode = $jrnl" execsql "PRAGMA aux.journal_mode = $jrnl" execsql { BEGIN; INSERT INTO t1 VALUES(5, 6); INSERT INTO t2 VALUES(7, 8); COMMIT; } db close sqlite3 db test.db execsql { ATTACH 'test.db2' AS aux } execsql { SELECT * FROM t1; SELECT * FROM t2; } } {1 2 5 6 3 4 7 8} } } finish_test |
Changes to test/sysfault.test.
︙ | ︙ | |||
61 62 63 64 65 66 67 68 69 70 | INSERT INTO t2 VALUES('y'); } } -test { faultsim_test_result {0 {wal 1 2 3 4}} \ {1 {unable to open database file}} \ {1 {attempt to write a readonly database}} } finish_test | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | INSERT INTO t2 VALUES('y'); } } -test { faultsim_test_result {0 {wal 1 2 3 4}} \ {1 {unable to open database file}} \ {1 {attempt to write a readonly database}} } #------------------------------------------------------------------------- # Check that a single EINTR error does not affect processing. # proc vfsfault_install {} { test_syscall reset test_syscall install {open ftruncate close} } forcedelete test.db test.db2 sqlite3 db test.db do_test 2.setup { execsql { CREATE TABLE t1(a, b, c, PRIMARY KEY(a)); INSERT INTO t1 VALUES('abc', 'def', 'ghi'); ATTACH 'test.db2' AS 'aux'; CREATE TABLE aux.t2(x); INSERT INTO t2 VALUES(1); } faultsim_save_and_close } {} do_faultsim_test 2.1 -faults vfsfault-transient -prep { catch { db close } faultsim_restore } -body { test_syscall errno open EINTR test_syscall errno ftruncate EINTR test_syscall errno close EINTR sqlite3 db test.db set res [db eval { ATTACH 'test.db2' AS 'aux'; SELECT * FROM t1; PRAGMA journal_mode = truncate; BEGIN; INSERT INTO t1 VALUES('jkl', 'mno', 'pqr'); UPDATE t2 SET x = 2; COMMIT; SELECT * FROM t1; SELECT * FROM t2; }] db close set res } -test { faultsim_test_result {0 {abc def ghi truncate abc def ghi jkl mno pqr 2}} } do_faultsim_test 2.2 -faults vfsfault-* -prep { catch { db close } faultsim_restore } -body { sqlite3 db test.db set res [db eval { ATTACH 'test.db2' AS 'aux'; SELECT * FROM t1; PRAGMA journal_mode = truncate; BEGIN; INSERT INTO t1 VALUES('jkl', 'mno', 'pqr'); UPDATE t2 SET x = 2; COMMIT; SELECT * FROM t1; SELECT * FROM t2; }] db close set res } -test { faultsim_test_result {0 {abc def ghi truncate abc def ghi jkl mno pqr 2}} \ {1 {unable to open database file}} \ {1 {unable to open database: test.db2}} \ {1 {attempt to write a readonly database}} \ {1 {disk I/O error}} } finish_test |