/ Check-in [6da0e962]
Login

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

Overview
Comment:Add some test cases and fix some small problems with BEGIN UNLOCKED transactions.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | begin-concurrent
Files: files | file ages | folders
SHA1: 6da0e962ad2aa5e52c1f1b5c3dbf77a2cb16ac2d
User & Date: dan 2015-07-28 16:46:49
Wiki:begin-concurrent
Context
2015-07-29
12:14
Only allow UNLOCKED transactions to commit if none of the pages read by the transaction have been modified since it was opened. check-in: 0b971842 user: dan tags: begin-concurrent
2015-07-28
16:46
Add some test cases and fix some small problems with BEGIN UNLOCKED transactions. check-in: 6da0e962 user: dan tags: begin-concurrent
2015-07-27
19:31
Add an experimental "BEGIN UNLOCKED" command. check-in: 80794216 user: dan tags: begin-concurrent
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/wal.c.

  2504   2504   Pgno sqlite3WalDbsize(Wal *pWal){
  2505   2505     if( pWal && ALWAYS(pWal->readLock>=0) ){
  2506   2506       return pWal->hdr.nPage;
  2507   2507     }
  2508   2508     return 0;
  2509   2509   }
  2510   2510   
         2511  +/*
         2512  +** Take the WRITER lock on the WAL file. Return SQLITE_OK if successful,
         2513  +** or an SQLite error code otherwise. This routine does not invoke any
         2514  +** busy-handler callbacks, that is done at a higher level.
         2515  +*/
         2516  +static int walWriteLock(Wal *pWal){
         2517  +  int rc;
         2518  +
         2519  +  /* Cannot start a write transaction without first holding a read lock */
         2520  +  assert( pWal->readLock>=0 );
         2521  +  assert( pWal->writeLock==0 );
         2522  +
         2523  +  /* If this is a read-only connection, obtaining a write-lock is not
         2524  +  ** possible. In this case return SQLITE_READONLY. Otherwise, attempt
         2525  +  ** to grab the WRITER lock. Set Wal.writeLock to true and return
         2526  +  ** SQLITE_OK if successful, or leave Wal.writeLock clear and return 
         2527  +  ** an SQLite error code (possibly SQLITE_BUSY) otherwise. */
         2528  +  if( pWal->readOnly ){
         2529  +    rc = SQLITE_READONLY;
         2530  +  }else{
         2531  +    rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1, 0);
         2532  +    if( rc==SQLITE_OK ){
         2533  +      pWal->writeLock = 1;
         2534  +    }
         2535  +  }
         2536  +
         2537  +  return rc;
         2538  +}
  2511   2539   
  2512   2540   /* 
  2513   2541   ** This function starts a write transaction on the WAL.
  2514   2542   **
  2515   2543   ** A read transaction must have already been started by a prior call
  2516   2544   ** to sqlite3WalBeginReadTransaction().
  2517   2545   **
................................................................................
  2519   2547   ** the read transaction was started, then it is not possible for this
  2520   2548   ** thread to write as doing so would cause a fork.  So this routine
  2521   2549   ** returns SQLITE_BUSY in that case and no write transaction is started.
  2522   2550   **
  2523   2551   ** There can only be a single writer active at a time.
  2524   2552   */
  2525   2553   int sqlite3WalBeginWriteTransaction(Wal *pWal){
  2526         -  int rc;
  2527         -
  2528         -  /* Cannot start a write transaction without first holding a read
  2529         -  ** transaction. */
  2530         -  assert( pWal->readLock>=0 );
  2531         -
  2532         -  if( pWal->readOnly ){
  2533         -    return SQLITE_READONLY;
         2554  +  int rc = walWriteLock(pWal);
         2555  +  if( rc==SQLITE_OK ){
         2556  +    /* If another connection has written to the database file since the
         2557  +    ** time the read transaction on this connection was started, then
         2558  +    ** the write is disallowed. Release the WRITER lock and return
         2559  +    ** SQLITE_BUSY_SNAPSHOT in this case.  */
         2560  +    if( memcmp(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0 ){
         2561  +      walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
         2562  +      pWal->writeLock = 0;
         2563  +      rc = SQLITE_BUSY_SNAPSHOT;
         2564  +    }
  2534   2565     }
  2535         -
  2536         -  /* Only one writer allowed at a time.  Get the write lock.  Return
  2537         -  ** SQLITE_BUSY if unable.
  2538         -  */
  2539         -  rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1, 0);
  2540         -  if( rc ){
  2541         -    return rc;
  2542         -  }
  2543         -  pWal->writeLock = 1;
  2544         -
  2545         -  /* If another connection has written to the database file since the
  2546         -  ** time the read transaction on this connection was started, then
  2547         -  ** the write is disallowed.
  2548         -  */
  2549         -  if( memcmp(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0 ){
  2550         -    walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
  2551         -    pWal->writeLock = 0;
  2552         -    rc = SQLITE_BUSY_SNAPSHOT;
  2553         -  }
  2554         -
  2555   2566     return rc;
  2556   2567   }
  2557   2568   
  2558   2569   /* 
  2559   2570   ** TODO: Combine some code with BeginWriteTransaction()
  2560   2571   **
  2561   2572   ** This function is only ever called when committing a "BEGIN UNLOCKED"
  2562   2573   ** transaction. It may be assumed that no frames have been written to
  2563   2574   ** the wal file.
  2564   2575   */
  2565   2576   int sqlite3WalLockForCommit(Wal *pWal, PgHdr *pList, PgHdr *pPage1){
  2566         -  volatile WalIndexHdr *pHead;    /* Head of the wal file */
  2567         -  int rc;
  2568         -
  2569         -  /* Cannot start a write transaction without first holding a read
  2570         -  ** transaction. */
  2571         -  assert( pWal->readLock>=0 );
  2572         -
  2573         -  if( pWal->readOnly ){
  2574         -    return SQLITE_READONLY;
  2575         -  }
  2576         -
  2577         -  /* Only one writer allowed at a time.  Get the write lock.  Return
  2578         -  ** SQLITE_BUSY if unable.
  2579         -  */
  2580         -  rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1, 0);
  2581         -  if( rc ){
  2582         -    return rc;
  2583         -  }
  2584         -  pWal->writeLock = 1;
         2577  +  int rc = walWriteLock(pWal);
  2585   2578   
  2586   2579     /* If the database has been modified since this transaction was started,
  2587   2580     ** check if it is still possible to commit. The transaction can be 
  2588   2581     ** committed if:
  2589   2582     **
  2590   2583     **   a) None of the pages in pList have been modified since the 
  2591   2584     **      transaction opened, and
  2592   2585     **
  2593   2586     **   b) The database schema cookie has not been modified since the
  2594   2587     **      transaction was started.
  2595   2588     */
  2596         -  pHead = walIndexHdr(pWal);
  2597         -  if( memcmp(&pWal->hdr, (void*)pHead, sizeof(WalIndexHdr))!=0 ){
  2598         -    /* TODO: Is this safe? Because it holds the WRITER lock this thread
  2599         -    ** has exclusive access to the live header, but might it be corrupt? */
  2600         -    PgHdr *pPg;
  2601         -    u32 iLast = pHead->mxFrame;
  2602         -    for(pPg=pList; rc==SQLITE_OK && pPg; pPg=pPg->pDirty){
  2603         -      u32 iSlot = 0;
  2604         -      rc = walFindFrame(pWal, pPg->pgno, iLast, &iSlot);
  2605         -      if( iSlot>pWal->hdr.mxFrame ){
  2606         -        sqlite3_log(SQLITE_OK,
  2607         -            "cannot commit UNLOCKED transaction (conflict at page %d)",
  2608         -            (int)pPg->pgno
  2609         -        );
  2610         -        rc = SQLITE_BUSY_SNAPSHOT;
         2589  +  if( rc==SQLITE_OK ){
         2590  +    volatile WalIndexHdr *pHead;    /* Head of the wal file */
         2591  +    pHead = walIndexHdr(pWal);
         2592  +    if( memcmp(&pWal->hdr, (void*)pHead, sizeof(WalIndexHdr))!=0 ){
         2593  +      int bSeenPage1 = 0;           /* True if page 1 is in list pList */
         2594  +  
         2595  +      /* TODO: Is this safe? Because it holds the WRITER lock this thread
         2596  +      ** has exclusive access to the live header, but might it be corrupt? */
         2597  +      PgHdr *pPg;
         2598  +      u32 iLast = pHead->mxFrame;
         2599  +      for(pPg=pList; rc==SQLITE_OK && pPg; pPg=pPg->pDirty){
         2600  +        u32 iSlot = 0;
         2601  +        rc = walFindFrame(pWal, pPg->pgno, iLast, &iSlot);
         2602  +        if( iSlot>pWal->hdr.mxFrame ){
         2603  +          sqlite3_log(SQLITE_OK,
         2604  +              "cannot commit UNLOCKED transaction (conflict at page %d)",
         2605  +              (int)pPg->pgno
         2606  +          );
         2607  +          rc = SQLITE_BUSY_SNAPSHOT;
         2608  +        }
         2609  +        if( pPg->pgno==1 ) bSeenPage1 = 1;
  2611   2610         }
  2612         -    }
  2613         -
  2614         -    if( rc==SQLITE_OK ){
  2615         -      /* Read the newest schema cookie from the wal file. */
  2616         -      u32 iSlot = 0;
  2617         -      rc = walFindFrame(pWal, 1, iLast, &iSlot);
  2618         -      if( rc==SQLITE_OK && iSlot>pWal->hdr.mxFrame ){
  2619         -        u8 aNew[4];
  2620         -        u8 *aOld = &((u8*)pPage1->pData)[40];
  2621         -        int sz;
  2622         -        i64 iOffset;
  2623         -        sz = pWal->hdr.szPage;
  2624         -        sz = (sz&0xfe00) + ((sz&0x0001)<<16);
  2625         -        iOffset = walFrameOffset(iSlot, sz) + WAL_FRAME_HDRSIZE + 40;
  2626         -        rc = sqlite3OsRead(pWal->pWalFd, aNew, sizeof(aNew), iOffset);
  2627         -        if( rc==SQLITE_OK && memcmp(aOld, aNew, sizeof(aNew)) ){
  2628         -          /* TODO: New error code? SQLITE_BUSY_SCHEMA. */
  2629         -          rc = SQLITE_BUSY_SNAPSHOT;
         2611  +  
         2612  +      /* If the current transaction does not modify page 1 of the database,
         2613  +      ** check if page 1 has been modified since the transaction was started.
         2614  +      ** If it has, check if the schema cookie value (the 4 bytes beginning at
         2615  +      ** byte offset 40) is the same as it is on pPage1. If not, this indicates
         2616  +      ** that the current head of the wal file uses a different schema than 
         2617  +      ** the snapshot against which the current transaction was prepared. Return
         2618  +      ** SQLITE_BUSY_SNAPSHOT in this case.  */
         2619  +      if( rc==SQLITE_OK && bSeenPage1==0 ){
         2620  +        u32 iSlot = 0;
         2621  +        rc = walFindFrame(pWal, 1, iLast, &iSlot);
         2622  +        if( rc==SQLITE_OK && iSlot>pWal->hdr.mxFrame ){
         2623  +          u8 aNew[4];
         2624  +          u8 *aOld = &((u8*)pPage1->pData)[40];
         2625  +          int sz;
         2626  +          i64 iOffset;
         2627  +          sz = pWal->hdr.szPage;
         2628  +          sz = (sz&0xfe00) + ((sz&0x0001)<<16);
         2629  +          iOffset = walFrameOffset(iSlot, sz) + WAL_FRAME_HDRSIZE + 40;
         2630  +          rc = sqlite3OsRead(pWal->pWalFd, aNew, sizeof(aNew), iOffset);
         2631  +          if( rc==SQLITE_OK && memcmp(aOld, aNew, sizeof(aNew)) ){
         2632  +            /* TODO: New error code? SQLITE_BUSY_SCHEMA. */
         2633  +            rc = SQLITE_BUSY_SNAPSHOT;
         2634  +          }
  2630   2635           }
  2631   2636         }
  2632   2637       }
  2633   2638     }
  2634   2639   
  2635   2640     return rc;
  2636   2641   }
  2637   2642   
  2638   2643   /*
  2639         -** The caller holds the WRITER lock. This function returns true if a snapshot
  2640         -** upgrade is required before the transaction can be committed, or false
  2641         -** otherwise.
         2644  +** This function is only ever called while committing an UNLOCKED 
         2645  +** transaction, after the caller has already obtained the WRITER lock
         2646  +** (by calling the sqlite3WalLockForCommit() routine). This function 
         2647  +** returns true if the transaction was prepared against a database 
         2648  +** snapshot older than the current head of the wal file.
         2649  +**
         2650  +** Note that this will only work as described if the database is 
         2651  +** currently executing an UNLOCKED transaction, as it assumes that 
         2652  +** pWal->hdr has not been modified since the beginning of the 
         2653  +** transaction. This may not be true for a non-UNLOCKED transaction,
         2654  +** as pWal->hdr is updated if any pages are spilled to the wal file
         2655  +** while the transaction is executing.
  2642   2656   */
  2643   2657   int sqlite3WalCommitRequiresUpgrade(Wal *pWal){
  2644   2658     assert( pWal->writeLock );
  2645   2659     return memcmp(&pWal->hdr, (void*)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0;
  2646   2660   }
  2647   2661   
  2648   2662   /*
................................................................................
  2707   2721   /* 
  2708   2722   ** Argument aWalData must point to an array of WAL_SAVEPOINT_NDATA u32 
  2709   2723   ** values. This function populates the array with values required to 
  2710   2724   ** "rollback" the write position of the WAL handle back to the current 
  2711   2725   ** point in the event of a savepoint rollback (via WalSavepointUndo()).
  2712   2726   */
  2713   2727   void sqlite3WalSavepoint(Wal *pWal, u32 *aWalData){
  2714         -  /* assert( pWal->writeLock ); */
  2715   2728     aWalData[0] = pWal->hdr.mxFrame;
  2716   2729     aWalData[1] = pWal->hdr.aFrameCksum[0];
  2717   2730     aWalData[2] = pWal->hdr.aFrameCksum[1];
  2718   2731     aWalData[3] = pWal->nCkpt;
  2719   2732   }
  2720   2733   
  2721   2734   /* 

Changes to test/unlocked.test.

   275    275     # 1. Begin and UNLOCKED write to "t1" using [db]
   276    276     #
   277    277     # 2. Create an index on t1 using [db2].
   278    278     #
   279    279     # 3. Attempt to commit the UNLOCKED write. This is an SQLITE_BUSY_SNAPSHOT,
   280    280     #    even though there is no page collision.
   281    281     #
   282         -  
   283    282     do_test 2.$tn.5.1 {
   284    283       sql1 {
   285    284         BEGIN UNLOCKED;
   286    285           INSERT INTO t1 VALUES(6, 'six');
   287    286       }
   288    287     } {}
   289    288   
................................................................................
   295    294       list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db]
   296    295     } {1 {database is locked} SQLITE_BUSY_SNAPSHOT}
   297    296   
   298    297     do_test 2.$tn.5.4 {
   299    298       sql2 { PRAGMA integrity_check }
   300    299     } {ok}
   301    300     catch { sql1 ROLLBACK }
          301  +
          302  +  #-----------------------------------------------------------------------
          303  +  # The "schema cookie" issue.
          304  +  #
          305  +  # 1. Begin an UNLOCKED write to "t1" using [db]
          306  +  #
          307  +  # 2. Lots of inserts into t2. Enough to grow the db file.
          308  +  #
          309  +  # 3. Check that the UNLOCKED transaction can still be committed.
          310  +  #
          311  +  do_test 2.$tn.6.1 {
          312  +    sql1 {
          313  +      BEGIN UNLOCKED;
          314  +        INSERT INTO t1 VALUES(6, 'six');
          315  +    }
          316  +  } {}
          317  +
          318  +  do_test 2.$tn.6.2 {
          319  +    sql2 { 
          320  +      WITH src(a,b) AS (
          321  +        VALUES(1,1) UNION ALL SELECT a+1,b+1 FROM src WHERE a<10000
          322  +      ) INSERT INTO t2 SELECT * FROM src;
          323  +    }
          324  +  } {}
          325  +
          326  +  do_test 2.$tn.6.3 {
          327  +    sql1 {
          328  +      SELECT count(*) FROM t2;
          329  +      COMMIT;
          330  +      SELECT count(*) FROM t2;
          331  +    }
          332  +  } {1 10001}
          333  +
          334  +  #-----------------------------------------------------------------------
          335  +  # 
          336  +  # 1. Begin an big UNLOCKED write to "t1" using [db] - large enough to
          337  +  #    grow the db file.
          338  +  #
          339  +  # 2. Lots of inserts into t2. Also enough to grow the db file.
          340  +  #
          341  +  # 3. Check that the UNLOCKED transaction cannot be committed (due to a clash
          342  +  #    on page 1 - the db size field).
          343  +  #
          344  +  do_test 2.$tn.7.1 {
          345  +    sql1 {
          346  +      BEGIN UNLOCKED;
          347  +        WITH src(a,b) AS (
          348  +          VALUES(10000,10000) UNION ALL SELECT a+1,b+1 FROM src WHERE a<20000
          349  +        ) INSERT INTO t1 SELECT * FROM src;
          350  +    }
          351  +  } {}
          352  +
          353  +  do_test 2.$tn.7.2 {
          354  +    sql2 { 
          355  +      WITH src(a,b) AS (
          356  +        VALUES(1,1) UNION ALL SELECT a+1,b+1 FROM src WHERE a<10000
          357  +      ) INSERT INTO t2 SELECT * FROM src;
          358  +    }
          359  +  } {}
          360  +
          361  +  do_test 2.$tn.7.3 {
          362  +    list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db]
          363  +  } {1 {database is locked} SQLITE_BUSY_SNAPSHOT}
          364  +  sql1 ROLLBACK
          365  +
   302    366   
   303    367   }
   304    368   
   305    369   
   306    370   
   307    371   finish_test