SQLite

Check-in [a1bb1aef0e]
Login

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

Overview
Comment:Make calls to sqlite3BtreeRollbackStmt() no-ops when passed a Btree* handle that does not have an open statement transaction. Ticket #3718. (CVS 6342)
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: a1bb1aef0e06140a2d5d5e4b6c10c73ce95c89e0
User & Date: danielk1977 2009-03-12 14:43:28.000
Context
2009-03-12
15:43
Avoid fts3 crash on (MATCH '""') expressions. Ticket #3717. (CVS 6343) (check-in: 03679857a3 user: danielk1977 tags: trunk)
14:43
Make calls to sqlite3BtreeRollbackStmt() no-ops when passed a Btree* handle that does not have an open statement transaction. Ticket #3718. (CVS 6342) (check-in: a1bb1aef0e user: danielk1977 tags: trunk)
2009-03-05
14:59
Begin purging dirty pages from the cache once 90% of the cache is dirty (insteadof waiting until it is 100% dirty). This improves performance in some circumstances by effectively reserving 10% of the configured page-cache for frequently reused read-only pages. (CVS 6341) (check-in: 823fe7f555 user: danielk1977 tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/btree.c.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
** 2004 April 6
**
** The author disclaims copyright to this source code.  In place of
** 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.571 2009/03/05 04:20:32 shane Exp $
**
** This file implements a external (disk-based) database using BTrees.
** See the header comment on "btreeInt.h" for additional information.
** Including a description of file format and an overview of operation.
*/
#include "btreeInt.h"












|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
** 2004 April 6
**
** The author disclaims copyright to this source code.  In place of
** 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.572 2009/03/12 14:43:28 danielk1977 Exp $
**
** This file implements a external (disk-based) database using BTrees.
** See the header comment on "btreeInt.h" for additional information.
** Including a description of file format and an overview of operation.
*/
#include "btreeInt.h"

2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831

2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856

2857
2858
2859
2860
2861
2862
2863
}

/*
** Commit the statment subtransaction currently in progress.  If no
** subtransaction is active, this is a no-op.
*/
int sqlite3BtreeCommitStmt(Btree *p){
  int rc;
  BtShared *pBt = p->pBt;
  sqlite3BtreeEnter(p);
  pBt->db = p->db;
  assert( pBt->readOnly==0 );
  if( pBt->inStmt ){
    int iStmtpoint = p->db->nSavepoint;

    rc = sqlite3PagerSavepoint(pBt->pPager, SAVEPOINT_RELEASE, iStmtpoint);
  }else{
    rc = SQLITE_OK;
  }
  pBt->inStmt = 0;
  sqlite3BtreeLeave(p);
  return rc;
}

/*
** Rollback the active statement subtransaction.  If no subtransaction
** is active this routine is a no-op.
**
** All cursors will be invalidated by this operation.  Any attempt
** to use a cursor that was open at the beginning of this operation
** will result in an error.
*/
int sqlite3BtreeRollbackStmt(Btree *p){
  int rc = SQLITE_OK;
  BtShared *pBt = p->pBt;
  sqlite3BtreeEnter(p);
  pBt->db = p->db;
  assert( pBt->readOnly==0 );
  if( pBt->inStmt ){
    int iStmtpoint = p->db->nSavepoint;

    rc = sqlite3PagerSavepoint(pBt->pPager, SAVEPOINT_ROLLBACK, iStmtpoint);
    if( rc==SQLITE_OK ){
      rc = sqlite3PagerSavepoint(pBt->pPager, SAVEPOINT_RELEASE, iStmtpoint);
    }
    pBt->inStmt = 0;
  }
  sqlite3BtreeLeave(p);







|



<
|

>

<
|

<

















<
|

>







2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828

2829
2830
2831
2832

2833
2834

2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851

2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
}

/*
** Commit the statment subtransaction currently in progress.  If no
** subtransaction is active, this is a no-op.
*/
int sqlite3BtreeCommitStmt(Btree *p){
  int rc = SQLITE_OK;
  BtShared *pBt = p->pBt;
  sqlite3BtreeEnter(p);
  pBt->db = p->db;

  if( p->inTrans==TRANS_WRITE && pBt->inStmt ){
    int iStmtpoint = p->db->nSavepoint;
    assert( pBt->readOnly==0 );
    rc = sqlite3PagerSavepoint(pBt->pPager, SAVEPOINT_RELEASE, iStmtpoint);

    pBt->inStmt = 0;
  }

  sqlite3BtreeLeave(p);
  return rc;
}

/*
** Rollback the active statement subtransaction.  If no subtransaction
** is active this routine is a no-op.
**
** All cursors will be invalidated by this operation.  Any attempt
** to use a cursor that was open at the beginning of this operation
** will result in an error.
*/
int sqlite3BtreeRollbackStmt(Btree *p){
  int rc = SQLITE_OK;
  BtShared *pBt = p->pBt;
  sqlite3BtreeEnter(p);
  pBt->db = p->db;

  if( p->inTrans==TRANS_WRITE && pBt->inStmt ){
    int iStmtpoint = p->db->nSavepoint;
    assert( pBt->readOnly==0 );
    rc = sqlite3PagerSavepoint(pBt->pPager, SAVEPOINT_ROLLBACK, iStmtpoint);
    if( rc==SQLITE_OK ){
      rc = sqlite3PagerSavepoint(pBt->pPager, SAVEPOINT_RELEASE, iStmtpoint);
    }
    pBt->inStmt = 0;
  }
  sqlite3BtreeLeave(p);
Changes to test/quick.test.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#
#    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.
#
#***********************************************************************
# This file runs all tests.
#
# $Id: quick.test,v 1.93 2009/02/26 07:15:59 danielk1977 Exp $

proc lshift {lvar} {
  upvar $lvar l
  set ret [lindex $l 0]
  set l [lrange $l 1 end]
  return $ret
}








|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#
#    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.
#
#***********************************************************************
# This file runs all tests.
#
# $Id: quick.test,v 1.94 2009/03/12 14:43:28 danielk1977 Exp $

proc lshift {lvar} {
  upvar $lvar l
  set ret [lindex $l 0]
  set l [lrange $l 1 end]
  return $ret
}
82
83
84
85
86
87
88

89
90
91
92
93
94
95
  speed4p.test
  sqllimits1.test
  tkt2686.test
  thread001.test
  thread002.test
  thread003.test
  thread004.test

  trans2.test
  vacuum3.test

  incrvacuum_ioerr.test
  autovacuum_crash.test
  btree8.test
  shared_err.test







>







82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
  speed4p.test
  sqllimits1.test
  tkt2686.test
  thread001.test
  thread002.test
  thread003.test
  thread004.test
  thread005.test
  trans2.test
  vacuum3.test

  incrvacuum_ioerr.test
  autovacuum_crash.test
  btree8.test
  shared_err.test
Changes to test/thread003.test.
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#    May you share freely, never taking more than you give.
#
#***********************************************************************
#
#   This file contains tests that attempt to break the pcache module
#   by bombarding it with simultaneous requests from multiple threads.
#     
# $Id: thread003.test,v 1.6 2009/02/12 17:06:41 drh Exp $

set testdir [file dirname $argv0]

source $testdir/tester.tcl
source $testdir/thread_common.tcl
if {[info commands sqlthread] eq ""} {
  finish_test







|







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#    May you share freely, never taking more than you give.
#
#***********************************************************************
#
#   This file contains tests that attempt to break the pcache module
#   by bombarding it with simultaneous requests from multiple threads.
#     
# $Id: thread003.test,v 1.7 2009/03/12 14:43:28 danielk1977 Exp $

set testdir [file dirname $argv0]

source $testdir/tester.tcl
source $testdir/thread_common.tcl
if {[info commands sqlthread] eq ""} {
  finish_test
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
} {0}

# In this test case, one thread is continually querying the database.
# The other thread does not have a database connection, but calls
# sqlite3_release_memory() over and over again.
#
set nSecond 30
puts "Starting thread003.3 (should run for ~$nSecond seconds)"
unset -nocomplain finished(1)
unset -nocomplain finished(2)
do_test thread003.4 {
  thread_spawn finished(1) $thread_procs [format {
    set iEnd [expr {[clock_seconds] + %d}]
    set ::DB [sqlthread open test.db]








|







154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
} {0}

# In this test case, one thread is continually querying the database.
# The other thread does not have a database connection, but calls
# sqlite3_release_memory() over and over again.
#
set nSecond 30
puts "Starting thread003.4 (should run for ~$nSecond seconds)"
unset -nocomplain finished(1)
unset -nocomplain finished(2)
do_test thread003.4 {
  thread_spawn finished(1) $thread_procs [format {
    set iEnd [expr {[clock_seconds] + %d}]
    set ::DB [sqlthread open test.db]

Added test/thread005.test.




























































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# 2009 March 11
#
# The author disclaims copyright to this source code.  In place of
# 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.
#
#***********************************************************************
#
# Test a race-condition that shows up in shared-cache mode.
#
# $Id: thread005.test,v 1.1 2009/03/12 14:43:28 danielk1977 Exp $

set testdir [file dirname $argv0]

source $testdir/tester.tcl
ifcapable !mutex||!shared_cache {
  return
}
source $testdir/thread_common.tcl
if {[info commands sqlthread] eq ""} {
  return
}

#-------------------------------------------------------------------------
# This test tries to exercise a race-condition that existed in shared-cache
# mode at one point. The test uses two threads; each has a database connection
# open on the same shared cache. The schema of the database is:
#
#    CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE);
#
# One thread is a reader and the other thread a reader and a writer. The 
# writer thread repeats the following transaction as fast as possible:
# 
#      BEGIN;
#        DELETE FROM t1 WHERE a = (SELECT max(a) FROM t1);
#        INSERT INTO t1 VALUES(NULL, NULL);
#        UPDATE t1 SET b = a WHERE a = (SELECT max(a) FROM t1);
#        SELECT count(*) FROM t1 WHERE b IS NULL;
#      COMMIT;
#
# The reader thread does the following over and over as fast as possible:
#
#      BEGIN;
#        SELECT count(*) FROM t1 WHERE b IS NULL;
#      COMMIT;
#
# The test runs for 20 seconds or until one of the "SELECT count(*)" 
# statements returns a non-zero value. If an SQLITE_LOCKED error occurs,
# the connection issues a ROLLBACK immediately to abandon the current
# transaction.
#
# If everything is working correctly, the "SELECT count(*)" statements 
# should never return a value other than 0. The "INSERT" statement 
# executed by the writer adds a row with "b IS NULL" to the table, but
# the subsequent UPDATE statement sets its "b" value to an integer
# immediately afterwards.
#
# However, before the race-condition was fixed, if the reader's SELECT
# statement hit an error (say an SQLITE_LOCKED) at the same time as the
# writer was executing the UPDATE statement, then it could incorrectly
# rollback the statement-transaction belonging to the UPDATE statement.
# The UPDATE statement would still be reported as successful to the user,
# but it would have no effect on the database contents.
# 
# Note that it has so far only proved possible to hit this race-condition
# when using an ATTACHed database. There doesn't seem to be any reason
# for this, other than that operating on an ATTACHed database means there
# are a few more mutex grabs and releases during the window of time open
# for the race-condition. Maybe this encourages the scheduler to context
# switch or something...
#

# Use shared-cache mode for this test.
# 
db close
set ::enable_shared_cache [sqlite3_enable_shared_cache]
sqlite3_enable_shared_cache 1

file delete -force test.db test2.db

do_test thread005-1.1 {
  sqlite3 db test.db
  execsql { ATTACH 'test2.db' AS aux }
  execsql {
    CREATE TABLE aux.t1(a INTEGER PRIMARY KEY, b UNIQUE);
    INSERT INTO t1 VALUES(1, 1);
    INSERT INTO t1 VALUES(2, 2);
  }
  db close
} {}

set ThreadProgram {
  proc execsql {zSql {db {}}} {
    if {$db eq ""} {set db $::DB}

    set lRes [list]
    set rc SQLITE_OK

    while {$rc=="SQLITE_OK" && $zSql ne ""} {
      set STMT [sqlite3_prepare_v2 $db $zSql -1 zSql]
      while {[set rc [sqlite3_step $STMT]] eq "SQLITE_ROW"} {
        for {set i 0} {$i < [sqlite3_column_count $STMT]} {incr i} {
          lappend lRes [sqlite3_column_text $STMT 0]
        }
      }
      set rc [sqlite3_finalize $STMT]
    }

    if {$rc != "SQLITE_OK"} { error "$rc [sqlite3_errmsg $db]" }
    return $lRes
  }

  if {$isWriter} {
    set Sql {
      BEGIN;
        DELETE FROM t1 WHERE a = (SELECT max(a) FROM t1);
        INSERT INTO t1 VALUES(NULL, NULL);
        UPDATE t1 SET b = a WHERE a = (SELECT max(a) FROM t1);
        SELECT count(*) FROM t1 WHERE b IS NULL;
      COMMIT;
    }
  } else {
    set Sql {
      BEGIN;
      SELECT count(*) FROM t1 WHERE b IS NULL;
      COMMIT;
    }
  }

  set ::DB [sqlite3_open test.db]

  execsql { ATTACH 'test2.db' AS aux }

  set result "ok"
  set finish [expr [clock_seconds]+5]
  while {$result eq "ok" && [clock_seconds] < $finish} {
    set rc [catch {execsql $Sql} msg]
    if {$rc} {
      if {[string match "SQLITE_LOCKED*" $msg]} {
        catch { execsql ROLLBACK }
      } else {
        error $msg
      }
    } elseif {$msg ne "0"} {
      set result "failed"
    }
  }

  sqlite3_close $::DB
  set result
}

puts "Running thread-tests for ~20 seconds"
thread_spawn finished(0) {set isWriter 0} $ThreadProgram
thread_spawn finished(1) {set isWriter 1} $ThreadProgram
if {![info exists finished(0)]} { vwait finished(0) }
if {![info exists finished(1)]} { vwait finished(1) }

do_test thread005-1.2 {
  list $finished(0) $finished(1)
} {ok ok}

do_test thread005-1.3 {
  sqlite3 db test.db
  execsql { ATTACH 'test2.db' AS aux }
  execsql { SELECT count(*) FROM t1 WHERE b IS NULL }
} {0}

sqlite3_enable_shared_cache $::enable_shared_cache
finish_test