diff options
author | drh <drh@noemail.net> | 2004-06-09 17:37:22 +0000 |
---|---|---|
committer | drh <drh@noemail.net> | 2004-06-09 17:37:22 +0000 |
commit | a6abd04193e2f9bd07d3c01e8c5dc6e09177b1cf (patch) | |
tree | bab8c8f6f5b3fff231e4e2c26b6f01fcbc634a33 | |
parent | 89ac8c1a702dcdedcb65dfff9acae31c5339f0e3 (diff) | |
download | sqlite-a6abd04193e2f9bd07d3c01e8c5dc6e09177b1cf.tar.gz sqlite-a6abd04193e2f9bd07d3c01e8c5dc6e09177b1cf.zip |
Fixes to the file locking. 109 tests are now failing. (CVS 1548)
FossilOrigin-Name: dc0763455bbf54c1d8728e16033709caedd6e1c6
-rw-r--r-- | manifest | 32 | ||||
-rw-r--r-- | manifest.uuid | 2 | ||||
-rw-r--r-- | src/btree.c | 21 | ||||
-rw-r--r-- | src/os.h | 6 | ||||
-rw-r--r-- | src/os_common.h | 2 | ||||
-rw-r--r-- | src/os_unix.c | 160 | ||||
-rw-r--r-- | src/os_unix.h | 2 | ||||
-rw-r--r-- | src/os_win.c | 23 | ||||
-rw-r--r-- | src/pager.c | 314 | ||||
-rw-r--r-- | src/pager.h | 3 | ||||
-rw-r--r-- | src/test1.c | 4 | ||||
-rw-r--r-- | src/vdbeaux.c | 10 | ||||
-rw-r--r-- | test/attach2.test | 65 |
13 files changed, 380 insertions, 264 deletions
@@ -1,5 +1,5 @@ -C Add\sthe\s"lock_status"\spragma\s-\sonly\savailable\swhen\sSQLITE_DEBUG\sis\sdefined.\nUsed\sfor\stesting\sonly.\s(CVS\s1547) -D 2004-06-09T14:17:21 +C Fixes\sto\sthe\sfile\slocking.\s\s109\stests\sare\snow\sfailing.\s(CVS\s1548) +D 2004-06-09T17:37:23 F Makefile.in ab7b0d5118e2da97bac66be8684a1034e3500f5a F Makefile.linux-gcc a9e5a0d309fa7c38e7c14d3ecf7690879d3a5457 F README f1de682fbbd94899d50aca13d387d1b3fd3be2dd @@ -25,7 +25,7 @@ F sqlite.def fc4f5734786fe4743cfe2aa98eb2da4b089edb5f F sqlite.pc.in 30552343140c53304c2a658c080fbe810cd09ca2 F src/attach.c 93b8ecec4a8d7b4e9f2479e2327d90c9d01765e8 F src/auth.c 5c2f0bea4729c98c2be3b69d6b466fc51448fe79 -F src/btree.c edb38affc2e83f4299e49104cfe14e6570d8bd32 +F src/btree.c 7526d697cad3fbf731a9238d047337581d19b43b F src/btree.h 589427ac13bb544d298cd99726e2572a6fe4bdaa F src/build.c 4ea78aba171f02b96254dd7a312e4266d3693bfe F src/date.c 8e6fa3173386fb29fdef012ee08a853c1e9908b2 @@ -39,16 +39,16 @@ F src/insert.c 4268d9e3959cc845ea243fb4ec7507269404dad9 F src/legacy.c ad23746f15f67e34577621b1875f639c94839e1f F src/main.c 8a7725b40fbe645883b2162aee782ad1063435b4 F src/md5.c 4302e84ae516c616bb079c4e6d038c0addb33481 -F src/os.h a3a9c2df29acbff54aef742e85c302d23634019f -F src/os_common.h 12074232439f904b3805beeff1245bd1b5532994 +F src/os.h 23c69c5084e71b5fe199ff1c4e35a4aded0f1380 +F src/os_common.h 6393ac67a3a7b4aea19ff17529980ecf77eb2348 F src/os_mac.c b823874690615ace0dd520d3ad1fe8bfd864b7e0 F src/os_mac.h 51d2445f47e182ed32d3bd6937f81070c6fd9bd4 -F src/os_unix.c 3747274f2712e95f605c0ec66df5ad26e3711e82 -F src/os_unix.h 7999f2246c6347707e98f7078871ea8ca605df3f -F src/os_win.c a13b85a0d4889e3d0b254ed2a61354acddc59fc4 +F src/os_unix.c 1d6f3d1a87d4aa0e4490bcc47b3f0ff9b2e37e7a +F src/os_unix.h 1cd6133cf66dea704b8646b70b2dfdcbdd9b3738 +F src/os_win.c 6b8f9fcc683bb888e07fc485372803baa68faadb F src/os_win.h 004eec47b1780fcaf07420ddc2072294b698d48c -F src/pager.c c483bef234b89531f9fdf6067231d248260d762b -F src/pager.h 3576e5f1a0719f74df3082df73f7b39890b06654 +F src/pager.c ee2e2fa71584ce402e0266b342f96efbf74cb45f +F src/pager.h ca8f293e1d623a7c628a1c5e0c6cf43d5bbb80bf F src/parse.y 097438674976355a10cf177bd97326c548820b86 F src/pragma.c 3251628662963f30a95133b19d59e4bc08226c76 F src/printf.c 63b15f1ea9fe3daa066bb7430fd20d4a2d717dc8 @@ -59,7 +59,7 @@ F src/sqlite.h.in 6ad05abc7fd72da74691e1eb45f0eff4117eba4e F src/sqliteInt.h c0a328c7567cdeebe14d7a4668f22946e281ebc9 F src/table.c af14284fa36c8d41f6829e3f2819dce07d3e2de2 F src/tclsqlite.c f5c5116720baefb7de5d6acf18baedf1e42756cc -F src/test1.c fa01e3db097df48963e29cec66f2e080735767d4 +F src/test1.c f78d6ac0675bc5db48dac9c5379c965bdadb9113 F src/test2.c ae18537d8a85e5028c955837797f9da461b908b8 F src/test3.c beafd0ccf7b9ae784744be1b1e66ffe8f64c25da F src/test4.c a921a69821fd30209589228e64f94e9f715b6fe2 @@ -74,12 +74,12 @@ F src/vdbe.c fec13be8b6f03158bfb3069c7bd6182eb3ef4fca F src/vdbe.h 46f74444a213129bc4b5ce40124dd8ed613b0cde F src/vdbeInt.h ab592f23ed5a1913f9a506bd7b76c5e39377942a F src/vdbeapi.c 4ac95766b0515538037a7aec172ed26142f97cf9 -F src/vdbeaux.c 136442a40335974a5088dbd684a2168d4d0fec9a +F src/vdbeaux.c 5efecdafe745a90e7c5173403d0065bdd38395a7 F src/vdbemem.c 04502b81039f9a2b1c9a096e894eecf6d4877508 F src/where.c 32578882a245f8ac3303c5cea4664cd51fc73891 F test/all.test 569a92a8ee88f5300c057cc4a8f50fbbc69a3242 F test/attach.test aed659e52635662bcd5069599aaca823533edf5a -F test/attach2.test fe8480cd1aecbe393f2ae60a92c4f60734d89b49 +F test/attach2.test 228eaca954ddb850c35fba7bdc39aac2ab0c80c0 F test/attach3.test 8259ab833b5dcdf4acd75d9653f42f703ce2e013 F test/auth.test 95809b8f6a9bec18b94d28cafd03fe27d2f8a9e9 F test/bigfile.test ea904b853ce2d703b16c5ce90e2b54951bc1ae81 @@ -218,7 +218,7 @@ F www/support.tcl 1801397edd271cc39a2aadd54e701184b5181248 F www/tclsqlite.tcl 19191cf2a1010eaeff74c51d83fd5f5a4d899075 F www/vdbe.tcl 59288db1ac5c0616296b26dce071c36cb611dfe9 F www/whentouse.tcl a8335bce47cc2fddb07f19052cb0cb4d9129a8e4 -P 428b685b7174ef4589176def1028ad1c9461ff7e -R b811ffa94f6a9084cf35549f784cc75b +P 0ecbba78fcde8f7715cd74c674b5040ef4953f6e +R 81671c9563d17cd7ee532f949a6e58e5 U drh -Z 897e459988003c5f46676db9047d5c51 +Z 32ba6607d3f053fb955e6ec92a8309ce diff --git a/manifest.uuid b/manifest.uuid index 704888bcb..c586cfabc 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0ecbba78fcde8f7715cd74c674b5040ef4953f6e
\ No newline at end of file +dc0763455bbf54c1d8728e16033709caedd6e1c6
\ No newline at end of file diff --git a/src/btree.c b/src/btree.c index ed4c5d157..aed38e0ab 100644 --- a/src/btree.c +++ b/src/btree.c @@ -9,7 +9,7 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** $Id: btree.c,v 1.161 2004/06/07 16:27:46 drh Exp $ +** $Id: btree.c,v 1.162 2004/06/09 17:37:23 drh Exp $ ** ** This file implements a external (disk-based) database using BTrees. ** For a detailed discussion of BTrees, refer to @@ -261,7 +261,7 @@ static const char zMagicHeader[] = "SQLite format 3"; ** The pageDestructor() routine handles that chore. */ struct MemPage { - u8 isInit; /* True if previously initialized */ + u8 isInit; /* True if previously initialized. MUST BE FIRST! */ u8 idxShift; /* True if Cell indices have changed */ u8 nOverflow; /* Number of overflow cell bodies in aCell[] */ u8 intKey; /* True if intkey flag is set */ @@ -963,6 +963,22 @@ static void pageDestructor(void *pData, int pageSize){ } /* +** During a rollback, when the pager reloads information into the cache +** so that the cache is restored to its original state at the start of +** the transaction, for each page restored this routine is called. +** +** This routine needs to reset the extra data section at the end of the +** page to agree with the restored data. +*/ +static void pageReinit(void *pData, int pageSize){ + MemPage *pPage = (MemPage*)&((char*)pData)[pageSize]; + if( pPage->isInit ){ + pPage->isInit = 0; + initPage(pPage, pPage->pParent); + } +} + +/* ** Open a new database. ** ** Actually, this routine just sets up the internal data structures @@ -1011,6 +1027,7 @@ int sqlite3BtreeOpen( return rc; } sqlite3pager_set_destructor(pBt->pPager, pageDestructor); + sqlite3pager_set_reiniter(pBt->pPager, pageReinit); pBt->pCursor = 0; pBt->pPage1 = 0; pBt->readOnly = sqlite3pager_isreadonly(pBt->pPager); @@ -156,9 +156,6 @@ int sqlite3OsSeek(OsFile*, off_t offset); int sqlite3OsSync(OsFile*); int sqlite3OsTruncate(OsFile*, off_t size); int sqlite3OsFileSize(OsFile*, off_t *pSize); -int sqlite3OsReadLock(OsFile*); -int sqlite3OsWriteLock(OsFile*); -int sqlite3OsUnlock(OsFile*); int sqlite3OsRandomSeed(char*); int sqlite3OsSleep(int ms); int sqlite3OsCurrentTime(double*); @@ -166,6 +163,7 @@ void sqlite3OsEnterMutex(void); void sqlite3OsLeaveMutex(void); char *sqlite3OsFullPathname(const char*); int sqlite3OsLock(OsFile*, int); -int sqlite3OsCheckWriteLock(OsFile *id); +int sqlite3OsUnlock(OsFile*, int); +int sqlite3OsCheckReservedLock(OsFile *id); #endif /* _SQLITE_OS_H_ */ diff --git a/src/os_common.h b/src/os_common.h index 8bea7f2df..088750963 100644 --- a/src/os_common.h +++ b/src/os_common.h @@ -43,6 +43,7 @@ static unsigned int elapse; #define TRACE3(X,Y,Z) if( sqlite3_os_trace ) sqlite3DebugPrintf(X,Y,Z) #define TRACE4(X,Y,Z,A) if( sqlite3_os_trace ) sqlite3DebugPrintf(X,Y,Z,A) #define TRACE5(X,Y,Z,A,B) if( sqlite3_os_trace ) sqlite3DebugPrintf(X,Y,Z,A,B) +#define TRACE6(X,Y,Z,A,B,C) if(sqlite3_os_trace) sqlite3DebugPrintf(X,Y,Z,A,B,C) #else #define TIMER_START #define TIMER_END @@ -52,6 +53,7 @@ static unsigned int elapse; #define TRACE3(X,Y,Z) #define TRACE4(X,Y,Z,A) #define TRACE5(X,Y,Z,A,B) +#define TRACE6(X,Y,Z,A,B,C) #endif diff --git a/src/os_unix.c b/src/os_unix.c index 3d4710e35..d62cfdd08 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -342,10 +342,10 @@ int sqlite3OsOpenReadWrite( ){ int rc; id->dirfd = -1; - id->fd = open(zFilename, O_RDWR|O_CREAT|O_LARGEFILE|O_BINARY, 0644); - if( id->fd<0 ){ - id->fd = open(zFilename, O_RDONLY|O_LARGEFILE|O_BINARY); - if( id->fd<0 ){ + id->h = open(zFilename, O_RDWR|O_CREAT|O_LARGEFILE|O_BINARY, 0644); + if( id->h<0 ){ + id->h = open(zFilename, O_RDONLY|O_LARGEFILE|O_BINARY); + if( id->h<0 ){ return SQLITE_CANTOPEN; } *pReadonly = 1; @@ -353,14 +353,14 @@ int sqlite3OsOpenReadWrite( *pReadonly = 0; } sqlite3OsEnterMutex(); - rc = findLockInfo(id->fd, &id->pLock, &id->pOpen); + rc = findLockInfo(id->h, &id->pLock, &id->pOpen); sqlite3OsLeaveMutex(); if( rc ){ - close(id->fd); + close(id->h); return SQLITE_NOMEM; } id->locktype = 0; - TRACE3("OPEN %-3d %s\n", id->fd, zFilename); + TRACE3("OPEN %-3d %s\n", id->h, zFilename); OpenCounter(+1); return SQLITE_OK; } @@ -386,16 +386,16 @@ int sqlite3OsOpenExclusive(const char *zFilename, OsFile *id, int delFlag){ return SQLITE_CANTOPEN; } id->dirfd = -1; - id->fd = open(zFilename, + id->h = open(zFilename, O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW|O_LARGEFILE|O_BINARY, 0600); - if( id->fd<0 ){ + if( id->h<0 ){ return SQLITE_CANTOPEN; } sqlite3OsEnterMutex(); - rc = findLockInfo(id->fd, &id->pLock, &id->pOpen); + rc = findLockInfo(id->h, &id->pLock, &id->pOpen); sqlite3OsLeaveMutex(); if( rc ){ - close(id->fd); + close(id->h); unlink(zFilename); return SQLITE_NOMEM; } @@ -403,7 +403,7 @@ int sqlite3OsOpenExclusive(const char *zFilename, OsFile *id, int delFlag){ if( delFlag ){ unlink(zFilename); } - TRACE3("OPEN-EX %-3d %s\n", id->fd, zFilename); + TRACE3("OPEN-EX %-3d %s\n", id->h, zFilename); OpenCounter(+1); return SQLITE_OK; } @@ -418,19 +418,19 @@ int sqlite3OsOpenExclusive(const char *zFilename, OsFile *id, int delFlag){ int sqlite3OsOpenReadOnly(const char *zFilename, OsFile *id){ int rc; id->dirfd = -1; - id->fd = open(zFilename, O_RDONLY|O_LARGEFILE|O_BINARY); - if( id->fd<0 ){ + id->h = open(zFilename, O_RDONLY|O_LARGEFILE|O_BINARY); + if( id->h<0 ){ return SQLITE_CANTOPEN; } sqlite3OsEnterMutex(); - rc = findLockInfo(id->fd, &id->pLock, &id->pOpen); + rc = findLockInfo(id->h, &id->pLock, &id->pOpen); sqlite3OsLeaveMutex(); if( rc ){ - close(id->fd); + close(id->h); return SQLITE_NOMEM; } id->locktype = 0; - TRACE3("OPEN-RO %-3d %s\n", id->fd, zFilename); + TRACE3("OPEN-RO %-3d %s\n", id->h, zFilename); OpenCounter(+1); return SQLITE_OK; } @@ -455,7 +455,7 @@ int sqlite3OsOpenDirectory( const char *zDirname, OsFile *id ){ - if( id->fd<0 ){ + if( id->h<0 ){ /* Do not open the directory if the corresponding file is not already ** open. */ return SQLITE_CANTOPEN; @@ -510,7 +510,7 @@ int sqlite3OsTempFileName(char *zBuf){ ** Close a file. */ int sqlite3OsClose(OsFile *id){ - sqlite3OsUnlock(id); + sqlite3OsUnlock(id, NO_LOCK); if( id->dirfd>=0 ) close(id->dirfd); id->dirfd = -1; sqlite3OsEnterMutex(); @@ -528,16 +528,16 @@ int sqlite3OsClose(OsFile *id){ /* If a malloc fails, just leak the file descriptor */ }else{ pOpen->aPending = aNew; - pOpen->aPending[pOpen->nPending-1] = id->fd; + pOpen->aPending[pOpen->nPending-1] = id->h; } }else{ /* There are no outstanding locks so we can close the file immediately */ - close(id->fd); + close(id->h); } releaseLockInfo(id->pLock); releaseOpenCnt(id->pOpen); sqlite3OsLeaveMutex(); - TRACE2("CLOSE %-3d\n", id->fd); + TRACE2("CLOSE %-3d\n", id->h); OpenCounter(-1); return SQLITE_OK; } @@ -551,9 +551,9 @@ int sqlite3OsRead(OsFile *id, void *pBuf, int amt){ int got; SimulateIOError(SQLITE_IOERR); TIMER_START; - got = read(id->fd, pBuf, amt); + got = read(id->h, pBuf, amt); TIMER_END; - TRACE4("READ %-3d %7d %d\n", id->fd, last_page, elapse); + TRACE4("READ %-3d %7d %d\n", id->h, last_page, elapse); SEEK(0); /* if( got<0 ) got = 0; */ if( got==amt ){ @@ -571,12 +571,12 @@ int sqlite3OsWrite(OsFile *id, const void *pBuf, int amt){ int wrote = 0; SimulateIOError(SQLITE_IOERR); TIMER_START; - while( amt>0 && (wrote = write(id->fd, pBuf, amt))>0 ){ + while( amt>0 && (wrote = write(id->h, pBuf, amt))>0 ){ amt -= wrote; pBuf = &((char*)pBuf)[wrote]; } TIMER_END; - TRACE4("WRITE %-3d %7d %d\n", id->fd, last_page, elapse); + TRACE4("WRITE %-3d %7d %d\n", id->h, last_page, elapse); SEEK(0); if( amt>0 ){ return SQLITE_FULL; @@ -589,7 +589,7 @@ int sqlite3OsWrite(OsFile *id, const void *pBuf, int amt){ */ int sqlite3OsSeek(OsFile *id, off_t offset){ SEEK(offset/1024 + 1); - lseek(id->fd, offset, SEEK_SET); + lseek(id->h, offset, SEEK_SET); return SQLITE_OK; } @@ -606,8 +606,8 @@ int sqlite3OsSeek(OsFile *id, off_t offset){ */ int sqlite3OsSync(OsFile *id){ SimulateIOError(SQLITE_IOERR); - TRACE2("SYNC %-3d\n", id->fd); - if( fsync(id->fd) ){ + TRACE2("SYNC %-3d\n", id->h); + if( fsync(id->h) ){ return SQLITE_IOERR; }else{ if( id->dirfd>=0 ){ @@ -625,7 +625,7 @@ int sqlite3OsSync(OsFile *id){ */ int sqlite3OsTruncate(OsFile *id, off_t nByte){ SimulateIOError(SQLITE_IOERR); - return ftruncate(id->fd, nByte)==0 ? SQLITE_OK : SQLITE_IOERR; + return ftruncate(id->h, nByte)==0 ? SQLITE_OK : SQLITE_IOERR; } /* @@ -634,7 +634,7 @@ int sqlite3OsTruncate(OsFile *id, off_t nByte){ int sqlite3OsFileSize(OsFile *id, off_t *pSize){ struct stat buf; SimulateIOError(SQLITE_IOERR); - if( fstat(id->fd, &buf)!=0 ){ + if( fstat(id->h, &buf)!=0 ){ return SQLITE_IOERR; } *pSize = buf.st_size; @@ -647,7 +647,7 @@ int sqlite3OsFileSize(OsFile *id, off_t *pSize){ ** non-zero. If the file is unlocked or holds only SHARED locks, then ** return zero. */ -int sqlite3OsCheckWriteLock(OsFile *id){ +int sqlite3OsCheckReservedLock(OsFile *id){ int r = 0; sqlite3OsEnterMutex(); /* Needed because id->pLock is shared across threads */ @@ -665,14 +665,14 @@ int sqlite3OsCheckWriteLock(OsFile *id){ lock.l_start = RESERVED_BYTE; lock.l_len = 1; lock.l_type = F_WRLCK; - fcntl(id->fd, F_GETLK, &lock); + fcntl(id->h, F_GETLK, &lock); if( lock.l_type!=F_UNLCK ){ r = 1; } } sqlite3OsLeaveMutex(); - TRACE3("TEST WR-LOCK %d %d\n", id->fd, r); + TRACE3("TEST WR-LOCK %d %d\n", id->h, r); return r; } @@ -698,10 +698,8 @@ int sqlite3OsCheckWriteLock(OsFile *id){ ** RESERVED -> (PENDING) -> EXCLUSIVE ** PENDING -> EXCLUSIVE ** -** This routine will only increase a lock. The sqlite3OsUnlock() routine -** erases all locks at once and returns us immediately to locking level 0. -** It is not possible to lower the locking level one step at a time. You -** must go straight to locking level 0. +** This routine will only increase a lock. Use the sqlite3OsUnlock() +** routine to lower a locking level. */ int sqlite3OsLock(OsFile *id, int locktype){ int rc = SQLITE_OK; @@ -709,8 +707,8 @@ int sqlite3OsLock(OsFile *id, int locktype){ struct flock lock; int s; - TRACE5("LOCK %d %d was %d(%d)\n", - id->fd, locktype, id->locktype, pLock->locktype); + TRACE6("LOCK %d %d was %d(%d,%d)\n", + id->h, locktype, id->locktype, pLock->locktype, pLock->cnt); /* If there is already a lock of this type or more restrictive on the ** OsFile, do nothing. Don't use the end_lock: exit path, as @@ -771,7 +769,7 @@ int sqlite3OsLock(OsFile *id, int locktype){ */ lock.l_type = F_RDLCK; lock.l_start = PENDING_BYTE; - s = fcntl(id->fd, F_SETLK, &lock); + s = fcntl(id->h, F_SETLK, &lock); if( s ){ rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY; goto end_lock; @@ -780,13 +778,13 @@ int sqlite3OsLock(OsFile *id, int locktype){ /* Now get the read-lock */ lock.l_start = SHARED_FIRST; lock.l_len = SHARED_SIZE; - s = fcntl(id->fd, F_SETLK, &lock); + s = fcntl(id->h, F_SETLK, &lock); /* Drop the temporary PENDING lock */ lock.l_start = PENDING_BYTE; lock.l_len = 1L; lock.l_type = F_UNLCK; - fcntl(id->fd, F_SETLK, &lock); + fcntl(id->h, F_SETLK, &lock); if( s ){ rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY; }else{ @@ -815,7 +813,7 @@ int sqlite3OsLock(OsFile *id, int locktype){ default: assert(0); } - s = fcntl(id->fd, F_SETLK, &lock); + s = fcntl(id->h, F_SETLK, &lock); if( s ){ rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY; } @@ -828,47 +826,63 @@ int sqlite3OsLock(OsFile *id, int locktype){ end_lock: sqlite3OsLeaveMutex(); - TRACE4("LOCK %d %d %s\n", id->fd, locktype, rc==SQLITE_OK ? "ok" : "failed"); + TRACE4("LOCK %d %d %s\n", id->h, locktype, rc==SQLITE_OK ? "ok" : "failed"); return rc; } /* -** Unlock the given file descriptor. If the file descriptor was -** not previously locked, then this routine is a no-op. If this -** library was compiled with large file support (LFS) but LFS is not -** available on the host, then an SQLITE_NOLFS is returned. +** Lower the locking level on file descriptor id to locktype. locktype +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +** +** It is not possible for this routine to fail. */ -int sqlite3OsUnlock(OsFile *id){ - int rc; - if( !id->locktype ) return SQLITE_OK; - id->locktype = 0; +int sqlite3OsUnlock(OsFile *id, int locktype){ + struct lockInfo *pLock; + struct flock lock; + + TRACE6("UNLOCK %d %d was %d(%d,%d)\n", + id->h, locktype, id->locktype, id->pLock->locktype, id->pLock->cnt); + + assert( locktype<=SHARED_LOCK ); + if( id->locktype<=locktype ){ + return SQLITE_OK; + } sqlite3OsEnterMutex(); - assert( id->pLock->cnt!=0 ); - if( id->pLock->cnt>1 ){ - id->pLock->cnt--; - rc = SQLITE_OK; - }else{ - struct flock lock; - int s; + pLock = id->pLock; + assert( pLock->cnt!=0 ); + if( id->locktype>SHARED_LOCK ){ + assert( pLock->locktype==id->locktype ); lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; - lock.l_start = lock.l_len = 0L; - s = fcntl(id->fd, F_SETLK, &lock); - if( s!=0 ){ - rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY; - }else{ - rc = SQLITE_OK; - id->pLock->cnt = 0; - id->pLock->locktype = 0; - } + lock.l_start = PENDING_BYTE; + lock.l_len = 2L; assert( PENDING_BYTE+1==RESERVED_BYTE ); + fcntl(id->h, F_SETLK, &lock); + pLock->locktype = SHARED_LOCK; } + if( locktype==NO_LOCK ){ + struct openCnt *pOpen; + + /* Decrement the shared lock counter. Release the lock using an + ** OS call only when all threads in this same process have released + ** the lock. + */ + pLock->cnt--; + if( pLock->cnt==0 ){ + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = lock.l_len = 0L; + fcntl(id->h, F_SETLK, &lock); + pLock->locktype = NO_LOCK; + } - if( rc==SQLITE_OK ){ /* Decrement the count of locks against this same file. When the ** count reaches zero, close any other file descriptors whose close ** was deferred because of outstanding locks. */ - struct openCnt *pOpen = id->pOpen; + pOpen = id->pOpen; pOpen->nLock--; assert( pOpen->nLock>=0 ); if( pOpen->nLock==0 && pOpen->nPending>0 ){ @@ -882,8 +896,8 @@ int sqlite3OsUnlock(OsFile *id){ } } sqlite3OsLeaveMutex(); - id->locktype = 0; - return rc; + id->locktype = locktype; + return SQLITE_OK; } /* diff --git a/src/os_unix.h b/src/os_unix.h index e9a016f33..b42b87bdc 100644 --- a/src/os_unix.h +++ b/src/os_unix.h @@ -64,7 +64,7 @@ typedef struct OsFile OsFile; struct OsFile { struct openCnt *pOpen; /* Info about all open fd's on this inode */ struct lockInfo *pLock; /* Info about locks on this inode */ - int fd; /* The file descriptor */ + int h; /* The file descriptor */ int locktype; /* The type of lock held on this fd */ int dirfd; /* File descriptor for the directory */ }; diff --git a/src/os_win.c b/src/os_win.c index e9356a1b7..217a61cbb 100644 --- a/src/os_win.c +++ b/src/os_win.c @@ -508,7 +508,7 @@ int sqlite3OsLock(OsFile *id, int locktype){ ** file by this or any other process. If such a lock is held, return ** non-zero, otherwise zero. */ -int sqlite3OsCheckWriteLock(OsFile *id){ +int sqlite3OsCheckReservedLock(OsFile *id){ int rc; if( id->locktype>=RESERVED_LOCK ){ rc = 1; @@ -525,14 +525,19 @@ int sqlite3OsCheckWriteLock(OsFile *id){ } /* -** Unlock the given file descriptor. If the file descriptor was -** not previously locked, then this routine is a no-op. If this -** library was compiled with large file support (LFS) but LFS is not -** available on the host, then an SQLITE_NOLFS is returned. +** Lower the locking level on file descriptor id to locktype. locktype +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +** +** It is not possible for this routine to fail. */ -int sqlite3OsUnlock(OsFile *id){ +int sqlite3OsUnlock(OsFile *id, int locktype){ int rc, type; - TRACE4("UNLOCK %d was %d(%d)\n", id->h, id->locktype, id->sharedLockByte); + assert( locktype<=SHARED_LOCK ); + TRACE4("UNLOCK %d to %d was %d(%d)\n", id->h, locktype, + id->locktype, id->sharedLockByte); type = id->locktype; if( type>=EXCLUSIVE_LOCK ){ UnlockFile(id->h, SHARED_FIRST, 0, SHARED_SIZE, 0); @@ -540,13 +545,13 @@ int sqlite3OsUnlock(OsFile *id){ if( type>=RESERVED_LOCK ){ UnlockFile(id->h, RESERVED_BYTE, 0, 1, 0); } - if( type>=SHARED_LOCK && type<EXCLUSIVE_LOCK ){ + if( locktype==NO_LOCK && type>=SHARED_LOCK && type<EXCLUSIVE_LOCK ){ unlockReadLock(id); } if( type>=PENDING_LOCK ){ UnlockFile(id->h, PENDING_BYTE, 0, 1, 0); } - id->locktype = NO_LOCK; + id->locktype = locktype; return SQLITE_OK; } diff --git a/src/pager.c b/src/pager.c index e8f94296e..4dabfca29 100644 --- a/src/pager.c +++ b/src/pager.c @@ -18,7 +18,7 @@ ** file simultaneously, or one process from reading the database while ** another is writing. ** -** @(#) $Id: pager.c,v 1.114 2004/06/09 14:17:21 drh Exp $ +** @(#) $Id: pager.c,v 1.115 2004/06/09 17:37:28 drh Exp $ */ #include "os.h" /* Must be first to enable large file support */ #include "sqliteInt.h" @@ -49,35 +49,42 @@ static Pager *mainPager = 0; ** The page cache as a whole is always in one of the following ** states: ** -** SQLITE_UNLOCK The page cache is not currently reading or +** PAGER_UNLOCK The page cache is not currently reading or ** writing the database file. There is no ** data held in memory. This is the initial ** state. ** -** SQLITE_READLOCK The page cache is reading the database. +** PAGER_SHARED The page cache is reading the database. ** Writing is not permitted. There can be ** multiple readers accessing the same database ** file at the same time. ** -** SQLITE_WRITELOCK The page cache is writing the database. +** PAGER_RESERVED Writing is permitted to the page cache only. +** The original database file has not been modified. +** Other processes may still be reading the on-disk +** database file. +** +** PAGER_EXCLUSIVE The page cache is writing the database. ** Access is exclusive. No other processes or ** threads can be reading or writing while one ** process is writing. ** -** The page cache comes up in SQLITE_UNLOCK. The first time a -** sqlite_page_get() occurs, the state transitions to SQLITE_READLOCK. +** The page cache comes up in PAGER_UNLOCK. The first time a +** sqlite_page_get() occurs, the state transitions to PAGER_SHARED. ** After all pages have been released using sqlite_page_unref(), -** the state transitions back to SQLITE_UNLOCK. The first time +** the state transitions back to PAGER_UNLOCK. The first time ** that sqlite_page_write() is called, the state transitions to -** SQLITE_WRITELOCK. (Note that sqlite_page_write() can only be +** PAGER_RESERVED. (Note that sqlite_page_write() can only be ** called on an outstanding page which means that the pager must -** be in SQLITE_READLOCK before it transitions to SQLITE_WRITELOCK.) +** be in PAGER_SHARED before it transitions to PAGER_RESERVED.) ** The sqlite_page_rollback() and sqlite_page_commit() functions -** transition the state from SQLITE_WRITELOCK back to SQLITE_READLOCK. +** transition the state from PAGER_RESERVED to PAGER_EXCLUSIVE to +** PAGER_SHARED. */ -#define SQLITE_UNLOCK 0 -#define SQLITE_READLOCK 1 -#define SQLITE_WRITELOCK 2 +#define PAGER_UNLOCK 0 +#define PAGER_SHARED 1 +#define PAGER_RESERVED 2 +#define PAGER_EXCLUSIVE 3 /* @@ -182,6 +189,7 @@ struct Pager { int stmtNRec; /* Number of records in stmt subjournal */ int nExtra; /* Add this many bytes to each in-memory page */ void (*xDestructor)(void*,int); /* Call this routine when freeing pages */ + void (*xReiniter)(void*,int); /* Call this routine when reloading pages */ int pageSize; /* Number of bytes in a page */ int nPage; /* Total number of in-memory pages */ int nRef; /* Number of in-memory pages with PgHdr.nRef>0 */ @@ -197,12 +205,12 @@ struct Pager { u8 stmtAutoopen; /* Open stmt journal when main journal is opened*/ u8 noSync; /* Do not sync the journal if true */ u8 fullSync; /* Do extra syncs of the journal for robustness */ - u8 state; /* SQLITE_UNLOCK, _READLOCK or _WRITELOCK */ + u8 state; /* PAGER_UNLOCK, _SHARED, _RESERVED, etc. */ u8 errMask; /* One of several kinds of errors */ u8 tempFile; /* zFilename is a temporary file */ u8 readOnly; /* True for a read-only database */ u8 needSync; /* True if an fsync() is needed on the journal */ - u8 dirtyFile; /* True if database file has changed in any way */ + u8 dirtyCache; /* True if cached pages have changed */ u8 alwaysRollback; /* Disable dont_rollback() for all pages */ u8 memDb; /* True to inhibit all file I/O */ u8 *aInJournal; /* One bit for each page in the database file */ @@ -479,11 +487,11 @@ static void pager_reset(Pager *pPager){ pPager->pAll = 0; memset(pPager->aHash, 0, sizeof(pPager->aHash)); pPager->nPage = 0; - if( pPager->state>=SQLITE_WRITELOCK ){ + if( pPager->state>=PAGER_RESERVED ){ sqlite3pager_rollback(pPager); } - sqlite3OsUnlock(&pPager->fd); - pPager->state = SQLITE_UNLOCK; + sqlite3OsUnlock(&pPager->fd, NO_LOCK); + pPager->state = PAGER_UNLOCK; pPager->dbSize = -1; pPager->nRef = 0; assert( pPager->journalOpen==0 ); @@ -491,18 +499,19 @@ static void pager_reset(Pager *pPager){ /* ** When this routine is called, the pager has the journal file open and -** a write lock on the database. This routine releases the database -** write lock and acquires a read lock in its place. The journal file -** is deleted and closed. +** a RESERVED or EXCLUSIVE lock on the database. This routine releases +** the database lock and acquires a SHARED lock in its place. The journal +** file is deleted and closed. ** ** TODO: Consider keeping the journal file open for temporary databases. ** This might give a performance improvement on windows where opening ** a file is an expensive operation. */ static int pager_unwritelock(Pager *pPager){ - int rc; PgHdr *pPg; - if( pPager->state<SQLITE_WRITELOCK ) return SQLITE_OK; + if( pPager->state<PAGER_RESERVED ){ + return SQLITE_OK; + } sqlite3pager_stmt_commit(pPager); if( pPager->stmtOpen ){ sqlite3OsClose(&pPager->stfd); @@ -520,19 +529,11 @@ static int pager_unwritelock(Pager *pPager){ pPg->needSync = 0; } }else{ - assert( pPager->dirtyFile==0 || pPager->useJournal==0 ); - } - rc = sqlite3OsLock(&pPager->fd, SHARED_LOCK); - if( rc==SQLITE_OK ){ - pPager->state = SQLITE_READLOCK; - }else{ - /* This can only happen if a process does a BEGIN, then forks and the - ** child process does the COMMIT. Because of the semantics of unix - ** file locking, the unlock will fail. - */ - pPager->state = SQLITE_UNLOCK; + assert( pPager->dirtyCache==0 || pPager->useJournal==0 ); } - return rc; + sqlite3OsUnlock(&pPager->fd, SHARED_LOCK); + pPager->state = PAGER_SHARED; + return SQLITE_OK; } /* @@ -588,7 +589,7 @@ static int pager_playback_one_page(Pager *pPager, OsFile *jfd, int format){ ** at the same time, if there is one. */ pPg = pager_lookup(pPager, pgRec.pgno); - TRACE2("PLAYBACK %d\n", pgRec.pgno); + TRACE2("PLAYBACK page %d\n", pgRec.pgno); sqlite3OsSeek(&pPager->fd, (pgRec.pgno-1)*(off_t)SQLITE_PAGE_SIZE); rc = sqlite3OsWrite(&pPager->fd, pgRec.aData, SQLITE_PAGE_SIZE); if( pPg ){ @@ -722,6 +723,45 @@ delmaster_out: } /* +** Make every page in the cache agree with what is on disk. In other words, +** reread the disk to reset the state of the cache. +** +** This routine is called after a rollback in which some of the dirty cache +** pages had never been written out to disk. We need to roll back the +** cache content and the easiest way to do that is to reread the old content +** back from the disk. +*/ +static int pager_reload_cache(Pager *pPager){ + PgHdr *pPg; + int rc = SQLITE_OK; + for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){ + char zBuf[SQLITE_PAGE_SIZE]; + if( !pPg->dirty ) continue; + if( (int)pPg->pgno <= pPager->origDbSize ){ + sqlite3OsSeek(&pPager->fd, SQLITE_PAGE_SIZE*(off_t)(pPg->pgno-1)); + rc = sqlite3OsRead(&pPager->fd, zBuf, SQLITE_PAGE_SIZE); + TRACE2("REFETCH page %d\n", pPg->pgno); + CODEC(pPager, zBuf, pPg->pgno, 2); + if( rc ) break; + }else{ + memset(zBuf, 0, SQLITE_PAGE_SIZE); + } + if( pPg->nRef==0 || memcmp(zBuf, PGHDR_TO_DATA(pPg), SQLITE_PAGE_SIZE) ){ + memcpy(PGHDR_TO_DATA(pPg), zBuf, SQLITE_PAGE_SIZE); + if( pPager->xReiniter ){ + pPager->xReiniter(PGHDR_TO_DATA(pPg), pPager->pageSize); + }else{ + memset(PGHDR_TO_EXTRA(pPg), 0, pPager->nExtra); + } + } + pPg->needSync = 0; + pPg->dirty = 0; + } + return rc; +} + + +/* ** Playback the journal and thus restore the database file to ** the state it was in before we started making changes. ** @@ -887,26 +927,7 @@ static int pager_playback(Pager *pPager, int useJournalSize){ ** pages by reading them back from the original database. */ if( rc==SQLITE_OK ){ - PgHdr *pPg; - for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){ - char zBuf[SQLITE_PAGE_SIZE]; - if( !pPg->dirty ) continue; - if( (int)pPg->pgno <= pPager->origDbSize ){ - sqlite3OsSeek(&pPager->fd, SQLITE_PAGE_SIZE*(off_t)(pPg->pgno-1)); - rc = sqlite3OsRead(&pPager->fd, zBuf, SQLITE_PAGE_SIZE); - TRACE2("REFETCH %d\n", pPg->pgno); - CODEC(pPager, zBuf, pPg->pgno, 2); - if( rc ) break; - }else{ - memset(zBuf, 0, SQLITE_PAGE_SIZE); - } - if( pPg->nRef==0 || memcmp(zBuf, PGHDR_TO_DATA(pPg), SQLITE_PAGE_SIZE) ){ - memcpy(PGHDR_TO_DATA(pPg), zBuf, SQLITE_PAGE_SIZE); - memset(PGHDR_TO_EXTRA(pPg), 0, pPager->nExtra); - } - pPg->needSync = 0; - pPg->dirty = 0; - } + pager_reload_cache(pPager); } end_playback: @@ -1165,7 +1186,7 @@ int sqlite3pager_open( pPager->stmtJSize = 0; pPager->nPage = 0; pPager->mxPage = mxPage>5 ? mxPage : 10; - pPager->state = SQLITE_UNLOCK; + pPager->state = PAGER_UNLOCK; pPager->errMask = 0; pPager->tempFile = tempFile; pPager->memDb = memDb; @@ -1195,6 +1216,17 @@ void sqlite3pager_set_destructor(Pager *pPager, void (*xDesc)(void*,int)){ } /* +** Set the reinitializer for this pager. If not NULL, the reinitializer +** is called when the content of a page in cache is restored to its original +** value as a result of a rollback. The callback gives higher-level code +** an opportunity to restore the EXTRA section to agree with the restored +** page data. +*/ +void sqlite3pager_set_reiniter(Pager *pPager, void (*xReinit)(void*,int)){ + pPager->xReiniter = xReinit; +} + +/* ** Return the total number of pages in the disk file associated with ** pPager. */ @@ -1209,7 +1241,7 @@ int sqlite3pager_pagecount(Pager *pPager){ return 0; } n /= SQLITE_PAGE_SIZE; - if( pPager->state!=SQLITE_UNLOCK ){ + if( pPager->state!=PAGER_UNLOCK ){ pPager->dbSize = n; } return n; @@ -1330,17 +1362,18 @@ int sqlite3pager_truncate(Pager *pPager, Pgno nPage){ int sqlite3pager_close(Pager *pPager){ PgHdr *pPg, *pNext; switch( pPager->state ){ - case SQLITE_WRITELOCK: { + case PAGER_RESERVED: + case PAGER_EXCLUSIVE: { sqlite3pager_rollback(pPager); if( !pPager->memDb ){ - sqlite3OsUnlock(&pPager->fd); + sqlite3OsUnlock(&pPager->fd, NO_LOCK); } assert( pPager->journalOpen==0 ); break; } - case SQLITE_READLOCK: { + case PAGER_SHARED: { if( !pPager->memDb ){ - sqlite3OsUnlock(&pPager->fd); + sqlite3OsUnlock(&pPager->fd, NO_LOCK); } break; } @@ -1485,7 +1518,7 @@ static int syncJournal(Pager *pPager, const char *zMaster){ /* Write the nRec value into the journal file header */ off_t szJ; if( pPager->fullSync ){ - TRACE1("SYNC\n"); + TRACE2("SYNC journal of %d\n", pPager->fd.h); rc = sqlite3OsSync(&pPager->jfd); if( rc!=0 ) return rc; } @@ -1506,7 +1539,7 @@ static int syncJournal(Pager *pPager, const char *zMaster){ pPager->nRec*JOURNAL_PG_SZ(journal_format); sqlite3OsSeek(&pPager->jfd, szJ); } - TRACE1("SYNC\n"); + TRACE2("SYNC journal of %d\n", pPager->fd.h); rc = sqlite3OsSync(&pPager->jfd); if( rc!=0 ) return rc; pPager->journalStarted = 1; @@ -1554,15 +1587,17 @@ static int pager_write_pagelist(PgHdr *pList){ ** database file. If there is already an EXCLUSIVE lock, the following ** calls to sqlite3OsLock() are no-ops. ** - ** The upgrade from a RESERVED to PENDING might return SQLITE_BUSY on - ** windows because the windows locking mechanism acquires a transient - ** PENDING lock during its attempts to get a SHARED lock. So if another - ** process were trying to get a SHARED lock at the same time this process - ** is upgrading from RESERVED to PENDING, the two could collide. + ** Moving the lock from RESERVED to EXCLUSIVE actually involves going + ** through an intermediate state PENDING. A PENDING lock prevents new + ** readers from attaching to the database but is unsufficient for us to + ** write. The idea of a PENDING lock is to prevent new readers from + ** coming in while we wait for existing readers to clear. ** - ** The upgrade from PENDING to EXCLUSIVE can return SQLITE_BUSY if there - ** are still active readers that were created before the PENDING lock - ** was acquired. + ** While the pager is in the RESERVED state, the original database file + ** is unchanged and we can rollback without having to playback the + ** journal into the original database file. Once we transition to + ** EXCLUSIVE, it means the database file has been changed and any rollback + ** will require a journal playback. */ do { rc = sqlite3OsLock(&pPager->fd, EXCLUSIVE_LOCK); @@ -1574,12 +1609,13 @@ static int pager_write_pagelist(PgHdr *pList){ if( rc!=SQLITE_OK ){ return rc; } + pPager->state = PAGER_EXCLUSIVE; while( pList ){ assert( pList->dirty ); sqlite3OsSeek(&pPager->fd, (pList->pgno-1)*(off_t)SQLITE_PAGE_SIZE); CODEC(pPager, PGHDR_TO_DATA(pList), pList->pgno, 6); - TRACE2("STORE %d\n", pList->pgno); + TRACE2("STORE page %d\n", pList->pgno); rc = sqlite3OsWrite(&pPager->fd, PGHDR_TO_DATA(pList), SQLITE_PAGE_SIZE); CODEC(pPager, PGHDR_TO_DATA(pList), pList->pgno, 0); if( rc ) return rc; @@ -1657,27 +1693,25 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){ if( rc!=SQLITE_OK ){ return rc; } - pPager->state = SQLITE_READLOCK; + pPager->state = PAGER_SHARED; /* If a journal file exists, and there is no RESERVED lock on the ** database file, then it either needs to be played back or deleted. */ if( pPager->useJournal && sqlite3OsFileExists(pPager->zJournal) && - !sqlite3OsCheckWriteLock(&pPager->fd) + !sqlite3OsCheckReservedLock(&pPager->fd) ){ int rc; /* Get an EXCLUSIVE lock on the database file. */ rc = sqlite3OsLock(&pPager->fd, EXCLUSIVE_LOCK); if( rc!=SQLITE_OK ){ - if( sqlite3OsUnlock(&pPager->fd)!=SQLITE_OK ){ - /* This should never happen! */ - rc = SQLITE_INTERNAL; - } + sqlite3OsUnlock(&pPager->fd, NO_LOCK); + pPager->state = PAGER_UNLOCK; return rc; } - pPager->state = SQLITE_WRITELOCK; + pPager->state = PAGER_EXCLUSIVE; /* Open the journal for reading only. Return SQLITE_BUSY if ** we are unable to open the journal file. @@ -1689,8 +1723,8 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){ */ rc = sqlite3OsOpenReadOnly(pPager->zJournal, &pPager->jfd); if( rc!=SQLITE_OK ){ - rc = sqlite3OsUnlock(&pPager->fd); - assert( rc==SQLITE_OK ); + sqlite3OsUnlock(&pPager->fd, NO_LOCK); + pPager->state = PAGER_UNLOCK; return SQLITE_BUSY; } pPager->journalOpen = 1; @@ -1708,8 +1742,8 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){ }else{ /* Search for page in cache */ pPg = pager_lookup(pPager, pgno); - if( pPager->memDb && pPager->state==SQLITE_UNLOCK ){ - pPager->state = SQLITE_READLOCK; + if( pPager->memDb && pPager->state==PAGER_UNLOCK ){ + pPager->state = PAGER_SHARED; } } if( pPg==0 ){ @@ -1828,7 +1862,7 @@ int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage){ assert( pPager->memDb==0 ); sqlite3OsSeek(&pPager->fd, (pgno-1)*(off_t)SQLITE_PAGE_SIZE); rc = sqlite3OsRead(&pPager->fd, PGHDR_TO_DATA(pPg), SQLITE_PAGE_SIZE); - TRACE2("FETCH %d\n", pPg->pgno); + TRACE2("FETCH page %d\n", pPg->pgno); CODEC(pPager, PGHDR_TO_DATA(pPg), pPg->pgno, 3); if( rc!=SQLITE_OK ){ off_t fileSize; @@ -1927,30 +1961,30 @@ int sqlite3pager_unref(void *pData){ } /* -** Create a journal file for pPager. There should already be a write -** lock on the database file when this routine is called. +** Create a journal file for pPager. There should already be a RESERVED +** or EXCLUSIVE lock on the database file when this routine is called. ** ** Return SQLITE_OK if everything. Return an error code and release the ** write lock if anything goes wrong. */ static int pager_open_journal(Pager *pPager){ int rc; - assert( pPager->state==SQLITE_WRITELOCK ); + assert( pPager->state>=PAGER_RESERVED ); assert( pPager->journalOpen==0 ); assert( pPager->useJournal ); sqlite3pager_pagecount(pPager); pPager->aInJournal = sqliteMalloc( pPager->dbSize/8 + 1 ); if( pPager->aInJournal==0 ){ - sqlite3OsLock(&pPager->fd, SHARED_LOCK); - pPager->state = SQLITE_READLOCK; + sqlite3OsUnlock(&pPager->fd, SHARED_LOCK); + pPager->state = PAGER_SHARED; return SQLITE_NOMEM; } rc = sqlite3OsOpenExclusive(pPager->zJournal, &pPager->jfd,pPager->tempFile); if( rc!=SQLITE_OK ){ sqliteFree(pPager->aInJournal); pPager->aInJournal = 0; - sqlite3OsLock(&pPager->fd, SHARED_LOCK); - pPager->state = SQLITE_READLOCK; + sqlite3OsUnlock(&pPager->fd, SHARED_LOCK); + pPager->state = PAGER_SHARED; return SQLITE_CANTOPEN; } sqlite3OsOpenDirectory(pPager->zDirectory, &pPager->jfd); @@ -2039,7 +2073,7 @@ static int pager_open_journal(Pager *pPager){ ** files, the opening of the journal file is deferred until there is an ** actual need to write to the journal. ** -** If the database is already write-locked, this routine is a no-op. +** If the database is already reserved for writing, this routine is a no-op. */ int sqlite3pager_begin(void *pData, int nMaster){ PgHdr *pPg = DATA_TO_PGHDR(pData); @@ -2047,20 +2081,15 @@ int sqlite3pager_begin(void *pData, int nMaster){ int rc = SQLITE_OK; assert( pPg->nRef>0 ); assert( nMaster>=0 ); - assert( pPager->state!=SQLITE_UNLOCK ); - if( pPager->state==SQLITE_READLOCK ){ + assert( pPager->state!=PAGER_UNLOCK ); + if( pPager->state==PAGER_SHARED ){ assert( pPager->aInJournal==0 ); if( pPager->memDb ){ - pPager->state = SQLITE_WRITELOCK; + pPager->state = PAGER_EXCLUSIVE; pPager->origDbSize = pPager->dbSize; }else{ int busy = 1; do { - /* If the library grabs an EXCLUSIVE lock here, as in the commented - ** out line, then it exhibits the old locking behaviour - a writer - ** excludes all readers, not just other writers. - */ - /* rc = sqlite3OsLock(&pPager->fd, EXCLUSIVE_LOCK); */ rc = sqlite3OsLock(&pPager->fd, RESERVED_LOCK); }while( rc==SQLITE_BUSY && pPager->pBusyHandler && @@ -2071,9 +2100,9 @@ int sqlite3pager_begin(void *pData, int nMaster){ return rc; } pPager->nMaster = nMaster; - pPager->state = SQLITE_WRITELOCK; - pPager->dirtyFile = 0; - TRACE1("TRANSACTION\n"); + pPager->state = PAGER_RESERVED; + pPager->dirtyCache = 0; + TRACE3("TRANSACTION %d nMaster=%d\n", pPager->fd.h, nMaster); if( pPager->useJournal && !pPager->tempFile ){ rc = pager_open_journal(pPager); } @@ -2088,7 +2117,7 @@ int sqlite3pager_begin(void *pData, int nMaster){ ** changes to a page. ** ** The first time this routine is called, the pager creates a new -** journal and acquires a write lock on the database. If the write +** journal and acquires a RESERVED lock on the database. If the RESERVED ** lock could not be acquired, this routine returns SQLITE_BUSY. The ** calling routine must check for that return value and be careful not to ** change any page data until this routine returns SQLITE_OK. @@ -2118,7 +2147,7 @@ int sqlite3pager_write(void *pData){ */ pPg->dirty = 1; if( pPg->inJournal && (pPg->inStmt || pPager->stmtInUse==0) ){ - pPager->dirtyFile = 1; + pPager->dirtyCache = 1; return SQLITE_OK; } @@ -2129,22 +2158,22 @@ int sqlite3pager_write(void *pData){ ** First check to see that the transaction journal exists and ** create it if it does not. */ - assert( pPager->state!=SQLITE_UNLOCK ); + assert( pPager->state!=PAGER_UNLOCK ); rc = sqlite3pager_begin(pData, 0); if( rc!=SQLITE_OK ){ return rc; } - assert( pPager->state==SQLITE_WRITELOCK ); + assert( pPager->state>=PAGER_RESERVED ); if( !pPager->journalOpen && pPager->useJournal ){ rc = pager_open_journal(pPager); if( rc!=SQLITE_OK ) return rc; } assert( pPager->journalOpen || !pPager->useJournal ); - pPager->dirtyFile = 1; + pPager->dirtyCache = 1; - /* The transaction journal now exists and we have a write lock on the - ** main database file. Write the current page to the transaction - ** journal if it is not there already. + /* The transaction journal now exists and we have a RESERVED or an + ** EXCLUSIVE lock on the main database file. Write the current page to + ** the transaction journal if it is not there already. */ if( !pPg->inJournal && (pPager->useJournal || pPager->memDb) ){ if( (int)pPg->pgno <= pPager->origDbSize ){ @@ -2152,7 +2181,7 @@ int sqlite3pager_write(void *pData){ u32 saved; if( pPager->memDb ){ PgHistory *pHist = PGHDR_TO_HIST(pPg, pPager); - TRACE2("JOURNAL %d\n", pPg->pgno); + TRACE2("JOURNAL page %d\n", pPg->pgno); assert( pHist->pOrig==0 ); pHist->pOrig = sqliteMallocRaw( pPager->pageSize ); if( pHist->pOrig ){ @@ -2171,7 +2200,7 @@ int sqlite3pager_write(void *pData){ store32bits(pPg->pgno, pPg, -4); CODEC(pPager, pData, pPg->pgno, 7); rc = sqlite3OsWrite(&pPager->jfd, &((char*)pData)[-4], szPg); - TRACE3("JOURNAL %d %d\n", pPg->pgno, pPg->needSync); + TRACE3("JOURNAL page %d needSync=%d\n", pPg->pgno, pPg->needSync); CODEC(pPager, pData, pPg->pgno, 0); if( journal_format>=JOURNAL_FORMAT_3 ){ *(u32*)PGHDR_TO_EXTRA(pPg) = saved; @@ -2193,7 +2222,7 @@ int sqlite3pager_write(void *pData){ } }else{ pPg->needSync = !pPager->journalStarted && !pPager->noSync; - TRACE3("APPEND %d %d\n", pPg->pgno, pPg->needSync); + TRACE3("APPEND page %d needSync=%d\n", pPg->pgno, pPg->needSync); } if( pPg->needSync ){ pPager->needSync = 1; @@ -2214,12 +2243,12 @@ int sqlite3pager_write(void *pData){ if( pHist->pStmt ){ memcpy(pHist->pStmt, PGHDR_TO_DATA(pPg), pPager->pageSize); } - TRACE2("STMT-JOURNAL %d\n", pPg->pgno); + TRACE2("STMT-JOURNAL page %d\n", pPg->pgno); }else{ store32bits(pPg->pgno, pPg, -4); CODEC(pPager, pData, pPg->pgno, 7); rc = sqlite3OsWrite(&pPager->stfd, ((char*)pData)-4, SQLITE_PAGE_SIZE+4); - TRACE2("STMT-JOURNAL %d\n", pPg->pgno); + TRACE2("STMT-JOURNAL page %d\n", pPg->pgno); CODEC(pPager, pData, pPg->pgno, 0); if( rc!=SQLITE_OK ){ sqlite3pager_rollback(pPager); @@ -2310,7 +2339,7 @@ void sqlite3pager_dont_write(Pager *pPager, Pgno pgno){ ** corruption during the next transaction. */ }else{ - TRACE2("DONT_WRITE %d\n", pgno); + TRACE3("DONT_WRITE page %d of %d\n", pgno, pPager->fd.h); pPg->dirty = 0; } } @@ -2326,7 +2355,7 @@ void sqlite3pager_dont_rollback(void *pData){ PgHdr *pPg = DATA_TO_PGHDR(pData); Pager *pPager = pPg->pPager; - if( pPager->state!=SQLITE_WRITELOCK || pPager->journalOpen==0 ) return; + if( pPager->state!=PAGER_EXCLUSIVE || pPager->journalOpen==0 ) return; if( pPg->alwaysRollback || pPager->alwaysRollback || pPager->memDb ) return; if( !pPg->inJournal && (int)pPg->pgno <= pPager->origDbSize ){ assert( pPager->aInJournal!=0 ); @@ -2336,7 +2365,7 @@ void sqlite3pager_dont_rollback(void *pData){ pPager->aInStmt[pPg->pgno/8] |= 1<<(pPg->pgno&7); page_add_to_stmt_list(pPg); } - TRACE2("DONT_ROLLBACK %d\n", pPg->pgno); + TRACE3("DONT_ROLLBACK page %d of %d\n", pPg->pgno, pPager->fd.h); } if( pPager->stmtInUse && !pPg->inStmt && (int)pPg->pgno<=pPager->stmtSize ){ assert( pPg->inJournal || (int)pPg->pgno>pPager->origDbSize ); @@ -2379,10 +2408,10 @@ int sqlite3pager_commit(Pager *pPager){ rc = pager_errcode(pPager); return rc; } - if( pPager->state!=SQLITE_WRITELOCK ){ + if( pPager->state<PAGER_RESERVED ){ return SQLITE_ERROR; } - TRACE1("COMMIT\n"); + TRACE2("COMMIT %d\n", pPager->fd.h); if( pPager->memDb ){ pPg = pager_get_all_dirty_pages(pPager); while( pPg ){ @@ -2394,11 +2423,10 @@ int sqlite3pager_commit(Pager *pPager){ pPg = pPg->pDirty; } pPager->pStmt = 0; - pPager->state = SQLITE_READLOCK; + pPager->state = PAGER_SHARED; return SQLITE_OK; } -#if 0 - if( pPager->dirtyFile==0 ){ + if( pPager->dirtyCache==0 ){ /* Exit early (without doing the time-consuming sqlite3OsSync() calls) ** if there have been no changes to the database file. */ assert( pPager->needSync==0 ); @@ -2407,6 +2435,7 @@ int sqlite3pager_commit(Pager *pPager){ return rc; } assert( pPager->journalOpen ); +#if 0 rc = syncJournal(pPager, 0); if( rc!=SQLITE_OK ){ goto commit_abort; @@ -2420,8 +2449,9 @@ int sqlite3pager_commit(Pager *pPager){ } #endif rc = sqlite3pager_sync(pPager, 0); - if( rc!=SQLITE_OK ) goto commit_abort; - + if( rc!=SQLITE_OK ){ + goto commit_abort; + } rc = pager_unwritelock(pPager); pPager->dbSize = -1; return rc; @@ -2437,7 +2467,7 @@ commit_abort: } /* -** Rollback all changes. The database falls back to read-only mode. +** Rollback all changes. The database falls back to PAGER_SHARED mode. ** All in-memory cache pages revert to their original data contents. ** The journal is deleted. ** @@ -2450,7 +2480,7 @@ commit_abort: */ int sqlite3pager_rollback(Pager *pPager){ int rc; - TRACE1("ROLLBACK\n"); + TRACE2("ROLLBACK %d\n", pPager->fd.h); if( pPager->memDb ){ PgHdr *p; for(p=pPager->pAll; p; p=p->pNextAll){ @@ -2473,26 +2503,32 @@ int sqlite3pager_rollback(Pager *pPager){ pPager->dbSize = pPager->origDbSize; memoryTruncate(pPager); pPager->stmtInUse = 0; - pPager->state = SQLITE_READLOCK; + pPager->state = PAGER_SHARED; return SQLITE_OK; } - if( !pPager->dirtyFile || !pPager->journalOpen ){ + if( !pPager->dirtyCache || !pPager->journalOpen ){ rc = pager_unwritelock(pPager); pPager->dbSize = -1; return rc; } if( pPager->errMask!=0 && pPager->errMask!=PAGER_ERR_FULL ){ - if( pPager->state>=SQLITE_WRITELOCK ){ + if( pPager->state>=PAGER_EXCLUSIVE ){ pager_playback(pPager, 1); } return pager_errcode(pPager); } - if( pPager->state!=SQLITE_WRITELOCK ){ - return SQLITE_OK; + if( pPager->state==PAGER_RESERVED ){ + int rc2; + rc = pager_reload_cache(pPager); + rc2 = pager_unwritelock(pPager); + if( rc==SQLITE_OK ){ + rc = rc2; + } + }else{ + rc = pager_playback(pPager, 1); } - rc = pager_playback(pPager, 1); if( rc!=SQLITE_OK ){ rc = SQLITE_CORRUPT; pPager->errMask |= PAGER_ERR_CORRUPT; @@ -2537,7 +2573,7 @@ int sqlite3pager_stmt_begin(Pager *pPager){ int rc; char zTemp[SQLITE_TEMPNAME_SIZE]; assert( !pPager->stmtInUse ); - TRACE1("STMT-BEGIN\n"); + TRACE2("STMT-BEGIN %d\n", pPager->fd.h); if( pPager->memDb ){ pPager->stmtInUse = 1; pPager->stmtSize = pPager->dbSize; @@ -2586,7 +2622,7 @@ stmt_begin_failed: int sqlite3pager_stmt_commit(Pager *pPager){ if( pPager->stmtInUse ){ PgHdr *pPg, *pNext; - TRACE1("STMT-COMMIT\n"); + TRACE2("STMT-COMMIT %d\n", pPager->fd.h); if( !pPager->memDb ){ sqlite3OsSeek(&pPager->stfd, 0); /* sqlite3OsTruncate(&pPager->stfd, 0); */ @@ -2618,7 +2654,7 @@ int sqlite3pager_stmt_commit(Pager *pPager){ int sqlite3pager_stmt_rollback(Pager *pPager){ int rc; if( pPager->stmtInUse ){ - TRACE1("STMT-ROLLBACK\n"); + TRACE2("STMT-ROLLBACK %d\n", pPager->fd.h); if( pPager->memDb ){ PgHdr *pPg; for(pPg=pPager->pStmt; pPg; pPg=pPg->pNextStmt){ @@ -2682,7 +2718,7 @@ int sqlite3pager_sync(Pager *pPager, const char *zMaster){ /* If this is an in-memory db, or no pages have been written to, this ** function is a no-op. */ - if( !pPager->memDb && pPager->dirtyFile ){ + if( !pPager->memDb && pPager->dirtyCache ){ PgHdr *pPg; assert( pPager->journalOpen ); diff --git a/src/pager.h b/src/pager.h index 580b9f884..122129bf0 100644 --- a/src/pager.h +++ b/src/pager.h @@ -13,7 +13,7 @@ ** subsystem. The page cache subsystem reads and writes a file a page ** at a time and provides a journal for rollback. ** -** @(#) $Id: pager.h,v 1.32 2004/06/09 14:17:21 drh Exp $ +** @(#) $Id: pager.h,v 1.33 2004/06/09 17:37:28 drh Exp $ */ /* @@ -73,6 +73,7 @@ int sqlite3pager_open(Pager **ppPager, const char *zFilename, int nPage, int nExtra, int useJournal, void *pBusyHandler); void sqlite3pager_set_destructor(Pager*, void(*)(void*,int)); +void sqlite3pager_set_reiniter(Pager*, void(*)(void*,int)); void sqlite3pager_set_cachesize(Pager*, int); int sqlite3pager_close(Pager *pPager); int sqlite3pager_get(Pager *pPager, Pgno pgno, void **ppPage); diff --git a/src/test1.c b/src/test1.c index 9fb1b5904..911e6d12e 100644 --- a/src/test1.c +++ b/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.73 2004/06/09 14:01:51 drh Exp $ +** $Id: test1.c,v 1.74 2004/06/09 17:37:28 drh Exp $ */ #include "sqliteInt.h" #include "tcl.h" @@ -1782,7 +1782,7 @@ static int test_sqlite3OsUnlock( if( getFilePointer(interp, Tcl_GetString(objv[1]), &pFile) ){ return TCL_ERROR; } - rc = sqlite3OsUnlock(pFile); + rc = sqlite3OsUnlock(pFile, NO_LOCK); if( rc!=SQLITE_OK ){ Tcl_SetResult(interp, (char *)errorName(rc), TCL_STATIC); return TCL_ERROR; diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 7b19eb1ca..e97f99c69 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -934,7 +934,7 @@ static int vdbeCommit(sqlite *db){ ** database) has a transaction active. There is no need for the ** master-journal. */ - if( nTrans<=1 ){ + if( nTrans<=100 ){ /**** FIX ME ****/ for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ Btree *pBt = db->aDb[i].pBt; if( pBt ){ @@ -964,12 +964,10 @@ static int vdbeCommit(sqlite *db){ /* Select a master journal file name */ do { - int random; - if( zMaster ){ - sqliteFree(zMaster); - } + u32 random; + sqliteFree(zMaster); sqlite3Randomness(sizeof(random), &random); - zMaster = sqlite3_mprintf("%s%d", zMainFile, random); + zMaster = sqlite3MPrintf("%s-mj%08X", zMainFile, random); if( !zMaster ){ return SQLITE_NOMEM; } diff --git a/test/attach2.test b/test/attach2.test index 70d72b84d..a8f5100da 100644 --- a/test/attach2.test +++ b/test/attach2.test @@ -12,9 +12,8 @@ # focus of this script is testing the ATTACH and DETACH commands # and related functionality. # -# $Id: attach2.test,v 1.13 2004/06/09 14:01:58 drh Exp $ +# $Id: attach2.test,v 1.14 2004/06/09 17:37:29 drh Exp $ # -set sqlite_os_trace 0 set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -147,6 +146,14 @@ db close for {set i 2} {$i<=15} {incr i} { catch {db$i close} } + +# A procedure to verify the status of locks on a database. +# +proc lock_status {testnum db expected_result} { + do_test attach2-$testnum [subst { + execsql {PRAGMA lock_status} $db + }] $expected_result +} set sqlite_os_trace 0 # Tests attach2-4.* test that read-locks work correctly with attached @@ -158,6 +165,9 @@ do_test attach2-4.1 { execsql {ATTACH 'test2.db' as file2} db2 } {} +lock_status 4.1.1 db {main unlocked temp unlocked file2 unlocked} +lock_status 4.1.2 db2 {main unlocked temp unlocked file2 unlocked} + do_test attach2-4.2 { # Handle 'db' read-locks test.db execsql {BEGIN} @@ -166,10 +176,18 @@ do_test attach2-4.2 { # db - shared(main) # db2 - } {} + +lock_status 4.2.1 db {main shared temp shared file2 unlocked} +lock_status 4.2.2 db2 {main unlocked temp unlocked file2 unlocked} + do_test attach2-4.3 { # The read lock held by db does not prevent db2 from reading test.db execsql {SELECT * FROM t1} db2 } {} + +lock_status 4.3.1 db {main shared temp shared file2 unlocked} +lock_status 4.3.2 db2 {main unlocked temp unlocked file2 unlocked} + do_test attach2-4.4 { # db is holding a read lock on test.db, so we should not be able # to commit a write to test.db from db2 @@ -177,6 +195,10 @@ do_test attach2-4.4 { INSERT INTO t1 VALUES(1, 2) } db2 } {1 {database is locked}} + +lock_status 4.4.1 db {main shared temp shared file2 unlocked} +lock_status 4.4.2 db2 {main unlocked temp unlocked file2 unlocked} + do_test attach2-4.5 { # Handle 'db2' reserves file2. execsql {BEGIN} db2 @@ -185,6 +207,10 @@ do_test attach2-4.5 { # db - shared(main) # db2 - reserved(file2) } {} + +lock_status 4.5.1 db {main shared temp shared file2 unlocked} +lock_status 4.5.2 db2 {main unlocked temp reserved file2 reserved} + do_test attach2-4.6.1 { # Reads are allowed against a reserved database. catchsql { @@ -194,38 +220,57 @@ do_test attach2-4.6.1 { # db - shared(main), shared(file2) # db2 - reserved(file2) } {0 {}} + +lock_status 4.6.1.1 db {main shared temp shared file2 shared} +lock_status 4.6.1.2 db2 {main unlocked temp reserved file2 reserved} + do_test attach2-4.6.2 { # Writes against a reserved database are not allowed. catchsql { UPDATE file2.t1 SET a=0; } } {1 {database is locked}} + +lock_status 4.6.2.1 db {main shared temp reserved file2 shared} +lock_status 4.6.2.2 db2 {main unlocked temp reserved file2 reserved} + do_test attach2-4.7 { # Ensure handle 'db' retains the lock on the main file after # failing to obtain a write-lock on file2. catchsql { INSERT INTO t1 VALUES(1, 2) } db2 -} {1 {database is locked}} +} {0 {}} + +lock_status 4.7.1 db {main shared temp reserved file2 shared} +lock_status 4.7.2 db2 {main reserved temp reserved file2 reserved} + do_test attach2-4.8 { - # Read lock the main file with db2. Now both db and db2 have a read lock - # on the main file, db2 has a write-lock on file2. + # We should still be able to read test.db from db2 execsql {SELECT * FROM t1} db2 - # Lock status: - # db - shared(main), shared(file2) - # db2 - shared(main), reserved(file2) -} {} +} {1 2} + +lock_status 4.8.1 db {main shared temp reserved file2 shared} +lock_status 4.8.2 db2 {main reserved temp reserved file2 reserved} + do_test attach2-4.9 { # Try to upgrade the handle 'db' lock. catchsql { INSERT INTO t1 VALUES(1, 2) } - list $r $msg } {1 {database is locked}} + +lock_status 4.9.1 db {main shared temp reserved file2 shared} +lock_status 4.9.2 db2 {main reserved temp reserved file2 reserved} + do_test attach2-4.10 { # Release the locks held by handle 'db2' execsql {COMMIT} db2 } {} + +lock_status 4.10.1 db {main shared temp reserved file2 shared} +lock_status 4.10.2 db2 {main unlocked temp unlocked file2 unlocked} + do_test attach2-4.11 { execsql {SELECT * FROM file2.t1} } {1 2} |