mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-30 19:03:16 +03:00
Add the sqlite3_unlock_notify() API. (CVS 6348)
FossilOrigin-Name: b649a6cc5bfefddd6a04b1183647d2923e0a0daa
This commit is contained in:
3
main.mk
3
main.mk
@ -59,7 +59,7 @@ LIBOBJ+= alter.o analyze.o attach.o auth.o \
|
||||
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 \
|
||||
@ -112,6 +112,7 @@ SRC = \
|
||||
$(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 \
|
||||
|
57
manifest
57
manifest
@ -1,5 +1,5 @@
|
||||
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
|
||||
@ -83,7 +83,7 @@ F ext/rtree/tkt3363.test 6662237ea75bb431cd5d262dfc9535e1023315fc
|
||||
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
|
||||
@ -100,12 +100,12 @@ F src/alter.c b95815ccc92477b1b5874fb0fe20f65c867e9cc8
|
||||
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
|
||||
@ -122,7 +122,7 @@ F src/insert.c 71286d081a919a27ef22eaeccbe2718f93dc6aa9
|
||||
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
|
||||
@ -136,6 +136,7 @@ F src/mutex_noop.c f5a07671f25a1a9bd7c10ad7107bc2585446200f
|
||||
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
|
||||
@ -149,21 +150,21 @@ F src/pcache.c fcf7738c83c4d3e9d45836b2334c8a368cc41274
|
||||
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
|
||||
@ -176,7 +177,7 @@ F src/test_async.c 75771172f91735f55ebbd2cbfe4b9804bd030206
|
||||
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
|
||||
@ -191,7 +192,7 @@ F src/test_pcache.c 29464896d9c67832e4eef916c0682b98d7283d00
|
||||
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
|
||||
@ -199,14 +200,14 @@ F src/update.c 8ededddcde6f7b6da981dd0429a5d34518a475b7
|
||||
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
|
||||
@ -232,7 +233,7 @@ F test/autoinc.test ab549b48b389cabd92967b86c379ec8b31fa6c16
|
||||
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
|
||||
@ -391,7 +392,7 @@ F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75
|
||||
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
|
||||
@ -477,6 +478,8 @@ F test/misuse.test 30b3a458e5a70c31e74c291937b6c82204c59f33
|
||||
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
|
||||
@ -524,7 +527,7 @@ F test/selectA.test 06d1032fa9009314c95394f2ca2e60d9f7ae8532
|
||||
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
|
||||
@ -549,7 +552,7 @@ F test/substr.test 18f57c4ca8a598805c4d64e304c418734d843c1a
|
||||
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
|
||||
@ -597,7 +600,7 @@ F test/tkt2817.test 94646b604c7dbae7058782f6582c05e200700aa9
|
||||
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
|
||||
@ -690,7 +693,7 @@ F tool/lempar.c aeba88b8566ff66f8a67c96b3eb2dd95e7d8908d
|
||||
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
|
||||
@ -704,7 +707,7 @@ F tool/speedtest16.c c8a9c793df96db7e4933f0852abb7a03d48f2e81
|
||||
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
|
||||
|
@ -1 +1 @@
|
||||
2fcccca3e56e2e3a95bdedeb01ab7da1b24b7ac2
|
||||
b649a6cc5bfefddd6a04b1183647d2923e0a0daa
|
@ -12,7 +12,7 @@
|
||||
** 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"
|
||||
@ -292,10 +292,10 @@ int sqlite3_backup_step(sqlite3_backup *p, int nPage){
|
||||
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;
|
||||
}
|
||||
|
115
src/btree.c
115
src/btree.c
@ -9,7 +9,7 @@
|
||||
** 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.
|
||||
@ -109,8 +109,9 @@ static int querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock){
|
||||
/* 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
|
||||
@ -137,7 +138,12 @@ static int querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock){
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -233,7 +239,7 @@ static void clearAllSharedCacheTableLocks(Btree *p){
|
||||
|
||||
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);
|
||||
@ -242,8 +248,22 @@ static void clearAllSharedCacheTableLocks(Btree *p){
|
||||
}
|
||||
}
|
||||
|
||||
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 */
|
||||
@ -2052,6 +2072,7 @@ static int newDatabase(BtShared *pBt){
|
||||
** proceed.
|
||||
*/
|
||||
int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
|
||||
sqlite3 *pBlock = 0;
|
||||
BtShared *pBt = p->pBt;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
@ -2073,25 +2094,27 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
|
||||
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;
|
||||
pBlock = pIter->pBtree->db;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( pBlock ){
|
||||
sqlite3ConnectionBlocked(p->db, pBlock);
|
||||
rc = SQLITE_LOCKED_SHAREDCACHE;
|
||||
goto trans_begun;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
do {
|
||||
@ -2129,9 +2152,10 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
|
||||
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
|
||||
}
|
||||
@ -2940,8 +2964,10 @@ static int btreeCursor(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5968,9 +5994,10 @@ static int checkForReadConflicts(
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6007,8 +6034,11 @@ int sqlite3BtreeInsert(
|
||||
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;
|
||||
@ -6104,10 +6134,11 @@ int sqlite3BtreeDelete(BtCursor *pCur){
|
||||
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
|
||||
@ -6538,7 +6569,8 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){
|
||||
** 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);
|
||||
@ -7378,14 +7410,16 @@ void *sqlite3BtreeSchema(Btree *p, int nBytes, void(*xFree)(void *)){
|
||||
}
|
||||
|
||||
/*
|
||||
** 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;
|
||||
}
|
||||
@ -7423,6 +7457,8 @@ int sqlite3BtreeLockTable(Btree *p, int iTab, u8 isWriteLock){
|
||||
** 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);
|
||||
@ -7443,8 +7479,11 @@ int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void *z){
|
||||
}
|
||||
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;
|
||||
|
@ -9,7 +9,7 @@
|
||||
** 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
|
||||
@ -356,6 +356,24 @@ struct Btree {
|
||||
** 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 */
|
||||
@ -385,7 +403,9 @@ 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 */
|
||||
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 */
|
||||
};
|
||||
|
@ -14,7 +14,7 @@
|
||||
** 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"
|
||||
|
||||
@ -629,6 +629,12 @@ int sqlite3_close(sqlite3 *db){
|
||||
}
|
||||
}
|
||||
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++){
|
||||
|
310
src/notify.c
Normal file
310
src/notify.c
Normal file
@ -0,0 +1,310 @@
|
||||
/*
|
||||
** 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
|
||||
|
@ -13,7 +13,7 @@
|
||||
** 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"
|
||||
|
||||
@ -570,10 +570,10 @@ static int sqlite3Prepare(
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
125
src/sqlite.h.in
125
src/sqlite.h.in
@ -30,7 +30,7 @@
|
||||
** 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_
|
||||
@ -383,6 +383,8 @@ int sqlite3_exec(
|
||||
#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>
|
||||
**
|
||||
@ -5349,6 +5351,127 @@ int sqlite3_backup_finish(sqlite3_backup *p);
|
||||
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.
|
||||
|
@ -11,7 +11,7 @@
|
||||
*************************************************************************
|
||||
** 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_
|
||||
@ -808,6 +808,17 @@ struct sqlite3 {
|
||||
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
|
||||
};
|
||||
|
||||
/*
|
||||
@ -2749,6 +2760,17 @@ int sqlite3IsMemJournal(sqlite3_file *);
|
||||
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
|
||||
|
@ -12,7 +12,7 @@
|
||||
** 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>
|
||||
@ -109,6 +109,7 @@ struct SqliteDb {
|
||||
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 */
|
||||
@ -574,6 +575,31 @@ static void DbRollbackHandler(void *clientData){
|
||||
}
|
||||
}
|
||||
|
||||
#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,
|
||||
@ -993,8 +1019,8 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
||||
"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,
|
||||
@ -1007,7 +1033,8 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
||||
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 */
|
||||
|
||||
@ -2450,6 +2477,42 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
|
||||
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?
|
||||
|
59
src/test1.c
59
src/test1.c
@ -13,7 +13,7 @@
|
||||
** 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"
|
||||
@ -125,6 +125,7 @@ const char *sqlite3TestErrorName(int rc){
|
||||
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;
|
||||
@ -3661,6 +3662,24 @@ static int test_step(
|
||||
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
|
||||
**
|
||||
@ -4816,6 +4835,40 @@ static int test_pcache_stats(
|
||||
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.
|
||||
@ -4916,6 +4969,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
|
||||
{ "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},
|
||||
@ -5000,6 +5054,9 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
|
||||
{ "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;
|
||||
|
@ -16,7 +16,7 @@
|
||||
** 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"
|
||||
@ -493,6 +493,12 @@ Tcl_SetVar2(interp, "sqlite_options", "long_double",
|
||||
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
|
||||
|
@ -14,7 +14,7 @@
|
||||
** 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"
|
||||
@ -55,6 +55,7 @@ struct EvalEvent {
|
||||
|
||||
static Tcl_ObjCmdProc sqlthread_proc;
|
||||
static Tcl_ObjCmdProc clock_seconds_proc;
|
||||
static Tcl_ObjCmdProc blocking_step_proc;
|
||||
int Sqlitetest1_Init(Tcl_Interp *);
|
||||
|
||||
/*
|
||||
@ -106,6 +107,9 @@ static Tcl_ThreadCreateType tclScriptThread(ClientData pSqlThread){
|
||||
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);
|
||||
|
||||
@ -359,12 +363,157 @@ static int clock_seconds_proc(
|
||||
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
|
||||
|
@ -43,7 +43,7 @@
|
||||
** 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"
|
||||
@ -4819,7 +4819,7 @@ case OP_TableLock: {
|
||||
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);
|
||||
}
|
||||
@ -4834,8 +4834,8 @@ case OP_TableLock: {
|
||||
** 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;
|
||||
|
@ -14,7 +14,7 @@
|
||||
** 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"
|
||||
@ -1744,6 +1744,14 @@ int sqlite3VdbeHalt(Vdbe *p){
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
*************************************************************************
|
||||
** 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"
|
||||
@ -711,7 +711,7 @@ int sqlite3VtabBegin(sqlite3 *db, sqlite3_vtab *pVtab){
|
||||
/* 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;
|
||||
|
@ -11,7 +11,7 @@
|
||||
# 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
|
||||
@ -736,7 +736,7 @@ do_test backup-7.2.1 {
|
||||
} {}
|
||||
do_test backup-7.2.2 {
|
||||
B step 5000
|
||||
} {SQLITE_LOCKED}
|
||||
} {SQLITE_BUSY}
|
||||
do_test backup-7.2.3 {
|
||||
execsql { ROLLBACK }
|
||||
B step 5000
|
||||
|
@ -12,7 +12,7 @@
|
||||
# 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]
|
||||
@ -265,7 +265,7 @@ ifcapable shared_cache {
|
||||
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
|
||||
|
470
test/notify1.test
Normal file
470
test/notify1.test
Normal file
@ -0,0 +1,470 @@
|
||||
# 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
|
207
test/notify2.test
Normal file
207
test/notify2.test
Normal file
@ -0,0 +1,207 @@
|
||||
# 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
|
||||
|
@ -9,7 +9,7 @@
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# $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
|
||||
@ -875,7 +875,7 @@ do_test shared-$av.11.4 {
|
||||
} {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 {}}
|
||||
|
@ -15,7 +15,7 @@
|
||||
# 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
|
||||
@ -35,7 +35,7 @@ do_test tcl-1.1 {
|
||||
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
|
||||
|
@ -9,7 +9,7 @@
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# $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
|
||||
@ -56,7 +56,7 @@ do_test tkt2854-1.2 {
|
||||
} {}
|
||||
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
|
||||
} {}
|
||||
@ -99,10 +99,10 @@ do_test tkt2854-1.11 {
|
||||
} {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]
|
||||
@ -136,7 +136,7 @@ do_test tkt2854-1.19 {
|
||||
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
|
||||
} {}
|
||||
|
@ -287,6 +287,7 @@ foreach file {
|
||||
complete.c
|
||||
|
||||
main.c
|
||||
notify.c
|
||||
|
||||
fts3.c
|
||||
fts3_expr.c
|
||||
|
Reference in New Issue
Block a user