/ Check-in [5df9f022]
Login

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

Overview
Comment:Handle errors in saving cursor positions during a rollback by aborting all active statements. (CVS 3027)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 5df9f022bfb22976f22b996bda169635354b825c
User & Date: danielk1977 2006-01-24 16:37:58
Context
2006-01-25
15:55
Rename files using for testing only so that their name begins with "test". (CVS 3028) check-in: e4e6a205 user: drh tags: trunk
2006-01-24
16:37
Handle errors in saving cursor positions during a rollback by aborting all active statements. (CVS 3027) check-in: 5df9f022 user: danielk1977 tags: trunk
14:21
Save the position of any open cursors before a rollback. (CVS 3026) check-in: 32d45bcf user: danielk1977 tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/btree.c.

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
....
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709



1710
1711
1712
1713
1714
1715
1716
....
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545

2546
2547











2548



2549
2550
2551
2552
2553
2554

2555




2556
2557
2558
2559
2560
2561
2562
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** $Id: btree.c,v 1.310 2006/01/24 14:21:24 danielk1977 Exp $
**
** This file implements a external (disk-based) database using BTrees.
** For a detailed discussion of BTrees, refer to
**
**     Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3:
**     "Sorting And Searching", pages 473-480. Addison-Wesley
**     Publishing Company, Reading, Massachusetts.
................................................................................
  BtShared *pBt = p->pBt;
  BtCursor *pCur;

#ifndef SQLITE_OMIT_SHARED_CACHE
  ThreadData *pTsd;
#endif

  /* Drop any table-locks */
  unlockAllTables(p);

  /* Close all cursors opened via this handle.  */
  pCur = pBt->pCursor;
  while( pCur ){
    BtCursor *pTmp = pCur;
    pCur = pCur->pNext;
    if( pTmp->pBtree==p ){
      sqlite3BtreeCloseCursor(pTmp);
    }
  }

  /* Rollback any active transaction and free the handle structure */



  sqlite3BtreeRollback(p);
  sqliteFree(p);

#ifndef SQLITE_OMIT_SHARED_CACHE
  /* If there are still other outstanding references to the shared-btree
  ** structure, return now. The remainder of this procedure cleans 
  ** up the shared-btree.
................................................................................
** that was open at the beginning of this operation will result
** in an error.
**
** This will release the write lock on the database file.  If there
** are no active cursors, it also releases the read lock.
*/
int sqlite3BtreeRollback(Btree *p){
  int rc = SQLITE_OK;
  BtShared *pBt = p->pBt;
  MemPage *pPage1;

  rc = saveAllCursors(pBt, 0, 0);

  if( rc!=SQLITE_OK ){
    return rc;











  }



  btreeIntegrity(p);
  unlockAllTables(p);

  if( p->inTrans==TRANS_WRITE ){
    assert( TRANS_WRITE==pBt->inTransaction );


    rc = sqlite3pager_rollback(pBt->pPager);




    /* The rollback may have destroyed the pPage1->aData value.  So
    ** call getPage() on page 1 again to make sure pPage1->aData is
    ** set correctly. */
    if( getPage(pBt, 1, &pPage1)==SQLITE_OK ){
      releasePage(pPage1);
    }
    assert( countWriteCursors(pBt)==0 );







|







 







<
<
<










|
>
>
>







 







|




>

<
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>




|

>
|
>
>
>
>







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
....
1689
1690
1691
1692
1693
1694
1695



1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
....
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547

2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** $Id: btree.c,v 1.311 2006/01/24 16:37:58 danielk1977 Exp $
**
** This file implements a external (disk-based) database using BTrees.
** For a detailed discussion of BTrees, refer to
**
**     Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3:
**     "Sorting And Searching", pages 473-480. Addison-Wesley
**     Publishing Company, Reading, Massachusetts.
................................................................................
  BtShared *pBt = p->pBt;
  BtCursor *pCur;

#ifndef SQLITE_OMIT_SHARED_CACHE
  ThreadData *pTsd;
#endif




  /* Close all cursors opened via this handle.  */
  pCur = pBt->pCursor;
  while( pCur ){
    BtCursor *pTmp = pCur;
    pCur = pCur->pNext;
    if( pTmp->pBtree==p ){
      sqlite3BtreeCloseCursor(pTmp);
    }
  }

  /* Rollback any active transaction and free the handle structure.
  ** The call to sqlite3BtreeRollback() drops any table-locks held by
  ** this handle.
  */
  sqlite3BtreeRollback(p);
  sqliteFree(p);

#ifndef SQLITE_OMIT_SHARED_CACHE
  /* If there are still other outstanding references to the shared-btree
  ** structure, return now. The remainder of this procedure cleans 
  ** up the shared-btree.
................................................................................
** that was open at the beginning of this operation will result
** in an error.
**
** This will release the write lock on the database file.  If there
** are no active cursors, it also releases the read lock.
*/
int sqlite3BtreeRollback(Btree *p){
  int rc;
  BtShared *pBt = p->pBt;
  MemPage *pPage1;

  rc = saveAllCursors(pBt, 0, 0);
#ifndef SQLITE_OMIT_SHARED_CACHE
  if( rc!=SQLITE_OK ){

    /* This is a horrible situation. An IO or malloc() error occured whilst
    ** trying to save cursor positions. If this is an automatic rollback (as
    ** the result of a constraint, malloc() failure or IO error) then 
    ** the cache may be internally inconsistent (not contain valid trees) so
    ** we cannot simply return the error to the caller. Instead, abort 
    ** all queries that may be using any of the cursors that failed to save.
    */
    while( pBt->pCursor ){
      sqlite3 *db = pBt->pCursor->pBtree->pSqlite;
      if( db ){
        sqlite3AbortOtherActiveVdbes(db, 0);
      }
    }
  }
#endif
  btreeIntegrity(p);
  unlockAllTables(p);

  if( p->inTrans==TRANS_WRITE ){
    int rc2;

    assert( TRANS_WRITE==pBt->inTransaction );
    rc2 = sqlite3pager_rollback(pBt->pPager);
    if( rc2!=SQLITE_OK ){
      rc = rc2;
    }

    /* The rollback may have destroyed the pPage1->aData value.  So
    ** call getPage() on page 1 again to make sure pPage1->aData is
    ** set correctly. */
    if( getPage(pBt, 1, &pPage1)==SQLITE_OK ){
      releasePage(pPage1);
    }
    assert( countWriteCursors(pBt)==0 );

Changes to src/main.c.

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
...
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
**
*************************************************************************
** Main file for the SQLite library.  The routines in this file
** implement the programmer interface to the library.  Routines in
** other files are for internal use by SQLite and should not be
** accessed by users of the library.
**
** $Id: main.c,v 1.330 2006/01/24 13:09:33 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "os.h"
#include <ctype.h>

/*
** The following constant value is used by the SQLITE_BIGENDIAN and
................................................................................
  ** "failed to open" state.
  */
  if( db->magic!=SQLITE_MAGIC_CLOSED && sqlite3SafetyOn(db) ){
    /* printf("DID NOT CLOSE\n"); fflush(stdout); */
    return SQLITE_ERROR;
  }

  /* sqlite3_close() may not invoke sqliteMalloc(). */
  sqlite3MallocDisallow();

  for(j=0; j<db->nDb; j++){
    struct Db *pDb = &db->aDb[j];
    if( pDb->pBt ){
      sqlite3BtreeClose(pDb->pBt);
      pDb->pBt = 0;
      if( j!=1 ){
        pDb->pSchema = 0;
................................................................................
  ** objects (using sqliteMalloc() directly, instead of sqlite3BtreeSchema()).
  ** So it needs to be freed here. Todo: Why not roll the temp schema into
  ** the same sqliteMalloc() as the one that allocates the database 
  ** structure?
  */
  sqliteFree(db->aDb[1].pSchema);
  sqliteFree(db);
  sqlite3MallocAllow();
  sqlite3ReleaseThreadData();
  return SQLITE_OK;
}

/*
** Rollback all database files.
*/







|







 







<
<
<







 







<







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
127
128
129
130
131
132
133



134
135
136
137
138
139
140
...
170
171
172
173
174
175
176

177
178
179
180
181
182
183
**
*************************************************************************
** Main file for the SQLite library.  The routines in this file
** implement the programmer interface to the library.  Routines in
** other files are for internal use by SQLite and should not be
** accessed by users of the library.
**
** $Id: main.c,v 1.331 2006/01/24 16:37:58 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "os.h"
#include <ctype.h>

/*
** The following constant value is used by the SQLITE_BIGENDIAN and
................................................................................
  ** "failed to open" state.
  */
  if( db->magic!=SQLITE_MAGIC_CLOSED && sqlite3SafetyOn(db) ){
    /* printf("DID NOT CLOSE\n"); fflush(stdout); */
    return SQLITE_ERROR;
  }




  for(j=0; j<db->nDb; j++){
    struct Db *pDb = &db->aDb[j];
    if( pDb->pBt ){
      sqlite3BtreeClose(pDb->pBt);
      pDb->pBt = 0;
      if( j!=1 ){
        pDb->pSchema = 0;
................................................................................
  ** objects (using sqliteMalloc() directly, instead of sqlite3BtreeSchema()).
  ** So it needs to be freed here. Todo: Why not roll the temp schema into
  ** the same sqliteMalloc() as the one that allocates the database 
  ** structure?
  */
  sqliteFree(db->aDb[1].pSchema);
  sqliteFree(db);

  sqlite3ReleaseThreadData();
  return SQLITE_OK;
}

/*
** Rollback all database files.
*/

Changes to src/sqliteInt.h.

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
....
1743
1744
1745
1746
1747
1748
1749

1750
1751
1752
1753
1754
1755
1756
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** Internal interface definitions for SQLite.
**
** @(#) $Id: sqliteInt.h,v 1.478 2006/01/23 13:28:54 drh Exp $
*/
#ifndef _SQLITEINT_H_
#define _SQLITEINT_H_

/*
** Extra interface definitions for those who need them
*/
................................................................................
KeyInfo *sqlite3IndexKeyinfo(Parse *, Index *);
int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *, 
  void (*)(sqlite3_context*,int,sqlite3_value **),
  void (*)(sqlite3_context*,int,sqlite3_value **), void (*)(sqlite3_context*));
int sqlite3ApiExit(sqlite3 *db, int);
int sqlite3MallocFailed(void);
void sqlite3FailedMalloc(void);


#ifndef SQLITE_OMIT_SHARED_CACHE
  void sqlite3TableLock(Parse *, int, int, u8, const char *);
#else
  #define sqlite3TableLock(v,w,x,y,z)
#endif








|







 







>







7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
....
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** Internal interface definitions for SQLite.
**
** @(#) $Id: sqliteInt.h,v 1.479 2006/01/24 16:37:58 danielk1977 Exp $
*/
#ifndef _SQLITEINT_H_
#define _SQLITEINT_H_

/*
** Extra interface definitions for those who need them
*/
................................................................................
KeyInfo *sqlite3IndexKeyinfo(Parse *, Index *);
int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *, 
  void (*)(sqlite3_context*,int,sqlite3_value **),
  void (*)(sqlite3_context*,int,sqlite3_value **), void (*)(sqlite3_context*));
int sqlite3ApiExit(sqlite3 *db, int);
int sqlite3MallocFailed(void);
void sqlite3FailedMalloc(void);
void sqlite3AbortOtherActiveVdbes(sqlite3 *, Vdbe *);

#ifndef SQLITE_OMIT_SHARED_CACHE
  void sqlite3TableLock(Parse *, int, int, u8, const char *);
#else
  #define sqlite3TableLock(v,w,x,y,z)
#endif

Changes to src/vdbeaux.c.

1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
....
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
....
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
/*
** Find every active VM other than pVdbe and change its status to
** aborted.  This happens when one VM causes a rollback due to an
** ON CONFLICT ROLLBACK clause (for example).  The other VMs must be
** aborted so that they do not have data rolled out from underneath
** them leading to a segfault.
*/
static void abortOtherActiveVdbes(Vdbe *pVdbe){
  Vdbe *pOther;
  for(pOther=pVdbe->db->pVdbe; pOther; pOther=pOther->pNext){
    if( pOther==pVdbe ) continue;
    if( pOther->magic!=VDBE_MAGIC_RUN || pOther->pc<0 ) continue;
    closeAllCursors(pOther);
    pOther->aborted = 1;
  }
}

/* 
................................................................................
      if( !isReadOnly ){
        if( p->rc==SQLITE_NOMEM && isStatement ){
          xFunc = sqlite3BtreeRollbackStmt;
        }else{
          /* We are forced to roll back the active transaction. Before doing
          ** so, abort any other statements this handle currently has active.
          */
          abortOtherActiveVdbes(p);
          sqlite3RollbackAll(db);
          db->autoCommit = 1;
        }
      }
    }
  
    /* If the auto-commit flag is set and this is the only active vdbe, then
................................................................................
      }
    }else if( !xFunc ){
      if( p->rc==SQLITE_OK || p->errorAction==OE_Fail ){
        xFunc = sqlite3BtreeCommitStmt;
      }else if( p->errorAction==OE_Abort ){
        xFunc = sqlite3BtreeRollbackStmt;
      }else{
        abortOtherActiveVdbes(p);
        sqlite3RollbackAll(db);
        db->autoCommit = 1;
      }
    }
  
    /* If xFunc is not NULL, then it is one of sqlite3BtreeRollbackStmt or
    ** sqlite3BtreeCommitStmt. Call it once on each backend. If an error occurs







|

|
|







 







|







 







|







1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
....
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
....
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
/*
** Find every active VM other than pVdbe and change its status to
** aborted.  This happens when one VM causes a rollback due to an
** ON CONFLICT ROLLBACK clause (for example).  The other VMs must be
** aborted so that they do not have data rolled out from underneath
** them leading to a segfault.
*/
void sqlite3AbortOtherActiveVdbes(sqlite3 *db, Vdbe *pExcept){
  Vdbe *pOther;
  for(pOther=db->pVdbe; pOther; pOther=pOther->pNext){
    if( pOther==pExcept ) continue;
    if( pOther->magic!=VDBE_MAGIC_RUN || pOther->pc<0 ) continue;
    closeAllCursors(pOther);
    pOther->aborted = 1;
  }
}

/* 
................................................................................
      if( !isReadOnly ){
        if( p->rc==SQLITE_NOMEM && isStatement ){
          xFunc = sqlite3BtreeRollbackStmt;
        }else{
          /* We are forced to roll back the active transaction. Before doing
          ** so, abort any other statements this handle currently has active.
          */
          sqlite3AbortOtherActiveVdbes(db, p);
          sqlite3RollbackAll(db);
          db->autoCommit = 1;
        }
      }
    }
  
    /* If the auto-commit flag is set and this is the only active vdbe, then
................................................................................
      }
    }else if( !xFunc ){
      if( p->rc==SQLITE_OK || p->errorAction==OE_Fail ){
        xFunc = sqlite3BtreeCommitStmt;
      }else if( p->errorAction==OE_Abort ){
        xFunc = sqlite3BtreeRollbackStmt;
      }else{
        sqlite3AbortOtherActiveVdbes(db, p);
        sqlite3RollbackAll(db);
        db->autoCommit = 1;
      }
    }
  
    /* If xFunc is not NULL, then it is one of sqlite3BtreeRollbackStmt or
    ** sqlite3BtreeCommitStmt. Call it once on each backend. If an error occurs

Changes to test/shared_err.test.

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
307
308
309
310
311
312
313
314


315
316
317
318

319
320
321
322
323
324
325
...
344
345
346
347
348
349
350
351























































352
353
354
#
#***********************************************************************
#
# The focus of the tests in this file are IO errors that occur in a shared
# cache context. What happens to connection B if one connection A encounters
# an IO-error whilst reading or writing the file-system?
#
# $Id: shared_err.test,v 1.8 2006/01/24 11:30:27 danielk1977 Exp $

proc skip {args} {}


set testdir [file dirname $argv0]
source $testdir/tester.tcl
db close
................................................................................
  sqlite3_step $::STMT       ;# Cursor points at 1111111111
} -tclbody {
  execsql {
    INSERT INTO t1 VALUES(6, NULL);
  }
} -cleanup {
  do_test shared_malloc-4.$::n.cleanup.1 {
    sqlite3_step $::STMT


  } {SQLITE_ROW}
  do_test shared_malloc-4.$::n.cleanup.2 {
    sqlite3_column_text $::STMT 0
  } {2222222222}

  do_test shared_malloc-4.$::n.cleanup.3 {
    sqlite3_finalize $::STMT
  } {SQLITE_OK}
# db2 eval {select * from sqlite_master}
  db2 close
}

................................................................................
do_test shared_misuse-7.1 {
  sqlite3 db test.db
  catch {
    sqlite3_enable_shared_cache 0
  } msg
  set msg
} {library routine called out of sequence}
























































catch {db close}
sqlite3_enable_shared_cache $::enable_shared_cache
finish_test







|







 







|
>
>
|
|
|
|
>







 








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



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
...
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
#
#***********************************************************************
#
# The focus of the tests in this file are IO errors that occur in a shared
# cache context. What happens to connection B if one connection A encounters
# an IO-error whilst reading or writing the file-system?
#
# $Id: shared_err.test,v 1.9 2006/01/24 16:37:59 danielk1977 Exp $

proc skip {args} {}


set testdir [file dirname $argv0]
source $testdir/tester.tcl
db close
................................................................................
  sqlite3_step $::STMT       ;# Cursor points at 1111111111
} -tclbody {
  execsql {
    INSERT INTO t1 VALUES(6, NULL);
  }
} -cleanup {
  do_test shared_malloc-4.$::n.cleanup.1 {
    set ::rc [sqlite3_step $::STMT]
    expr {$::rc=="SQLITE_ROW" || $::rc=="SQLITE_ABORT"}
  } {1}
  if {$::rc=="SQLITE_ROW"} {
    do_test shared_malloc-4.$::n.cleanup.2 {
      sqlite3_column_text $::STMT 0
    } {2222222222}
  }
  do_test shared_malloc-4.$::n.cleanup.3 {
    sqlite3_finalize $::STMT
  } {SQLITE_OK}
# db2 eval {select * from sqlite_master}
  db2 close
}

................................................................................
do_test shared_misuse-7.1 {
  sqlite3 db test.db
  catch {
    sqlite3_enable_shared_cache 0
  } msg
  set msg
} {library routine called out of sequence}

# Again provoke a malloc() failure when a cursor position is being saved, 
# this time during a ROLLBACK operation by some other handle. 
#
# The library should return an SQLITE_NOMEM to the caller. The query that
# owns the cursor (the one for which the position is not saved) should
# be aborted.
# 
set ::aborted 0
do_malloc_test 8 -tclprep {
  sqlite3 db2 test.db
  execsql {
    PRAGMA read_uncommitted = 1;
    BEGIN;
    CREATE TABLE t1(a, b, UNIQUE(a, b));
  } db2
  for {set i 0} {$i < 2} {incr i} {
    set a [string repeat $i 10]
    set b [string repeat $i 2000]
    execsql {INSERT INTO t1 VALUES($a, $b)} db2
  }
  execsql {COMMIT} db2
  set ::DB2 [sqlite3_connection_pointer db2]
  set ::STMT [sqlite3_prepare $::DB2 "SELECT a FROM t1 ORDER BY a" -1 DUMMY]
  sqlite3_step $::STMT       ;# Cursor points at 0000000000
  sqlite3_step $::STMT       ;# Cursor points at 1111111111
} -tclbody {
  execsql {
    BEGIN;
    INSERT INTO t1 VALUES(6, NULL);
    ROLLBACK;
  }
} -cleanup {
  do_test shared_malloc-8.$::n.cleanup.1 {
    lrange [execsql {
      SELECT a FROM t1;
    } db2] 0 1
  } {0000000000 1111111111}
  do_test shared_malloc-8.$::n.cleanup.2 {
    set rc1 [sqlite3_step $::STMT]
    set rc2 [sqlite3_finalize $::STMT]
    if {$rc1=="SQLITE_ABORT"} {
      incr ::aborted
    }
    expr {
      ($rc1=="SQLITE_DONE" && $rc2=="SQLITE_OK") || 
      ($rc1=="SQLITE_ABORT" && $rc2=="SQLITE_OK")
    }
  } {1}
  db2 close
}
do_test shared_malloc-8.X {
  # Test that one or more queries were aborted due to the malloc() failure.
  expr $::aborted>=1
} {1}

catch {db close}
sqlite3_enable_shared_cache $::enable_shared_cache
finish_test