From 641b0f4ffa8ca75d1a0868e86180e20bc451eb06 Mon Sep 17 00:00:00 2001 From: danielk1977 Date: Fri, 21 Dec 2007 04:47:25 +0000 Subject: [PATCH] Fix for #2854. "BEGIN EXCLUSIVE" excludes other shared cache users from using the database. (CVS 4642) FossilOrigin-Name: 2e59b1d07ee422bd799b5b7aeea44ebc998d9481 --- manifest | 17 +++--- manifest.uuid | 2 +- src/btree.c | 35 +++++++++++- src/btreeInt.h | 3 +- test/tkt2854.test | 140 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 185 insertions(+), 12 deletions(-) create mode 100644 test/tkt2854.test diff --git a/manifest b/manifest index 620d0a9fc5..f2a5798bb6 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\ssome\sissues\swith\slemon.\s\sTickets\s#2852\sand\s#2835.\s(CVS\s4641) -D 2007-12-21T00:02:11 +C Fix\sfor\s#2854.\s"BEGIN\sEXCLUSIVE"\sexcludes\sother\sshared\scache\susers\sfrom\susing\sthe\sdatabase.\s(CVS\s4642) +D 2007-12-21T04:47:26 F Makefile.arm-wince-mingw32ce-gcc ac5f7b2cef0cd850d6f755ba6ee4ab961b1fadf7 F Makefile.in 30789bf70614bad659351660d76b8e533f3340e9 F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654 @@ -83,9 +83,9 @@ F src/analyze.c fd1a3d756c1a20fca3c505bed0398f4cdca83cb8 F src/attach.c a01d55157d46a1234909f3a7f21fb09549c947bd F src/auth.c c8b2ab5c8bad4bd90ed7c294694f48269162c627 F src/btmutex.c 5d39da37c9d1282f3c6f9967afae6a34ee36b7ff -F src/btree.c 10cc24011d5602ad332fa8c579a036e678274ee8 +F src/btree.c bef8ceec858b167bd0ce0863fe0bdd527126c213 F src/btree.h 19dcf5ad23c17b98855da548e9a8e3eb4429d5eb -F src/btreeInt.h 68ec997e34e426093f706da965c147ece75185b9 +F src/btreeInt.h 1c5a9da165718ef7de81e35ce9ab5d9ba9283f76 F src/build.c 580561a0d9e070ff2741f3b115cae51c1ef08260 F src/callback.c 77b302b0d41468dcda78c70e706e5b84577f0fa0 F src/complete.c 4cf68fd75d60257524cbe74f87351b9848399131 @@ -482,6 +482,7 @@ F test/tkt2817.test 709a2201a5590bf56cb97f6fb168a62282203fd1 F test/tkt2820.test 017fdee33aaef7abc092beab6088816f1942304b F test/tkt2822.test 8b1526b1e5b0d38a1a993f7828fbb81759093686 F test/tkt2832.test cd56dc66bb31898b7eb2146baa5bde2eb80f96fe +F test/tkt2854.test aebd5a9904d36d1ef7a074fc5e7c7da3ab00c32a F test/trace.test 75ffc1b992c780d054748a656e3e7fd674f18567 F test/trans.test b73289992b46d38d9479ecc4fdc03d8edb2413dc F test/trigger1.test 7c13f39ca36f529bf856e05c7d004fc0531d48b4 @@ -601,7 +602,7 @@ F www/tclsqlite.tcl 8be95ee6dba05eabcd27a9d91331c803f2ce2130 F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0 F www/version3.tcl 890248cf7b70e60c383b0e84d77d5132b3ead42b F www/whentouse.tcl fc46eae081251c3c181bd79c5faef8195d7991a5 -P d3efec148968538e225f5716359a6a25e8941362 -R 85004d42a22e2eebf7a2858fba477cd5 -U drh -Z ad03768828df010e4ad9480b93fc71b4 +P 5283e0d1467e0fc0ebbd1be553b5a8762e9c7975 +R e37b64edde771d68293d63348dff6a3a +U danielk1977 +Z 9a8d28eb42d02907bda6dd836ef8b3b4 diff --git a/manifest.uuid b/manifest.uuid index 953858c166..a014342e4f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -5283e0d1467e0fc0ebbd1be553b5a8762e9c7975 \ No newline at end of file +2e59b1d07ee422bd799b5b7aeea44ebc998d9481 \ No newline at end of file diff --git a/src/btree.c b/src/btree.c index a9312da2ac..b5c1ea7cc6 100644 --- a/src/btree.c +++ b/src/btree.c @@ -9,7 +9,7 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** $Id: btree.c,v 1.433 2007/12/13 21:54:11 drh Exp $ +** $Id: btree.c,v 1.434 2007/12/21 04:47:26 danielk1977 Exp $ ** ** This file implements a external (disk-based) database using BTrees. ** See the header comment on "btreeInt.h" for additional information. @@ -103,6 +103,13 @@ static int queryTableLock(Btree *p, Pgno iTab, u8 eLock){ return SQLITE_OK; } + /* If some other connection is holding an exclusive lock, the + ** requested lock may not be obtained. + */ + if( pBt->pExclusive && pBt->pExclusive!=p ){ + return SQLITE_LOCKED; + } + /* This (along with lockTable()) is where the ReadUncommitted flag is ** dealt with. If the caller is querying for a read-lock and the flag is ** set, it is unconditionally granted - even if there are write-locks @@ -212,13 +219,15 @@ static int lockTable(Btree *p, Pgno iTable, u8 eLock){ ** procedure) held by Btree handle p. */ static void unlockAllTables(Btree *p){ - BtLock **ppIter = &p->pBt->pLock; + BtShared *pBt = p->pBt; + BtLock **ppIter = &pBt->pLock; assert( sqlite3BtreeHoldsMutex(p) ); assert( p->sharable || 0==*ppIter ); while( *ppIter ){ BtLock *pLock = *ppIter; + assert( pBt->pExclusive==0 || pBt->pExclusive==pLock->pBtree ); if( pLock->pBtree==p ){ *ppIter = pLock->pNext; sqlite3_free(pLock); @@ -226,6 +235,10 @@ static void unlockAllTables(Btree *p){ ppIter = &pLock->pNext; } } + + if( pBt->pExclusive==p ){ + pBt->pExclusive = 0; + } } #endif /* SQLITE_OMIT_SHARED_CACHE */ @@ -1850,6 +1863,18 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag){ goto trans_begun; } +#ifndef SQLITE_OMIT_SHARED_CACHE + if( wrflag>1 ){ + BtLock *pIter; + for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){ + if( pIter->pBtree!=p ){ + rc = SQLITE_BUSY; + goto trans_begun; + } + } + } +#endif + do { if( pBt->pPage1==0 ){ rc = lockBtree(pBt); @@ -1882,6 +1907,12 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag){ if( p->inTrans>pBt->inTransaction ){ pBt->inTransaction = p->inTrans; } +#ifndef SQLITE_OMIT_SHARED_CACHE + if( wrflag>1 ){ + assert( !pBt->pExclusive ); + pBt->pExclusive = p; + } +#endif } diff --git a/src/btreeInt.h b/src/btreeInt.h index 18db47f01f..a1e5d42a11 100644 --- a/src/btreeInt.h +++ b/src/btreeInt.h @@ -9,7 +9,7 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** $Id: btreeInt.h,v 1.14 2007/12/07 18:55:28 drh Exp $ +** $Id: btreeInt.h,v 1.15 2007/12/21 04:47:26 danielk1977 Exp $ ** ** This file implements a external (disk-based) database using BTrees. ** For a detailed discussion of BTrees, refer to @@ -395,6 +395,7 @@ struct BtShared { int nRef; /* Number of references to this structure */ BtShared *pNext; /* Next on a list of sharable BtShared structs */ BtLock *pLock; /* List of locks held on this shared-btree struct */ + Btree *pExclusive; /* Btree with an EXCLUSIVE lock on the whole db */ #endif }; diff --git a/test/tkt2854.test b/test/tkt2854.test new file mode 100644 index 0000000000..282597fc6b --- /dev/null +++ b/test/tkt2854.test @@ -0,0 +1,140 @@ +# 2007 December 20 +# +# 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: tkt2854.test,v 1.1 2007/12/21 04:47:27 danielk1977 Exp $ + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +db close + +ifcapable !shared_cache { + finish_test + return +} + +set ::enable_shared_cache [sqlite3_enable_shared_cache 1] + +# Open 3 database connections. Connection "db" and "db2" share a cache. +# Connection "db3" has its own cache. +# +do_test tkt2854-1.1 { + sqlite3 db test.db + sqlite3 db2 test.db + sqlite3 db3 ./test.db + + db eval { + CREATE TABLE abc(a, b, c); + } +} {} + +# Check that an exclusive lock cannot be obtained if some other +# shared-cache connection has a read-lock on a table. +# +do_test tkt2854-1.2 { + execsql { + BEGIN; + SELECT * FROM abc; + } db2 +} {} +do_test tkt2854-1.3 { + catchsql { BEGIN EXCLUSIVE } db +} {1 {database is locked}} +do_test tkt2854-1.4 { + execsql { SELECT * FROM abc } db3 +} {} +do_test tkt2854-1.5 { + catchsql { INSERT INTO abc VALUES(1, 2, 3) } db3 +} {1 {database is locked}} +do_test tkt2854-1.6 { + execsql { COMMIT } db2 +} {} + +# Check that an exclusive lock prevents other shared-cache users from +# starting a transaction. +# +do_test tkt2854-1.7 { + set ::DB2 [sqlite3_connection_pointer db2] + set ::STMT1 [sqlite3_prepare $DB2 "SELECT * FROM abc" -1 TAIL] + set ::STMT2 [sqlite3_prepare $DB2 "BEGIN EXCLUSIVE" -1 TAIL] + set ::STMT3 [sqlite3_prepare $DB2 "BEGIN IMMEDIATE" -1 TAIL] + set ::STMT4 [sqlite3_prepare $DB2 "BEGIN" -1 TAIL] + set ::STMT5 [sqlite3_prepare $DB2 "COMMIT" -1 TAIL] + execsql { BEGIN EXCLUSIVE } db +} {} +do_test tkt2854-1.8 { + catchsql { BEGIN EXCLUSIVE } db2 +} {1 {database schema is locked: main}} +do_test tkt2854-1.9 { + catchsql { BEGIN IMMEDIATE } db2 +} {1 {database schema is locked: main}} +do_test tkt2854-1.10 { + # This fails because the schema of main cannot be verified. + catchsql { BEGIN } db2 +} {1 {database schema is locked: main}} + +# Check that an exclusive lock prevents other shared-cache users from +# reading the database. Use stored statements so that the error occurs +# at the b-tree level, not the schema level. +# +do_test tkt2854-1.11 { + list [sqlite3_step $::STMT1] [sqlite3_finalize $::STMT1] +} {SQLITE_ERROR SQLITE_LOCKED} +do_test tkt2854-1.12 { + list [sqlite3_step $::STMT2] [sqlite3_finalize $::STMT2] +} {SQLITE_BUSY SQLITE_BUSY} +do_test tkt2854-1.13 { + list [sqlite3_step $::STMT3] [sqlite3_finalize $::STMT3] +} {SQLITE_BUSY SQLITE_BUSY} +do_test tkt2854-1.14 { + # A regular "BEGIN" doesn't touch any databases. So it succeeds. + list [sqlite3_step $::STMT4] [sqlite3_finalize $::STMT4] +} {SQLITE_DONE SQLITE_OK} +do_test tkt2854-1.15 { + # As does a COMMIT. + list [sqlite3_step $::STMT5] [sqlite3_finalize $::STMT5] +} {SQLITE_DONE SQLITE_OK} + +# Try to read the database using connection "db3" (which does not share +# a cache with "db"). The database should be locked. +do_test tkt2854-1.16 { + catchsql { SELECT * FROM abc } db3 +} {1 {database is locked}} +do_test tkt2854-1.17 { + execsql { COMMIT } db +} {} +do_test tkt2854-1.18 { + execsql { SELECT * FROM abc } db2 +} {} + +# Check that if an attempt to obtain an exclusive lock fails because an +# attached db cannot be locked, the internal exclusive flag used by +# shared-cache users is correctly cleared. +do_test tkt2854-1.19 { + file delete -force test2.db test2.db-journal + sqlite3 db4 test2.db + execsql { CREATE TABLE def(d, e, f) } db4 + execsql { ATTACH 'test2.db' AS aux } db +} {} +do_test tkt2854-1.20 { + execsql {BEGIN IMMEDIATE} db4 + catchsql {BEGIN EXCLUSIVE} db +} {1 {database is locked}} +do_test tkt2854-1.21 { + execsql {SELECT * FROM abc} db2 +} {} + +db close +db2 close +db3 close +db4 close +sqlite3_enable_shared_cache $::enable_shared_cache +finish_test +