SQLite

Check-in [d1cadeed4e]
Login

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: d1cadeed4eea20d8892726cc8c69f4f3f57d0cd4
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
Unified Diff Ignore Whitespace Patch
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
1902
1903
1904
1905









1906
1907
1908
1909
1910
1911
1912
  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. */
  rc = logSummaryReadHdr(pLog, 0);
  if( rc==SQLITE_OK ){
    rc = logCheckpoint(pLog, pFd, sync_flags, zBuf);
  }










  /* Release the locks. */
  logLockRegion(pLog, LOG_REGION_A|LOG_REGION_B|LOG_REGION_C, LOG_UNLOCK);
  return rc;
}

int sqlite3WalCallback(Log *pLog){







>



















|



>
>
>
>
>
>
>
>
>







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
27
28
29

30
31
32
33
34
35
36
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)  0
set seconds(walthread-2)  0
set seconds(walthread-3) 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







|
|

>







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
376
377
378
379
380
381
382
383

    integrity_check
    db close
  }
  set {} "[expr $nRun-$nDel] w, $nDel r"
}

do_thread_test2 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);
  }







|







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