| /* |
| ** 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 */ |