/ Check-in [9f07d2d9]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Fix a change-counter bug similar to #3584. This one is much more obscure though, requiring a transient IO or malloc error to occur while running in exclusive mode. (CVS 6189)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 9f07d2d9226b73c4dc311fd142e0aaba4ffcb078
User & Date: danielk1977 2009-01-16 16:40:14
Context
2009-01-16
23:47
Remove a harmless UMR that occurs inside some debugging code. (CVS 6190) check-in: 191c399f user: drh tags: trunk
16:40
Fix a change-counter bug similar to #3584. This one is much more obscure though, requiring a transient IO or malloc error to occur while running in exclusive mode. (CVS 6189) check-in: 9f07d2d9 user: danielk1977 tags: trunk
16:23
Revert (6187). (CVS 6188) check-in: a353c1ab user: danielk1977 tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/pager.c.

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
....
1728
1729
1730
1731
1732
1733
1734











1735
1736
1737
1738
1739
1740
1741
** The pager is used to access a database disk file.  It implements
** atomic commit and rollback through the use of a journal file that
** is separate from the database file.  The pager also implements file
** locking to prevent two processes from writing the same database
** file simultaneously, or one process from reading the database while
** another is writing.
**
** @(#) $Id: pager.c,v 1.553 2009/01/16 16:23:38 danielk1977 Exp $
*/
#ifndef SQLITE_OMIT_DISKIO
#include "sqliteInt.h"

/*
** Macros for troubleshooting.  Normally turned off
*/
................................................................................
  ** SQLITE_FCNTL_DB_UNCHANGED file-control method to disable the
  ** assertion that the transaction counter was modified.
  */
  assert(
    pPager->fd->pMethods==0 ||
    sqlite3OsFileControl(pPager->fd,SQLITE_FCNTL_DB_UNCHANGED,0)>=SQLITE_OK
  );












  if( rc==SQLITE_OK ){
    zMaster = pPager->pTmpSpace;
    rc = readMasterJournal(pPager->jfd, zMaster, pPager->pVfs->mxPathname+1);
  }
  if( rc==SQLITE_OK ){
    rc = pager_end_transaction(pPager, zMaster[0]!='\0');







|







 







>
>
>
>
>
>
>
>
>
>
>







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
....
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
** The pager is used to access a database disk file.  It implements
** atomic commit and rollback through the use of a journal file that
** is separate from the database file.  The pager also implements file
** locking to prevent two processes from writing the same database
** file simultaneously, or one process from reading the database while
** another is writing.
**
** @(#) $Id: pager.c,v 1.554 2009/01/16 16:40:14 danielk1977 Exp $
*/
#ifndef SQLITE_OMIT_DISKIO
#include "sqliteInt.h"

/*
** Macros for troubleshooting.  Normally turned off
*/
................................................................................
  ** SQLITE_FCNTL_DB_UNCHANGED file-control method to disable the
  ** assertion that the transaction counter was modified.
  */
  assert(
    pPager->fd->pMethods==0 ||
    sqlite3OsFileControl(pPager->fd,SQLITE_FCNTL_DB_UNCHANGED,0)>=SQLITE_OK
  );

  /* If this playback is happening automatically as a result of an IO or 
  ** malloc error that occured after the change-counter was updated but 
  ** before the transaction was committed, then the change-counter 
  ** modification may just have been reverted. If this happens in exclusive 
  ** mode, then subsequent transactions performed by the connection will not
  ** update the change-counter at all. This may lead to cache inconsistency
  ** problems for other processes at some point in the future. So, just
  ** in case this has happened, clear the changeCountDone flag now.
  */
  pPager->changeCountDone = 0;

  if( rc==SQLITE_OK ){
    zMaster = pPager->pTmpSpace;
    rc = readMasterJournal(pPager->jfd, zMaster, pPager->pVfs->mxPathname+1);
  }
  if( rc==SQLITE_OK ){
    rc = pager_end_transaction(pPager, zMaster[0]!='\0');

Changes to test/malloc.test.

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
...
716
717
718
719
720
721
722


































































723
724
725
726
727
728
729
730
731
# This file attempts to check the behavior of the SQLite library in 
# an out-of-memory situation. When compiled with -DSQLITE_DEBUG=1, 
# the SQLite library accepts a special command (sqlite3_memdebug_fail N C)
# which causes the N-th malloc to fail.  This special feature is used
# to see what happens in the library if a malloc were to really fail
# due to an out-of-memory situation.
#
# $Id: malloc.test,v 1.73 2009/01/10 16:15:09 danielk1977 Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl


# Only run these tests if memory debugging is turned on.
#
................................................................................
do_malloc_test 31 -sqlprep {
  PRAGMA journal_mode = persist;
  PRAGMA journal_size_limit = 1024;
  CREATE TABLE t1(a PRIMARY KEY, b);
} -sqlbody {
  INSERT INTO t1 VALUES(1, 2);
}



































































# Ensure that no file descriptors were leaked.
do_test malloc-99.X {
  catch {db close}
  set sqlite_open_file_count
} {0}

puts open-file-count=$sqlite_open_file_count
finish_test







|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>









12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
...
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
# This file attempts to check the behavior of the SQLite library in 
# an out-of-memory situation. When compiled with -DSQLITE_DEBUG=1, 
# the SQLite library accepts a special command (sqlite3_memdebug_fail N C)
# which causes the N-th malloc to fail.  This special feature is used
# to see what happens in the library if a malloc were to really fail
# due to an out-of-memory situation.
#
# $Id: malloc.test,v 1.74 2009/01/16 16:40:14 danielk1977 Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl


# Only run these tests if memory debugging is turned on.
#
................................................................................
do_malloc_test 31 -sqlprep {
  PRAGMA journal_mode = persist;
  PRAGMA journal_size_limit = 1024;
  CREATE TABLE t1(a PRIMARY KEY, b);
} -sqlbody {
  INSERT INTO t1 VALUES(1, 2);
}

# When written, this test provoked an obscure change-counter bug.
# 
# If, when running in exclusive mode, a malloc() failure occurs
# after the database file change-counter has been written but
# before the transaction has been committed, then the transaction
# is automatically rolled back. However, internally the 
# Pager.changeCounterDone flag was being left set. This means
# that if the same connection attempts another transaction following
# the malloc failure and rollback, the change counter will not
# be updated. This could corrupt another processes cache.
#
do_malloc_test 32 -tclprep {
  # Build a small database containing an indexed table.
  #
  db eval {
    BEGIN;
    CREATE TABLE t1(a PRIMARY KEY, b);
    INSERT INTO t1 VALUES(1, 'one');
    INSERT INTO t1 VALUES(2, 'two');
    INSERT INTO t1 VALUES(3, 'three');
    COMMIT;
    PRAGMA locking_mode = exclusive;
  }

  # Open a second database connection. Load the table (but not index)
  # into the second connections pager cache.
  #
  sqlite3 db2 test.db
  db2 eval { SELECT b FROM t1 }

} -tclbody {
  # Running in exclusive mode, perform a database transaction that 
  # modifies both the database table and index. For iterations where
  # the malloc failure occurs after updating the change counter but
  # before committing the transaction, this should result in the
  # transaction being rolled back but the changeCounterDone flag
  # left set.
  #
  db eval { UPDATE t1 SET a = a + 3 }
} -cleanup {

  # Perform another transaction using the first connection. Unlock
  # the database after doing so. If this is one of the right iterations,
  # then this should result in the database contents being updated but
  # the change-counter left as it is.
  #
  db eval { 
    PRAGMA locking_mode = normal;
    UPDATE t1 SET a = a + 3;
  }

  # Now do an integrity check with the second connection. The second
  # connection still has the database table in its cache. If this is
  # one of the magic iterations and the change counter was not modified,
  # then it won't realize that the cached data is out of date. Since
  # the cached data won't match the up to date index data read from
  # the database file, the integrity check should fail.
  #
  set zRepeat "transient"
  if {$::iRepeat} {set zRepeat "persistent"}
  do_test malloc-32.$zRepeat.${::n}.integrity {
    execsql {PRAGMA integrity_check} db2
  } {ok}
  db2 close
}

# Ensure that no file descriptors were leaked.
do_test malloc-99.X {
  catch {db close}
  set sqlite_open_file_count
} {0}

puts open-file-count=$sqlite_open_file_count
finish_test