Compare commits

...

20 Commits

Author SHA1 Message Date
dan 6b0cfe5343 Fix bugs in test program tserver.c.
FossilOrigin-Name: 093b9108ea3532437683547c82f756df2413013a50a2fddad3d928cf8d74f9eb
2017-06-28 20:12:41 +00:00
dan bab38cbc9a Ensure that test tool "tserver" finalizes all statements before attempting to
close a database handle.

FossilOrigin-Name: d8568aacf083b63cdcf68d744ddb07a7615d14f169be6c632813a266a1e8a1cf
2017-06-20 19:20:07 +00:00
dan 538ed8e376 Add too/tserver.c - the implementation of a simple multi-threaded server
designed for interactive testing of concurrency between connections used by
different threads of the same process.

FossilOrigin-Name: 05b4fc4340011847c8585bb822d339dd7b8351266a12b26fdc85edce38fc9dd3
2017-06-07 15:55:59 +00:00
dan b4c4601adf Add code to this branch to emit a log message after each cumulative second
that the WRITER lock has been held.

FossilOrigin-Name: a726d98122704e1e702cfa7ae61d680497df6a826d98082161e0823e115d40a5
2017-05-22 08:01:58 +00:00
dan 60dd40a62c Avoid writer starvation by adding a RESERVED state to page locks.
FossilOrigin-Name: 9b7f80246f2b9921483ab23457865e783ee70b93f67bcecc0c16516447a05875
2017-05-15 19:32:58 +00:00
dan 2ac63b9812 Avoid running recovery while there is another read/write client.
FossilOrigin-Name: a38858a24c6edaa05966b7e158e603bcbde8b05c6233e3bb005cfb32bc91ea06
2017-05-13 19:07:10 +00:00
dan bf431367f3 Require exclusive access to the db to wrap the wal file. Have "PRAGMA
wal_checkpoint = restart" block for this.

FossilOrigin-Name: cbf44ed9758d577e1450b53e645b73c9ca1ee29d2354ce6375c234a41a063400
2017-05-12 18:52:27 +00:00
dan 730859568d Fix a problem causing a lock to be held past the end of a transaction. Use a
blocking lock to take the read-lock on page 1 taken by all transactions.

FossilOrigin-Name: 2584df3d42ece69d37f31f3655cd0d4760914bea73d4f4ccb7f2a7aa47f80f49
2017-05-10 16:18:05 +00:00
dan c9bcad12ea Use a blocking call to obtain the wal-mode WRITER lock in some cases.
FossilOrigin-Name: 4464ca1d686b5c457995cc885d4a8704e402ad387aa4cc37de199276b28cc08e
2017-05-10 13:46:29 +00:00
dan e376cac1d1 Fix a problem with wrapping the log file in server mode.
FossilOrigin-Name: 270b7d1eacb57827465946262b31c5c890d7fc5b5618ccd9e220bc2f9546de54
2017-05-09 16:32:19 +00:00
dan b7bcf5c16c Add some support for wal mode to the hack on this branch.
FossilOrigin-Name: b733afc1d0abc09861903ce8e27a8f2462ec871967f5d3dc2847b31bb28b55f3
2017-05-08 20:15:23 +00:00
dan 31349393ba Update this branch with latest trunk changes.
FossilOrigin-Name: ed6bad67f530afa2fba1bce339a9772f281bd108666ca6c23642efcefa2d98e5
2017-05-06 16:04:58 +00:00
dan 8df9974169 Update this branch with latest trunk changes.
FossilOrigin-Name: 83b1e163ef6ac01d252268415f26a2a999670de583491becc091d294075980bc
2017-05-01 20:07:59 +00:00
dan f427296520 Avoid reading the file-size before page 1 is read-locked.
FossilOrigin-Name: f6b025bf632501f51e691a05d309225671fd78bc62c9a43dd916d35ba4cf6d0e
2017-04-29 15:48:32 +00:00
dan 920a129de3 Fix various bugs revealed by running performance tests.
FossilOrigin-Name: a40b42c422a53aa99280eca7ba419a3937699e273efa3377e0fefe52582cbbd4
2017-04-28 17:32:57 +00:00
dan 34d02325be Add a missing releasePage() call to the server-mode free-list management code.
FossilOrigin-Name: a5a085483c66b17727cf4d2a15165621016031ab78990cb5977ccdbd231d13ac
2017-04-28 14:09:55 +00:00
dan aa189cf00f Use a different free-list format for server-mode databases in order to reduce
contention.

FossilOrigin-Name: 778e8a102d8dc7b0fa006c2d90b0a8cd36ebc1e16bd17477f2d536fc0cef4bf3
2017-04-28 10:20:03 +00:00
dan 2dab053e22 Do not write master journal filenames into server-mode journal files. Use
SQLITE_MUTEX_STATIC_APP1 to protect critical sections in server.c.

FossilOrigin-Name: 3144ae40d2eb63dfd5587579a49163ea1add5947d624daa478ada13339495af4
2017-04-27 14:12:43 +00:00
dan 0561247d07 If possible, delete the journal file when a database connection is closed.
FossilOrigin-Name: d5b5326df25b85b1c3926cd693bcde1ca08e6e03b8aea151a82d222fc9c23dd6
2017-04-27 13:05:43 +00:00
dan 1381bbc262 Experimental implementation of pessimistic page-level locking based on
rollback mode.

FossilOrigin-Name: 64ecf7c7e512827e8a5a42f9f3ad92ff57ec868820e3943dbc74d5823f9a889d
2017-04-26 20:45:00 +00:00
21 changed files with 2076 additions and 49 deletions
+3
View File
@@ -69,6 +69,7 @@ LIBOBJ+= vdbe.o parse.o \
notify.o opcodes.o os.o os_unix.o os_win.o \
pager.o pcache.o pcache1.o pragma.o prepare.o printf.o \
random.o resolve.o rowset.o rtree.o select.o sqlite3rbu.o status.o \
server.o \
table.o threads.o tokenize.o treeview.o trigger.o \
update.o userauth.o util.o vacuum.o \
vdbeapi.o vdbeaux.o vdbeblob.o vdbemem.o vdbesort.o \
@@ -144,6 +145,8 @@ SRC = \
$(TOP)/src/resolve.c \
$(TOP)/src/rowset.c \
$(TOP)/src/select.c \
$(TOP)/src/server.c \
$(TOP)/src/server.h \
$(TOP)/src/status.c \
$(TOP)/src/shell.c \
$(TOP)/src/sqlite.h.in \
+25 -18
View File
@@ -1,5 +1,5 @@
C Fix\sa\scollision\sof\sthe\s"B0"\sidentifier\sname\sbetween\sthe\stermios.h\sheader\nfile\sand\sthe\sSHA3\simplementation\sin\sthe\sshell.
D 2017-05-04T11:13:50.417
C Fix\sbugs\sin\stest\sprogram\stserver.c.
D 2017-06-28T20:12:41.779
F Makefile.in 1cc758ce3374a32425e4d130c2fe7b026b20de5b8843243de75f087c0a2661fb
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
F Makefile.msc 6a8c838220f7c00820e1fc0ac1bccaaa8e5676067e1dbfa1bafa7a4ffecf8ae6
@@ -326,7 +326,7 @@ F ext/userauth/userauth.c 3410be31283abba70255d71fd24734e017a4497f
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
F main.mk 9abb506e717887d57f754bae139b85c1a06d6f2ac25b589f3e792e310567f278
F main.mk 0116e0f17f9bb71a111a41fc560c677bba20c557e4c4c11cebf66124c472b22d
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
@@ -345,7 +345,7 @@ F src/auth.c 930b376a9c56998557367e6f7f8aaeac82a2a792
F src/backup.c faf17e60b43233c214aae6a8179d24503a61e83b
F src/bitvec.c 17ea48eff8ba979f1f5b04cc484c7bb2be632f33
F src/btmutex.c 0e9ce2d56159b89b9bc8e197e023ee11e39ff8ca
F src/btree.c 8c1fd4cfa2b0bf021386e0a1f4e30b64eea7a2c1bc2e0c3e5901a626b1ab6aa9
F src/btree.c ca26f48d5c9f2f1e930b062bb1e550ea8d26c9b64522eb2003e10c226ecfc884
F src/btree.h 80f518c0788be6cec8d9f8e13bd8e380df299d2b5e4ac340dc887b0642647cfc
F src/btreeInt.h a392d353104b4add58b4a59cb185f5d5693dde832c565b77d8d4c343ed98f610
F src/build.c 4026a9c554b233e50c5e9ad46963e676cf54dd2306d952aa1eaa07a1bc9ce14f
@@ -389,8 +389,8 @@ F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586
F src/os_unix.c 30e2c43e4955db990e5b5a81e901f8aa74cc8820
F src/os_win.c 2a6c73eef01c51a048cc4ddccd57f981afbec18a
F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
F src/pager.c ff1232b3088a39806035ecfac4fffeb22717d80b
F src/pager.h f2a99646c5533ffe11afa43e9e0bea74054e4efa
F src/pager.c f957574dc2f8a66d8ee2b5fd978ecd98760d6669182a023a3901e98ab5e4df33
F src/pager.h ea05992c581c3366279fb1d436944604b4be17208ebb41fa407306e5e4b34205
F src/parse.y 0513387ce02fea97897d8caef82d45f347818593f24f1bdc48e0c530a8af122d
F src/pcache.c 62835bed959e2914edd26afadfecce29ece0e870
F src/pcache.h 2cedcd8407eb23017d92790b112186886e179490
@@ -403,11 +403,13 @@ F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
F src/resolve.c 3e518b962d932a997fae373366880fc028c75706
F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
F src/select.c 4f0adefaa5e9417459b07757e0f6060cac97930a86f0fba9797bab233ced66c0
F src/server.c 63c3799a184cba3d3b9f102f10e815104ba8d1d4a01c70b0e709e8173b5f9ae7
F src/server.h e1ce2da1e4d21f335904539e3f98a7c24e015e1201b4bb16d61f0044b8bd2884
F src/shell.c e5950029da103c5d378e71d548759459b9a7fc76177a71562c22082c705745ab
F src/sqlite.h.in eeb1da70a61d52e1d58e5b55446b85bbac571699421d3cf857421c56214013ce
F src/sqlite.h.in 8d126e4cfbd1f4bc6f4043aacd77f78b45613e7d630185d49a5d099394247483
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h 58fd0676d3111d02e62e5a35992a7d3da5d3f88753acc174f2d37b774fbbdd28
F src/sqliteInt.h aea3aa1b81e0d07d5b1c39b8c5a54a1dc5e4f10136cb63da392aef9eb2a5108b
F src/sqliteInt.h 50a6bec0b83965edb0a805e1e0f3ebeab536084109a97bdd18a524eb42579a4e
F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b
F src/status.c a9e66593dfb28a9e746cba7153f84d49c1ddc4b1
F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34
@@ -473,15 +475,15 @@ F src/vdbe.c 9bac2bc2313ed682e6f48ccff6644d3263341885bfcbb3cdea7b720c722be2d5
F src/vdbe.h f7d1456e28875c2dcb964056589b5b7149ab7edf39edeca801596a39bb3d3848
F src/vdbeInt.h c070bc5c8b913bda0ceaa995cd4d939ded5e4fc96cf7c3c1c602d41b871f8ade
F src/vdbeapi.c 5b08d82592bcff4470601fe78aaabebd50837860
F src/vdbeaux.c b4999c744e59deba7ab8733640219ecbc771721b362d7e26ce4c57db575ad80b
F src/vdbeaux.c ef484bc29e56fc309285e92490e3a547a86b7f7ed3a8701d145a60f9e5ac9ad9
F src/vdbeblob.c 359891617358deefc85bef7bcf787fa6b77facb9
F src/vdbemem.c 2c70f8f5de6c71fb99a22c5b83be9fab5c47cdd8e279fa44a8c00cfed06d7e89
F src/vdbesort.c e72fe02a2121386ba767ede8942e9450878b8fc873abf3d1b6824485f092570c
F src/vdbetrace.c 41963d5376f0349842b5fc4aaaaacd7d9cdc0834
F src/vtab.c 35b9bdc2b41de32a417141d12097bcc4e29a77ed7cdb8f836d1d2305d946b61b
F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
F src/wal.c 40c543f0a2195d1b0dc88ef12142bea690009344
F src/wal.h 06b2a0b599cc0f53ea97f497cf8c6b758c999f71
F src/wal.c 8f71654244baf38b95a277e79677d5444737c326182ba81476bc101c001f2e07
F src/wal.h 739d92494eb18b6d8f3e353e66c10eb8f94534bafd336ece9f3f60235317ea08
F src/walker.c b71a992b413b3a022572eccf29ef4b4890223791
F src/where.c c6352f15be5031907c68bcbde96cad1a6da20e9f4051d10168a59235de9a8566
F src/whereInt.h 2a4b634d63ce488b46d4b0da8f2eaa8f9aeab202bc25ef76f007de5e3fba1f20
@@ -1034,7 +1036,7 @@ F test/parser1.test 391b9bf9a229547a129c61ac345ed1a6f5eb1854
F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b
F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442
F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff
F test/permutations.test 8aaa22a0f428a7e6b8446b97bc7691a273eaeff5dc290fb9129bf79fa9813a6e
F test/permutations.test ca1840b39571fc87f432c104e6df00279ab67edeec0f75d8015dcfea648fee2e
F test/pragma.test 1e94755164a3a3264cd39836de4bebcb7809e5f8
F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f
F test/pragma3.test 14c12bc5352b1e100e0b6b44f371053a81ccf8ed
@@ -1110,6 +1112,10 @@ F test/selectE.test a8730ca330fcf40ace158f134f4fe0eb00c7edbf
F test/selectF.test 21c94e6438f76537b72532fa9fd4710cdd455fc3
F test/selectG.test e8600e379589e85e9fefd2fe4d44a4cdd63f6982
F test/server1.test 46803bd3fe8b99b30dbc5ff38ffc756f5c13a118
F test/server2.test 11dda300ebef43b4abe0fdc086b6f51b964beddb529fb65bc1f026db5895c36e
F test/server3.test c33343f2f6bc23f2b4e2f047c3d083579f0cfac2795e0f1eb226ab34758967c0
F test/servercrash.test 816c132b26af008067cab2913783f67006d4003e3988f3f3ee1075742f6e0a6c
F test/serverwal.test 2d066ba70db35e28f0115a57385cf606513c56d6e2861119c2defb7471e4e2d9
F test/session.test 78fa2365e93d3663a6e933f86e7afc395adf18be
F test/shared.test 1da9dbad400cee0d93f252ccf76e1ae007a63746
F test/shared2.test 03eb4a8d372e290107d34b6ce1809919a698e879
@@ -1191,7 +1197,7 @@ F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30
F test/temptable2.test cd396beb41117a5302fff61767c35fa4270a0d5e
F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637
F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc
F test/tester.tcl 581f0185434daf7026ccede4c07e8d1479186ec5
F test/tester.tcl d74fefbba7cdd4ad2846c73ed0e02734809d8f824bf25f44a8dbe43dd8a74283
F test/thread001.test 9f22fd3525a307ff42a326b6bc7b0465be1745a5
F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58
F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7
@@ -1519,7 +1525,7 @@ F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e
F tool/mkpragmatab.tcl 32bb40741df11bddc8451de9ea4d130e7b4476d8064794b1cf402ac110840fba
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
F tool/mksqlite3c-noext.tcl fef88397668ae83166735c41af99d79f56afaabb
F tool/mksqlite3c.tcl 06b2e6a0f21cc0a5d70fbbd136b3e0a96470645e
F tool/mksqlite3c.tcl 44d4b9b9d7398801164e66f866f1da0ee1e12e47d1b718f1bbf09a19fd2f3a44
F tool/mksqlite3h.tcl b9836752c3d08f9fab2dfc0017ca9fd5d90ac863
F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b
F tool/mkvsix.tcl b9e0777a213c23156b6542842c238479e496ebf5
@@ -1551,6 +1557,7 @@ F tool/stack_usage.tcl f8e71b92cdb099a147dad572375595eae55eca43
F tool/symbols-mingw.sh 4dbcea7e74768305384c9fd2ed2b41bbf9f0414d
F tool/symbols.sh c5a617b8c61a0926747a56c65f5671ef8ac0e148
F tool/tostr.tcl 96022f35ada2194f6f8ccf6fd95809e90ed277c4
F tool/tserver.c 8067d85410c827c9d1b33fd3f269b96615ab404a277c268e1838bfa768b58949
F tool/varint.c 5d94cb5003db9dbbcbcc5df08d66f16071aee003
F tool/vdbe-compress.tcl 5926c71f9c12d2ab73ef35c29376e756eb68361c
F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f
@@ -1578,7 +1585,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P e24b73820cdca07eee87853fe6dd9f60d76e039eeb4aebef03654db3c7c94f14
R e8b739e45c08ec8468647ac109d9456f
U drh
Z 82e99d9ee4b60fac8b8163f812301e67
P d8568aacf083b63cdcf68d744ddb07a7615d14f169be6c632813a266a1e8a1cf
R fca1d20100f6bb2d0932053fa9b009ce
U dan
Z abee9fae5ac9c5e9468404f3e0fbaf84
+1 -1
View File
@@ -1 +1 @@
b9a58daca80a815e87e541cb5fff9bc8b93f131d223f322c5b83dd5a5f0c0312
093b9108ea3532437683547c82f756df2413013a50a2fddad3d928cf8d74f9eb
+316 -8
View File
@@ -5616,6 +5616,255 @@ int sqlite3BtreePrevious(BtCursor *pCur, int *pRes){
return SQLITE_OK;
}
#ifdef SQLITE_SERVER_EDITION
#define SERVER_DEFAULT_FREELISTS 16
#define SERVER_DEFAULT_FREELIST_SIZE 128
/*
** Allocate the free-node and the first SERVER_DEFAULT_FREELISTS
** trunk pages.
*/
static int allocateServerFreenode(BtShared *pBt){
int rc;
MemPage *pPage1 = pBt->pPage1;
rc = sqlite3PagerWrite(pPage1->pDbPage);
if( rc==SQLITE_OK ){
Pgno pgnoNode = (++pBt->nPage);
MemPage *pNode = 0;
int i;
put4byte(&pPage1->aData[32], pgnoNode);
rc = btreeGetUnusedPage(pBt, pgnoNode, &pNode, PAGER_GET_NOCONTENT);
if( rc==SQLITE_OK ){
rc = sqlite3PagerWrite(pNode->pDbPage);
}
if( rc==SQLITE_OK ){
put4byte(&pNode->aData[0], 0);
put4byte(&pNode->aData[4], SERVER_DEFAULT_FREELISTS);
}
for(i=0; rc==SQLITE_OK && i<SERVER_DEFAULT_FREELISTS; i++){
MemPage *pTrunk = 0;
Pgno pgnoTrunk;
if( ++pBt->nPage==PENDING_BYTE_PAGE(pBt) ) pBt->nPage++;
pgnoTrunk = pBt->nPage;
rc = btreeGetUnusedPage(pBt, pgnoTrunk, &pTrunk, PAGER_GET_NOCONTENT);
if( rc==SQLITE_OK ){
rc = sqlite3PagerWrite(pTrunk->pDbPage);
}
if( rc==SQLITE_OK ){
memset(pTrunk->aData, 0, 8);
put4byte(&pNode->aData[8+i*4], pgnoTrunk);
}
releasePage(pTrunk);
}
releasePage(pNode);
}
return rc;
}
/*
** Return a reference to the first trunk page in one of the database free-lists.
** Allocate the database free-lists if required.
*/
static int findServerTrunk(BtShared *pBt, int bAlloc, MemPage **ppTrunk){
MemPage *pPage1 = pBt->pPage1;
MemPage *pNode = 0; /* The node page */
MemPage *pTrunk = 0; /* The returned page */
Pgno iNode; /* Page number of node page */
int rc = SQLITE_OK;
/* If the node page and free-list trunks have not yet been allocated, allocate
** them now. */
pPage1 = pBt->pPage1;
iNode = get4byte(&pPage1->aData[32]);
if( iNode==0 ){
rc = allocateServerFreenode(pBt);
iNode = get4byte(&pPage1->aData[32]);
}
/* Grab the node page */
if( rc==SQLITE_OK ){
rc = btreeGetUnusedPage(pBt, iNode, &pNode, 0);
}
if( rc==SQLITE_OK ){
int nList; /* Number of free-lists in this db */
int i;
/* Try to lock a free-list trunk. If bAlloc is true, it has to be a
** free-list trunk with at least one entry in the free-list. */
nList = (int)get4byte(&pNode->aData[4]);
for(i=0; i<nList; i++){
Pgno iTrunk = get4byte(&pNode->aData[8+i*4]);
if( SQLITE_OK==sqlite3PagerPagelock(pBt->pPager, iTrunk, 1) ){
rc = btreeGetUnusedPage(pBt, iTrunk, &pTrunk, 0);
if( rc==SQLITE_OK && bAlloc ){
if( !get4byte(&pTrunk->aData[0]) && !get4byte(&pTrunk->aData[4]) ){
releasePage(pTrunk);
pTrunk = 0;
}
}
if( rc!=SQLITE_OK || pTrunk ) break;
}
}
/* No free pages in any free-list. Or perhaps we were locked out. In
** either case, try to allocate more from the end of the file now. */
if( i==nList ){
assert( rc==SQLITE_OK && pTrunk==0 );
rc = sqlite3PagerWrite(pPage1->pDbPage);
for(i=0; rc==SQLITE_OK && i<nList; i++){
/* Add some free pages to each free-list. No server-locks are required
** to do this as we have a write-lock on page 1 - guaranteeing
** exclusive access to the db file. */
MemPage *pT = 0;
Pgno iTrunk = get4byte(&pNode->aData[8+i*4]);
rc = btreeGetUnusedPage(pBt, iTrunk, &pT, 0);
if( rc==SQLITE_OK ){
rc = sqlite3PagerWrite(pT->pDbPage);
}
if( rc==SQLITE_OK ){
int iPg = get4byte(&pT->aData[4]);
for(/*no-op*/; iPg<SERVER_DEFAULT_FREELIST_SIZE; iPg++){
if( ++pBt->nPage==PENDING_BYTE_PAGE(pBt) ) pBt->nPage++;
put4byte(&pT->aData[8+iPg*4], pBt->nPage);
}
put4byte(&pT->aData[4], iPg);
if( pTrunk==0 ){
pTrunk = pT;
pT = 0;
}
}
releasePage(pT);
}
if( rc==SQLITE_OK ){
MemPage *pLast = 0;
rc = btreeGetUnusedPage(pBt, pBt->nPage, &pLast, 0);
if( rc==SQLITE_OK ){
rc = sqlite3PagerWrite(pLast->pDbPage);
releasePage(pLast);
put4byte(28 + (u8*)pPage1->aData, pBt->nPage);
}
}
}
}
releasePage(pNode);
if( rc==SQLITE_OK ){
assert( pTrunk );
rc = sqlite3PagerWrite(pTrunk->pDbPage);
}
if( rc!=SQLITE_OK ){
releasePage(pTrunk);
pTrunk = 0;
}
*ppTrunk = pTrunk;
return rc;
}
static int allocateServerPage(
BtShared *pBt, /* The btree */
MemPage **ppPage, /* Store pointer to the allocated page here */
Pgno *pPgno, /* Store the page number here */
Pgno nearby, /* Search for a page near this one */
u8 eMode /* BTALLOC_EXACT, BTALLOC_LT, or BTALLOC_ANY */
){
int rc; /* Return code */
MemPage *pTrunk = 0; /* The node page */
Pgno pgnoNew = 0;
#ifdef SQLITE_DEBUG
int nRef = sqlite3PagerRefcount(pBt->pPager);
#endif
assert( eMode==BTALLOC_ANY );
assert( sqlite3_mutex_held(pBt->mutex) );
*ppPage = 0;
rc = findServerTrunk(pBt, 1, &pTrunk);
if( rc==SQLITE_OK ){
int nFree; /* Number of free pages on this trunk page */
nFree = (int)get4byte(&pTrunk->aData[4]);
if( nFree==0 ){
pgnoNew = get4byte(&pTrunk->aData[0]);
assert( pgnoNew );
}else{
nFree--;
pgnoNew = get4byte(&pTrunk->aData[8+4*nFree]);
put4byte(&pTrunk->aData[4], (u32)nFree);
releasePage(pTrunk);
pTrunk = 0;
}
}
if( rc==SQLITE_OK ){
MemPage *pNew = 0;
int flags = pTrunk ? 0 : PAGER_GET_NOCONTENT;
rc = btreeGetUnusedPage(pBt, pgnoNew, &pNew, flags);
if( rc==SQLITE_OK ){
rc = sqlite3PagerWrite(pNew->pDbPage);
if( rc!=SQLITE_OK ){
releasePage(pNew);
pNew = 0;
}
}
if( rc==SQLITE_OK && pTrunk ){
memcpy(pTrunk->aData, pNew->aData, pBt->usableSize);
}
*ppPage = pNew;
*pPgno = pgnoNew;
}
releasePage(pTrunk);
assert( (rc==SQLITE_OK)==(*ppPage!=0) );
assert( sqlite3PagerRefcount(pBt->pPager)==(nRef+(*ppPage!=0)) );
return rc;
}
static int freeServerPage2(BtShared *pBt, MemPage *pPage, Pgno iPage){
int rc; /* Return code */
MemPage *pTrunk = 0; /* The node page */
#ifdef SQLITE_DEBUG
int nRef = sqlite3PagerRefcount(pBt->pPager);
#endif
assert( sqlite3_mutex_held(pBt->mutex) );
rc = findServerTrunk(pBt, 0, &pTrunk);
if( rc==SQLITE_OK ){
int nFree; /* Number of free pages on this trunk page */
nFree = (int)get4byte(&pTrunk->aData[4]);
if( nFree>=((pBt->usableSize / 4) - 2) ){
if( pPage==0 ){
rc = btreeGetUnusedPage(pBt, iPage, &pPage, 0);
}else{
sqlite3PagerRef(pPage->pDbPage);
}
rc = sqlite3PagerWrite(pPage->pDbPage);
if( rc==SQLITE_OK ){
memcpy(pPage->aData, pTrunk->aData, pBt->usableSize);
put4byte(&pTrunk->aData[0], iPage);
put4byte(&pTrunk->aData[4], 0);
}
releasePage(pPage);
}else{
put4byte(&pTrunk->aData[8+nFree*4], iPage);
put4byte(&pTrunk->aData[4], (u32)nFree+1);
}
releasePage(pTrunk);
}
assert( nRef==sqlite3PagerRefcount(pBt->pPager) );
return rc;
}
#else
# define allocateServerPage(v, w, x, y, z) SQLITE_OK
# define freeServerPage2(x, y, z) SQLITE_OK
#endif /* SQLITE_SERVER_EDITION */
/*
** Allocate a new page from the database file.
**
@@ -5653,6 +5902,10 @@ static int allocateBtreePage(
MemPage *pPrevTrunk = 0;
Pgno mxPage; /* Total size of the database file */
if( sqlite3PagerIsServer(pBt->pPager) ){
return allocateServerPage(pBt, ppPage, pPgno, nearby, eMode);
}
assert( sqlite3_mutex_held(pBt->mutex) );
assert( eMode==BTALLOC_ANY || (nearby>0 && IfNotOmitAV(pBt->autoVacuum)) );
pPage1 = pBt->pPage1;
@@ -5980,12 +6233,6 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){
pPage = btreePageLookup(pBt, iPage);
}
/* Increment the free page count on pPage1 */
rc = sqlite3PagerWrite(pPage1->pDbPage);
if( rc ) goto freepage_out;
nFree = get4byte(&pPage1->aData[36]);
put4byte(&pPage1->aData[36], nFree+1);
if( pBt->btsFlags & BTS_SECURE_DELETE ){
/* If the secure_delete option is enabled, then
** always fully overwrite deleted information with zeros.
@@ -5997,6 +6244,17 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){
}
memset(pPage->aData, 0, pPage->pBt->pageSize);
}
if( sqlite3PagerIsServer(pBt->pPager) ){
rc = freeServerPage2(pBt, pPage, iPage);
goto freepage_out;
}
/* Increment the free page count on pPage1 */
rc = sqlite3PagerWrite(pPage1->pDbPage);
if( rc ) goto freepage_out;
nFree = get4byte(&pPage1->aData[36]);
put4byte(&pPage1->aData[36], nFree+1);
/* If the database supports auto-vacuum, write an entry in the pointer-map
** to indicate that the page is free.
@@ -9444,6 +9702,49 @@ end_of_check:
#endif /* SQLITE_OMIT_INTEGRITY_CHECK */
#ifndef SQLITE_OMIT_INTEGRITY_CHECK
#if !defined(SQLITE_OMIT_INTEGRITY_CHECK) && defined(SQLITE_SERVER_EDITION)
static void checkServerList(IntegrityCk *pCheck){
u32 pgnoNode = get4byte(&pCheck->pBt->pPage1->aData[32]);
if( pgnoNode ){
DbPage *pNode = 0;
u8 *aNodeData;
u32 nList; /* Number of free-lists */
int i;
checkRef(pCheck, pgnoNode);
if( sqlite3PagerGet(pCheck->pPager, (Pgno)pgnoNode, &pNode, 0) ){
checkAppendMsg(pCheck, "failed to get node page %d", pgnoNode);
return;
}
aNodeData = sqlite3PagerGetData(pNode);
nList = get4byte(&aNodeData[4]);
for(i=0; i<nList; i++){
u32 pgnoTrunk = get4byte(&aNodeData[8+4*i]);
while( pgnoTrunk ){
DbPage *pTrunk = 0;
checkRef(pCheck, pgnoTrunk);
if( sqlite3PagerGet(pCheck->pPager, (Pgno)pgnoTrunk, &pTrunk, 0) ){
checkAppendMsg(pCheck, "failed to get page %d", pgnoTrunk);
pgnoTrunk = 0;
}else{
u8 *aTrunkData = sqlite3PagerGetData(pTrunk);
int nLeaf = (int)get4byte(&aTrunkData[4]);
int iLeaf;
for(iLeaf=0; iLeaf<nLeaf; iLeaf++){
u32 pgnoLeaf = get4byte(&aTrunkData[8+iLeaf*4]);
checkRef(pCheck, pgnoLeaf);
}
pgnoTrunk = get4byte(&aTrunkData[0]);
sqlite3PagerUnref(pTrunk);
}
}
}
sqlite3PagerUnref(pNode);
}
}
#endif
/*
** This routine does a complete check of the given BTree file. aRoot[] is
** an array of pages numbers were each page number is the root page of
@@ -9509,8 +9810,15 @@ char *sqlite3BtreeIntegrityCheck(
/* Check the integrity of the freelist
*/
sCheck.zPfx = "Main freelist: ";
checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]),
get4byte(&pBt->pPage1->aData[36]));
#ifdef SQLITE_SERVER_EDITION
if( sqlite3PagerIsServer(pBt->pPager) ){
checkServerList(&sCheck);
}else
#endif
{
checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]),
get4byte(&pBt->pPage1->aData[36]));
}
sCheck.zPfx = 0;
/* Check all the tables.
+159 -5
View File
@@ -706,6 +706,9 @@ struct Pager {
Wal *pWal; /* Write-ahead log used by "journal_mode=wal" */
char *zWal; /* File name for write-ahead log */
#endif
#ifdef SQLITE_SERVER_EDITION
Server *pServer;
#endif
};
/*
@@ -836,6 +839,12 @@ int sqlite3PagerUseWal(Pager *pPager, Pgno pgno){
# define pagerBeginReadTransaction(z) SQLITE_OK
#endif
#ifdef SQLITE_SERVER_EDITION
# define pagerIsServer(x) ((x)->pServer!=0)
#else
# define pagerIsServer(x) 0
#endif
#ifndef NDEBUG
/*
** Usage:
@@ -1132,6 +1141,7 @@ static int pagerUnlockDb(Pager *pPager, int eLock){
assert( !pPager->exclusiveMode || pPager->eLock==eLock );
assert( eLock==NO_LOCK || eLock==SHARED_LOCK );
assert( eLock!=NO_LOCK || pagerUseWal(pPager)==0 );
assert( eLock!=NO_LOCK || pagerIsServer(pPager)==0 );
if( isOpen(pPager->fd) ){
assert( pPager->eLock>=eLock );
rc = pPager->noLock ? SQLITE_OK : sqlite3OsUnlock(pPager->fd, eLock);
@@ -1807,6 +1817,12 @@ static void pager_unlock(Pager *pPager){
pPager->pInJournal = 0;
releaseAllSavepoints(pPager);
#ifdef SQLITE_SERVER_EDITION
if( pagerIsServer(pPager) ){
sqlite3ServerEnd(pPager->pServer);
pPager->eState = PAGER_OPEN;
}else
#endif
if( pagerUseWal(pPager) ){
assert( !isOpen(pPager->jfd) );
sqlite3WalEndReadTransaction(pPager->pWal);
@@ -2105,6 +2121,11 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){
if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK;
}
#ifdef SQLITE_SERVER_EDITION
if( pagerIsServer(pPager) ){
rc2 = sqlite3ServerReleaseWriteLocks(pPager->pServer);
}else
#endif
if( !pPager->exclusiveMode
&& (!pagerUseWal(pPager) || sqlite3WalExclusiveMode(pPager->pWal, 0))
){
@@ -4095,10 +4116,24 @@ int sqlite3PagerClose(Pager *pPager, sqlite3 *db){
** rollback before accessing the database file.
*/
if( isOpen(pPager->jfd) ){
if( pagerIsServer(pPager) ){
assert( pPager->journalMode==PAGER_JOURNALMODE_PERSIST );
pPager->journalMode = PAGER_JOURNALMODE_DELETE;
/* If necessary, change the pager state so that the journal file
** is deleted by the call to pagerUnlockAndRollback() below. */
if( pPager->eState==PAGER_OPEN ) pPager->eState = PAGER_READER;
}
pager_error(pPager, pagerSyncHotJournal(pPager));
}
pagerUnlockAndRollback(pPager);
}
#ifdef SQLITE_SERVER_EDITION
if( pagerIsServer(pPager) ){
sqlite3ServerDisconnect(pPager->pServer, pPager->fd);
pPager->pServer = 0;
sqlite3_free(pPager->zJournal);
}
#endif
sqlite3EndBenignMalloc();
enable_simulated_io_errors();
PAGERTRACE(("CLOSE %d\n", PAGERID(pPager)));
@@ -5051,6 +5086,80 @@ static int hasHotJournal(Pager *pPager, int *pExists){
return rc;
}
#ifdef SQLITE_SERVER_EDITION
static int pagerServerConnect(Pager *pPager){
int rc = SQLITE_OK;
if( pPager->tempFile==0 ){
int iClient = 0;
pPager->noLock = 1;
pPager->journalMode = PAGER_JOURNALMODE_PERSIST;
rc = sqlite3ServerConnect(pPager, &pPager->pServer, &iClient);
if( rc==SQLITE_OK ){
pPager->zJournal = sqlite3_mprintf(
"%s-journal%d", pPager->zFilename, iClient
);
if( pPager->zJournal==0 ){
rc = SQLITE_NOMEM_BKPT;
}
}
}
return rc;
}
int sqlite3PagerRollbackJournal(Pager *pPager, int iClient){
int rc;
char *zJrnl = sqlite3_mprintf("%s-journal%d", pPager->zFilename, iClient);
if( zJrnl ){
int bExists = 0;
sqlite3_file *jfd = 0;
sqlite3_vfs * const pVfs = pPager->pVfs;
rc = sqlite3OsAccess(pVfs, zJrnl, SQLITE_ACCESS_EXISTS, &bExists);
if( rc==SQLITE_OK && bExists ){
int flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_MAIN_JOURNAL;
rc = sqlite3OsOpenMalloc(pVfs, zJrnl, &jfd, flags, &flags);
}
assert( rc==SQLITE_OK || jfd==0 );
if( jfd ){
sqlite3_file *saved_jfd = pPager->jfd;
u8 saved_eState = pPager->eState;
u8 saved_eLock = pPager->eLock;
i64 saved_journalOff = pPager->journalOff;
i64 saved_journalHdr = pPager->journalHdr;
char *saved_zJournal = pPager->zJournal;
pPager->eLock = EXCLUSIVE_LOCK;
pPager->eState = PAGER_WRITER_DBMOD;
pPager->jfd = jfd;
rc = pagerSyncHotJournal(pPager);
if( rc==SQLITE_OK ) rc = pager_playback(pPager, 1);
pPager->jfd = saved_jfd;
pPager->eState = saved_eState;
pPager->eLock = saved_eLock;
pPager->journalOff = saved_journalOff;
pPager->journalHdr = saved_journalHdr;
pPager->zJournal = saved_zJournal;
sqlite3OsCloseFree(jfd);
if( rc==SQLITE_OK ){
rc = sqlite3OsDelete(pVfs, zJrnl, 0);
}
}
sqlite3_free(zJrnl);
}else{
rc = SQLITE_NOMEM_BKPT;
}
return rc;
}
#else
# define pagerServerConnect(pPager) SQLITE_OK
#endif
/*
** This function is called to obtain a shared lock on the database file.
** It is illegal to call sqlite3PagerGet() until after this function
@@ -5090,7 +5199,9 @@ int sqlite3PagerSharedLock(Pager *pPager){
assert( pPager->eState==PAGER_OPEN || pPager->eState==PAGER_READER );
assert( pPager->errCode==SQLITE_OK );
if( !pagerUseWal(pPager) && pPager->eState==PAGER_OPEN ){
if( !pagerUseWal(pPager)
&& !pagerIsServer(pPager)
&& pPager->eState==PAGER_OPEN ){
int bHotJournal = 1; /* True if there exists a hot journal-file */
assert( !MEMDB );
@@ -5262,16 +5373,27 @@ int sqlite3PagerSharedLock(Pager *pPager){
}
}
rc = pagerServerConnect(pPager);
/* If there is a WAL file in the file-system, open this database in WAL
** mode. Otherwise, the following function call is a no-op.
*/
rc = pagerOpenWalIfPresent(pPager);
if( rc==SQLITE_OK ){
rc = pagerOpenWalIfPresent(pPager);
}
#ifndef SQLITE_OMIT_WAL
assert( pPager->pWal==0 || rc==SQLITE_OK );
#endif
}
if( pagerUseWal(pPager) ){
#ifdef SQLITE_SERVER_EDITION
if( pagerIsServer(pPager) ){
assert( rc==SQLITE_OK );
pager_reset(pPager);
rc = sqlite3ServerBegin(pPager->pServer);
}
#endif
if( rc==SQLITE_OK && pagerUseWal(pPager) ){
assert( rc==SQLITE_OK );
rc = pagerBeginReadTransaction(pPager);
}
@@ -5564,6 +5686,12 @@ int sqlite3PagerGet(
DbPage **ppPage, /* Write a pointer to the page here */
int flags /* PAGER_GET_XXX flags */
){
#ifdef SQLITE_SERVER_EDITION
if( pagerIsServer(pPager) ){
int rc = sqlite3ServerLock(pPager->pServer, pgno, 0, 0);
if( rc!=SQLITE_OK ) return rc;
}
#endif
return pPager->xGet(pPager, pgno, ppPage, flags);
}
@@ -5866,6 +5994,13 @@ static int pager_write(PgHdr *pPg){
assert( pPager->readOnly==0 );
CHECK_PAGE(pPg);
#ifdef SQLITE_SERVER_EDITION
if( pagerIsServer(pPager) ){
rc = sqlite3ServerLock(pPager->pServer, pPg->pgno, 1, 0);
if( rc!=SQLITE_OK ) return rc;
}
#endif
/* The journal file needs to be opened. Higher level routines have already
** obtained the necessary locks to begin the write-transaction, but the
** rollback journal might not yet be open. Open it now if this is the case.
@@ -6144,7 +6279,10 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){
# define DIRECT_MODE isDirectMode
#endif
if( !pPager->changeCountDone && ALWAYS(pPager->dbSize>0) ){
if( 0==pagerIsServer(pPager)
&& !pPager->changeCountDone
&& ALWAYS(pPager->dbSize>0)
){
PgHdr *pPgHdr; /* Reference to page 1 */
assert( !pPager->tempFile && isOpen(pPager->fd) );
@@ -6303,6 +6441,10 @@ int sqlite3PagerCommitPhaseOne(
** backup in progress needs to be restarted. */
sqlite3BackupRestart(pPager->pBackup);
}else{
/* If this connection is in server mode, ignore any master journal. */
if( pagerIsServer(pPager) ){
zMaster = 0;
}
if( pagerUseWal(pPager) ){
PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache);
PgHdr *pPageOne = 0;
@@ -7313,7 +7455,7 @@ int sqlite3PagerWalCallback(Pager *pPager){
*/
int sqlite3PagerWalSupported(Pager *pPager){
const sqlite3_io_methods *pMethods = pPager->fd->pMethods;
if( pPager->noLock ) return 0;
if( pPager->noLock && !pagerIsServer(pPager) ) return 0;
return pPager->exclusiveMode || (pMethods->iVersion>=2 && pMethods->xShmMap);
}
@@ -7408,6 +7550,9 @@ int sqlite3PagerOpenWal(
if( rc==SQLITE_OK ){
pPager->journalMode = PAGER_JOURNALMODE_WAL;
pPager->eState = PAGER_OPEN;
#ifdef SQLITE_SERVER_EDITION
sqlite3WalServer(pPager->pWal, pPager->pServer);
#endif
}
}else{
*pbOpen = 1;
@@ -7521,4 +7666,13 @@ int sqlite3PagerWalFramesize(Pager *pPager){
}
#endif
#ifdef SQLITE_SERVER_EDITION
int sqlite3PagerIsServer(Pager *pPager){
return pagerIsServer(pPager);
}
int sqlite3PagerPagelock(Pager *pPager, Pgno pgno, int bWrite){
return sqlite3ServerLock(pPager->pServer, pgno, bWrite, 0);
}
#endif
#endif /* SQLITE_OMIT_DISKIO */
+6
View File
@@ -236,4 +236,10 @@ void *sqlite3PagerCodec(DbPage *);
# define enable_simulated_io_errors()
#endif
#ifdef SQLITE_SERVER_EDITION
int sqlite3PagerRollbackJournal(Pager*, int);
int sqlite3PagerIsServer(Pager *pPager);
int sqlite3PagerPagelock(Pager *pPager, Pgno, int);
#endif
#endif /* SQLITE_PAGER_H */
+601
View File
@@ -0,0 +1,601 @@
/*
** 2017 April 24
**
** 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.
**
*************************************************************************
*/
#include "sqliteInt.h"
/*
** HMA file layout:
**
** 4 bytes - DMS slot. All connections read-lock this slot.
**
** 16*4 bytes - locking slots. Connections hold a read-lock on a locking slot
** when they are connected, a write lock when they have an open
** transaction.
**
** N*4 bytes - Page locking slots. N is HMA_PAGELOCK_SLOTS.
**
** Page-locking slot format:
**
** Each page-locking slot provides SHARED/RESERVED/EXCLUSIVE locks on a
** single page. A RESERVED lock is similar to a RESERVED in SQLite's
** rollback mode - existing SHARED locks may continue but new SHARED locks
** may not be established. As in rollback mode, EXCLUSIVE and RESERVED
** locks are mutually exclusive.
**
** Each 32-bit locking slot is divided into two sections - a bitmask for
** read-locks and a single integer field for the write lock. The bitmask
** occupies the least-significant 27 bits of the slot. The integer field
** occupies the remaining 5 bits (so that it can store values from 0-31).
**
** Each client has a unique integer client id. Currently these range from
** 0-15 (maximum of 16 concurrent connections). The page-locking slot format
** allows this to be increased to 0-26 (maximum of 26 connections). To
** take a SHARED lock, the corresponding bit is set in the locking slot
** bitmask:
**
** slot = slot | (1 << iClient);
**
** To take an EXCLUSIVE or RESERVED lock, the integer part of the locking
** slot is set to the client-id of the locker plus one (a value of zero
** indicates that no connection holds a RESERVED or EXCLUSIVE lock):
**
** slot = slot | ((iClient+1) << 27)
*/
#ifdef SQLITE_SERVER_EDITION
#define HMA_CLIENT_SLOTS 16
#define HMA_PAGELOCK_SLOTS (256*1024)
#define HMA_FILE_SIZE (4 + 4*HMA_CLIENT_SLOTS + 4*HMA_PAGELOCK_SLOTS)
#include "unistd.h"
#include "fcntl.h"
#include "sys/mman.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "errno.h"
typedef struct ServerHMA ServerHMA;
struct ServerGlobal {
ServerHMA *pHma; /* Linked list of all ServerHMA objects */
};
static struct ServerGlobal g_server;
/*
** There is one instance of the following structure for each distinct
** HMA file opened by clients within this process.
*/
struct ServerHMA {
char *zName; /* hma file path */
int fd; /* Fd open on hma file */
int nClient; /* Current number of clients */
Server *aClient[HMA_CLIENT_SLOTS]; /* Local (this process) clients */
u32 *aMap; /* MMapped hma file */
ServerHMA *pNext; /* Next HMA in this process */
dev_t st_dev;
ino_t st_ino;
};
struct Server {
ServerHMA *pHma; /* Hma file object */
int iClient; /* Client id */
Pager *pPager; /* Associated pager object */
i64 nUsWrite; /* Cumulative us holding WRITER lock */
i64 iUsWrite; /* Time WRITER lock was taken */
int nAlloc; /* Allocated size of aLock[] array */
int nLock; /* Number of entries in aLock[] */
u32 *aLock; /* Mapped lock file */
};
#define SERVER_WRITE_LOCK 3
#define SERVER_READ_LOCK 2
#define SERVER_NO_LOCK 1
/*
** Global mutex functions used by code in this file.
*/
static void serverEnterMutex(void){
sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_APP1));
}
static void serverLeaveMutex(void){
sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_APP1));
}
static void serverAssertMutexHeld(void){
assert( sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_APP1)) );
}
static int posixLock(int fd, int iSlot, int eLock, int bBlock){
int res;
struct flock l;
short aType[4] = {0, F_UNLCK, F_RDLCK, F_WRLCK};
assert( eLock==SERVER_WRITE_LOCK
|| eLock==SERVER_READ_LOCK
|| eLock==SERVER_NO_LOCK
);
memset(&l, 0, sizeof(l));
l.l_type = aType[eLock];
l.l_whence = SEEK_SET;
l.l_start = iSlot*sizeof(u32);
l.l_len = 1;
res = fcntl(fd, (bBlock ? F_SETLKW : F_SETLK), &l);
if( res && bBlock && errno==EDEADLK ){
return SQLITE_BUSY_DEADLOCK;
}
return (res==0 ? SQLITE_OK : SQLITE_BUSY);
}
static int serverMapFile(ServerHMA *p){
assert( p->aMap==0 );
p->aMap = mmap(0, HMA_FILE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, p->fd, 0);
if( p->aMap==0 ){
return SQLITE_ERROR;
}
return SQLITE_OK;
}
static void serverDecrHmaRefcount(ServerHMA *pHma){
if( pHma ){
pHma->nClient--;
if( pHma->nClient<=0 ){
ServerHMA **pp;
if( pHma->aMap ) munmap(pHma->aMap, HMA_FILE_SIZE);
if( pHma->fd>=0 ) close(pHma->fd);
for(pp=&g_server.pHma; *pp!=pHma; pp=&(*pp)->pNext);
*pp = pHma->pNext;
sqlite3_free(pHma);
}
}
}
static int serverOpenHma(Pager *pPager, const char *zPath, ServerHMA **ppHma){
struct stat sStat; /* Structure populated by stat() */
int res; /* result of stat() */
int rc = SQLITE_OK; /* Return code */
ServerHMA *pHma = 0;
serverAssertMutexHeld();
res = stat(zPath, &sStat);
if( res!=0 ){
sqlite3_log(SQLITE_CANTOPEN, "Failed to stat(%s)", zPath);
rc = SQLITE_ERROR;
}else{
for(pHma=g_server.pHma; pHma; pHma=pHma->pNext){
if( sStat.st_dev==pHma->st_dev && sStat.st_ino==pHma->st_ino ) break;
}
if( pHma==0 ){
int nPath = strlen(zPath);
int nByte = sizeof(ServerHMA) + nPath+1 + 4;
pHma = (ServerHMA*)sqlite3_malloc(nByte);
if( pHma==0 ){
rc = SQLITE_NOMEM;
}else{
int i;
memset(pHma, 0, nByte);
pHma->zName = (char*)&pHma[1];
pHma->nClient = 1;
pHma->st_dev = sStat.st_dev;
pHma->st_ino = sStat.st_ino;
pHma->pNext = g_server.pHma;
g_server.pHma = pHma;
memcpy(pHma->zName, zPath, nPath);
memcpy(&pHma->zName[nPath], "-hma", 5);
pHma->fd = open(pHma->zName, O_RDWR|O_CREAT, 0644);
if( pHma->fd<0 ){
sqlite3_log(SQLITE_CANTOPEN, "Failed to open(%s)", pHma->zName);
rc = SQLITE_ERROR;
}
if( rc==SQLITE_OK ){
/* Write-lock the DMS slot. If successful, initialize the hma file. */
rc = posixLock(pHma->fd, 0, SERVER_WRITE_LOCK, 0);
if( rc==SQLITE_OK ){
res = ftruncate(pHma->fd, HMA_FILE_SIZE);
if( res!=0 ){
sqlite3_log(SQLITE_CANTOPEN,
"Failed to ftruncate(%s)", pHma->zName
);
rc = SQLITE_ERROR;
}
if( rc==SQLITE_OK ){
rc = serverMapFile(pHma);
}
if( rc==SQLITE_OK ){
memset(pHma->aMap, 0, HMA_FILE_SIZE);
}else{
rc = SQLITE_ERROR;
}
for(i=0; rc==SQLITE_OK && i<HMA_CLIENT_SLOTS; i++){
rc = sqlite3PagerRollbackJournal(pPager, i);
}
}else{
rc = serverMapFile(pHma);
}
if( rc==SQLITE_OK ){
rc = posixLock(pHma->fd, 0, SERVER_READ_LOCK, 1);
}
}
if( rc!=SQLITE_OK ){
serverDecrHmaRefcount(pHma);
pHma = 0;
}
}
}else{
pHma->nClient++;
}
}
*ppHma = pHma;
return rc;
}
static u32 *serverPageLockSlot(Server *p, Pgno pgno){
int iSlot = pgno % HMA_PAGELOCK_SLOTS;
return &p->pHma->aMap[1 + HMA_CLIENT_SLOTS + iSlot];
}
static u32 *serverClientSlot(Server *p, int iClient){
return &p->pHma->aMap[1 + iClient];
}
/*
** Close the "connection" and *-hma file. This deletes the object passed
** as the first argument.
*/
void sqlite3ServerDisconnect(Server *p, sqlite3_file *dbfd){
if( p->pHma ){
ServerHMA *pHma = p->pHma;
serverEnterMutex();
if( p->iClient>=0 ){
u32 *pSlot = serverClientSlot(p, p->iClient);
*pSlot = 0;
assert( pHma->aClient[p->iClient]==p );
pHma->aClient[p->iClient] = 0;
posixLock(pHma->fd, p->iClient+1, SERVER_NO_LOCK, 0);
}
if( dbfd
&& pHma->nClient==1
&& SQLITE_OK==sqlite3OsLock(dbfd, SQLITE_LOCK_EXCLUSIVE)
){
unlink(pHma->zName);
}
serverDecrHmaRefcount(pHma);
serverLeaveMutex();
}
sqlite3_free(p->aLock);
sqlite3_free(p);
}
static int serverRollbackClient(Server *p, int iBlock){
int rc;
sqlite3_log(SQLITE_NOTICE, "Rolling back failed client %d", iBlock);
/* Roll back any journal file for client iBlock. */
rc = sqlite3PagerRollbackJournal(p->pPager, iBlock);
/* Clear any locks held by client iBlock from the HMA file. */
if( rc==SQLITE_OK ){
int i;
for(i=0; i<HMA_PAGELOCK_SLOTS; i++){
u32 *pSlot = serverPageLockSlot(p, (Pgno)i);
u32 v = *pSlot;
while( 1 ){
u32 n = v & ~(1 << iBlock);
if( (v>>HMA_CLIENT_SLOTS)==iBlock+1 ){
n = n & ((1<<HMA_CLIENT_SLOTS)-1);
}
if( __sync_val_compare_and_swap(pSlot, v, n)==v ) break;
v = *pSlot;
}
}
}
return rc;
}
/*
** Open the *-hma file and "connect" to the system.
*/
int sqlite3ServerConnect(
Pager *pPager,
Server **ppOut,
int *piClient
){
const char *zPath = sqlite3PagerFilename(pPager, 0);
int rc = SQLITE_OK;
Server *p;
p = (Server*)sqlite3_malloc(sizeof(Server));
if( p==0 ){
rc = SQLITE_NOMEM;
}else{
memset(p, 0, sizeof(Server));
p->iClient = -1;
p->pPager = pPager;
serverEnterMutex();
rc = serverOpenHma(pPager, zPath, &p->pHma);
/* File is now mapped. Find a free client slot. */
if( rc==SQLITE_OK ){
int i;
Server **aClient = p->pHma->aClient;
int fd = p->pHma->fd;
for(i=0; i<HMA_CLIENT_SLOTS; i++){
if( aClient[i]==0 ){
int res = posixLock(fd, i+1, SERVER_WRITE_LOCK, 0);
if( res==SQLITE_OK ){
u32 *pSlot = serverClientSlot(p, i);
if( *pSlot ){
rc = serverRollbackClient(p, i);
}
posixLock(fd, i+1, (!rc ? SERVER_READ_LOCK : SERVER_NO_LOCK), 0);
break;
}
}
}
if( rc==SQLITE_OK ){
if( i>HMA_CLIENT_SLOTS ){
rc = SQLITE_BUSY;
}else{
u32 *pSlot = serverClientSlot(p, i);
*piClient = p->iClient = i;
aClient[i] = p;
*pSlot = 1;
}
}
}
serverLeaveMutex();
}
if( rc!=SQLITE_OK ){
sqlite3ServerDisconnect(p, 0);
p = 0;
}
*ppOut = p;
return rc;
}
static int serverOvercomeLock(
Server *p, /* Server connection */
int bWrite, /* True for a write-lock */
int bBlock, /* If true, block for this lock */
u32 v, /* Value of blocking page locking slot */
int *pbRetry /* OUT: True if caller should retry lock */
){
int rc = SQLITE_OK;
int iBlock = ((int)(v>>HMA_CLIENT_SLOTS))-1;
if( iBlock<0 || iBlock==p->iClient ){
for(iBlock=0; iBlock<HMA_CLIENT_SLOTS; iBlock++){
if( iBlock!=p->iClient && (v & (1<<iBlock)) ) break;
}
}
assert( iBlock<HMA_CLIENT_SLOTS );
serverEnterMutex();
if( 0==p->pHma->aClient[iBlock] ){
rc = posixLock(p->pHma->fd, iBlock+1, SERVER_WRITE_LOCK, 0);
if( rc==SQLITE_OK ){
rc = serverRollbackClient(p, iBlock);
/* Release the lock on slot iBlock */
posixLock(p->pHma->fd, iBlock+1, SERVER_NO_LOCK, 0);
if( rc==SQLITE_OK ){
*pbRetry = 1;
}
}else if( rc==SQLITE_BUSY ){
if( bBlock ){
rc = posixLock(p->pHma->fd, iBlock+1, SERVER_READ_LOCK, 1);
if( rc==SQLITE_OK ){
posixLock(p->pHma->fd, iBlock+1, SERVER_NO_LOCK, 0);
*pbRetry = 1;
}
}
if( rc==SQLITE_BUSY ){
rc = SQLITE_OK;
}
}
}
serverLeaveMutex();
return rc;
}
/*
** Begin a transaction.
*/
int sqlite3ServerBegin(Server *p){
#if 1
int rc = posixLock(p->pHma->fd, p->iClient+1, SERVER_WRITE_LOCK, 1);
if( rc ) return rc;
#endif
return sqlite3ServerLock(p, 1, 0, 1);
}
/*
** End a transaction (and release all locks).
*/
int sqlite3ServerEnd(Server *p){
int i;
for(i=0; i<p->nLock; i++){
u32 *pSlot = serverPageLockSlot(p, p->aLock[i]);
while( 1 ){
u32 v = *pSlot;
u32 n = v;
if( (v>>HMA_CLIENT_SLOTS)==p->iClient+1 ){
n = n & ((1 << HMA_CLIENT_SLOTS)-1);
}
n = n & ~(1 << p->iClient);
if( __sync_val_compare_and_swap(pSlot, v, n)==v ) break;
}
if( p->aLock[i]==0 ){
struct timeval t2;
i64 nUs;
gettimeofday(&t2, 0);
nUs = (i64)t2.tv_sec * 1000000 + t2.tv_usec - p->iUsWrite;
p->nUsWrite += nUs;
if( (p->nUsWrite / 1000000)!=((p->nUsWrite + nUs)/1000000) ){
sqlite3_log(SQLITE_WARNING,
"Cumulative WRITER time: %lldms\n", p->nUsWrite/1000
);
}
}
}
p->nLock = 0;
#if 1
return posixLock(p->pHma->fd, p->iClient+1, SERVER_READ_LOCK, 0);
#endif
return SQLITE_OK;
}
/*
** Release all write-locks.
*/
int sqlite3ServerReleaseWriteLocks(Server *p){
int rc = SQLITE_OK;
return rc;
}
/*
** Return the client id of the client that currently holds the EXCLUSIVE
** or RESERVED lock according to page-locking slot value v. Or -1 if no
** client holds such a lock.
*/
int serverWriteLocker(u32 v){
return ((int)(v >> HMA_CLIENT_SLOTS)) - 1;
}
/*
** Lock page pgno for reading (bWrite==0) or writing (bWrite==1).
**
** If parameter bBlock is non-zero, then make this a blocking lock if
** possible.
*/
int sqlite3ServerLock(Server *p, Pgno pgno, int bWrite, int bBlock){
int rc = SQLITE_OK;
int bReserved = 0;
u32 *pSlot = serverPageLockSlot(p, pgno);
/* Grow the aLock[] array, if required */
if( p->nLock==p->nAlloc ){
int nNew = p->nAlloc ? p->nAlloc*2 : 128;
u32 *aNew;
aNew = (u32*)sqlite3_realloc(p->aLock, sizeof(u32)*nNew);
if( aNew==0 ){
rc = SQLITE_NOMEM_BKPT;
}else{
p->aLock = aNew;
p->nAlloc = nNew;
}
}
if( rc==SQLITE_OK ){
u32 v = *pSlot;
/* Check if the required lock is already held. If so, exit this function
** early. Otherwise, add an entry to the aLock[] array to record the fact
** that the lock may need to be released. */
if( bWrite ){
int iLock = ((int)(v>>HMA_CLIENT_SLOTS)) - 1;
if( iLock==p->iClient ) goto server_lock_out;
}else{
if( v & (1<<p->iClient) ) goto server_lock_out;
}
p->aLock[p->nLock++] = pgno;
while( 1 ){
u32 n;
int w;
u32 mask = (bWrite ? (((1<<HMA_CLIENT_SLOTS)-1) & ~(1<<p->iClient)) : 0);
while( ((w = serverWriteLocker(v))>=0 && w!=p->iClient) || (v & mask) ){
int bRetry = 0;
if( w<0 && bWrite && bBlock ){
/* Attempt a RESERVED lock before anything else */
n = v | ((p->iClient+1) << HMA_CLIENT_SLOTS);
assert( serverWriteLocker(n)==p->iClient );
if( __sync_val_compare_and_swap(pSlot, v, n)!=v ){
v = *pSlot;
continue;
}
v = n;
bReserved = 1;
}
rc = serverOvercomeLock(p, bWrite, bBlock, v, &bRetry);
if( rc!=SQLITE_OK ) goto server_lock_out;
if( bRetry==0 ){
/* There is a conflicting lock. Cannot obtain this lock. */
sqlite3_log(SQLITE_BUSY_DEADLOCK, "Conflict at page %d", (int)pgno);
rc = SQLITE_BUSY_DEADLOCK;
goto server_lock_out;
}
v = *pSlot;
}
n = v | (1 << p->iClient);
if( bWrite ){
n = n | ((p->iClient+1) << HMA_CLIENT_SLOTS);
}
if( __sync_val_compare_and_swap(pSlot, v, n)==v ) break;
v = *pSlot;
}
}
server_lock_out:
if( rc!=SQLITE_OK && bReserved ){
u32 n;
u32 v;
do{
v = *pSlot;
assert( serverWriteLocker(v)==p->iClient );
n = v & ((1<<HMA_CLIENT_SLOTS)-1);
}while( __sync_val_compare_and_swap(pSlot, v, n)!=v );
}
if( pgno==0 ){
struct timeval t1;
gettimeofday(&t1, 0);
p->iUsWrite = ((i64)t1.tv_sec * 1000000) + (i64)t1.tv_usec;
}
assert( rc!=SQLITE_OK || sqlite3ServerHasLock(p, pgno, bWrite) );
return rc;
}
int sqlite3ServerHasLock(Server *p, Pgno pgno, int bWrite){
u32 v = *serverPageLockSlot(p, pgno);
if( bWrite ){
return (v>>HMA_CLIENT_SLOTS)==(p->iClient+1);
}
return (v & (1 << p->iClient))!=0;
}
#endif /* ifdef SQLITE_SERVER_EDITION */
+36
View File
@@ -0,0 +1,36 @@
/*
** 2017 April 24
**
** 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.
**
*************************************************************************
*/
#ifdef SQLITE_SERVER_EDITION
#ifndef SQLITE_SERVER_H
#define SQLITE_SERVER_H
typedef struct Server Server;
int sqlite3ServerConnect(Pager *pPager, Server **ppOut, int *piClient);
void sqlite3ServerDisconnect(Server *p, sqlite3_file *dbfd);
int sqlite3ServerBegin(Server *p);
int sqlite3ServerEnd(Server *p);
int sqlite3ServerReleaseWriteLocks(Server *p);
int sqlite3ServerLock(Server *p, Pgno pgno, int bWrite, int bBlock);
int sqlite3ServerHasLock(Server *p, Pgno pgno, int bWrite);
#endif /* SQLITE_SERVER_H */
#endif /* SQLITE_SERVER_EDITION */
+1
View File
@@ -497,6 +497,7 @@ int sqlite3_exec(
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
#define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8))
#define SQLITE_BUSY_DEADLOCK (SQLITE_BUSY | (3<<8))
#define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8))
#define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2<<8))
#define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3<<8))
+1
View File
@@ -1101,6 +1101,7 @@ typedef int VList;
#include "pcache.h"
#include "os.h"
#include "mutex.h"
#include "server.h"
/* The SQLITE_EXTRA_DURABLE compile-time option used to set the default
** synchronous setting to EXTRA. It is no longer supported.
+7 -6
View File
@@ -2639,12 +2639,13 @@ int sqlite3VdbeHalt(Vdbe *p){
/* Check for one of the special errors */
mrc = p->rc & 0xff;
isSpecialError = mrc==SQLITE_NOMEM || mrc==SQLITE_IOERR
|| mrc==SQLITE_INTERRUPT || mrc==SQLITE_FULL;
|| mrc==SQLITE_INTERRUPT || mrc==SQLITE_FULL
|| p->rc==SQLITE_BUSY_DEADLOCK;
if( isSpecialError ){
/* If the query was read-only and the error code is SQLITE_INTERRUPT,
** no rollback is necessary. Otherwise, at least a savepoint
** transaction must be rolled back to restore the database to a
** consistent state.
/* If the query was read-only and the error code is SQLITE_INTERRUPT
** or SQLITE_BUSY_SERVER, no rollback is necessary. Otherwise, at
** least a savepoint transaction must be rolled back to restore the
** database to a consistent state.
**
** Even if the statement is read-only, it is important to perform
** a statement or transaction rollback operation. If the error
@@ -2653,7 +2654,7 @@ int sqlite3VdbeHalt(Vdbe *p){
** pagerStress() in pager.c), the rollback is required to restore
** the pager to a consistent state.
*/
if( !p->readOnly || mrc!=SQLITE_INTERRUPT ){
if( !p->readOnly || (mrc!=SQLITE_INTERRUPT && mrc!=SQLITE_BUSY) ){
if( (mrc==SQLITE_NOMEM || mrc==SQLITE_FULL) && p->usesStmtJournal ){
eStatementOp = SAVEPOINT_ROLLBACK;
}else{
+91 -11
View File
@@ -454,8 +454,17 @@ struct Wal {
#ifdef SQLITE_ENABLE_SNAPSHOT
WalIndexHdr *pSnapshot; /* Start transaction here if not NULL */
#endif
#ifdef SQLITE_SERVER_EDITION
Server *pServer;
#endif
};
#ifdef SQLITE_SERVER_EDITION
# define walIsServer(p) ((p)->pServer!=0)
#else
# define walIsServer(p) 0
#endif
/*
** Candidate values for Wal.exclusiveMode.
*/
@@ -1261,6 +1270,14 @@ static void walIndexClose(Wal *pWal, int isDelete){
}
}
#ifdef SQLITE_SERVER_EDITION
int sqlite3WalServer(Wal *pWal, Server *pServer){
assert( pWal->pServer==0 );
pWal->pServer = pServer;
return SQLITE_OK;
}
#endif
/*
** Open a connection to the WAL file zWalName. The database file must
** already be opened on connection pDbFd. The buffer that zWalName points
@@ -1892,6 +1909,9 @@ static int walCheckpoint(
** indicate that the log file contains zero valid frames. */
walRestartHdr(pWal, salt1);
rc = sqlite3OsTruncate(pWal->pWalFd, 0);
}else if( walIsServer(pWal) ){
assert( eMode==SQLITE_CHECKPOINT_RESTART );
walRestartHdr(pWal, salt1);
}
walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
}
@@ -2051,6 +2071,14 @@ static int walIndexTryHdr(Wal *pWal, int *pChanged){
return 0;
}
static int walIndexWriteLock(Wal *pWal){
if( walIsServer(pWal) ){
return sqlite3ServerLock(pWal->pServer, 0, 1, 0);
}else{
return walLockExclusive(pWal, WAL_WRITE_LOCK, 1);
}
}
/*
** Read the wal-index header from the wal-index and into pWal->hdr.
** If the wal-header appears to be corrupt, try to reconstruct the
@@ -2091,11 +2119,12 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){
assert( badHdr==0 || pWal->writeLock==0 );
if( badHdr ){
if( pWal->readOnly & WAL_SHM_RDONLY ){
assert( walIsServer(pWal)==0 );
if( SQLITE_OK==(rc = walLockShared(pWal, WAL_WRITE_LOCK)) ){
walUnlockShared(pWal, WAL_WRITE_LOCK);
rc = SQLITE_READONLY_RECOVERY;
}
}else if( SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1)) ){
}else if( SQLITE_OK==(rc = walIndexWriteLock(pWal)) ){
pWal->writeLock = 1;
if( SQLITE_OK==(rc = walIndexPage(pWal, 0, &page0)) ){
badHdr = walIndexTryHdr(pWal, pChanged);
@@ -2109,7 +2138,9 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){
}
}
pWal->writeLock = 0;
walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
if( walIsServer(pWal)==0 ){
walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
}
}
}
@@ -2249,6 +2280,9 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
}
}
assert( rc==SQLITE_OK );
if( walIsServer(pWal) ) return SQLITE_OK;
pInfo = walCkptInfo(pWal);
if( !useWal && pInfo->nBackfill==pWal->hdr.mxFrame
#ifdef SQLITE_ENABLE_SNAPSHOT
@@ -2588,7 +2622,15 @@ int sqlite3WalFindFrame(
int iMinHash;
/* This routine is only be called from within a read transaction. */
assert( pWal->readLock>=0 || pWal->lockError );
assert( walIsServer(pWal) || pWal->readLock>=0 || pWal->lockError );
assert( walIsServer(pWal)==0 || pWal->writeLock==0
|| sqlite3ServerHasLock(pWal->pServer, 0, 1)
);
if( walIsServer(pWal) && pWal->writeLock==0 ){
/* A server mode connection must read from the most recent snapshot. */
iLast = walIndexHdr(pWal)->mxFrame;
}
/* If the "last page" field of the wal-index header snapshot is 0, then
** no data will be read from the wal under any circumstances. Return early
@@ -2700,7 +2742,7 @@ int sqlite3WalReadFrame(
** Return the size of the database in pages (or zero, if unknown).
*/
Pgno sqlite3WalDbsize(Wal *pWal){
if( pWal && ALWAYS(pWal->readLock>=0) ){
if( pWal && (walIsServer(pWal) || ALWAYS(pWal->readLock>=0)) ){
return pWal->hdr.nPage;
}
return 0;
@@ -2725,13 +2767,18 @@ int sqlite3WalBeginWriteTransaction(Wal *pWal){
/* Cannot start a write transaction without first holding a read
** transaction. */
assert( pWal->readLock>=0 );
assert( walIsServer(pWal) || pWal->readLock>=0 );
assert( pWal->writeLock==0 && pWal->iReCksum==0 );
if( pWal->readOnly ){
return SQLITE_READONLY;
}
/* For a server connection, do nothing at this point. */
if( walIsServer(pWal) ){
return SQLITE_OK;
}
/* Only one writer allowed at a time. Get the write lock. Return
** SQLITE_BUSY if unable.
*/
@@ -2782,7 +2829,7 @@ int sqlite3WalEndWriteTransaction(Wal *pWal){
*/
int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx){
int rc = SQLITE_OK;
if( ALWAYS(pWal->writeLock) ){
if( pWal->writeLock ){
Pgno iMax = pWal->hdr.mxFrame;
Pgno iFrame;
@@ -2872,11 +2919,13 @@ int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){
** if an error occurs.
*/
static int walRestartLog(Wal *pWal){
volatile WalCkptInfo *pInfo = walCkptInfo(pWal);
int rc = SQLITE_OK;
int cnt;
if( pWal->readLock==0 ){
volatile WalCkptInfo *pInfo = walCkptInfo(pWal);
if( pWal->readLock==0
|| (walIsServer(pWal) && pInfo->nBackfill==pWal->hdr.mxFrame)
){
assert( pInfo->nBackfill==pWal->hdr.mxFrame );
if( pInfo->nBackfill>0 ){
u32 salt1;
@@ -2898,6 +2947,7 @@ static int walRestartLog(Wal *pWal){
return rc;
}
}
if( walIsServer(pWal) ) return rc;
walUnlockShared(pWal, WAL_READ_LOCK(0));
pWal->readLock = -1;
cnt = 0;
@@ -3058,7 +3108,23 @@ int sqlite3WalFrames(
WalIndexHdr *pLive; /* Pointer to shared header */
assert( pList );
assert( pWal->writeLock );
assert( pWal->writeLock || walIsServer(pWal) );
if( pWal->writeLock==0 ){
int bDummy = 0;
#if 0
rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1);
#endif
rc = sqlite3ServerLock(pWal->pServer, 0, 1, 1);
if( rc==SQLITE_OK ){
pWal->writeLock = 1;
rc = walIndexTryHdr(pWal, &bDummy);
}
if( rc!=SQLITE_OK ){
return rc;
}
assert( sqlite3ServerHasLock(pWal->pServer, 0, 1) );
}
assert( walIsServer(pWal)==0 || sqlite3ServerHasLock(pWal->pServer, 0, 1) );
/* If this frame set completes a transaction, then nTruncate>0. If
** nTruncate==0 then this frame set does not complete the transaction. */
@@ -3079,7 +3145,7 @@ int sqlite3WalFrames(
/* See if it is possible to write these frames into the start of the
** log file, instead of appending to it at pWal->hdr.mxFrame.
*/
if( SQLITE_OK!=(rc = walRestartLog(pWal)) ){
if( walIsServer(pWal)==0 && SQLITE_OK!=(rc = walRestartLog(pWal)) ){
return rc;
}
@@ -3331,7 +3397,19 @@ int sqlite3WalCheckpoint(
** lock is successfully obtained.
*/
if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){
rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_WRITE_LOCK, 1);
if( walIsServer(pWal) ){
rc = sqlite3ServerBegin(pWal->pServer);
if( rc!=SQLITE_OK ) goto ckpt_out;
if( eMode>=SQLITE_CHECKPOINT_RESTART ){
/* Exclusive lock on page 1. This is exclusive access to the db. */
rc = sqlite3ServerLock(pWal->pServer, 1, 1, 1);
}else{
/* Take the server write-lock ("page" 0) */
rc = sqlite3ServerLock(pWal->pServer, 0, 1, 1);
}
}else{
rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_WRITE_LOCK, 1);
}
if( rc==SQLITE_OK ){
pWal->writeLock = 1;
}else if( rc==SQLITE_BUSY ){
@@ -3376,10 +3454,12 @@ int sqlite3WalCheckpoint(
}
/* Release the locks. */
ckpt_out:
sqlite3WalEndWriteTransaction(pWal);
walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1);
pWal->ckptLock = 0;
WALTRACE(("WAL%p: checkpoint %s\n", pWal, rc ? "failed" : "ok"));
if( walIsServer(pWal) ) sqlite3ServerEnd(pWal->pServer);
return (rc==SQLITE_OK && eMode!=eMode2 ? SQLITE_BUSY : rc);
}
+4
View File
@@ -144,5 +144,9 @@ int sqlite3WalFramesize(Wal *pWal);
/* Return the sqlite3_file object for the WAL file */
sqlite3_file *sqlite3WalFile(Wal *pWal);
#ifdef SQLITE_SERVER_EDITION
int sqlite3WalServer(Wal *pWal, Server *pServer);
#endif
#endif /* ifndef SQLITE_OMIT_WAL */
#endif /* SQLITE_WAL_H */
+9
View File
@@ -273,6 +273,15 @@ test_suite "fts5" -prefix "" -description {
All FTS5 tests.
} -files [glob -nocomplain $::testdir/../ext/fts5/test/*.test]
test_suite "server" -prefix "" -description {
All server-edition tests.
} -files [
test_set \
select1.test \
[glob -nocomplain $::testdir/server*.test] \
-exclude *server1.test
]
test_suite "fts5-light" -prefix "" -description {
All FTS5 tests.
} -files [
+117
View File
@@ -0,0 +1,117 @@
# 2017 April 25
#
# 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 script is testing the server mode of SQLite.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix server2
#-------------------------------------------------------------------------
# Check that the *-hma file is deleted correctly.
#
do_execsql_test 1.0 {
CREATE TABLE t1(a, b);
} {}
do_test 1.1 {
file exists test.db-hma
} {1}
do_test 1.2 {
db close
file exists test.db-hma
} {0}
do_test 1.3 {
sqlite3 db test.db
db eval { CREATE TABLE t2(a, b) }
sqlite3 db2 test.db
db2 eval { CREATE TABLE t3(a, b) }
file exists test.db-hma
} {1}
do_test 1.4 {
db2 close
file exists test.db-hma
} {1}
integrity_check 1.5
do_test 1.6 {
db close
file exists test.db-hma
} {0}
#-------------------------------------------------------------------------
#
reset_db
sqlite3 db2 test.db
do_execsql_test 2.0 {
CREATE TABLE t1(a, b);
CREATE TABLE t2(c, d);
}
# Two concurrent transactions committed.
#
do_test 2.1 {
db eval {
BEGIN;
INSERT INTO t1 VALUES(1, 2);
}
db2 eval {
BEGIN;
INSERT INTO t2 VALUES(3, 4);
}
} {}
do_test 2.2 {
lsort [glob test.db*]
} {test.db test.db-hma test.db-journal0 test.db-journal1}
do_test 2.3.1 { db eval COMMIT } {}
do_test 2.3.2 { db2 eval COMMIT } {}
do_execsql_test 2.4 {SELECT * FROM t1, t2} {1 2 3 4}
do_test 2.5 {
lsort [glob test.db*]
} {test.db test.db-hma test.db-journal0 test.db-journal1}
do_test 2.6 {
execsql {BEGIN}
execsql {INSERT INTO t1 VALUES(5, 6)}
execsql {BEGIN} db2
catchsql {INSERT INTO t1 VALUES(7, 8)} db2
} {1 {database is locked}}
do_test 2.7 {
# Transaction is automatically rolled back in this case.
sqlite3_get_autocommit db2
} {1}
do_test 2.8 {
execsql COMMIT
execsql { SELECT * FROM t1 } db2
} {1 2 5 6}
db2 close
#-------------------------------------------------------------------------
#
reset_db
do_execsql_test 3.0 {
CREATE TABLE t1(a, b);
}
do_test 3.1 {
glob test.db*
} {test.db-journal0 test.db test.db-hma}
do_test 3.2 {
db close
glob test.db*
} {test.db}
finish_test
+45
View File
@@ -0,0 +1,45 @@
# 2017 April 25
#
# 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 script is testing the server mode of SQLite.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/lock_common.tcl
set testprefix server3
db close
do_multiclient_test tn {
do_test $tn.1 {
sql1 { CREATE TABLE t1(a, b) }
sql2 { CREATE TABLE t2(a, b) }
} {}
do_test $tn.2 {
sql1 {
INSERT INTO t2 VALUES(1, 2);
BEGIN;
INSERT INTO t1 VALUES(1, 2);
}
} {}
do_test $tn.3 { csql2 { SELECT * FROM t1 } } {1 {database is locked}}
do_test $tn.4 { csql2 { SELECT * FROM t1 } } {1 {database is locked}}
do_test $tn.5 { sql2 { SELECT * FROM t2 } } {1 2}
}
finish_test
+68
View File
@@ -0,0 +1,68 @@
# 2017 April 27
#
# 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.
#
#***********************************************************************
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix servercrash
ifcapable !crashtest {
finish_test
return
}
do_not_use_codec
do_execsql_test 1.0 {
PRAGMA page_siBlockze = 4096;
PRAGMA auto_vacuum = OFF;
CREATE TABLE t1(a, b);
CREATE TABLE t2(c, d);
INSERT INTO t1 VALUES(1, 2), (3, 4);
INSERT INTO t2 VALUES(1, 2), (3, 4);
}
for {set i 0} {$i < 10} {incr i} {
do_test 1.$i.1 {
crashsql -delay 1 -file test.db { INSERT INTO t1 VALUES(5, 6) }
} {1 {child process exited abnormally}}
do_execsql_test 1.$i.2 {
SELECT * FROM t1
} {1 2 3 4}
}
for {set i 0} {$i < 10} {incr i} {
do_test 2.$i.1 {
crashsql -delay 1 -file test.db { INSERT INTO t1 VALUES(5, 6) }
} {1 {child process exited abnormally}}
do_test 2.$i.2 {
sqlite3 dbX test.db
execsql { SELECT * FROM t1 } dbX
} {1 2 3 4}
dbX close
}
db close
for {set i 0} {$i < 10} {incr i} {
do_test 3.$i.1 {
crashsql -delay 1 -file test.db { INSERT INTO t1 VALUES(5, 6) }
} {1 {child process exited abnormally}}
sqlite3 db test.db
do_execsql_test 3.$i.2 { SELECT * FROM t1 } {1 2 3 4}
db close
}
finish_test
+138
View File
@@ -0,0 +1,138 @@
# 2017 April 25
#
# 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 script is testing the server mode of SQLite.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix serverwal
# Check files are created and deleted as expected.
#
do_execsql_test 1.0 {
PRAGMA journal_mode = wal;
} {wal}
do_execsql_test 1.1 {
CREATE TABLE t1(a, b);
}
do_execsql_test 1.2 {
SELECT * FROM t1;
} {}
do_test 1.3 {
lsort [glob test.db*]
} {test.db test.db-hma test.db-shm test.db-wal}
do_test 1.4 {
db close
glob test.db*
} {test.db}
#-------------------------------------------------------------------------
# Two concurrent transactions.
#
do_test 2.0 {
sqlite3 db test.db
sqlite3 db2 test.db
db eval {
CREATE TABLE t2(a, b);
}
} {}
do_test 2.1 {
execsql {
BEGIN;
INSERT INTO t1 VALUES(1, 2);
} db
execsql {
BEGIN;
INSERT INTO t2 VALUES(1, 2);
} db2
} {}
do_test 2.2 {
execsql COMMIT db
execsql COMMIT db2
} {}
db close
db2 close
#-------------------------------------------------------------------------
# That the wal file can be wrapped around.
#
reset_db
do_execsql_test 3.0 {
PRAGMA journal_mode = wal;
CREATE TABLE ttt(a, b);
INSERT INTO ttt VALUES(1, 2);
INSERT INTO ttt VALUES(3, 4);
INSERT INTO ttt VALUES(5, 6);
INSERT INTO ttt VALUES(7, 8);
INSERT INTO ttt VALUES(9, 10);
} {wal}
do_test 3.1 {
set N [file size test.db-wal]
execsql {
PRAGMA wal_checkpoint = restart;
INSERT INTO ttt VALUES(11, 12);
INSERT INTO ttt VALUES(13, 14);
}
expr {$N == [file size test.db-wal]}
} {1}
#-------------------------------------------------------------------------
# That ROLLBACK appears to work.
#
reset_db
do_execsql_test 4.0 {
PRAGMA cache_size = 10;
CREATE TABLE ttt(a, b);
CREATE INDEX yyy ON ttt(b, a);
PRAGMA journal_mode = wal;
WITH s(i) AS (
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100
)
INSERT INTO ttt SELECT randomblob(100), randomblob(100) FROM s;
} {wal}
do_execsql_test 4.1 {
PRAGMA integrity_check;
BEGIN;
UPDATE ttt SET b=a;
ROLLBACK;
PRAGMA integrity_check;
} {ok ok}
reset_db
do_execsql_test 5.1 {
CREATE TABLE xyz(a);
PRAGMA journal_mode = wal;
INSERT INTO xyz VALUES(1);
INSERT INTO xyz VALUES(2);
INSERT INTO xyz VALUES(3);
} {wal}
breakpoint
do_test 5.2 {
sqlite3 db2 test.db
execsql { SELECT * FROM xyz } db2
} {1 2 3}
do_execsql_test 5.3 {
PRAGMA wal_checkpoint = restart
} {0 0 0}
do_test 5.4 {
execsql { SELECT * FROM xyz } db2
} {1 2 3}
finish_test
+4
View File
@@ -586,6 +586,10 @@ proc reset_db {} {
forcedelete test.db
forcedelete test.db-journal
forcedelete test.db-wal
for {set i 0} {$i < 16} {incr i} {
forcedelete test.db-journal$i
}
sqlite3 db ./test.db
set ::DB [sqlite3_connection_pointer db]
if {[info exists ::SETUP_SQL]} {
+2
View File
@@ -114,6 +114,7 @@ foreach hdr {
pcache.h
pragma.h
rtree.h
server.h
sqlite3session.h
sqlite3.h
sqlite3ext.h
@@ -319,6 +320,7 @@ foreach file {
rowset.c
pager.c
wal.c
server.c
btmutex.c
btree.c
+442
View File
@@ -0,0 +1,442 @@
/*
** 2017 June 7
**
** 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.
**
*************************************************************************
**
** Simple multi-threaded server used for informal testing of concurrency
** between connections in different threads. Listens for tcp/ip connections
** on port 9999 of the 127.0.0.1 interface only. To build:
**
** gcc -g $(TOP)/tool/tserver.c sqlite3.o -lpthread -o tserver
**
** To run using "x.db" as the db file:
**
** ./tserver x.db
**
** To connect, open a client socket on port 9999 and start sending commands.
** Commands are either SQL - which must be terminated by a semi-colon, or
** dot-commands, which must be terminated by a newline. If an SQL statement
** is seen, it is prepared and added to an internal list.
**
** Dot-commands are:
**
** .list Display all SQL statements in the list.
** .quit Disconnect.
** .run Run all SQL statements in the list.
** .repeats N Configure the number of repeats per ".run".
** .seconds N Configure the number of seconds to ".run" for.
**
** Example input:
**
** BEGIN;
** INSERT INTO t1 VALUES(randomblob(10), randomblob(100));
** INSERT INTO t1 VALUES(randomblob(10), randomblob(100));
** INSERT INTO t1 VALUES(randomblob(10), randomblob(100));
** COMMIT;
** .repeats 100000
** .run
**
*/
#define TSERVER_PORTNUMBER 9999
#include <arpa/inet.h>
#include <assert.h>
#include <pthread.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include "sqlite3.h"
/* Database used by this server */
static char *zDatabaseName = 0;
typedef struct ClientCtx ClientCtx;
struct ClientCtx {
sqlite3 *db; /* Database handle for this client */
int fd; /* Client fd */
int nRepeat; /* Number of times to repeat SQL */
int nSecond; /* Number of seconds to run for */
sqlite3_stmt **apPrepare; /* Array of prepared statements */
int nPrepare; /* Valid size of apPrepare[] */
int nAlloc; /* Allocated size of apPrepare[] */
};
static int is_eol(int i){
return (i=='\n' || i=='\r');
}
static int is_whitespace(int i){
return (i==' ' || i=='\t' || is_eol(i));
}
static void trim_string(const char **pzStr, int *pnStr){
const char *zStr = *pzStr;
int nStr = *pnStr;
while( nStr>0 && is_whitespace(zStr[0]) ){
zStr++;
nStr--;
}
while( nStr>0 && is_whitespace(zStr[nStr-1]) ){
nStr--;
}
*pzStr = zStr;
*pnStr = nStr;
}
static int send_message(ClientCtx *p, const char *zFmt, ...){
char *zMsg;
va_list ap; /* Vararg list */
va_start(ap, zFmt);
int res = -1;
zMsg = sqlite3_vmprintf(zFmt, ap);
if( zMsg ){
res = write(p->fd, zMsg, strlen(zMsg));
}
sqlite3_free(zMsg);
va_end(ap);
return (res<0);
}
static int handle_some_sql(ClientCtx *p, const char *zSql, int nSql){
const char *zTail = zSql;
int nTail = nSql;
int rc = SQLITE_OK;
while( rc==SQLITE_OK ){
if( p->nPrepare>=p->nAlloc ){
int nByte = (p->nPrepare+32) * sizeof(sqlite3_stmt*);
sqlite3_stmt **apNew = sqlite3_realloc(p->apPrepare, nByte);
if( apNew ){
p->apPrepare = apNew;
p->nAlloc = p->nPrepare+32;
}else{
rc = SQLITE_NOMEM;
break;
}
}
rc = sqlite3_prepare_v2(
p->db, zTail, nTail, &p->apPrepare[p->nPrepare], &zTail
);
if( rc!=SQLITE_OK ){
send_message(p, "error - %s\n", sqlite3_errmsg(p->db));
rc = 1;
break;
}
if( p->apPrepare[p->nPrepare]==0 ){
break;
}
p->nPrepare++;
nTail = nSql - (zTail-zSql);
rc = send_message(p, "ok (%d SQL statements)\n", p->nPrepare);
}
return rc;
}
static sqlite3_int64 get_timer(void){
struct timeval t;
gettimeofday(&t, 0);
return ((sqlite3_int64)t.tv_usec / 1000) + ((sqlite3_int64)t.tv_sec * 1000);
}
static void clear_sql(ClientCtx *p){
int j;
for(j=0; j<p->nPrepare; j++){
sqlite3_finalize(p->apPrepare[j]);
}
p->nPrepare = 0;
}
static int handle_dot_command(ClientCtx *p, const char *zCmd, int nCmd){
assert( zCmd[0]=='.' );
int n;
int rc = 0;
const char *z = &zCmd[1];
const char *zArg;
int nArg;
for(n=0; n<(nCmd-1); n++){
if( is_whitespace(z[n]) ) break;
}
zArg = &z[n];
nArg = nCmd-n;
trim_string(&zArg, &nArg);
if( n>=1 && n<=4 && 0==strncmp(z, "list", n) ){
int i;
for(i=0; rc==0 && i<p->nPrepare; i++){
const char *zSql = sqlite3_sql(p->apPrepare[i]);
int nSql = strlen(zSql);
trim_string(&zSql, &nSql);
rc = send_message(p, "%d: %.*s\n", i, nSql, zSql);
}
}
else if( n>=1 && n<=4 && 0==strncmp(z, "quit", n) ){
rc = 1;
}
else if( n>=2 && n<=7 && 0==strncmp(z, "repeats", n) ){
if( nArg ){
p->nRepeat = strtol(zArg, 0, 0);
if( p->nRepeat>0 ) p->nSecond = 0;
}
rc = send_message(p, "ok (repeat=%d)\n", p->nRepeat);
}
else if( n>=2 && n<=3 && 0==strncmp(z, "run", n) ){
int i, j;
int nBusy = 0;
sqlite3_int64 t0 = get_timer();
sqlite3_int64 t1 = t0;
int nT1 = 0;
int nTBusy1 = 0;
for(j=0; (p->nRepeat<=0 || j<p->nRepeat) && rc==SQLITE_OK; j++){
sqlite3_int64 t2;
for(i=0; i<p->nPrepare && rc==SQLITE_OK; i++){
sqlite3_stmt *pStmt = p->apPrepare[i];
/* Execute the statement */
while( sqlite3_step(pStmt)==SQLITE_ROW );
rc = sqlite3_reset(pStmt);
if( (rc & 0xFF)==SQLITE_BUSY ){
if( sqlite3_get_autocommit(p->db)==0 ){
sqlite3_exec(p->db, "ROLLBACK", 0, 0, 0);
}
nBusy++;
rc = SQLITE_OK;
break;
}
else if( rc!=SQLITE_OK ){
send_message(p, "error - %s\n", sqlite3_errmsg(p->db));
}
}
t2 = get_timer();
if( t2>=(t1+1000) ){
int nMs = (t2 - t1);
int nDone = (j+1 - nBusy - nT1);
rc = send_message(
p, "(%d done @ %d per second, %d busy)\n",
nDone, (1000*nDone + nMs/2) / nMs, nBusy - nTBusy1
);
t1 = t2;
nT1 = j+1 - nBusy;
nTBusy1 = nBusy;
if( p->nSecond>0 && (p->nSecond*1000)<=t1-t0 ) break;
}
}
if( rc==SQLITE_OK ){
send_message(p, "ok (%d/%d SQLITE_BUSY)\n", nBusy, j);
}
clear_sql(p);
}
else if( n>=1 && n<=7 && 0==strncmp(z, "seconds", n) ){
if( nArg ){
p->nSecond = strtol(zArg, 0, 0);
if( p->nSecond>0 ) p->nRepeat = 0;
}
rc = send_message(p, "ok (repeat=%d)\n", p->nRepeat);
}
else{
send_message(p,
"unrecognized dot command: %.*s\n"
"should be \"list\", \"run\", \"repeats\", or \"seconds\"\n", n, z
);
rc = 1;
}
return rc;
}
static void *handle_client(void *pArg){
char zCmd[32*1024]; /* Read buffer */
int nCmd = 0; /* Valid bytes in zCmd[] */
int res; /* Result of read() call */
int rc = SQLITE_OK;
int j;
ClientCtx ctx;
memset(&ctx, 0, sizeof(ClientCtx));
ctx.fd = (int)(intptr_t)pArg;
ctx.nRepeat = 1;
rc = sqlite3_open(zDatabaseName, &ctx.db);
if( rc!=SQLITE_OK ){
fprintf(stderr, "sqlite3_open(): %s\n", sqlite3_errmsg(ctx.db));
return 0;
}
while( rc==SQLITE_OK ){
int i;
int iStart;
int nConsume;
res = read(ctx.fd, &zCmd[nCmd], sizeof(zCmd)-nCmd-1);
if( res<=0 ) break;
nCmd += res;
if( nCmd>=sizeof(zCmd)-1 ){
fprintf(stderr, "oversized (>32KiB) message\n");
res = 0;
break;
}
zCmd[nCmd] = '\0';
do {
nConsume = 0;
/* Gobble up any whitespace */
iStart = 0;
while( is_whitespace(zCmd[iStart]) ) iStart++;
if( zCmd[iStart]=='.' ){
/* This is a dot-command. Search for end-of-line. */
for(i=iStart; i<nCmd; i++){
if( is_eol(zCmd[i]) ){
rc = handle_dot_command(&ctx, &zCmd[iStart], i-iStart);
nConsume = i+1;
break;
}
}
}else{
int iSemi;
char c = 0;
for(iSemi=iStart; iSemi<nCmd; iSemi++){
if( zCmd[iSemi]==';' ){
c = zCmd[iSemi+1];
zCmd[iSemi+1] = '\0';
break;
}
}
if( iSemi<nCmd ){
if( sqlite3_complete(zCmd) ){
rc = handle_some_sql(&ctx, zCmd, iSemi+1);
nConsume = iSemi+1;
}
if( c ){
zCmd[iSemi+1] = c;
}
}
}
if( nConsume>0 ){
nCmd = nCmd-nConsume;
if( nCmd>0 ){
memmove(zCmd, &zCmd[nConsume], nCmd);
}
}
}while( rc==SQLITE_OK && nConsume>0 );
}
fprintf(stdout, "Client %d disconnects\n", ctx.fd);
close(ctx.fd);
clear_sql(&ctx);
sqlite3_free(ctx.apPrepare);
sqlite3_close(ctx.db);
return 0;
}
int main(int argc, char *argv[]) {
sqlite3 *db;
int sfd;
int rc;
int yes = 1;
struct sockaddr_in server;
/* Ignore SIGPIPE. Otherwise the server exits if a client disconnects
** abruptly. */
signal(SIGPIPE, SIG_IGN);
if( argc!=2 ){
fprintf(stderr, "Usage: %s DATABASE\n", argv[0]);
return 1;
}
zDatabaseName = argv[1];
rc = sqlite3_open(zDatabaseName, &db);
if( rc!=SQLITE_OK ){
fprintf(stderr, "sqlite3_open(): %s\n", sqlite3_errmsg(db));
return 1;
}
rc = sqlite3_exec(db, "SELECT * FROM sqlite_master", 0, 0, 0);
if( rc!=SQLITE_OK ){
fprintf(stderr, "sqlite3_exec(): %s\n", sqlite3_errmsg(db));
return 1;
}
sfd = socket(AF_INET, SOCK_STREAM, 0);
if( sfd<0 ){
fprintf(stderr, "socket() failed\n");
return 1;
}
rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
if( rc<0 ){
perror("setsockopt");
return 1;
}
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr("127.0.0.1");
server.sin_port = htons(TSERVER_PORTNUMBER);
rc = bind(sfd, (struct sockaddr *)&server, sizeof(struct sockaddr));
if( rc<0 ){
fprintf(stderr, "bind() failed\n");
return 1;
}
rc = listen(sfd, 8);
if( rc<0 ){
fprintf(stderr, "listen() failed\n");
return 1;
}
while( 1 ){
pthread_t tid;
int cfd = accept(sfd, NULL, NULL);
if( cfd<0 ){
perror("accept()");
return 1;
}
fprintf(stdout, "Client %d connects\n", cfd);
rc = pthread_create(&tid, NULL, handle_client, (void*)(intptr_t)cfd);
if( rc!=0 ){
perror("pthread_create()");
return 1;
}
pthread_detach(tid);
}
return 0;
}