diff options
Diffstat (limited to 'src/os_unix.c')
-rw-r--r-- | src/os_unix.c | 92 |
1 files changed, 61 insertions, 31 deletions
diff --git a/src/os_unix.c b/src/os_unix.c index 8ee48de88..110dca38d 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -174,7 +174,7 @@ struct lockKey { */ struct lockInfo { struct lockKey key; /* The lookup key */ - int cnt; /* Number of locks held */ + int cnt; /* Number of SHARED locks held */ int locktype; /* One of SHARED_LOCK, RESERVED_LOCK etc. */ int nRef; /* Number of pointers to this structure */ }; @@ -644,27 +644,27 @@ int sqlite3OsFileSize(OsFile *id, off_t *pSize){ /* ** This routine checks if there is a RESERVED lock held on the specified ** file by this or any other process. If such a lock is held, return -** non-zero, otherwise zero. +** non-zero. If the file is unlocked or holds only SHARED locks, then +** return zero. */ int sqlite3OsCheckWriteLock(OsFile *id){ int r = 0; - sqlite3OsEnterMutex(); + sqlite3OsEnterMutex(); /* Needed because id->pLock is shared across threads */ /* Check if a thread in this process holds such a lock */ if( id->pLock->locktype>SHARED_LOCK ){ r = 1; } - /* Otherwise see if some other process holds it. Just check the whole - ** file for write-locks, rather than any specific bytes. + /* Otherwise see if some other process holds it. */ if( !r ){ struct flock lock; lock.l_whence = SEEK_SET; - lock.l_start = 0; - lock.l_len = 0; - lock.l_type = F_RDLCK; + lock.l_start = RESERVED_BYTE; + lock.l_len = 1; + lock.l_type = F_WRLCK; fcntl(id->fd, F_GETLK, &lock); if( lock.l_type!=F_UNLCK ){ r = 1; @@ -672,6 +672,7 @@ int sqlite3OsCheckWriteLock(OsFile *id){ } sqlite3OsLeaveMutex(); + TRACE3("TEST WR-LOCK %d %d\n", id->fd, r); return r; } @@ -680,10 +681,25 @@ int sqlite3OsCheckWriteLock(OsFile *id){ ** Lock the file with the lock specified by parameter locktype - one ** of the following: ** -** SHARED_LOCK -** RESERVED_LOCK -** PENDING_LOCK -** EXCLUSIVE_LOCK +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Locks are are hierarchical. Getting a lock N implies getting all locks +** N-1, N-2, N-3, .... So, for example, getting a PENDING lock +** implies a SHARED and a RESERVED lock. This routine adds locks one +** at a time until the desired lock is acheived. A locking failure might +** occur at any point. When a failure occurs intermediate locks are +** retained. For example, if a SHARED lock is held and this routine +** is called with EXCLUSIVE, it might obtain a RESERVED and PENDING lock +** but fail to get the EXCLUSIVE lock. In that case, the file would be +** left in the PENDING lock state - it does not revert to SHARED. +** +** 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. */ int sqlite3OsLock(OsFile *id, int locktype){ int rc = SQLITE_OK; @@ -691,12 +707,8 @@ int sqlite3OsLock(OsFile *id, int locktype){ struct flock lock; int s; - /* It is an error to request any kind of lock before a shared lock */ - if( locktype>SHARED_LOCK && id->locktype==0 ){ - rc = sqlite3OsLock(id, SHARED_LOCK); - if( rc!=SQLITE_OK ) return rc; - } - assert( locktype==SHARED_LOCK || id->locktype!=0 ); + TRACE5("LOCK %d %d was %d(%d)\n", + id->fd, locktype, id->locktype, pLock->locktype); /* 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 @@ -706,14 +718,27 @@ int sqlite3OsLock(OsFile *id, int locktype){ return SQLITE_OK; } - sqlite3OsEnterMutex(); + /* Make sure locking is sequential. In other words, make sure we have + ** SHARED before trying for RESERVED, and that we have RESERVED before + ** trying for PENDING, and that we have PENDING before trying for + ** EXCLUSIVE. + */ + while( locktype>id->locktype+1 ){ + rc = sqlite3OsLock(id, id->locktype+1); + if( rc!=SQLITE_OK ){ + return rc; + } + } + assert( locktype==id->locktype+1 ); + + sqlite3OsEnterMutex(); /* Needed because pLock is shared across threads */ /* If some thread using this PID has a lock via a different OsFile* ** handle that precludes the requested lock, return BUSY. */ if( (id->locktype!=pLock->locktype && - (pLock->locktype>RESERVED_LOCK || locktype!=SHARED_LOCK)) || - (locktype>RESERVED_LOCK && pLock->cnt>1) + (pLock->locktype>=PENDING_LOCK || locktype>SHARED_LOCK)) + || (locktype==EXCLUSIVE_LOCK && pLock->cnt>1) ){ rc = SQLITE_BUSY; goto end_lock; @@ -744,21 +769,25 @@ int sqlite3OsLock(OsFile *id, int locktype){ assert( pLock->cnt==0 ); assert( pLock->locktype==0 ); - /* Grab a read-lock on byte 2. This ensures that no other process - ** has a PENDING lock. + /* Temporarily grab a PENDING lock. This prevents new SHARED locks from + ** being formed if a PENDING lock is already held. */ lock.l_type = F_RDLCK; - lock.l_start = 2; + lock.l_start = PENDING_BYTE; s = fcntl(id->fd, F_SETLK, &lock); if( s ){ rc = (errno==EINVAL) ? SQLITE_NOLFS : SQLITE_BUSY; goto end_lock; } - /* Now get a read-lock on byte 0 and renege on the byte 2 lock. */ - lock.l_start = 0; + /* Now get the read-lock */ + lock.l_start = SHARED_FIRST; + lock.l_len = SHARED_SIZE; s = fcntl(id->fd, F_SETLK, &lock); - lock.l_start = 2; + + /* 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); if( s ){ @@ -777,13 +806,14 @@ int sqlite3OsLock(OsFile *id, int locktype){ lock.l_type = F_WRLCK; switch( locktype ){ case RESERVED_LOCK: - lock.l_start = 1; + lock.l_start = RESERVED_BYTE; break; case PENDING_LOCK: - lock.l_start = 2; + lock.l_start = PENDING_BYTE; break; case EXCLUSIVE_LOCK: - lock.l_start = 0; + lock.l_start = SHARED_FIRST; + lock.l_len = SHARED_SIZE; break; default: assert(0); @@ -797,11 +827,11 @@ int sqlite3OsLock(OsFile *id, int locktype){ if( rc==SQLITE_OK ){ id->locktype = locktype; pLock->locktype = locktype; - assert( pLock->locktype==RESERVED_LOCK || pLock->cnt==1 ); } end_lock: sqlite3OsLeaveMutex(); + TRACE4("LOCK %d %d %s\n", id->fd, locktype, rc==SQLITE_OK ? "ok" : "failed"); return rc; } |