/ Check-in [6c5c04ee]
Login

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: 6c5c04eea1f0e8d61883ee8675c249fbf895dc01
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
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

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   #