main.o malloc.o mem0.o mem1.o mem2.o mem3.o mem5.o \
memjournal.o \
mutex.o mutex_noop.o mutex_os2.o mutex_unix.o mutex_w32.o \
- opcodes.o os.o os_os2.o os_unix.o os_win.o \
+ notify.o opcodes.o os.o os_os2.o os_unix.o os_win.o \
pager.o parse.o pcache.o pcache1.o pragma.o prepare.o printf.o \
random.o resolve.o rowset.o rtree.o select.o status.o \
table.o tokenize.o trigger.o \
$(TOP)/src/mutex_os2.c \
$(TOP)/src/mutex_unix.c \
$(TOP)/src/mutex_w32.c \
+ $(TOP)/src/notify.c \
$(TOP)/src/os.c \
$(TOP)/src/os.h \
$(TOP)/src/os_common.h \
-C Bump\sthe\sversion\snumber\sto\s3.6.12.\s(CVS\s6347)
-D 2009-03-16T12:30:52
+C Add\sthe\ssqlite3_unlock_notify()\sAPI.\s(CVS\s6348)
+D 2009-03-16T13:19:36
F Makefile.arm-wince-mingw32ce-gcc fcd5e9cd67fe88836360bb4f9ef4cb7f8e2fb5a0
F Makefile.in d64baddbf55cdf33ff030e14da837324711a4ef7
F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654
F ext/rtree/viewrtree.tcl 09526398dae87a5a87c5aac2b3854dbaf8376869
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
-F main.mk 2193e5939dbf91449f9b72178d543d31b2315360
+F main.mk bbb170882a34fe51dbd2d2e9c450c6cc0dad3325
F mkdll.sh 7d09b23c05d56532e9d44a50868eb4b12ff4f74a
F mkextu.sh 416f9b7089d80e5590a29692c9d9280a10dbad9f
F mkextw.sh 4123480947681d9b434a5e7b1ee08135abe409ac
F src/analyze.c 3585d1a4c480ee85b65cf0a676e05d2c29eb6bdb
F src/attach.c d34589d5c85d81e755e4a8fc946d313915a6fa6d
F src/auth.c c8b2ab5c8bad4bd90ed7c294694f48269162c627
-F src/backup.c 2d3f31148d7b086c5c72d9edcd04fc2751b0aa6e
+F src/backup.c 0082d0e5a63f04e88faee0dff0a7d63d3e92a78d
F src/bitvec.c 44f7059ac1f874d364b34af31b9617e52223ba75
F src/btmutex.c 341502bc496dc0840dcb00cde65680fb0e85c3ab
-F src/btree.c 6e7501d7a207dcc15b099e67231bc8cc86ef7fe9
+F src/btree.c 7d1c7e15ef8f5b139a9267c3f053d10f8885759b
F src/btree.h 96a019c9f28da38e79940512d7800e419cd8c702
-F src/btreeInt.h 0a4884e6152d7cae9c741e91b830064c19fd2c05
+F src/btreeInt.h 2f1fe3c5f48800de8b542e05a746796cfdda13bd
F src/build.c 7e5e93011bab2f142cb03faa72bb64ff30e321fc
F src/callback.c 09c6fedc77a45db99ba25a75d61382830314b357
F src/complete.c cb14e06dbe79dee031031f0d9e686ff306afe07c
F src/journal.c e00df0c0da8413ab6e1bb7d7cab5665d4a9000d0
F src/legacy.c 8b3b95d48d202614946d7ce7256e7ba898905c3b
F src/loadext.c 3f96631089fc4f3871a67f02f2e4fc7ea4d51edc
-F src/main.c fd98b89bcc80d27e303c913c8facab9ee939ef15
+F src/main.c 99955156dd3167e79a492187cf05e3a73196fbe2
F src/malloc.c b9c59b33539af74641362a7496ecc3efd6477f6d
F src/mem0.c f2f84062d1f35814d6535c9f9e33de3bfb3b132c
F src/mem1.c 3bfb39e4f60b0179713a7c087b2d4f0dc205735f
F src/mutex_os2.c 6b5a74f812082a8483c3df05b47bbaac2424b9a0
F src/mutex_unix.c 2f936339dfef1a4c142db290d575a3509b77315f
F src/mutex_w32.c f4b6a4a48f1dfff7f0089cba9b5a371691f17b8b
+F src/notify.c 5787adee6f119c7d36fd8937d31d680467e01ca5
F src/os.c ed93a6b46132a602c4fd7a58142e2981c829c79d
F src/os.h fa3f4aa0119ff721a2da4b47ffd74406ac864c05
F src/os_common.h 8c61457df58f1a4bd5f5adc3e90e01b37bf7afbc
F src/pcache.h 9b927ccc5a538e31b4c3bc7eec4f976db42a1324
F src/pcache1.c f12518540ba776df3051215c4244e9cdc06b09cd
F src/pragma.c 22ed04836aab8ce134c53be1ca896f3ad20fabdb
-F src/prepare.c 1a2d40c0c5fb9f244cf5e3aacc33abcc1e7b095a
+F src/prepare.c ebd40f8c6a33c52bfea2db710394d6e82c7e594b
F src/printf.c 9866a9a9c4a90f6d4147407f373df3fd5d5f9b6f
F src/random.c 676b9d7ac820fe81e6fb2394ac8c10cff7f38628
F src/resolve.c 094e44450371fb27869eb8bf679aacbe51fdc56d
F src/rowset.c ba9375f37053d422dd76965a9c370a13b6e1aac4
F src/select.c 4d0b77fd76ff80f09a798ee98953e344c9de8fbb
F src/shell.c 0a11f831603f17fea20ca97133c0f64e716af4a7
-F src/sqlite.h.in 14f4d065bafed8500ea558a75a8e2be89c784d61
+F src/sqlite.h.in 5efbb12037347fc1341ca996a5e629734ea1212e
F src/sqlite3ext.h 1db7d63ab5de4b3e6b83dd03d1a4e64fef6d2a17
-F src/sqliteInt.h ae2dc2e2a063edfae3043e725981e69855bd3c9c
+F src/sqliteInt.h 22332127b67d6eabb4bd09cca4ca74fd17f65238
F src/sqliteLimit.h ffe93f5a0c4e7bd13e70cd7bf84cfb5c3465f45d
F src/status.c 237b193efae0cf6ac3f0817a208de6c6c6ef6d76
F src/table.c 332ab0ea691e63862e2a8bdfe2c0617ee61062a3
-F src/tclsqlite.c c18d6b71b3a01ded68e4479b128116e67eecfd2c
-F src/test1.c f88b447699786d58a0136a3a48b12990abc72c8a
+F src/tclsqlite.c 8a472804b901d4559213eeda538c2eadb2ad7f2a
+F src/test1.c 17300af44640eea439778f5b5e03e0d68a6f00a2
F src/test2.c 71c22e2974f8094fe0fd1eba8f27872dde9b2a39
F src/test3.c 88a246b56b824275300e6c899634fbac1dc94b14
F src/test4.c f79ab52d27ff49b784b631a42e2ccd52cfd5c84c
F src/test_autoext.c f53b0cdf7bf5f08100009572a5d65cdb540bd0ad
F src/test_backup.c 5b41518c5499dafe65177b0813b71ac356ee9df1
F src/test_btree.c d7b8716544611c323860370ee364e897c861f1b0
-F src/test_config.c 9dd62f4bb725ad87d28b187b07377cb4f4a43197
+F src/test_config.c a05378089b6773ba36b85727dedf9ec0a16424ce
F src/test_devsym.c 9f4bc2551e267ce7aeda195f3897d0f30c5228f4
F src/test_func.c a55c4d5479ff2eb5c0a22d4d88e9528ab59c953b
F src/test_hexio.c 2f1122aa3f012fa0142ee3c36ce5c902a70cd12f
F src/test_schema.c 4b4bf7bb329326458c491b0e6facd4c8c4c5b479
F src/test_server.c f0a403b5f699c09bd2b1236b6f69830fd6221f6b
F src/test_tclvar.c 9e42fa59d3d2f064b7ab8628e7ab2dc8a9fe93d4
-F src/test_thread.c adb9175c466e1f487295b5b957603fc0a88ea293
+F src/test_thread.c 6805d05c3b7e08d51b31662b148cf2f51b9ca4cc
F src/test_wsd.c c297d7d6b8a990239e1bd25935e81d612d8ae31d
F src/tokenize.c 6987fb7f0d6a87ac53499aee568cabb05eb4bea8
F src/trigger.c 21f39db410cdc32166a94900ac1b3df98ea560e6
F src/utf.c 1da9c832dba0fa8f865b5b902d93f420a1ee4245
F src/util.c 469d74f5bf09ed6398702c7da2ef8a34e979a1c1
F src/vacuum.c 4929a585ef0fb1dfaf46302f8a9c4aa30c2d9cf5
-F src/vdbe.c a193ab2ce10ccb70825615870c5b4413dc3be636
+F src/vdbe.c 230b0b7e73d90e001e811fca94b95b664b6ece66
F src/vdbe.h d70a68bee196ab228914a3902c79dbd24342a0f2
F src/vdbeInt.h d12bc259b34d3d610ebf05d648eb6346d48478c3
F src/vdbeapi.c ffd5d8b493590da6e09fd54b1bea1a9d38247f11
-F src/vdbeaux.c cf84955182b48cd8c65c52c143e150bb4a71f2da
+F src/vdbeaux.c feeafee5f9de51c0d30907e0600ce4db5d032df8
F src/vdbeblob.c 2852bae14c87129835938db58a77c3121e3ae962
F src/vdbemem.c 543a79d722734d2f8b7ad70f08218c30bcc5bbf5
-F src/vtab.c e39e011d7443a8d574b1b9cde207a35522e6df43
+F src/vtab.c bf409d2dc068e1bb82beeb9eef120ccfff541afb
F src/walker.c 42bd3f00ca2ef5ae842304ec0d59903ef051412d
F src/where.c ac555c8f6ef71a80944b31dcb212f7127c9ae30c
F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
F test/autovacuum.test 61260e25744189ff766f61ca3df23c1eeec0060e
F test/autovacuum_ioerr2.test 598b0663074d3673a9c1bc9a16e80971313bafe6
F test/avtrans.test 1e901d8102706b63534dbd2bdd4d8f16c4082650
-F test/backup.test 9542691cb9199b1d27051756377ef12e27404cd5
+F test/backup.test 5e487ec8dad73e9d249e9bb9ca5346a03b601b07
F test/backup2.test 04b84c97b4b5d63b6756592c6d7afe578b52c3cf
F test/backup_ioerr.test a9b8084e488154341719833783ac9db321e14284
F test/backup_malloc.test 1e063c6d75143d0d6e0ae77971dd690070369387
F test/in3.test 3cbf58c87f4052cee3a58b37b6389777505aa0c0
F test/in4.test f795d65cbcb402d3e5c016ada8f9521d6119eca8
F test/incrblob.test 4b9437bbb38724343dadbbcca6356bc2a9b435d1
-F test/incrblob2.test 5cca1c3cb29064c504b3b0cc3e2cd43e8053cfdf
+F test/incrblob2.test 7ef4581745dd80155a451637aa779b49df90787d
F test/incrblob_err.test c577c91d4ed9e8336cdb188b15d6ee2a6fe9604e
F test/incrvacuum.test d0fb6ef6d747ef5c5ebe878aafa72dd3e178856b
F test/incrvacuum2.test 46ef65f377e3937cfd1ba66e818309dab46f590d
F test/mutex1.test 1e5c196d5170bbe3a7d8370b1b905e8c86a9e07c
F test/mutex2.test bfeaeac2e73095b2ac32285d2756e3a65e681660
F test/nan.test c627d79b3d36ea892563fd67584b3e8a18f0618a
+F test/notify1.test 9a985a94f34de1b24daf25fd86b6d5033ba532d0
+F test/notify2.test 828511802c3e899d91f753771cefbe1d41dcb854
F test/notnull.test 44d600f916b770def8b095a9962dbe3be5a70d82
F test/null.test a8b09b8ed87852742343b33441a9240022108993
F test/openv2.test f5dd6b23e4dce828eb211649b600763c42a668df
F test/selectB.test 31e81ac9af7d224850e0706350f070ecb92fcbc7
F test/selectC.test ae49d258c875bc1712898f1632062bc5c01a7470
F test/server1.test f5b790d4c0498179151ca8a7715a65a7802c859c
-F test/shared.test 2f6c65de8123c130b92e4e18a516f669eaa02fea
+F test/shared.test 3b448dc0f7a9356e641894ed81c27599f39d809d
F test/shared2.test 0ee9de8964d70e451936a48c41cb161d9134ccf4
F test/shared3.test 9c880afc081d797da514ef64bccf36f3fce2f09c
F test/shared4.test d0fadacb50bb6981b2fb9dc6d1da30fa1edddf83
F test/sync.test ded6b39d8d8ca3c0c5518516c6371b3316d3e3a3
F test/table.test bf1bc3d9634342a3470bdf64b6190e7445b6b8a6
F test/tableapi.test 505031f15b18a750184d967d2c896cf88fcc969c
-F test/tclsqlite.test 413a8a887d89ea8fa7055e8d118ffb03b0a4c91a
+F test/tclsqlite.test 8b1150d0486c4848c70d96422513a91c5342be0e
F test/tempdb.test b88ac8a19823cf771d742bf61eef93ef337c06b1
F test/temptable.test 5d8ca46be28cc06c887c5a77df650843b7edbae1
F test/temptrigger.test 03093be9967942623232dfdf2a63b832d4e0e4fa
F test/tkt2820.test 017fdee33aaef7abc092beab6088816f1942304b
F test/tkt2822.test a2b27a58df62d1b2e712f91dbe42ad3b7e0e77cc
F test/tkt2832.test 85cf382ff406de9de35534b86bc7227d609140c0
-F test/tkt2854.test a2bc584ac26bcebe174229e7a1ad4e6d43c3d569
+F test/tkt2854.test b81dc3144901b123fe5674471adf5a47ca48a7c3
F test/tkt2920.test a8737380e4ae6424e00c0273dc12775704efbebf
F test/tkt2927.test 4752868b9eeeb07a217f7f19f4cbaac98d6d086d
F test/tkt2942.test c5c87d179799ca6d1fbe83c815510b87cd5ec7ce
F tool/mkkeywordhash.c 8e57fbe8c4fe2f1800f9190fd361231cb8558407
F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e x
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
-F tool/mksqlite3c.tcl a416823e4c06e8d8e18e141e69a8a0e8e12ad861
+F tool/mksqlite3c.tcl 671833bd775e76ebd922b9e82c2508a344562511
F tool/mksqlite3internalh.tcl 7b43894e21bcb1bb39e11547ce7e38a063357e87
F tool/omittest.tcl 27d6f6e3b1e95aeb26a1c140e6eb57771c6d794a
F tool/opcodeDoc.awk b3a2a3d5d3075b8bd90b7afe24283efdd586659c
F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
-P 324a1aff300b7349b9fc1dea56d640d86500f100
-R 3843a549b4ba7feed5cd440b16a43795
-U drh
-Z 9336bd2967fe6f29f86215585309dca6
+P 2fcccca3e56e2e3a95bdedeb01ab7da1b24b7ac2
+R 5be2b218449f212c10c446da99e1fb31
+U danielk1977
+Z 7d9c2a290b69196df9bf164217432ca4
-2fcccca3e56e2e3a95bdedeb01ab7da1b24b7ac2
\ No newline at end of file
+b649a6cc5bfefddd6a04b1183647d2923e0a0daa
\ No newline at end of file
** This file contains the implementation of the sqlite3_backup_XXX()
** API functions and the related features.
**
-** $Id: backup.c,v 1.12 2009/02/16 17:55:47 shane Exp $
+** $Id: backup.c,v 1.13 2009/03/16 13:19:36 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "btreeInt.h"
int bCloseTrans = 0; /* True if src db requires unlocking */
/* If the source pager is currently in a write-transaction, return
- ** SQLITE_LOCKED immediately.
+ ** SQLITE_BUSY immediately.
*/
if( p->pDestDb && p->pSrc->pBt->inTransaction==TRANS_WRITE ){
- rc = SQLITE_LOCKED;
+ rc = SQLITE_BUSY;
}else{
rc = SQLITE_OK;
}
** May you share freely, never taking more than you give.
**
*************************************************************************
-** $Id: btree.c,v 1.572 2009/03/12 14:43:28 danielk1977 Exp $
+** $Id: btree.c,v 1.573 2009/03/16 13:19:36 danielk1977 Exp $
**
** This file implements a external (disk-based) database using BTrees.
** See the header comment on "btreeInt.h" for additional information.
/* 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;
+ if( pBt->pWriter!=p && pBt->isExclusive ){
+ sqlite3ConnectionBlocked(p->db, pBt->pWriter->db);
+ return SQLITE_LOCKED_SHAREDCACHE;
}
/* This (along with setSharedCacheTableLock()) is where
for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){
if( pIter->pBtree!=p && pIter->iTable==iTab &&
(pIter->eLock!=eLock || eLock!=READ_LOCK) ){
- return SQLITE_LOCKED;
+ sqlite3ConnectionBlocked(p->db, pIter->pBtree->db);
+ if( eLock==WRITE_LOCK ){
+ assert( p==pBt->pWriter );
+ pBt->isPending = 1;
+ }
+ return SQLITE_LOCKED_SHAREDCACHE;
}
}
}
while( *ppIter ){
BtLock *pLock = *ppIter;
- assert( pBt->pExclusive==0 || pBt->pExclusive==pLock->pBtree );
+ assert( pBt->isExclusive==0 || pBt->pWriter==pLock->pBtree );
if( pLock->pBtree==p ){
*ppIter = pLock->pNext;
sqlite3_free(pLock);
}
}
- if( pBt->pExclusive==p ){
- pBt->pExclusive = 0;
+ assert( pBt->isPending==0 || pBt->pWriter );
+ if( pBt->pWriter==p ){
+ pBt->pWriter = 0;
+ pBt->isExclusive = 0;
+ pBt->isPending = 0;
+ }else if( pBt->nTransaction==2 ){
+ /* This function is called when connection p is concluding its
+ ** transaction. If there currently exists a writer, and p is not
+ ** that writer, then the number of locks held by connections other
+ ** than the writer must be about to drop to zero. In this case
+ ** set the isPending flag to 0.
+ **
+ ** If there is not currently a writer, then BtShared.isPending must
+ ** be zero already. So this next line is harmless in that case.
+ */
+ pBt->isPending = 0;
}
}
#endif /* SQLITE_OMIT_SHARED_CACHE */
** proceed.
*/
int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
+ sqlite3 *pBlock = 0;
BtShared *pBt = p->pBt;
int rc = SQLITE_OK;
goto trans_begun;
}
+#ifndef SQLITE_OMIT_SHARED_CACHE
/* If another database handle has already opened a write transaction
** on this shared-btree structure and a second write transaction is
- ** requested, return SQLITE_BUSY.
+ ** requested, return SQLITE_LOCKED.
*/
- if( pBt->inTransaction==TRANS_WRITE && wrflag ){
- rc = SQLITE_BUSY;
- goto trans_begun;
- }
-
-#ifndef SQLITE_OMIT_SHARED_CACHE
- if( wrflag>1 ){
+ if( (wrflag && pBt->inTransaction==TRANS_WRITE) || pBt->isPending ){
+ pBlock = pBt->pWriter->db;
+ }else if( wrflag>1 ){
BtLock *pIter;
for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){
if( pIter->pBtree!=p ){
- rc = SQLITE_BUSY;
- goto trans_begun;
+ pBlock = pIter->pBtree->db;
+ break;
}
}
}
+ if( pBlock ){
+ sqlite3ConnectionBlocked(p->db, pBlock);
+ rc = SQLITE_LOCKED_SHAREDCACHE;
+ goto trans_begun;
+ }
#endif
do {
pBt->inTransaction = p->inTrans;
}
#ifndef SQLITE_OMIT_SHARED_CACHE
- if( wrflag>1 ){
- assert( !pBt->pExclusive );
- pBt->pExclusive = p;
+ if( wrflag ){
+ assert( !pBt->pWriter );
+ pBt->pWriter = p;
+ pBt->isExclusive = (wrflag>1);
}
#endif
}
if( NEVER(pBt->readOnly) ){
return SQLITE_READONLY;
}
- if( checkForReadConflicts(p, iTable, 0, 0) ){
- return SQLITE_LOCKED;
+ rc = checkForReadConflicts(p, iTable, 0, 0);
+ if( rc!=SQLITE_OK ){
+ assert( rc==SQLITE_LOCKED_SHAREDCACHE );
+ return rc;
}
}
#endif
){
sqlite3 *dbOther = p->pBtree->db;
- if( dbOther==0 ||
- (dbOther!=db && (dbOther->flags & SQLITE_ReadUncommitted)==0) ){
- return SQLITE_LOCKED;
+ assert(dbOther);
+ if( dbOther!=db && (dbOther->flags & SQLITE_ReadUncommitted)==0 ){
+ sqlite3ConnectionBlocked(db, dbOther);
+ return SQLITE_LOCKED_SHAREDCACHE;
}
}
}
assert( pBt->inTransaction==TRANS_WRITE );
assert( !pBt->readOnly );
assert( pCur->wrFlag );
- if( checkForReadConflicts(pCur->pBtree, pCur->pgnoRoot, pCur, nKey) ){
- return SQLITE_LOCKED; /* The table pCur points to has a read lock */
+ rc = checkForReadConflicts(pCur->pBtree, pCur->pgnoRoot, pCur, nKey);
+ if( rc ){
+ /* The table pCur points to has a read lock */
+ assert( rc==SQLITE_LOCKED_SHAREDCACHE );
+ return rc;
}
if( pCur->eState==CURSOR_FAULT ){
return pCur->skip;
return SQLITE_ERROR; /* The cursor is not pointing to anything */
}
assert( pCur->wrFlag );
- if( checkForReadConflicts(pCur->pBtree, pCur->pgnoRoot,
- pCur, pCur->info.nKey)
- ){
- return SQLITE_LOCKED; /* The table pCur points to has a read lock */
+ rc = checkForReadConflicts(p, pCur->pgnoRoot, pCur, pCur->info.nKey);
+ if( rc!=SQLITE_OK ){
+ /* The table pCur points to has a read lock */
+ assert( rc==SQLITE_LOCKED_SHAREDCACHE );
+ return rc;
}
/* Restore the current cursor position (a no-op if the cursor is not in
** occur.
*/
if( pBt->pCursor ){
- return SQLITE_LOCKED;
+ sqlite3ConnectionBlocked(p->db, pBt->pCursor->pBtree->db);
+ return SQLITE_LOCKED_SHAREDCACHE;
}
rc = sqlite3BtreeGetPage(pBt, (Pgno)iTable, &pPage, 0);
}
/*
-** Return true if another user of the same shared btree as the argument
-** handle holds an exclusive lock on the sqlite_master table.
+** Return SQLITE_LOCKED_SHAREDCACHE if another user of the same shared
+** btree as the argument handle holds an exclusive lock on the
+** sqlite_master table. Otherwise SQLITE_OK.
*/
int sqlite3BtreeSchemaLocked(Btree *p){
int rc;
assert( sqlite3_mutex_held(p->db->mutex) );
sqlite3BtreeEnter(p);
- rc = (querySharedCacheTableLock(p, MASTER_ROOT, READ_LOCK)!=SQLITE_OK);
+ rc = querySharedCacheTableLock(p, MASTER_ROOT, READ_LOCK);
+ assert( rc==SQLITE_OK || rc==SQLITE_LOCKED_SHAREDCACHE );
sqlite3BtreeLeave(p);
return rc;
}
** to change the length of the data stored.
*/
int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void *z){
+ int rc;
+
assert( cursorHoldsMutex(pCsr) );
assert( sqlite3_mutex_held(pCsr->pBtree->db->mutex) );
assert(pCsr->isIncrblobHandle);
}
assert( !pCsr->pBt->readOnly
&& pCsr->pBt->inTransaction==TRANS_WRITE );
- if( checkForReadConflicts(pCsr->pBtree, pCsr->pgnoRoot, pCsr, 0) ){
- return SQLITE_LOCKED; /* The table pCur points to has a read lock */
+ rc = checkForReadConflicts(pCsr->pBtree, pCsr->pgnoRoot, pCsr, 0);
+ if( rc!=SQLITE_OK ){
+ /* The table pCur points to has a read lock */
+ assert( rc==SQLITE_LOCKED_SHAREDCACHE );
+ return rc;
}
if( pCsr->eState==CURSOR_INVALID || !pCsr->apPage[pCsr->iPage]->intKey ){
return SQLITE_ERROR;
** May you share freely, never taking more than you give.
**
*************************************************************************
-** $Id: btreeInt.h,v 1.42 2009/02/03 16:51:25 danielk1977 Exp $
+** $Id: btreeInt.h,v 1.43 2009/03/16 13:19:36 danielk1977 Exp $
**
** This file implements a external (disk-based) database using BTrees.
** For a detailed discussion of BTrees, refer to
** may not be modified once it is initially set as long as nRef>0.
** The pSchema field may be set once under BtShared.mutex and
** thereafter is unchanged as long as nRef>0.
+**
+** isPending:
+**
+** If a BtShared client fails to obtain a write-lock on a database
+** table (because there exists one or more read-locks on the table),
+** the shared-cache enters 'pending-lock' state and isPending is
+** set to true.
+**
+** The shared-cache leaves the 'pending lock' state when either of
+** the following occur:
+**
+** 1) The current writer (BtShared.pWriter) concludes its transaction, OR
+** 2) The number of locks held by other connections drops to zero.
+**
+** while in the 'pending-lock' state, no connection may start a new
+** transaction.
+**
+** This feature is included to help prevent writer-starvation.
*/
struct BtShared {
Pager *pPager; /* The page cache */
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 */
+ Btree *pWriter; /* Btree with currently open write transaction */
+ u8 isExclusive; /* True if pWriter has an EXCLUSIVE lock on the db */
+ u8 isPending; /* If waiting for read-locks to clear */
#endif
u8 *pTmpSpace; /* BtShared.pageSize bytes of space for tmp use */
};
** other files are for internal use by SQLite and should not be
** accessed by users of the library.
**
-** $Id: main.c,v 1.530 2009/02/26 07:15:59 danielk1977 Exp $
+** $Id: main.c,v 1.531 2009/03/16 13:19:36 danielk1977 Exp $
*/
#include "sqliteInt.h"
}
}
sqlite3ResetInternalSchema(db, 0);
+
+ /* Tell the code in notify.c that the connection no longer holds any
+ ** locks and does not require any further unlock-notify callbacks.
+ */
+ sqlite3ConnectionClosed(db);
+
assert( db->nDb<=2 );
assert( db->aDb==db->aDbStatic );
for(j=0; j<ArraySize(db->aFunc.a); j++){
--- /dev/null
+/*
+** 2009 March 3
+**
+** 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.
+**
+*************************************************************************
+**
+** This file contains the implementation of the sqlite3_unlock_notify()
+** API method and its associated functionality.
+**
+** $Id: notify.c,v 1.1 2009/03/16 13:19:36 danielk1977 Exp $
+*/
+#include "sqliteInt.h"
+#include "btreeInt.h"
+
+/* Omit this entire file if SQLITE_ENABLE_UNLOCK_NOTIFY is not defined. */
+#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
+
+/*
+** Public interfaces:
+**
+** sqlite3ConnectionBlocked()
+** sqlite3ConnectionUnlocked()
+** sqlite3ConnectionClosed()
+** sqlite3_unlock_notify()
+*/
+
+#define assertMutexHeld() \
+ assert( sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)) )
+
+/*
+** Head of a linked list of all sqlite3 objects created by this process
+** for which either sqlite3.pBlockingConnection or sqlite3.pUnlockConnection
+** is not NULL. This variable may only accessed while the STATIC_MASTER
+** mutex is held.
+*/
+static sqlite3 *SQLITE_WSD sqlite3BlockedList = 0;
+
+#ifndef NDEBUG
+/*
+** This function is a complex assert() that verifies the following
+** properties of the blocked connections list:
+**
+** 1) Each entry in the list has a non-NULL value for either
+** pUnlockConnection or pBlockingConnection, or both.
+**
+** 2) All entries in the list that share a common value for
+** xUnlockNotify are grouped together.
+**
+** 3) If the argument db is not NULL, then none of the entries in the
+** blocked connections list have pUnlockConnection or pBlockingConnection
+** set to db. This is used when closing connection db.
+*/
+static void checkListProperties(sqlite3 *db){
+ sqlite3 *p;
+ for(p=sqlite3BlockedList; p; p=p->pNextBlocked){
+ int seen = 0;
+ sqlite3 *p2;
+
+ /* Verify property (1) */
+ assert( p->pUnlockConnection || p->pBlockingConnection );
+
+ /* Verify property (2) */
+ for(p2=sqlite3BlockedList; p2!=p; p2=p2->pNextBlocked){
+ if( p2->xUnlockNotify==p->xUnlockNotify ) seen = 1;
+ assert( p2->xUnlockNotify==p->xUnlockNotify || !seen );
+ assert( db==0 || p->pUnlockConnection!=db );
+ assert( db==0 || p->pBlockingConnection!=db );
+ }
+ }
+}
+#else
+# define checkListProperties(x)
+#endif
+
+/*
+** Remove connection db from the blocked connections list. If connection
+** db is not currently a part of the list, this function is a no-op.
+*/
+static void removeFromBlockedList(sqlite3 *db){
+ sqlite3 **pp;
+ assertMutexHeld();
+ for(pp=&sqlite3BlockedList; *pp; pp = &(*pp)->pNextBlocked){
+ if( *pp==db ){
+ *pp = (*pp)->pNextBlocked;
+ break;
+ }
+ }
+}
+
+/*
+** Add connection db to the blocked connections list. It is assumed
+** that it is not already a part of the list.
+*/
+static void addToBlockedList(sqlite3 *db){
+ sqlite3 **pp;
+ assertMutexHeld();
+ for(
+ pp=&sqlite3BlockedList;
+ *pp && (*pp)->xUnlockNotify!=db->xUnlockNotify;
+ pp=&(*pp)->pNextBlocked
+ );
+ db->pNextBlocked = *pp;
+ *pp = db;
+}
+
+/*
+** Obtain the STATIC_MASTER mutex.
+*/
+static void enterMutex(){
+ sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
+ checkListProperties(0);
+}
+
+/*
+** Release the STATIC_MASTER mutex.
+*/
+static void leaveMutex(){
+ assertMutexHeld();
+ checkListProperties(0);
+ sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER));
+}
+
+/*
+** Register an unlock-notify callback.
+*/
+int sqlite3_unlock_notify(
+ sqlite3 *db,
+ void (*xNotify)(void **, int),
+ void *pArg
+){
+ int rc = SQLITE_OK;
+
+ sqlite3_mutex_enter(db->mutex);
+ enterMutex();
+
+ if( 0==db->pBlockingConnection ){
+ /* The blocking transaction has been concluded. Or there never was a
+ ** blocking transaction. In either case, invoke the notify callback
+ ** immediately.
+ */
+ xNotify(&pArg, 1);
+ }else{
+ sqlite3 *p;
+
+ for(p=db->pBlockingConnection; p && p!=db; p=p->pUnlockConnection);
+ if( p ){
+ rc = SQLITE_LOCKED; /* Deadlock detected. */
+ }else{
+ db->pUnlockConnection = db->pBlockingConnection;
+ db->xUnlockNotify = xNotify;
+ db->pUnlockArg = pArg;
+ removeFromBlockedList(db);
+ addToBlockedList(db);
+ }
+ }
+
+ leaveMutex();
+ assert( !db->mallocFailed );
+ sqlite3Error(db, rc, (rc?"database is deadlocked":0));
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
+/*
+** This function is called while stepping or preparing a statement
+** associated with connection db. The operation will return SQLITE_LOCKED
+** to the user because it requires a lock that will not be available
+** until connection pBlocker concludes its current transaction.
+*/
+void sqlite3ConnectionBlocked(sqlite3 *db, sqlite3 *pBlocker){
+ enterMutex();
+ if( db->pBlockingConnection==0 && db->pUnlockConnection==0 ){
+ addToBlockedList(db);
+ }
+ db->pBlockingConnection = pBlocker;
+ leaveMutex();
+}
+
+/*
+** The transaction opened by database db has just finished. Locks held
+** by database connection db have been released.
+**
+** This function loops through each entry in the blocked connections
+** list and does the following:
+**
+** 1) If the sqlite3.pBlockingConnection member of a list entry is
+** set to db, then set pBlockingConnection=0.
+**
+** 2) If the sqlite3.pUnlockConnection member of a list entry is
+** set to db, then invoke the configured unlock-notify callback and
+** set pUnlockConnection=0.
+**
+** 3) If the two steps above mean that pBlockingConnection==0 and
+** pUnlockConnection==0, remove the entry from the blocked connections
+** list.
+*/
+void sqlite3ConnectionUnlocked(sqlite3 *db){
+ void (*xUnlockNotify)(void **, int) = 0; /* Unlock-notify cb to invoke */
+ int nArg = 0; /* Number of entries in aArg[] */
+ sqlite3 **pp; /* Iterator variable */
+
+ void *aStatic[16];
+ void **aArg = aStatic;
+ void **aDyn = 0;
+
+ enterMutex(); /* Enter STATIC_MASTER mutex */
+
+ /* This loop runs once for each entry in the blocked-connections list. */
+ for(pp=&sqlite3BlockedList; *pp; /* no-op */ ){
+ sqlite3 *p = *pp;
+
+ /* Step 1. */
+ if( p->pBlockingConnection==db ){
+ p->pBlockingConnection = 0;
+ }
+
+ /* Step 2. */
+ if( p->pUnlockConnection==db ){
+ assert( p->xUnlockNotify );
+ if( p->xUnlockNotify!=xUnlockNotify && nArg!=0 ){
+ xUnlockNotify(aArg, nArg);
+ nArg = 0;
+ }
+
+ sqlite3BeginBenignMalloc();
+ assert( aArg==aDyn || (aDyn==0 && aArg==aStatic) );
+ assert( nArg<=ArraySize(aStatic) || aArg==aDyn );
+ if( (!aDyn && nArg==ArraySize(aStatic))
+ || (aDyn && nArg==(sqlite3DbMallocSize(db, aDyn)/sizeof(void*)))
+ ){
+ /* The aArg[] array needs to grow. */
+ void **pNew = (void **)sqlite3Malloc(nArg*sizeof(void *)*2);
+ if( pNew ){
+ memcpy(pNew, aArg, nArg*sizeof(void *));
+ sqlite3_free(aDyn);
+ aDyn = aArg = pNew;
+ }else{
+ /* This occurs when the array of context pointers that need to
+ ** be passed to the unlock-notify callback is larger than the
+ ** aStatic[] array allocated on the stack and the attempt to
+ ** allocate a larger array from the heap has failed.
+ **
+ ** This is a difficult situation to handle. Returning an error
+ ** code to the caller is insufficient, as even if an error code
+ ** is returned the transaction on connection db will still be
+ ** closed and the unlock-notify callbacks on blocked connections
+ ** will go unissued. This might cause the application to wait
+ ** indefinitely for an unlock-notify callback that will never
+ ** arrive.
+ **
+ ** Instead, invoke the unlock-notify callback with the context
+ ** array already accumulated. We can then clear the array and
+ ** begin accumulating any further context pointers without
+ ** requiring any dynamic allocation. This is sub-optimal because
+ ** it means that instead of one callback with a large array of
+ ** context pointers the application will receive two or more
+ ** callbacks with smaller arrays of context pointers, which will
+ ** reduce the applications ability to prioritize multiple
+ ** connections. But it is the best that can be done under the
+ ** circumstances.
+ */
+ xUnlockNotify(aArg, nArg);
+ nArg = 0;
+ }
+ }
+ sqlite3EndBenignMalloc();
+
+ aArg[nArg++] = p->pUnlockArg;
+ xUnlockNotify = p->xUnlockNotify;
+ p->pUnlockConnection = 0;
+ p->xUnlockNotify = 0;
+ p->pUnlockArg = 0;
+ }
+
+ /* Step 3. */
+ if( p->pBlockingConnection==0 && p->pUnlockConnection==0 ){
+ /* Remove connection p from the blocked connections list. */
+ *pp = p->pNextBlocked;
+ p->pNextBlocked = 0;
+ }else{
+ pp = &p->pNextBlocked;
+ }
+ }
+
+ if( nArg!=0 ){
+ xUnlockNotify(aArg, nArg);
+ }
+ sqlite3_free(aDyn);
+ leaveMutex(); /* Leave STATIC_MASTER mutex */
+}
+
+/*
+** This is called when the database connection passed as an argument is
+** being closed. The connection is removed from the blocked list.
+*/
+void sqlite3ConnectionClosed(sqlite3 *db){
+ sqlite3ConnectionUnlocked(db);
+ enterMutex();
+ removeFromBlockedList(db);
+ checkListProperties(db);
+ leaveMutex();
+}
+#endif
+
** interface, and routines that contribute to loading the database schema
** from disk.
**
-** $Id: prepare.c,v 1.108 2009/03/05 04:20:32 shane Exp $
+** $Id: prepare.c,v 1.109 2009/03/16 13:19:36 danielk1977 Exp $
*/
#include "sqliteInt.h"
rc = sqlite3BtreeSchemaLocked(pBt);
if( rc ){
const char *zDb = db->aDb[i].zName;
- sqlite3Error(db, SQLITE_LOCKED, "database schema is locked: %s", zDb);
+ sqlite3Error(db, rc, "database schema is locked: %s", zDb);
(void)sqlite3SafetyOff(db);
testcase( db->flags & SQLITE_ReadUncommitted );
- return sqlite3ApiExit(db, SQLITE_LOCKED);
+ return sqlite3ApiExit(db, rc);
}
}
}
** the version number) and changes its name to "sqlite3.h" as
** part of the build process.
**
-** @(#) $Id: sqlite.h.in,v 1.433 2009/02/18 18:37:59 drh Exp $
+** @(#) $Id: sqlite.h.in,v 1.434 2009/03/16 13:19:36 danielk1977 Exp $
*/
#ifndef _SQLITE3_H_
#define _SQLITE3_H_
#define SQLITE_IOERR_CLOSE (SQLITE_IOERR | (16<<8))
#define SQLITE_IOERR_DIR_CLOSE (SQLITE_IOERR | (17<<8))
+#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8) )
+
/*
** CAPI3REF: Flags For File Open Operations {H10230} <H11120> <H12700>
**
int sqlite3_backup_remaining(sqlite3_backup *p);
int sqlite3_backup_pagecount(sqlite3_backup *p);
+/*
+** CAPI3REF: Unlock Notification
+** EXPERIMENTAL
+**
+** When running in shared-cache mode, a database operation may fail with
+** an SQLITE_LOCKED error if the required locks on the shared-cache or
+** individual tables within the shared-cache cannot be obtained. See
+** [SQLite Shared-Cache Mode] for a description of shared-cache locking.
+** This API may be used to register a callback that SQLite will invoke
+** when the connection currently holding the required lock relinquishes it.
+** This API is only available if the library was compiled with the
+** SQLITE_ENABLE_UNLOCK_NOTIFY C-preprocessor symbol defined.
+**
+** See Also: [Using the SQLite Unlock Notification Feature].
+**
+** Shared-cache locks are released when a database connection concludes
+** its current transaction, either by committing it or rolling it back.
+**
+** When a connection (known as the blocked connection) fails to obtain a
+** shared-cache lock and SQLITE_LOCKED is returned to the caller, the
+** identity of the database connection (the blocking connection) that
+** has locked the required resource is stored internally. After an
+** application receives an SQLITE_LOCKED error, it may call the
+** sqlite3_unlock_notify() method with the blocked connection handle as
+** the first argument to register for a callback that will be invoked
+** when the blocking connections current transaction is concluded. The
+** callback is invoked from within the [sqlite3_step] or [sqlite3_close]
+** call that concludes the blocking connections transaction.
+**
+** If sqlite3_unlock_notify() is called in a multi-threaded application,
+** there is a chance that the blocking connection will have already
+** concluded its transaction by the time sqlite3_unlock_notify() is invoked.
+** If this happens, then the specified callback is invoked immediately,
+** from within the call to sqlite3_unlock_notify().
+**
+** If the blocked connection is attempting to obtain a write-lock on a
+** shared-cache table, and more than one other connection currently holds
+** a read-lock on the same table, then SQLite arbitrarily selects one of
+** the other connections to use as the blocking connection.
+**
+** There may be at most one unlock-notify callback registered by a
+** blocked connection. If sqlite3_unlock_notify() is called when the
+** blocked connection already has a registered unlock-notify callback,
+** then the new callback replaces the old. If sqlite3_unlock_notify() is
+** called with a NULL pointer as its second argument, then any existing
+** unlock-notify callback is cancelled. The blocked connections
+** unlock-notify callback may also be canceled by closing the blocked
+** connection using [sqlite3_close()].
+**
+** The unlock-notify callback is not reentrant. If an application invokes
+** any sqlite3_xxx API functions from within an unlock-notify callback, a
+** crash or deadlock may be the result.
+**
+** Unless deadlock is detected (see below), sqlite3_unlock_notify() always
+** returns SQLITE_OK.
+**
+** <b>Callback Invocation Details</b>
+**
+** When an unlock-notify callback is registered, the application provides a
+** single void* pointer that is passed to the callback when it is invoked.
+** However, the signature of the callback function allows SQLite to pass
+** it an array of void* context pointers. The first argument passed to
+** an unlock-notify callback is a pointer to an array of void* pointers,
+** and the second is the number of entries in the array.
+**
+** When a blocking connections transaction is concluded, there may be
+** more than one blocked connection that has registered for an unlock-notify
+** callback. If two or more such blocked connections have specified the
+** same callback function, then instead of invoking the callback function
+** multiple times, it is invoked once with the set of void* context pointers
+** specified by the blocked connections bundled together into an array.
+** This gives the application an opportunity to prioritize any actions
+** related to the set of unblocked database connections.
+**
+** <b>Deadlock Detection</b>
+**
+** Assuming that after registering for an unlock-notify callback a
+** database waits for the callback to be issued before taking any further
+** action (a reasonable assumption), then using this API may cause the
+** application to deadlock. For example, if connection X is waiting for
+** connection Y's transaction to be concluded, and similarly connection
+** Y is waiting on connection X's transaction, then neither connection
+** will proceed and the system may remain deadlocked indefinitely.
+**
+** To avoid this scenario, the sqlite3_unlock_notify() performs deadlock
+** detection. If a given call to sqlite3_unlock_notify() would put the
+** system in a deadlocked state, then SQLITE_LOCKED is returned and no
+** unlock-notify callback is registered. The system is said to be in
+** a deadlocked state if connection A has registered for an unlock-notify
+** callback on the conclusion of connection B's transaction, and connection
+** B has itself registered for an unlock-notify callback when connection
+** A's transaction is concluded. Indirect deadlock is also detected, so
+** the system is also considered to be deadlocked if connection B has
+** registered for an unlock-notify callback on the conclusion of connection
+** C's transaction, where connection C is waiting on connection A. Any
+** number of levels of indirection are allowed.
+**
+** <b>The "DROP TABLE" Exception</b>
+**
+** When a call to [sqlite3_step()] returns SQLITE_LOCKED, it is almost
+** always appropriate to call sqlite3_unlock_notify(). There is however,
+** one exception. When executing a "DROP TABLE" or "DROP INDEX" statement,
+** SQLite checks if there are any currently executing SELECT statements
+** that belong to the same connection. If there are, SQLITE_LOCKED is
+** returned. In this case there is no "blocking connection", so invoking
+** sqlite3_unlock_notify() results in the unlock-notify callback being
+** invoked immediately. If the application then re-attempts the "DROP TABLE"
+** or "DROP INDEX" query, an infinite loop might be the result.
+**
+** One way around this problem is to check the extended error code returned
+** by an sqlite3_step() call. If there is a blocking connection, then the
+** extended error code is set to SQLITE_LOCKED_SHAREDCACHE. Otherwise, in
+** the special "DROP TABLE/INDEX" case, the extended error code is just
+** SQLITE_LOCKED.
+*/
+int sqlite3_unlock_notify(
+ sqlite3 *pBlocked, /* Waiting connection */
+ void (*xNotify)(void **apArg, int nArg), /* Callback function to invoke */
+ void *pNotifyArg /* Argument to pass to xNotify */
+);
+
/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
*************************************************************************
** Internal interface definitions for SQLite.
**
-** @(#) $Id: sqliteInt.h,v 1.840 2009/03/02 17:18:48 shane Exp $
+** @(#) $Id: sqliteInt.h,v 1.841 2009/03/16 13:19:36 danielk1977 Exp $
*/
#ifndef _SQLITEINT_H_
#define _SQLITEINT_H_
Savepoint *pSavepoint; /* List of active savepoints */
int nSavepoint; /* Number of non-transaction savepoints */
u8 isTransactionSavepoint; /* True if the outermost savepoint is a TS */
+
+#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
+ /* The following variables are all protected by the STATIC_MASTER
+ ** mutex, not by sqlite3.mutex. They are used by code in notify.c.
+ */
+ sqlite3 *pBlockingConnection; /* Connection that caused SQLITE_LOCKED */
+ sqlite3 *pUnlockConnection; /* Connection to watch for unlock */
+ void *pUnlockArg; /* Argument to xUnlockNotify */
+ void (*xUnlockNotify)(void **, int); /* Unlock notify callback */
+ sqlite3 *pNextBlocked; /* Next in list of all blocked connections */
+#endif
};
/*
u32 sqlite3Get4byte(const u8*);
void sqlite3Put4byte(u8*, u32);
+#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
+ void sqlite3ConnectionBlocked(sqlite3 *, sqlite3 *);
+ void sqlite3ConnectionUnlocked(sqlite3 *db);
+ void sqlite3ConnectionClosed(sqlite3 *db);
+#else
+ #define sqlite3ConnectionBlocked(x,y)
+ #define sqlite3ConnectionUnlocked(x)
+ #define sqlite3ConnectionClosed(x)
+#endif
+
+
#ifdef SQLITE_SSE
#include "sseInt.h"
#endif
** A TCL Interface to SQLite. Append this file to sqlite3.c and
** compile the whole thing to build a TCL-enabled version of SQLite.
**
-** $Id: tclsqlite.c,v 1.237 2009/02/17 16:29:11 danielk1977 Exp $
+** $Id: tclsqlite.c,v 1.238 2009/03/16 13:19:36 danielk1977 Exp $
*/
#include "tcl.h"
#include <errno.h>
SqlFunc *pFunc; /* List of SQL functions */
Tcl_Obj *pUpdateHook; /* Update hook script (if any) */
Tcl_Obj *pRollbackHook; /* Rollback hook script (if any) */
+ Tcl_Obj *pUnlockNotify; /* Unlock notify script (if any) */
SqlCollate *pCollate; /* List of SQL collation functions */
int rc; /* Return code of most recent sqlite3_exec() */
Tcl_Obj *pCollateNeeded; /* Collation needed script */
}
}
+#ifdef SQLITE_TEST
+static void setTestUnlockNotifyVars(Tcl_Interp *interp, int iArg, int nArg){
+ char zBuf[64];
+ sprintf(zBuf, "%d", iArg);
+ Tcl_SetVar(interp, "sqlite_unlock_notify_arg", zBuf, TCL_GLOBAL_ONLY);
+ sprintf(zBuf, "%d", nArg);
+ Tcl_SetVar(interp, "sqlite_unlock_notify_argcount", zBuf, TCL_GLOBAL_ONLY);
+}
+#else
+ #define setTestUnlockNotifyVars(x,y,z)
+#endif
+
+static void DbUnlockNotify(void **apArg, int nArg){
+ int i;
+ for(i=0; i<nArg; i++){
+ const int flags = (TCL_EVAL_GLOBAL|TCL_EVAL_DIRECT);
+ SqliteDb *pDb = (SqliteDb *)apArg[i];
+ setTestUnlockNotifyVars(pDb->interp, i, nArg);
+ assert( pDb->pUnlockNotify);
+ Tcl_EvalObjEx(pDb->interp, pDb->pUnlockNotify, flags);
+ Tcl_DecrRefCount(pDb->pUnlockNotify);
+ pDb->pUnlockNotify = 0;
+ }
+}
+
static void DbUpdateHandler(
void *p,
int op,
"profile", "progress", "rekey",
"restore", "rollback_hook", "status",
"timeout", "total_changes", "trace",
- "transaction", "update_hook", "version",
- 0
+ "transaction", "unlock_notify", "update_hook",
+ "version", 0
};
enum DB_enum {
DB_AUTHORIZER, DB_BACKUP, DB_BUSY,
DB_PROFILE, DB_PROGRESS, DB_REKEY,
DB_RESTORE, DB_ROLLBACK_HOOK, DB_STATUS,
DB_TIMEOUT, DB_TOTAL_CHANGES, DB_TRACE,
- DB_TRANSACTION, DB_UPDATE_HOOK, DB_VERSION,
+ DB_TRANSACTION, DB_UNLOCK_NOTIFY, DB_UPDATE_HOOK,
+ DB_VERSION,
};
/* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */
break;
}
+ /*
+ ** $db unlock_notify ?script?
+ */
+ case DB_UNLOCK_NOTIFY: {
+#ifndef SQLITE_ENABLE_UNLOCK_NOTIFY
+ Tcl_AppendResult(interp, "unlock_notify not available in this build", 0);
+ rc = TCL_ERROR;
+#else
+ if( objc!=2 && objc!=3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "?SCRIPT?");
+ rc = TCL_ERROR;
+ }else{
+ void (*xNotify)(void **, int) = 0;
+ void *pNotifyArg = 0;
+
+ if( pDb->pUnlockNotify ){
+ Tcl_DecrRefCount(pDb->pUnlockNotify);
+ pDb->pUnlockNotify = 0;
+ }
+
+ if( objc==3 ){
+ xNotify = DbUnlockNotify;
+ pNotifyArg = (void *)pDb;
+ pDb->pUnlockNotify = objv[2];
+ Tcl_IncrRefCount(pDb->pUnlockNotify);
+ }
+
+ if( sqlite3_unlock_notify(pDb->db, xNotify, pNotifyArg) ){
+ Tcl_AppendResult(interp, sqlite3_errmsg(pDb->db), 0);
+ rc = TCL_ERROR;
+ }
+ }
+#endif
+ break;
+ }
+
/*
** $db update_hook ?script?
** $db rollback_hook ?script?
** is not included in the SQLite library. It is used for automated
** testing of the SQLite library.
**
-** $Id: test1.c,v 1.347 2009/02/03 16:51:25 danielk1977 Exp $
+** $Id: test1.c,v 1.348 2009/03/16 13:19:36 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "tcl.h"
case SQLITE_ABORT: zName = "SQLITE_ABORT"; break;
case SQLITE_BUSY: zName = "SQLITE_BUSY"; break;
case SQLITE_LOCKED: zName = "SQLITE_LOCKED"; break;
+ case SQLITE_LOCKED_SHAREDCACHE: zName = "SQLITE_LOCKED_SHAREDCACHE";break;
case SQLITE_NOMEM: zName = "SQLITE_NOMEM"; break;
case SQLITE_READONLY: zName = "SQLITE_READONLY"; break;
case SQLITE_INTERRUPT: zName = "SQLITE_INTERRUPT"; break;
return TCL_OK;
}
+static int test_sql(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ sqlite3_stmt *pStmt;
+
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "STMT");
+ return TCL_ERROR;
+ }
+
+ if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+ Tcl_SetResult(interp, (char *)sqlite3_sql(pStmt), TCL_VOLATILE);
+ return TCL_OK;
+}
+
/*
** Usage: sqlite3_column_count STMT
**
return TCL_OK;
}
+static void test_unlock_notify_cb(void **aArg, int nArg){
+ int ii;
+ for(ii=0; ii<nArg; ii++){
+ Tcl_EvalEx((Tcl_Interp *)aArg[ii], "unlock_notify", -1, TCL_EVAL_GLOBAL);
+ }
+}
+
+/*
+** tclcmd: sqlite3_unlock_notify db
+*/
+#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
+static int test_unlock_notify(
+ ClientData clientData, /* Unused */
+ Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
+ int objc, /* Number of arguments */
+ Tcl_Obj *CONST objv[] /* Command arguments */
+){
+ sqlite3 *db;
+ int rc;
+
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "DB");
+ return TCL_ERROR;
+ }
+
+ if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
+ return TCL_ERROR;
+ }
+ rc = sqlite3_unlock_notify(db, test_unlock_notify_cb, (void *)interp);
+ Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
+ return TCL_OK;
+}
+#endif
+
/*
** Register commands with the TCL interpreter.
{ "sqlite3_transfer_bindings", test_transfer_bind ,0 },
{ "sqlite3_changes", test_changes ,0 },
{ "sqlite3_step", test_step ,0 },
+ { "sqlite3_sql", test_sql ,0 },
{ "sqlite3_next_stmt", test_next_stmt ,0 },
{ "sqlite3_release_memory", test_release_memory, 0},
{ "sqlite3_blob_write", test_blob_write, 0 },
#endif
{ "pcache_stats", test_pcache_stats, 0 },
+#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
+ { "sqlite3_unlock_notify", test_unlock_notify, 0 },
+#endif
};
static int bitmask_size = sizeof(Bitmask)*8;
int i;
** The focus of this file is providing the TCL testing layer
** access to compile-time constants.
**
-** $Id: test_config.c,v 1.47 2009/01/12 14:01:45 danielk1977 Exp $
+** $Id: test_config.c,v 1.48 2009/03/16 13:19:36 danielk1977 Exp $
*/
#include "sqliteLimit.h"
Tcl_SetVar2(interp, "sqlite_options", "update_delete_limit", "0", TCL_GLOBAL_ONLY);
#endif
+#if defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
+ Tcl_SetVar2(interp, "sqlite_options", "unlock_notify", "1", TCL_GLOBAL_ONLY);
+#else
+ Tcl_SetVar2(interp, "sqlite_options", "unlock_notify", "0", TCL_GLOBAL_ONLY);
+#endif
+
#ifdef SQLITE_SECURE_DELETE
Tcl_SetVar2(interp, "sqlite_options", "secure_delete", "1", TCL_GLOBAL_ONLY);
#else
** test that sqlite3 database handles may be concurrently accessed by
** multiple threads. Right now this only works on unix.
**
-** $Id: test_thread.c,v 1.10 2009/02/03 19:55:20 shane Exp $
+** $Id: test_thread.c,v 1.11 2009/03/16 13:19:36 danielk1977 Exp $
*/
#include "sqliteInt.h"
static Tcl_ObjCmdProc sqlthread_proc;
static Tcl_ObjCmdProc clock_seconds_proc;
+static Tcl_ObjCmdProc blocking_step_proc;
int Sqlitetest1_Init(Tcl_Interp *);
/*
interp = Tcl_CreateInterp();
Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0);
Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, pSqlThread, 0);
+#if defined(OS_UNIX) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
+ Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0);
+#endif
Sqlitetest1_Init(interp);
Sqlitetest_mutex_Init(interp);
return TCL_OK;
}
+/*************************************************************************
+** This block contains the implementation of the [sqlite3_blocking_step]
+** command available to threads created by [sqlthread spawn] commands. It
+** is only available on UNIX for now. This is because pthread condition
+** variables are used.
+**
+** The source code for the C functions sqlite3_blocking_step(),
+** blocking_step_notify() and the structure UnlockNotification is
+** automatically extracted from this file and used as part of the
+** documentation for the sqlite3_unlock_notify() API function. This
+** should be considered if these functions are to be extended (i.e. to
+** support windows) in the future.
+*/
+#if defined(OS_UNIX) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
+
+/* BEGIN_SQLITE_BLOCKING_STEP */
+/* This example uses the pthreads API */
+#include <pthread.h>
+
+/*
+** A pointer to an instance of this structure is passed as the user-context
+** pointer when registering for an unlock-notify callback.
+*/
+typedef struct UnlockNotification UnlockNotification;
+struct UnlockNotification {
+ int fired; /* True after unlock event has occured */
+ pthread_cond_t cond; /* Condition variable to wait on */
+ pthread_mutex_t mutex; /* Mutex to protect structure */
+};
+
+/*
+** This function is an unlock-notify callback registered with SQLite.
+*/
+static void blocking_step_notify(void **apArg, int nArg){
+ int i;
+ for(i=0; i<nArg; i++){
+ UnlockNotification *p = (UnlockNotification *)apArg[i];
+ pthread_mutex_lock(&p->mutex);
+ p->fired = 1;
+ pthread_cond_signal(&p->cond);
+ pthread_mutex_unlock(&p->mutex);
+ }
+}
+
+/*
+** This function is a wrapper around the SQLite function sqlite3_step().
+** It functions in the same way as step(), except that if a required
+** shared-cache lock cannot be obtained, this function may block waiting for
+** the lock to become available. In this scenario the normal API step()
+** function always returns SQLITE_LOCKED.
+**
+** If this function returns SQLITE_LOCKED, the caller should rollback
+** the current transaction (if any) and try again later. Otherwise, the
+** system may become deadlocked.
+*/
+int sqlite3_blocking_step(sqlite3_stmt *pStmt){
+ int rc = SQLITE_OK;
+
+ while( rc==SQLITE_OK && SQLITE_LOCKED==(rc = sqlite3_step(pStmt)) ){
+ sqlite3 *db = sqlite3_db_handle(pStmt);
+ UnlockNotification un;
+
+ /* Initialize the UnlockNotification structure. */
+ un.fired = 0;
+ pthread_mutex_init(&un.mutex, 0);
+ pthread_cond_init(&un.cond, 0);
+
+ rc = sqlite3_unlock_notify(db, blocking_step_notify, (void *)&un);
+ assert( rc==SQLITE_LOCKED || rc==SQLITE_OK );
+
+ /* The call to sqlite3_unlock_notify() always returns either
+ ** SQLITE_LOCKED or SQLITE_OK.
+ **
+ ** If SQLITE_LOCKED was returned, then the system is deadlocked. In this
+ ** case this function needs to return SQLITE_LOCKED to the caller so
+ ** that it can roll back the current transaction. Simply leaving rc
+ ** as it is is enough to accomplish that, as the next test of the
+ ** while() condition above will fail and the current value of rc
+ ** (SQLITE_LOCKED) will be returned to the caller. sqlite3_reset() is
+ ** not called on the statement handle, so the caller can still use either
+ ** sqlite3_finalize() or reset() to collect the statement's error code
+ ** after this function returns.
+ **
+ ** Otherwise, if SQLITE_OK was returned, do two things:
+ **
+ ** 1) Reset the SQL statement.
+ ** 2) Block until the unlock-notify callback is invoked.
+ */
+ if( rc==SQLITE_OK ){
+ sqlite3_reset(pStmt);
+ pthread_mutex_lock(&un.mutex);
+ if( !un.fired ){
+ pthread_cond_wait(&un.cond, &un.mutex);
+ }
+ pthread_mutex_unlock(&un.mutex);
+ }
+
+ /* Destroy the mutex and condition variables created at the top of
+ ** the while loop. */
+ pthread_cond_destroy(&un.cond);
+ pthread_mutex_destroy(&un.mutex);
+ }
+
+ return rc;
+}
+/* END_SQLITE_BLOCKING_STEP */
+
+/*
+** Usage: sqlite3_blocking_step STMT
+**
+** Advance the statement to the next row.
+*/
+static int blocking_step_proc(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ /* Functions from test1.c */
+ void *sqlite3TestTextToPtr(const char *);
+ const char *sqlite3TestErrorName(int);
+
+ sqlite3_stmt *pStmt;
+ int rc;
+
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "STMT");
+ return TCL_ERROR;
+ }
+
+ pStmt = (sqlite3_stmt*)sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
+ rc = sqlite3_blocking_step(pStmt);
+
+ Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), 0);
+ return TCL_OK;
+}
+
+#endif
+/*
+** End of implementation of [sqlite3_blocking_step].
+************************************************************************/
+
/*
** Register commands with the TCL interpreter.
*/
int SqlitetestThread_Init(Tcl_Interp *interp){
Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, 0, 0);
Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0);
+#if defined(OS_UNIX) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
+ Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0);
+#endif
return TCL_OK;
}
#else
** in this file for details. If in doubt, do not deviate from existing
** commenting and indentation practices when changing or adding code.
**
-** $Id: vdbe.c,v 1.824 2009/03/05 04:20:32 shane Exp $
+** $Id: vdbe.c,v 1.825 2009/03/16 13:19:36 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "vdbeInt.h"
assert( (p->btreeMask & (1<<p1))!=0 );
assert( isWriteLock==0 || isWriteLock==1 );
rc = sqlite3BtreeLockTable(db->aDb[p1].pBt, pOp->p2, isWriteLock);
- if( rc==SQLITE_LOCKED ){
+ if( (rc&0xFF)==SQLITE_LOCKED ){
const char *z = pOp->p4.z;
sqlite3SetString(&p->zErrMsg, db, "database table is locked: %s", z);
}
** xBegin method for that table.
**
** Also, whether or not P4 is set, check that this is not being called from
-** within a callback to a virtual table xSync() method. If it is, set the
-** error code to SQLITE_LOCKED.
+** within a callback to a virtual table xSync() method. If it is, the error
+** code will be set to SQLITE_LOCKED.
*/
case OP_VBegin: {
sqlite3_vtab *pVtab = pOp->p4.pVtab;
** to version 2.8.7, all this code was combined into the vdbe.c source file.
** But that file was getting too big so this subroutines were split out.
**
-** $Id: vdbeaux.c,v 1.441 2009/03/05 04:20:32 shane Exp $
+** $Id: vdbeaux.c,v 1.442 2009/03/16 13:19:36 danielk1977 Exp $
*/
#include "sqliteInt.h"
#include "vdbeInt.h"
p->rc = SQLITE_NOMEM;
}
+ /* If the auto-commit flag is set to true, then any locks that were held
+ ** by connection db have now been released. Call sqlite3ConnectionUnlocked()
+ ** to invoke any required unlock-notify callbacks.
+ */
+ if( db->autoCommit ){
+ sqlite3ConnectionUnlocked(db);
+ }
+
return SQLITE_OK;
}
*************************************************************************
** This file contains code used to help implement virtual tables.
**
-** $Id: vtab.c,v 1.81 2008/12/10 19:26:24 drh Exp $
+** $Id: vtab.c,v 1.82 2009/03/16 13:19:36 danielk1977 Exp $
*/
#ifndef SQLITE_OMIT_VIRTUALTABLE
#include "sqliteInt.h"
/* Special case: If db->aVTrans is NULL and db->nVTrans is greater
** than zero, then this function is being called from within a
** virtual module xSync() callback. It is illegal to write to
- ** virtual module tables in this case, so return SQLITE_LOCKED.
+ ** virtual module tables in this case, so return SQLITE_MISUSE.
*/
if( sqlite3VtabInSync(db) ){
return SQLITE_LOCKED;
# This file implements regression tests for SQLite library. The
# focus of this file is testing the sqlite3_backup_XXX API.
#
-# $Id: backup.test,v 1.8 2009/02/12 17:01:50 danielk1977 Exp $
+# $Id: backup.test,v 1.9 2009/03/16 13:19:36 danielk1977 Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
} {}
do_test backup-7.2.2 {
B step 5000
-} {SQLITE_LOCKED}
+} {SQLITE_BUSY}
do_test backup-7.2.3 {
execsql { ROLLBACK }
B step 5000
# Test that it is possible to have two open blob handles on a single
# blob object.
#
-# $Id: incrblob2.test,v 1.9 2008/10/03 08:44:54 danielk1977 Exp $
+# $Id: incrblob2.test,v 1.10 2009/03/16 13:19:36 danielk1977 Exp $
#
set testdir [file dirname $argv0]
do_test incrblob2-5.3 {
set blob [db incrblob t1 data 1]
catchsql { INSERT INTO t1 VALUES(3, 'klmno') } db2
- } {1 {database is locked}}
+ } {1 {database table is locked}}
do_test incrblob2-5.4 {
close $blob
--- /dev/null
+# 2009 March 04
+#
+# 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.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this file is testing the sqlite3_unlock_notify() API.
+#
+# $Id: notify1.test,v 1.1 2009/03/16 13:19:36 danielk1977 Exp $
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+ifcapable !unlock_notify||!shared_cache {
+ finish_test
+ return
+}
+db close
+set ::enable_shared_cache [sqlite3_enable_shared_cache 1]
+
+#-------------------------------------------------------------------------
+# Warm body test. Test that an unlock-notify callback can be registered
+# and that it is invoked.
+#
+do_test notify1-1.1 {
+ sqlite3 db test.db
+ sqlite3 db2 test.db
+ execsql { CREATE TABLE t1(a, b) }
+} {}
+do_test notify1-1.2 {
+ execsql {
+ BEGIN;
+ INSERT INTO t1 VALUES(1, 2);
+ }
+ catchsql { INSERT INTO t1 VALUES(3, 4) } db2
+} {1 {database table is locked}}
+do_test notify1-1.3 {
+ set zScript ""
+ db2 unlock_notify {
+ set zScript "db2 eval { INSERT INTO t1 VALUES(3, 4) }"
+ }
+ execsql { SELECT * FROM t1 }
+} {1 2}
+do_test notify1-1.4 {
+ set zScript
+} {}
+do_test notify1-1.5 {
+ execsql { COMMIT }
+ eval $zScript
+ execsql { SELECT * FROM t1 }
+} {1 2 3 4}
+
+#-------------------------------------------------------------------------
+# The following tests, notify1-2.*, test that deadlock is detected
+# correctly.
+#
+do_test notify1-2.1 {
+ execsql {
+ CREATE TABLE t2(a, b);
+ INSERT INTO t2 VALUES('I', 'II');
+ }
+} {}
+
+#
+# Test for simple deadlock involving two database connections.
+#
+# 1. Grab a write-lock on t1 with [db]. Then grab a read-lock on t2 with [db2].
+# 2. Try to grab a read-lock on t1 with [db2] (fails).
+# 3. Have [db2] wait on the read-lock it failed to obtain in step 2.
+# 4. Try to grab a write-lock on t2 with [db] (fails).
+# 5. Try to have [db] wait on the lock from step 4. Fails, as the system
+# would be deadlocked (since [db2] is already waiting on [db], and this
+# operation would have [db] wait on [db2]).
+#
+do_test notify1-2.2.1 {
+ execsql {
+ BEGIN;
+ INSERT INTO t1 VALUES(5, 6);
+ }
+ execsql {
+ BEGIN;
+ SELECT * FROM t2;
+ } db2
+} {I II}
+do_test notify1-2.2.2 {
+ catchsql { SELECT * FROM t1 } db2
+} {1 {database table is locked: t1}}
+do_test notify1-2.2.3 {
+ db2 unlock_notify {lappend unlock_notify db2}
+} {}
+do_test notify1-2.2.4 {
+ catchsql { INSERT INTO t2 VALUES('III', 'IV') }
+} {1 {database table is locked: t2}}
+do_test notify1-2.2.5 {
+ set rc [catch { db unlock_notify {lappend unlock_notify db} } msg]
+ list $rc $msg
+} {1 {database is deadlocked}}
+
+#
+# Test for slightly more complex deadlock involving three database
+# connections: db, db2 and db3.
+#
+do_test notify1-2.3.1 {
+ db close
+ db2 close
+ file delete -force test.db test2.db test3.db
+ foreach con {db db2 db3} {
+ sqlite3 $con test.db
+ $con eval { ATTACH 'test2.db' AS aux2 }
+ $con eval { ATTACH 'test3.db' AS aux3 }
+ }
+ execsql {
+ CREATE TABLE main.t1(a, b);
+ CREATE TABLE aux2.t2(a, b);
+ CREATE TABLE aux3.t3(a, b);
+ }
+} {}
+do_test notify1-2.3.2 {
+ execsql { BEGIN ; INSERT INTO t1 VALUES(1, 2) } db
+ execsql { BEGIN ; INSERT INTO t2 VALUES(1, 2) } db2
+ execsql { BEGIN ; INSERT INTO t3 VALUES(1, 2) } db3
+} {}
+do_test notify1-2.3.3 {
+ catchsql { SELECT * FROM t2 } db
+} {1 {database table is locked: t2}}
+do_test notify1-2.3.4 {
+ catchsql { SELECT * FROM t3 } db2
+} {1 {database table is locked: t3}}
+do_test notify1-2.3.5 {
+ catchsql { SELECT * FROM t1 } db3
+} {1 {database table is locked: t1}}
+do_test notify1-2.3.6 {
+ set lUnlock [list]
+ db unlock_notify {lappend lUnlock db}
+ db2 unlock_notify {lappend lUnlock db2}
+} {}
+do_test notify1-2.3.7 {
+ set rc [catch { db3 unlock_notify {lappend lUnlock db3} } msg]
+ list $rc $msg
+} {1 {database is deadlocked}}
+do_test notify1-2.3.8 {
+ execsql { COMMIT }
+ set lUnlock
+} {}
+do_test notify1-2.3.9 {
+ db3 unlock_notify {lappend lUnlock db3}
+ set lUnlock
+} {db3}
+do_test notify1-2.3.10 {
+ execsql { COMMIT } db2
+ set lUnlock
+} {db3 db}
+do_test notify1-2.3.11 {
+ execsql { COMMIT } db3
+ set lUnlock
+} {db3 db db2}
+catch { db3 close }
+catch { db2 close }
+catch { db close }
+
+#-------------------------------------------------------------------------
+# The following tests, notify1-3.* and notify1-4.*, test that callbacks
+# can be issued when there are many (>16) connections waiting on a single
+# unlock event.
+#
+foreach {tn nConn} {3 20 4 76} {
+ do_test notify1-$tn.1 {
+ sqlite3 db test.db
+ execsql {
+ BEGIN;
+ INSERT INTO t1 VALUES('a', 'b');
+ }
+ } {}
+ set lUnlock [list]
+ set lUnlockFinal [list]
+ for {set ii 1} {$ii <= $nConn} {incr ii} {
+ do_test notify1-$tn.2.$ii.1 {
+ set cmd "db$ii"
+ sqlite3 $cmd test.db
+ catchsql { SELECT * FROM t1 } $cmd
+ } {1 {database table is locked: t1}}
+ do_test notify1-$tn.2.$ii.2 {
+ $cmd unlock_notify "lappend lUnlock $ii"
+ } {}
+ lappend lUnlockFinal $ii
+ }
+ do_test notify1-$tn.3 {
+ set lUnlock
+ } {}
+ do_test notify1-$tn.4 {
+ execsql {COMMIT}
+ lsort -integer $lUnlock
+ } $lUnlockFinal
+ do_test notify1-$tn.5 {
+ for {set ii 1} {$ii <= $nConn} {incr ii} {
+ "db$ii" close
+ }
+ } {}
+}
+db close
+
+#-------------------------------------------------------------------------
+# These tests, notify1-5.*, test that a malloc() failure that occurs while
+# allocating an array to use as an argument to an unlock-notify callback
+# is handled correctly.
+#
+source $testdir/malloc_common.tcl
+breakpoint
+do_malloc_test notify1-5 -tclprep {
+ set ::lUnlock [list]
+ execsql {
+ CREATE TABLE t1(a, b);
+ BEGIN;
+ INSERT INTO t1 VALUES('a', 'b');
+ }
+ for {set ii 1} {$ii <= 60} {incr ii} {
+ set cmd "db$ii"
+ sqlite3 $cmd test.db
+ catchsql { SELECT * FROM t1 } $cmd
+ $cmd unlock_notify "lappend ::lUnlock $ii"
+ }
+} -sqlbody {
+ COMMIT;
+} -cleanup {
+ # One of two things should have happened:
+ #
+ # 1) The transaction opened by [db] was not committed. No unlock-notify
+ # callbacks were invoked, OR
+ # 2) The transaction opened by [db] was committed and 60 unlock-notify
+ # callbacks were invoked.
+ #
+ do_test notify1-5.systemstate {
+ expr { ([llength $::lUnlock]==0 && [sqlite3_get_autocommit db]==0)
+ || ([llength $::lUnlock]==60 && [sqlite3_get_autocommit db]==1)
+ }
+ } {1}
+ for {set ii 1} {$ii <= 60} {incr ii} { "db$ii" close }
+}
+
+#-------------------------------------------------------------------------
+# Test cases notify1-6.* test cases where the following occur:
+#
+# notify1-6.1.*: Test encountering an SQLITE_LOCKED error when the
+# "blocking connection" has already been set by a previous
+# SQLITE_LOCKED.
+#
+# notify1-6.2.*: Test encountering an SQLITE_LOCKED error when already
+# waiting on an unlock-notify callback.
+#
+# notify1-6.3.*: Test that if an SQLITE_LOCKED error is encountered while
+# already waiting on an unlock-notify callback, and then
+# the blocker that caused the SQLITE_LOCKED commits its
+# transaction, the unlock-notify callback is not invoked.
+#
+# notify1-6.4.*: Like 6.3.*, except that instead of the second blocker
+# committing its transaction, the first does. The
+# unlock-notify callback is therefore invoked.
+#
+db close
+do_test notify1-6.1.1 {
+ file delete -force test.db test2.db
+ foreach conn {db db2 db3} {
+ sqlite3 $conn test.db
+ execsql { ATTACH 'test2.db' AS two } $conn
+ }
+ execsql {
+ CREATE TABLE t1(a, b);
+ CREATE TABLE two.t2(a, b);
+ }
+ execsql {
+ BEGIN;
+ INSERT INTO t1 VALUES(1, 2);
+ } db2
+ execsql {
+ BEGIN;
+ INSERT INTO t2 VALUES(1, 2);
+ } db3
+} {}
+do_test notify1-6.1.2 {
+ catchsql { SELECT * FROM t2 }
+} {1 {database table is locked: t2}}
+do_test notify1-6.1.3 {
+ catchsql { SELECT * FROM t1 }
+} {1 {database table is locked: t1}}
+
+do_test notify1-6.2.1 {
+ set unlocked 0
+ db unlock_notify {set unlocked 1}
+ set unlocked
+} {0}
+do_test notify1-6.2.2 {
+ catchsql { SELECT * FROM t2 }
+} {1 {database table is locked: t2}}
+do_test notify1-6.2.3 {
+ execsql { COMMIT } db2
+ set unlocked
+} {1}
+
+do_test notify1-6.3.1 {
+ execsql {
+ BEGIN;
+ INSERT INTO t1 VALUES(3, 4);
+ } db2
+} {}
+do_test notify1-6.3.2 {
+ catchsql { SELECT * FROM t1 }
+} {1 {database table is locked: t1}}
+do_test notify1-6.3.3 {
+ set unlocked 0
+ db unlock_notify {set unlocked 1}
+ set unlocked
+} {0}
+do_test notify1-6.3.4 {
+ catchsql { SELECT * FROM t2 }
+} {1 {database table is locked: t2}}
+do_test notify1-6.3.5 {
+ execsql { COMMIT } db3
+ set unlocked
+} {0}
+
+do_test notify1-6.4.1 {
+ execsql {
+ BEGIN;
+ INSERT INTO t2 VALUES(3, 4);
+ } db3
+ catchsql { SELECT * FROM t2 }
+} {1 {database table is locked: t2}}
+do_test notify1-6.4.2 {
+ execsql { COMMIT } db2
+ set unlocked
+} {1}
+do_test notify1-6.4.3 {
+ execsql { COMMIT } db3
+} {}
+db close
+db2 close
+db3 close
+
+#-------------------------------------------------------------------------
+# Test cases notify1-7.* tests that when more than one distinct
+# unlock-notify function is registered, all are invoked correctly.
+#
+proc unlock_notify {} {
+ incr ::unlock_notify
+}
+do_test notify1-7.1 {
+ foreach conn {db db2 db3} {
+ sqlite3 $conn test.db
+ }
+ execsql {
+ BEGIN;
+ INSERT INTO t1 VALUES(5, 6);
+ }
+} {}
+do_test notify1-7.2 {
+ catchsql { SELECT * FROM t1 } db2
+} {1 {database table is locked: t1}}
+do_test notify1-7.3 {
+ catchsql { SELECT * FROM t1 } db3
+} {1 {database table is locked: t1}}
+do_test notify1-7.4 {
+ set unlock_notify 0
+ db2 unlock_notify unlock_notify
+ sqlite3_unlock_notify db3
+} {SQLITE_OK}
+do_test notify1-7.5 {
+ set unlock_notify
+} {0}
+do_test notify1-7.6 {
+ execsql { COMMIT }
+ set unlock_notify
+} {2}
+
+#-------------------------------------------------------------------------
+# Test cases notify1-8.* tests that the correct SQLITE_LOCKED extended
+# error code is returned in various scenarios.
+#
+do_test notify1-8.1 {
+ execsql {
+ BEGIN;
+ INSERT INTO t1 VALUES(7, 8);
+ }
+ sqlite3_extended_result_codes db2 1
+ catchsql { SELECT * FROM t1 } db2
+} {1 {database table is locked: t1}}
+do_test notify1-8.2 {
+ sqlite3_extended_errcode db2
+} {SQLITE_LOCKED_SHAREDCACHE}
+
+do_test notify1-8.3 {
+ execsql {
+ COMMIT;
+ BEGIN EXCLUSIVE;
+ }
+ catchsql { SELECT * FROM t1 } db2
+} {1 {database schema is locked: main}}
+do_test notify1-8.4 {
+ sqlite3_extended_errcode db2
+} {SQLITE_LOCKED_SHAREDCACHE}
+
+do_test notify1-8.X {
+ execsql { COMMIT }
+} {}
+
+#-------------------------------------------------------------------------
+# Test cases notify1-9.* test the shared-cache 'pending-lock' feature.
+#
+do_test notify1-9.1 {
+ execsql {
+ CREATE TABLE t2(a, b);
+ BEGIN;
+ SELECT * FROM t1;
+ } db2
+} {1 2 3 4 5 6 7 8}
+do_test notify1-9.2 {
+ execsql { SELECT * FROM t1 } db3
+} {1 2 3 4 5 6 7 8}
+do_test notify1-9.3 {
+ catchsql {
+ BEGIN;
+ INSERT INTO t1 VALUES(9, 10);
+ }
+} {1 {database table is locked: t1}}
+do_test notify1-9.4 {
+ catchsql { SELECT * FROM t2 } db3
+} {1 {database table is locked}}
+do_test notify1-9.5 {
+ execsql { COMMIT } db2
+ execsql { SELECT * FROM t2 } db3
+} {}
+do_test notify1-9.6 {
+ execsql { COMMIT }
+} {}
+
+do_test notify1-9.7 {
+ execsql {
+ BEGIN;
+ SELECT * FROM t1;
+ } db2
+} {1 2 3 4 5 6 7 8}
+do_test notify1-9.8 {
+ execsql { SELECT * FROM t1 } db3
+} {1 2 3 4 5 6 7 8}
+do_test notify1-9.9 {
+ catchsql {
+ BEGIN;
+ INSERT INTO t1 VALUES(9, 10);
+ }
+} {1 {database table is locked: t1}}
+do_test notify1-9.10 {
+ catchsql { SELECT * FROM t2 } db3
+} {1 {database table is locked}}
+do_test notify1-9.11 {
+ execsql { COMMIT }
+ execsql { SELECT * FROM t2 } db3
+} {}
+do_test notify1-9.12 {
+ execsql { COMMIT } db2
+} {}
+
+db close
+db2 close
+db3 close
+sqlite3_enable_shared_cache $::enable_shared_cache
+finish_test
--- /dev/null
+# 2009 March 04
+#
+# 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: notify2.test,v 1.1 2009/03/16 13:19:36 danielk1977 Exp $
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+# The tests in this file test the sqlite3_blocking_step() function in
+# test_thread.c. sqlite3_blocking_step() is not an SQLite API function,
+# it is just a demonstration of how the sqlite3_unlock_notify() function
+# can be used to synchronize multi-threaded access to SQLite databases
+# in shared-cache mode.
+#
+# Since the implementation of sqlite3_blocking_step() is included on the
+# website as example code, it is important to test that it works.
+#
+# notify2-1.*:
+#
+# This test uses $nThread threads. Each thread opens the main database
+# and attaches two other databases. Each database contains a single table.
+#
+# Each thread repeats transactions over and over for 20 seconds. Each
+# transaction consists of 3 operations. Each operation is either a read
+# or a write of one of the tables. The read operations verify an invariant
+# to make sure that things are working as expected. If an SQLITE_LOCKED
+# error is returned the current transaction is rolled back immediately.
+#
+# This exercise is repeated twice, once using sqlite3_step(), and the
+# other using sqlite3_blocking_step(). The results are compared to ensure
+# that sqlite3_blocking_step() resulted in higher transaction throughput.
+#
+
+if {[info commands sqlite3_blocking_step] eq ""} {
+ finish_test
+ return
+}
+db close
+set ::enable_shared_cache [sqlite3_enable_shared_cache 1]
+source $testdir/thread_common.tcl
+
+# Number of threads to run simultaneously.
+#
+set nThread 3
+set nSecond 5
+
+# The Tcl script executed by each of the $nThread threads used by this test.
+#
+set ThreadProgram {
+
+ # Proc used by threads to execute SQL.
+ #
+ proc execsql_blocking {db zSql} {
+ 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 [$::xStep $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
+ }
+
+ proc select_one {args} {
+ set n [llength $args]
+ lindex $args [expr int($n*rand())]
+ }
+
+ # Open a database connection. Attach the two auxillary databases.
+ set ::DB [sqlite3_open test.db]
+ execsql_blocking $::DB {
+ ATTACH 'test2.db' AS aux2;
+ ATTACH 'test3.db' AS aux3;
+ }
+
+ # This loop runs for ~20 seconds.
+ #
+ set iStart [clock_seconds]
+ while { ([clock_seconds]-$iStart) < $nSecond } {
+
+ # Each transaction does 3 operations. Each operation is either a read
+ # or write of a randomly selected table (t1, t2 or t3). Set the variables
+ # $SQL(1), $SQL(2) and $SQL(3) to the SQL commands used to implement
+ # each operation.
+ #
+ for {set ii 1} {$ii <= 3} {incr ii} {
+ set SQL($ii) [string map [list xxx [select_one t1 t2 t3]] [select_one {
+ SELECT
+ (SELECT b FROM xxx WHERE a=(SELECT max(a) FROM xxx))==total(a)
+ FROM xxx WHERE a!=(SELECT max(a) FROM xxx);
+ } {
+ DELETE FROM xxx WHERE a<(SELECT max(a)-100 FROM xxx);
+ INSERT INTO xxx SELECT NULL, total(a) FROM xxx;
+ }]]
+ }
+
+ # Execute the SQL transaction.
+ #
+ set rc [catch { execsql_blocking $::DB "
+ BEGIN;
+ $SQL(1);
+ $SQL(2);
+ $SQL(3);
+ COMMIT;
+ "
+ } msg]
+
+ if {$rc && [string match "SQLITE_LOCKED*" $msg]} {
+ # Hit an SQLITE_LOCKED error. Rollback the current transaction.
+ execsql_blocking $::DB ROLLBACK
+ } elseif {$rc} {
+ # Hit some other kind of error. This is a malfunction.
+ error $msg
+ } else {
+ # No error occured. Check that any SELECT statements in the transaction
+ # returned "1". Otherwise, the invariant was false, indicating that
+ # some malfunction has occured.
+ foreach r $msg { if {$r != 1} { puts "Invariant check failed: $msg" } }
+ }
+ }
+
+ # Close the database connection and return 0.
+ #
+ sqlite3_close $::DB
+ expr 0
+}
+
+foreach {iTest xStep} {1 sqlite3_blocking_step 2 sqlite3_step} {
+ file delete -force test.db test2.db test3.db
+
+ set ThreadSetup "set xStep $xStep ; set nSecond $nSecond"
+
+ # Set up the database schema used by this test. Each thread opens file
+ # test.db as the main database, then attaches files test2.db and test3.db
+ # as auxillary databases. Each file contains a single table (t1, t2 and t3, in
+ # files test.db, test2.db and test3.db, respectively).
+ #
+ do_test notify2-$iTest.1.1 {
+ sqlite3 db test.db
+ execsql {
+ ATTACH 'test2.db' AS aux2;
+ ATTACH 'test3.db' AS aux3;
+ CREATE TABLE main.t1(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE aux2.t2(a INTEGER PRIMARY KEY, b);
+ CREATE TABLE aux3.t3(a INTEGER PRIMARY KEY, b);
+ INSERT INTO t1 SELECT NULL, 0;
+ INSERT INTO t2 SELECT NULL, 0;
+ INSERT INTO t3 SELECT NULL, 0;
+ }
+ } {}
+ do_test notify2-$iTest.1.2 {
+ db close
+ } {}
+
+
+ # Launch $nThread threads. Then wait for them to finish.
+ #
+ puts "Running $xStep test for $nSecond seconds"
+ unset -nocomplain finished
+ for {set ii 0} {$ii < $nThread} {incr ii} {
+ thread_spawn finished($ii) $ThreadSetup $ThreadProgram
+ }
+ for {set ii 0} {$ii < $nThread} {incr ii} {
+ do_test notify2-$iTest.2.$ii {
+ if {![info exists finished($ii)]} { vwait finished($ii) }
+ set finished($ii)
+ } {0}
+ }
+
+ # Count the total number of succesful writes.
+ do_test notify2-$iTest.3.1 {
+ sqlite3 db test.db
+ execsql {
+ ATTACH 'test2.db' AS aux2;
+ ATTACH 'test3.db' AS aux3;
+ }
+ set anWrite($xStep) [execsql {
+ SELECT (SELECT max(a) FROM t1)
+ + (SELECT max(a) FROM t2)
+ + (SELECT max(a) FROM t3)
+ }]
+ db close
+ } {}
+}
+
+do_test notify2-3 {
+ expr {$anWrite(sqlite3_blocking_step) > $anWrite(sqlite3_step)}
+} {1}
+
+sqlite3_enable_shared_cache $::enable_shared_cache
+finish_test
+
#
#***********************************************************************
#
-# $Id: shared.test,v 1.35 2008/11/21 00:10:35 aswift Exp $
+# $Id: shared.test,v 1.36 2009/03/16 13:19:36 danielk1977 Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
} {0 {}}
do_test shared-$av.11.5 {
catchsql {INSERT INTO abc2 VALUES(1, 2, 3);} db2
-} {1 {database is locked}}
+} {1 {database table is locked}}
do_test shared-$av.11.6 {
catchsql {SELECT * FROM abc2}
} {0 {}}
# interface is pretty well tested. This file contains some addition
# tests for fringe issues that the main test suite does not cover.
#
-# $Id: tclsqlite.test,v 1.72 2009/02/04 22:46:47 drh Exp $
+# $Id: tclsqlite.test,v 1.73 2009/03/16 13:19:36 danielk1977 Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
do_test tcl-1.2 {
set v [catch {db bogus} msg]
lappend v $msg
-} {1 {bad option "bogus": must be authorizer, backup, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, enable_load_extension, errorcode, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, profile, progress, rekey, restore, rollback_hook, status, timeout, total_changes, trace, transaction, update_hook, or version}}
+} {1 {bad option "bogus": must be authorizer, backup, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, enable_load_extension, errorcode, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, profile, progress, rekey, restore, rollback_hook, status, timeout, total_changes, trace, transaction, unlock_notify, update_hook, or version}}
do_test tcl-1.2.1 {
set v [catch {db cache bogus} msg]
lappend v $msg
#
#***********************************************************************
#
-# $Id: tkt2854.test,v 1.3 2008/07/12 14:52:21 drh Exp $
+# $Id: tkt2854.test,v 1.4 2009/03/16 13:19:36 danielk1977 Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
} {}
do_test tkt2854-1.3 {
catchsql { BEGIN EXCLUSIVE } db
-} {1 {database is locked}}
+} {1 {database table is locked}}
do_test tkt2854-1.4 {
execsql { SELECT * FROM abc } db3
} {}
} {SQLITE_ERROR SQLITE_LOCKED}
do_test tkt2854-1.12 {
list [sqlite3_step $::STMT2] [sqlite3_finalize $::STMT2]
-} {SQLITE_BUSY SQLITE_BUSY}
+} {SQLITE_ERROR SQLITE_LOCKED}
do_test tkt2854-1.13 {
list [sqlite3_step $::STMT3] [sqlite3_finalize $::STMT3]
-} {SQLITE_BUSY SQLITE_BUSY}
+} {SQLITE_ERROR SQLITE_LOCKED}
do_test tkt2854-1.14 {
# A regular "BEGIN" doesn't touch any databases. So it succeeds.
list [sqlite3_step $::STMT4] [sqlite3_finalize $::STMT4]
do_test tkt2854-1.20 {
execsql {BEGIN IMMEDIATE} db4
catchsql {BEGIN EXCLUSIVE} db
-} {1 {database is locked}}
+} {1 {database table is locked}}
do_test tkt2854-1.21 {
execsql {SELECT * FROM abc} db2
} {}
complete.c
main.c
+ notify.c
fts3.c
fts3_expr.c