/ Check-in [611e704f]
Login

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

Overview
Comment:When a b-tree transaction is committed when there are open cursors, downgrade shared-cache write-locks to read-locks instead of relinquishing all locks. Fix for #3942. (CVS 6837)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 611e704fdf90a3d3932ca1cbab4be7e282bf1ddf
User & Date: danielk1977 2009-07-02 17:21:58
Context
FROM <tble>"). Ticket #3944. (CVS 6838) check-in: 45fd5419 user: danielk1977 tags: trunk
2009-07-02
18:40
Fix to sqlite3AuthRead to accommodate "new" or "old" references that are used in a context where a column reference may also be used (i.e. "SELECT new.
17:21
When a b-tree transaction is committed when there are open cursors, downgrade shared-cache write-locks to read-locks instead of relinquishing all locks. Fix for #3942. (CVS 6837) check-in: 611e704f user: danielk1977 tags: trunk
07:47
Cause opening a transaction on a sharable b-tree module automatically obtain a read-lock on page 1. This means there is no way for sqlite3BtreeGetMeta() to fail. (CVS 6836) check-in: e3c055f1 user: danielk1977 tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/btree.c.

     5      5   ** a legal notice, here is a blessing:
     6      6   **
     7      7   **    May you do good and not evil.
     8      8   **    May you find forgiveness for yourself and forgive others.
     9      9   **    May you share freely, never taking more than you give.
    10     10   **
    11     11   *************************************************************************
    12         -** $Id: btree.c,v 1.648 2009/07/02 07:47:33 danielk1977 Exp $
           12  +** $Id: btree.c,v 1.649 2009/07/02 17:21:58 danielk1977 Exp $
    13     13   **
    14     14   ** This file implements a external (disk-based) database using BTrees.
    15     15   ** See the header comment on "btreeInt.h" for additional information.
    16     16   ** Including a description of file format and an overview of operation.
    17     17   */
    18     18   #include "btreeInt.h"
    19     19   
................................................................................
    77     77     ** shared-cache feature disabled, then there is only ever one user
    78     78     ** of each BtShared structure and so this locking is not necessary. 
    79     79     ** So define the lock related functions as no-ops.
    80     80     */
    81     81     #define querySharedCacheTableLock(a,b,c) SQLITE_OK
    82     82     #define setSharedCacheTableLock(a,b,c) SQLITE_OK
    83     83     #define clearAllSharedCacheTableLocks(a)
           84  +  #define downgradeAllSharedCacheTableLocks(a)
    84     85     #define hasSharedCacheTableLock(a,b,c,d) 1
    85     86     #define hasReadConflicts(a, b) 0
    86     87   #endif
    87     88   
    88     89   #ifndef SQLITE_OMIT_SHARED_CACHE
    89     90   
    90     91   #ifdef SQLITE_DEBUG
................................................................................
   389    390       **
   390    391       ** If there is not currently a writer, then BtShared.isPending must
   391    392       ** be zero already. So this next line is harmless in that case.
   392    393       */
   393    394       pBt->isPending = 0;
   394    395     }
   395    396   }
          397  +
          398  +static void downgradeAllSharedCacheTableLocks(Btree *p){
          399  +  BtShared *pBt = p->pBt;
          400  +  if( pBt->pWriter==p ){
          401  +    BtLock *pLock;
          402  +    pBt->pWriter = 0;
          403  +    pBt->isExclusive = 0;
          404  +    pBt->isPending = 0;
          405  +    for(pLock=pBt->pLock; pLock; pLock=pLock->pNext){
          406  +      assert( pLock->eLock==READ_LOCK || pLock->pBtree==p );
          407  +      pLock->eLock = READ_LOCK;
          408  +    }
          409  +  }
          410  +}
          411  +
   396    412   #endif /* SQLITE_OMIT_SHARED_CACHE */
   397    413   
   398    414   static void releasePage(MemPage *pPage);  /* Forward reference */
   399    415   
   400    416   /*
   401    417   ** Verify that the cursor holds a mutex on the BtShared
   402    418   */
................................................................................
  2896   2912       }
  2897   2913   #endif
  2898   2914       rc = sqlite3PagerCommitPhaseOne(pBt->pPager, zMaster, 0);
  2899   2915       sqlite3BtreeLeave(p);
  2900   2916     }
  2901   2917     return rc;
  2902   2918   }
         2919  +
         2920  +/*
         2921  +** This function is called from both BtreeCommitPhaseTwo() and BtreeRollback()
         2922  +** at the conclusion of a transaction.
         2923  +*/
         2924  +static void btreeEndTransaction(Btree *p){
         2925  +  BtShared *pBt = p->pBt;
         2926  +  BtCursor *pCsr;
         2927  +  assert( sqlite3BtreeHoldsMutex(p) );
         2928  +
         2929  +  /* Search for a cursor held open by this b-tree connection. If one exists,
         2930  +  ** then the transaction will be downgraded to a read-only transaction
         2931  +  ** instead of actually concluded. A subsequent call to CommitPhaseTwo() 
         2932  +  ** or Rollback() will finish the transaction and unlock the database.  */
         2933  +  for(pCsr=pBt->pCursor; pCsr && pCsr->pBtree!=p; pCsr=pCsr->pNext);
         2934  +  assert( pCsr==0 || p->inTrans>TRANS_NONE );
         2935  +
         2936  +  btreeClearHasContent(pBt);
         2937  +  if( pCsr ){
         2938  +    downgradeAllSharedCacheTableLocks(p);
         2939  +    p->inTrans = TRANS_READ;
         2940  +  }else{
         2941  +    /* If the handle had any kind of transaction open, decrement the 
         2942  +    ** transaction count of the shared btree. If the transaction count 
         2943  +    ** reaches 0, set the shared state to TRANS_NONE. The unlockBtreeIfUnused()
         2944  +    ** call below will unlock the pager.  */
         2945  +    if( p->inTrans!=TRANS_NONE ){
         2946  +      clearAllSharedCacheTableLocks(p);
         2947  +      pBt->nTransaction--;
         2948  +      if( 0==pBt->nTransaction ){
         2949  +        pBt->inTransaction = TRANS_NONE;
         2950  +      }
         2951  +    }
         2952  +
         2953  +    /* Set the current transaction state to TRANS_NONE and unlock the 
         2954  +    ** pager if this call closed the only read or write transaction.  */
         2955  +    p->inTrans = TRANS_NONE;
         2956  +    unlockBtreeIfUnused(pBt);
         2957  +  }
         2958  +
         2959  +  btreeIntegrity(p);
         2960  +}
  2903   2961   
  2904   2962   /*
  2905   2963   ** Commit the transaction currently in progress.
  2906   2964   **
  2907   2965   ** This routine implements the second phase of a 2-phase commit.  The
  2908   2966   ** sqlite3BtreeCommitPhaseOne() routine does the first phase and should
  2909   2967   ** be invoked prior to calling this routine.  The sqlite3BtreeCommitPhaseOne()
................................................................................
  2933   2991       if( rc!=SQLITE_OK ){
  2934   2992         sqlite3BtreeLeave(p);
  2935   2993         return rc;
  2936   2994       }
  2937   2995       pBt->inTransaction = TRANS_READ;
  2938   2996     }
  2939   2997   
  2940         -  /* If the handle has any kind of transaction open, decrement the transaction
  2941         -  ** count of the shared btree. If the transaction count reaches 0, set
  2942         -  ** the shared state to TRANS_NONE. The unlockBtreeIfUnused() call below
  2943         -  ** will unlock the pager.
  2944         -  */
  2945         -  if( p->inTrans!=TRANS_NONE ){
  2946         -    clearAllSharedCacheTableLocks(p);
  2947         -    pBt->nTransaction--;
  2948         -    if( 0==pBt->nTransaction ){
  2949         -      pBt->inTransaction = TRANS_NONE;
  2950         -    }
  2951         -  }
  2952         -
  2953         -  /* Set the current transaction state to TRANS_NONE and unlock
  2954         -  ** the pager if this call closed the only read or write transaction.
  2955         -  */
  2956         -  btreeClearHasContent(pBt);
  2957         -  p->inTrans = TRANS_NONE;
  2958         -  unlockBtreeIfUnused(pBt);
  2959         -
  2960         -  btreeIntegrity(p);
         2998  +  btreeEndTransaction(p);
  2961   2999     sqlite3BtreeLeave(p);
  2962   3000     return SQLITE_OK;
  2963   3001   }
  2964   3002   
  2965   3003   /*
  2966   3004   ** Do both phases of a commit.
  2967   3005   */
................................................................................
  3075   3113       if( sqlite3BtreeGetPage(pBt, 1, &pPage1, 0)==SQLITE_OK ){
  3076   3114         releasePage(pPage1);
  3077   3115       }
  3078   3116       assert( countWriteCursors(pBt)==0 );
  3079   3117       pBt->inTransaction = TRANS_READ;
  3080   3118     }
  3081   3119   
  3082         -  if( p->inTrans!=TRANS_NONE ){
  3083         -    clearAllSharedCacheTableLocks(p);
  3084         -    assert( pBt->nTransaction>0 );
  3085         -    pBt->nTransaction--;
  3086         -    if( 0==pBt->nTransaction ){
  3087         -      pBt->inTransaction = TRANS_NONE;
  3088         -    }
  3089         -  }
  3090         -
  3091         -  btreeClearHasContent(pBt);
  3092         -  p->inTrans = TRANS_NONE;
  3093         -  unlockBtreeIfUnused(pBt);
  3094         -
  3095         -  btreeIntegrity(p);
         3120  +  btreeEndTransaction(p);
  3096   3121     sqlite3BtreeLeave(p);
  3097   3122     return rc;
  3098   3123   }
  3099   3124   
  3100   3125   /*
  3101   3126   ** Start a statement subtransaction. The subtransaction can can be rolled
  3102   3127   ** back independently of the main transaction. You must start a transaction 

Changes to src/prepare.c.

     9      9   **    May you share freely, never taking more than you give.
    10     10   **
    11     11   *************************************************************************
    12     12   ** This file contains the implementation of the sqlite3_prepare()
    13     13   ** interface, and routines that contribute to loading the database schema
    14     14   ** from disk.
    15     15   **
    16         -** $Id: prepare.c,v 1.126 2009/07/02 07:47:33 danielk1977 Exp $
           16  +** $Id: prepare.c,v 1.127 2009/07/02 17:21:58 danielk1977 Exp $
    17     17   */
    18     18   #include "sqliteInt.h"
    19     19   
    20     20   /*
    21     21   ** Fill the InitData structure with an error message that indicates
    22     22   ** that the database is corrupt.
    23     23   */
................................................................................
   132    132     Table *pTab;
   133    133     Db *pDb;
   134    134     char const *azArg[4];
   135    135     int meta[5];
   136    136     InitData initData;
   137    137     char const *zMasterSchema;
   138    138     char const *zMasterName = SCHEMA_TABLE(iDb);
   139         -  int openedTransaction;
          139  +  int openedTransaction = 0;
   140    140   
   141    141     /*
   142    142     ** The master database table has a structure like this
   143    143     */
   144    144     static const char master_schema[] = 
   145    145        "CREATE TABLE sqlite_master(\n"
   146    146        "  type text,\n"
................................................................................
   218    218     if( !sqlite3BtreeIsInReadTrans(pDb->pBt) ){
   219    219       rc = sqlite3BtreeBeginTrans(pDb->pBt, 0);
   220    220       if( rc!=SQLITE_OK ){
   221    221         sqlite3SetString(pzErrMsg, db, "%s", sqlite3ErrStr(rc));
   222    222         goto initone_error_out;
   223    223       }
   224    224       openedTransaction = 1;
   225         -  }else{
   226         -    openedTransaction = 0;
   227    225     }
   228    226   
   229    227     /* Get the database meta information.
   230    228     **
   231    229     ** Meta values are as follows:
   232    230     **    meta[0]   Schema cookie.  Changes with each schema change.
   233    231     **    meta[1]   File format of schema layer.

Added test/sharedlock.test.

            1  +# 2009 July 2
            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  +# $Id: sharedlock.test,v 1.1 2009/07/02 17:21:58 danielk1977 Exp $
           13  +
           14  +set testdir [file dirname $argv0]
           15  +source $testdir/tester.tcl
           16  +db close
           17  +
           18  +ifcapable !shared_cache {
           19  +  finish_test
           20  +  return
           21  +}
           22  +
           23  +set ::enable_shared_cache [sqlite3_enable_shared_cache 1]
           24  +sqlite3 db  test.db
           25  +sqlite3 db2 test.db
           26  +
           27  +do_test sharedlock-1.1 {
           28  +  execsql {
           29  +    CREATE TABLE t1(a, b);
           30  +    INSERT INTO t1 VALUES(1, 'one');
           31  +    INSERT INTO t1 VALUES(2, 'two');
           32  +  }
           33  +} {}
           34  +
           35  +do_test sharedlock-1.2 {
           36  +  set res [list]
           37  +  db eval { SELECT * FROM t1 ORDER BY rowid } {
           38  +    lappend res $a $b
           39  +    if {$a == 1} { catch { db  eval "INSERT INTO t1 VALUES(3, 'three')" } }
           40  +
           41  +    # This should fail. Connection [db] has a read-lock on t1, which should
           42  +    # prevent connection [db2] from obtaining the write-lock it needs to
           43  +    # modify t1. At one point there was a bug causing the previous INSERT
           44  +    # to drop the read-lock belonging to [db].
           45  +    if {$a == 2} { catch { db2 eval "INSERT INTO t1 VALUES(4, 'four')"  } }
           46  +  }
           47  +  set res
           48  +} {1 one 2 two 3 three}
           49  +
           50  +db close
           51  +db2 close
           52  +
           53  +sqlite3_enable_shared_cache $::enable_shared_cache
           54  +finish_test
           55  +