Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Fix a bug in the WAL checkpoint code causing SQLite to use an inconsistent cache in a subsequent transaction. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | wal |
Files: | files | file ages | folders |
SHA1: |
d1cadeed4eea20d8892726cc8c69f4f3 |
User & Date: | dan 2010-04-29 14:51:33.000 |
Original Comment: | Fix a but in the WAL checkpoint code causing SQLite to use an inconsistent cache in a subsequent transaction. |
Context
2010-04-29
| ||
14:58 | Close all open database connections at the end of wal.test. (check-in: 3cc55a7568 user: dan tags: wal) | |
14:51 | Fix a bug in the WAL checkpoint code causing SQLite to use an inconsistent cache in a subsequent transaction. (check-in: d1cadeed4e user: dan tags: wal) | |
08:47 | Add tests to walthread.test. (check-in: 9e891e7543 user: dan tags: wal) | |
Changes
Changes to src/wal.c.
︙ | ︙ | |||
1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 | sqlite3_file *pFd, /* File descriptor open on db file */ int sync_flags, /* Flags to sync db file with (or 0) */ u8 *zBuf, /* Temporary buffer to use */ int (*xBusyHandler)(void *), /* Pointer to busy-handler function */ void *pBusyHandlerArg /* Argument to pass to xBusyHandler */ ){ int rc; /* Return code */ assert( !pLog->isLocked ); /* Wait for an EXCLUSIVE lock on regions B and C. */ do { rc = logLockRegion(pLog, LOG_REGION_B|LOG_REGION_C, LOG_WRLOCK); }while( rc==SQLITE_BUSY && xBusyHandler(pBusyHandlerArg) ); if( rc!=SQLITE_OK ) return rc; /* Wait for an EXCLUSIVE lock on region A. */ do { rc = logLockRegion(pLog, LOG_REGION_A, LOG_WRLOCK); }while( rc==SQLITE_BUSY && xBusyHandler(pBusyHandlerArg) ); if( rc!=SQLITE_OK ){ logLockRegion(pLog, LOG_REGION_B|LOG_REGION_C, LOG_UNLOCK); return rc; } /* Copy data from the log to the database file. */ | > | > > > > > > > > > | 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 | sqlite3_file *pFd, /* File descriptor open on db file */ int sync_flags, /* Flags to sync db file with (or 0) */ u8 *zBuf, /* Temporary buffer to use */ int (*xBusyHandler)(void *), /* Pointer to busy-handler function */ void *pBusyHandlerArg /* Argument to pass to xBusyHandler */ ){ int rc; /* Return code */ int isChanged = 0; /* True if a new wal-index header is loaded */ assert( !pLog->isLocked ); /* Wait for an EXCLUSIVE lock on regions B and C. */ do { rc = logLockRegion(pLog, LOG_REGION_B|LOG_REGION_C, LOG_WRLOCK); }while( rc==SQLITE_BUSY && xBusyHandler(pBusyHandlerArg) ); if( rc!=SQLITE_OK ) return rc; /* Wait for an EXCLUSIVE lock on region A. */ do { rc = logLockRegion(pLog, LOG_REGION_A, LOG_WRLOCK); }while( rc==SQLITE_BUSY && xBusyHandler(pBusyHandlerArg) ); if( rc!=SQLITE_OK ){ logLockRegion(pLog, LOG_REGION_B|LOG_REGION_C, LOG_UNLOCK); return rc; } /* Copy data from the log to the database file. */ rc = logSummaryReadHdr(pLog, &isChanged); if( rc==SQLITE_OK ){ rc = logCheckpoint(pLog, pFd, sync_flags, zBuf); } if( isChanged ){ /* If a new wal-index header was loaded before the checkpoint was ** performed, then the pager-cache associated with log pLog is now ** out of date. So zero the cached wal-index header to ensure that ** next time the pager opens a snapshot on this database it knows that ** the cache needs to be reset. */ memset(&pLog->hdr, 0, sizeof(LogSummaryHdr)); } /* Release the locks. */ logLockRegion(pLog, LOG_REGION_A|LOG_REGION_B|LOG_REGION_C, LOG_UNLOCK); return rc; } int sqlite3WalCallback(Log *pLog){ |
︙ | ︙ |
Changes to test/wal.test.
︙ | ︙ | |||
913 914 915 916 917 918 919 920 921 922 | } [expr (1<<$ii)] } catch { db2 close } catch { close $::buddy } db close } finish_test | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 | } [expr (1<<$ii)] } catch { db2 close } catch { close $::buddy } db close } #------------------------------------------------------------------------- # Check a fun corruption case has been fixed. # # The problem was that after performing a checkpoint using a connection # that had an out-of-date pager-cache, the next time the connection was # used it did not realize the cache was out-of-date and proceeded to # operate with an inconsistent cache. Leading to corruption. # catch { db close } catch { db2 close } catch { db3 close } file delete -force test.db test.db-wal sqlite3 db test.db sqlite3 db2 test.db do_test wal-14 { execsql { PRAGMA journal_mode = WAL; CREATE TABLE t1(a PRIMARY KEY, b); INSERT INTO t1 VALUES(randomblob(10), randomblob(100)); INSERT INTO t1 SELECT randomblob(10), randomblob(100) FROM t1; INSERT INTO t1 SELECT randomblob(10), randomblob(100) FROM t1; INSERT INTO t1 SELECT randomblob(10), randomblob(100) FROM t1; } db2 eval { INSERT INTO t1 SELECT randomblob(10), randomblob(100); INSERT INTO t1 SELECT randomblob(10), randomblob(100); INSERT INTO t1 SELECT randomblob(10), randomblob(100); INSERT INTO t1 SELECT randomblob(10), randomblob(100); } # After executing the "PRAGMA checkpoint", connection [db] was being # left with an inconsistent cache. Running the CREATE INDEX statement # in this state led to database corruption. catchsql { PRAGMA checkpoint; CREATE INDEX i1 on t1(b); } db2 eval { PRAGMA integrity_check } } {ok} finish_test |
Changes to test/walthread.test.
︙ | ︙ | |||
20 21 22 23 24 25 26 | if {[run_thread_tests]==0} { finish_test ; return } set sqlite_walsummary_mmap_incr 64 # How long, in seconds, to run each test for. If a test is set to run for # 0 seconds, it is omitted entirely. # | | | > | 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | if {[run_thread_tests]==0} { finish_test ; return } set sqlite_walsummary_mmap_incr 64 # How long, in seconds, to run each test for. If a test is set to run for # 0 seconds, it is omitted entirely. # set seconds(walthread-1) 20 set seconds(walthread-2) 20 set seconds(walthread-3) 20 set seconds(walthread-4) 20 # The parameter is the name of a variable in the callers context. The # variable may or may not exist when this command is invoked. # # If the variable does exist, its value is returned. Otherwise, this # command uses [vwait] to wait until it is set, then returns the value. # In other words, this is a version of the [set VARNAME] command that |
︙ | ︙ | |||
369 370 371 372 373 374 375 | integrity_check db close } set {} "[expr $nRun-$nDel] w, $nDel r" } | | | 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 | integrity_check db close } set {} "[expr $nRun-$nDel] w, $nDel r" } do_thread_test walthread-3 -seconds $seconds(walthread-3) -init { execsql { PRAGMA journal_mode = WAL; CREATE TABLE t1(cnt PRIMARY KEY, sum1, sum2); CREATE INDEX i1 ON t1(sum1); CREATE INDEX i2 ON t1(sum2); INSERT INTO t1 VALUES(0, 0, 0); } |
︙ | ︙ | |||
420 421 422 423 424 425 426 427 428 429 | error "database content is invalid" } incr s2 $s1 incr s1 $c incr c 1 } } finish_test | > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 | error "database content is invalid" } incr s2 $s1 incr s1 $c incr c 1 } } do_thread_test2 walthread-4 -seconds $seconds(walthread-4) -init { execsql { PRAGMA journal_mode = WAL; CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE); } } -thread r 1 { # This connection only ever reads the database. Therefore the # busy-handler is not required. Disable it to check that this is true. db busy {} while {[tt_continue]} integrity_check set {} ok } -thread w 1 { proc wal_hook {zDb nEntry} { if {$nEntry>15} { return 1 } return 0 } db wal_hook wal_hook set row 1 while {[tt_continue]} { db eval { REPLACE INTO t1 VALUES($row, randomblob(300)) } incr row if {$row == 10} { set row 1 } } set {} ok } finish_test |