Index: main.mk ================================================================== --- main.mk +++ main.mk @@ -249,10 +249,11 @@ $(TOP)/src/test_quota.c \ $(TOP)/src/test_rtree.c \ $(TOP)/src/test_schema.c \ $(TOP)/src/test_server.c \ $(TOP)/src/test_stat.c \ + $(TOP)/src/test_superlock.c \ $(TOP)/src/test_tclvar.c \ $(TOP)/src/test_thread.c \ $(TOP)/src/test_vfs.c \ $(TOP)/src/test_wsd.c Index: src/btree.c ================================================================== --- src/btree.c +++ src/btree.c @@ -2103,15 +2103,21 @@ ** there is a high probability of damage) Level 2 is the default. There ** is a very low but non-zero probability of damage. Level 3 reduces the ** probability of damage to near zero but with a write performance reduction. */ #ifndef SQLITE_OMIT_PAGER_PRAGMAS -int sqlite3BtreeSetSafetyLevel(Btree *p, int level, int fullSync){ +int sqlite3BtreeSetSafetyLevel( + Btree *p, /* The btree to set the safety level on */ + int level, /* PRAGMA synchronous. 1=OFF, 2=NORMAL, 3=FULL */ + int fullSync, /* PRAGMA fullfsync. */ + int ckptFullSync /* PRAGMA checkpoint_fullfync */ +){ BtShared *pBt = p->pBt; assert( sqlite3_mutex_held(p->db->mutex) ); + assert( level>=1 && level<=3 ); sqlite3BtreeEnter(p); - sqlite3PagerSetSafetyLevel(pBt->pPager, level, fullSync); + sqlite3PagerSetSafetyLevel(pBt->pPager, level, fullSync, ckptFullSync); sqlite3BtreeLeave(p); return SQLITE_OK; } #endif Index: src/btree.h ================================================================== --- src/btree.h +++ src/btree.h @@ -73,11 +73,11 @@ #define BTREE_SINGLE 8 /* The file contains at most 1 b-tree */ #define BTREE_UNORDERED 16 /* Use of a hash implementation is OK */ int sqlite3BtreeClose(Btree*); int sqlite3BtreeSetCacheSize(Btree*,int); -int sqlite3BtreeSetSafetyLevel(Btree*,int,int); +int sqlite3BtreeSetSafetyLevel(Btree*,int,int,int); int sqlite3BtreeSyncDisabled(Btree*); int sqlite3BtreeSetPageSize(Btree *p, int nPagesize, int nReserve, int eFix); int sqlite3BtreeGetPageSize(Btree*); int sqlite3BtreeMaxPageCount(Btree*,int); u32 sqlite3BtreeLastPage(Btree*); Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -2383,11 +2383,14 @@ sqlite3BtreeEnter(pBtree); pPager = sqlite3BtreePager(pBtree); assert( pPager!=0 ); fd = sqlite3PagerFile(pPager); assert( fd!=0 ); - if( fd->pMethods ){ + if( op==SQLITE_FCNTL_FILE_POINTER ){ + *(sqlite3_file**)pArg = fd; + rc = SQLITE_OK; + }else if( fd->pMethods ){ rc = sqlite3OsFileControl(fd, op, pArg); } sqlite3BtreeLeave(pBtree); } } Index: src/pager.c ================================================================== --- src/pager.c +++ src/pager.c @@ -613,11 +613,12 @@ u8 journalMode; /* One of the PAGER_JOURNALMODE_* values */ u8 useJournal; /* Use a rollback journal on this file */ u8 noReadlock; /* Do not bother to obtain readlocks */ u8 noSync; /* Do not sync the journal if true */ u8 fullSync; /* Do extra syncs of the journal for robustness */ - u8 sync_flags; /* One of SYNC_NORMAL or SYNC_FULL */ + u8 ckptSyncFlags; /* SYNC_NORMAL or SYNC_FULL for checkpoint */ + u8 syncFlags; /* SYNC_NORMAL or SYNC_FULL otherwise */ u8 tempFile; /* zFilename is a temporary file */ u8 readOnly; /* True for a read-only database */ u8 memDb; /* True to inhibit all file I/O */ /************************************************************************** @@ -1297,11 +1298,11 @@ }else{ static const char zeroHdr[28] = {0}; rc = sqlite3OsWrite(pPager->jfd, zeroHdr, sizeof(zeroHdr), 0); } if( rc==SQLITE_OK && !pPager->noSync ){ - rc = sqlite3OsSync(pPager->jfd, SQLITE_SYNC_DATAONLY|pPager->sync_flags); + rc = sqlite3OsSync(pPager->jfd, SQLITE_SYNC_DATAONLY|pPager->syncFlags); } /* At this point the transaction is committed but the write lock ** is still held on the file. If there is a size limit configured for ** the persistent journal and the journal file currently consumes more @@ -2749,11 +2750,11 @@ testcase( rc!=SQLITE_OK ); } if( rc==SQLITE_OK && !pPager->noSync && (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN) ){ - rc = sqlite3OsSync(pPager->fd, pPager->sync_flags); + rc = sqlite3OsSync(pPager->fd, pPager->syncFlags); } if( rc==SQLITE_OK ){ rc = pager_end_transaction(pPager, zMaster[0]!='\0'); testcase( rc!=SQLITE_OK ); } @@ -2923,17 +2924,17 @@ static int pagerWalFrames( Pager *pPager, /* Pager object */ PgHdr *pList, /* List of frames to log */ Pgno nTruncate, /* Database size after this commit */ int isCommit, /* True if this is a commit */ - int sync_flags /* Flags to pass to OsSync() (or 0) */ + int syncFlags /* Flags to pass to OsSync() (or 0) */ ){ int rc; /* Return code */ assert( pPager->pWal ); rc = sqlite3WalFrames(pPager->pWal, - pPager->pageSize, pList, nTruncate, isCommit, sync_flags + pPager->pageSize, pList, nTruncate, isCommit, syncFlags ); if( rc==SQLITE_OK && pPager->pBackup ){ PgHdr *p; for(p=pList; p; p=p->pDirty){ sqlite3BackupUpdate(pPager->pBackup, p->pgno, (u8 *)p->pData); @@ -3258,19 +3259,54 @@ ** of the journal header - being written in between the two ** syncs). If we assume that writing a ** single disk sector is atomic, then this mode provides ** assurance that the journal will not be corrupted to the ** point of causing damage to the database during rollback. +** +** The above is for a rollback-journal mode. For WAL mode, OFF continues +** to mean that no syncs ever occur. NORMAL means that the WAL is synced +** prior to the start of checkpoint and that the database file is synced +** at the conclusion of the checkpoint if the entire content of the WAL +** was written back into the database. But no sync operations occur for +** an ordinary commit in NORMAL mode with WAL. FULL means that the WAL +** file is synced following each commit operation, in addition to the +** syncs associated with NORMAL. +** +** Do not confuse synchronous=FULL with SQLITE_SYNC_FULL. The +** SQLITE_SYNC_FULL macro means to use the MacOSX-style full-fsync +** using fcntl(F_FULLFSYNC). SQLITE_SYNC_NORMAL means to do an +** ordinary fsync() call. There is no difference between SQLITE_SYNC_FULL +** and SQLITE_SYNC_NORMAL on platforms other than MacOSX. But the +** synchronous=FULL versus synchronous=NORMAL setting determines when +** the xSync primitive is called and is relevant to all platforms. ** ** Numeric values associated with these states are OFF==1, NORMAL=2, ** and FULL=3. */ #ifndef SQLITE_OMIT_PAGER_PRAGMAS -void sqlite3PagerSetSafetyLevel(Pager *pPager, int level, int bFullFsync){ +void sqlite3PagerSetSafetyLevel( + Pager *pPager, /* The pager to set safety level for */ + int level, /* PRAGMA synchronous. 1=OFF, 2=NORMAL, 3=FULL */ + int bFullFsync, /* PRAGMA fullfsync */ + int bCkptFullFsync /* PRAGMA checkpoint_fullfsync */ +){ + assert( level>=1 && level<=3 ); pPager->noSync = (level==1 || pPager->tempFile) ?1:0; pPager->fullSync = (level==3 && !pPager->tempFile) ?1:0; - pPager->sync_flags = (bFullFsync?SQLITE_SYNC_FULL:SQLITE_SYNC_NORMAL); + if( pPager->noSync ){ + pPager->syncFlags = 0; + pPager->ckptSyncFlags = 0; + }else if( bFullFsync ){ + pPager->syncFlags = SQLITE_SYNC_FULL; + pPager->ckptSyncFlags = SQLITE_SYNC_FULL; + }else if( bCkptFullFsync ){ + pPager->syncFlags = SQLITE_SYNC_NORMAL; + pPager->ckptSyncFlags = SQLITE_SYNC_FULL; + }else{ + pPager->syncFlags = SQLITE_SYNC_NORMAL; + pPager->ckptSyncFlags = SQLITE_SYNC_NORMAL; + } } #endif /* ** The following global variable is incremented whenever the library @@ -3652,14 +3688,11 @@ disable_simulated_io_errors(); sqlite3BeginBenignMalloc(); /* pPager->errCode = 0; */ pPager->exclusiveMode = 0; #ifndef SQLITE_OMIT_WAL - sqlite3WalClose(pPager->pWal, - (pPager->noSync ? 0 : pPager->sync_flags), - pPager->pageSize, pTmp - ); + sqlite3WalClose(pPager->pWal, pPager->ckptSyncFlags, pPager->pageSize, pTmp); pPager->pWal = 0; #endif pager_reset(pPager); if( MEMDB ){ pager_unlock(pPager); @@ -3821,11 +3854,11 @@ ** and never needs to be updated. */ if( pPager->fullSync && 0==(iDc&SQLITE_IOCAP_SEQUENTIAL) ){ PAGERTRACE(("SYNC journal of %d\n", PAGERID(pPager))); IOTRACE(("JSYNC %p\n", pPager)) - rc = sqlite3OsSync(pPager->jfd, pPager->sync_flags); + rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags); if( rc!=SQLITE_OK ) return rc; } IOTRACE(("JHDR %p %lld\n", pPager, pPager->journalHdr)); rc = sqlite3OsWrite( pPager->jfd, zHeader, sizeof(zHeader), pPager->journalHdr @@ -3833,12 +3866,12 @@ if( rc!=SQLITE_OK ) return rc; } if( 0==(iDc&SQLITE_IOCAP_SEQUENTIAL) ){ PAGERTRACE(("SYNC journal of %d\n", PAGERID(pPager))); IOTRACE(("JSYNC %p\n", pPager)) - rc = sqlite3OsSync(pPager->jfd, pPager->sync_flags| - (pPager->sync_flags==SQLITE_SYNC_FULL?SQLITE_SYNC_DATAONLY:0) + rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags| + (pPager->syncFlags==SQLITE_SYNC_FULL?SQLITE_SYNC_DATAONLY:0) ); if( rc!=SQLITE_OK ) return rc; } pPager->journalHdr = pPager->journalOff; @@ -4424,11 +4457,12 @@ pPager->memDb = (u8)memDb; pPager->readOnly = (u8)readOnly; assert( useJournal || pPager->tempFile ); pPager->noSync = pPager->tempFile; pPager->fullSync = pPager->noSync ?0:1; - pPager->sync_flags = SQLITE_SYNC_NORMAL; + pPager->syncFlags = pPager->noSync ? 0 : SQLITE_SYNC_NORMAL; + pPager->ckptSyncFlags = pPager->syncFlags; /* pPager->pFirst = 0; */ /* pPager->pFirstSynced = 0; */ /* pPager->pLast = 0; */ pPager->nExtra = (u16)nExtra; pPager->journalSizeLimit = SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT; @@ -5547,23 +5581,23 @@ } return rc; } /* -** Sync the pager file to disk. This is a no-op for in-memory files +** Sync the database file to disk. This is a no-op for in-memory databases ** or pages with the Pager.noSync flag set. ** -** If successful, or called on a pager for which it is a no-op, this +** If successful, or if called on a pager for which it is a no-op, this ** function returns SQLITE_OK. Otherwise, an IO error code is returned. */ int sqlite3PagerSync(Pager *pPager){ int rc; /* Return code */ assert( !MEMDB ); if( pPager->noSync ){ rc = SQLITE_OK; }else{ - rc = sqlite3OsSync(pPager->fd, pPager->sync_flags); + rc = sqlite3OsSync(pPager->fd, pPager->syncFlags); } return rc; } /* @@ -5648,11 +5682,11 @@ }else{ if( pagerUseWal(pPager) ){ PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache); if( pList ){ rc = pagerWalFrames(pPager, pList, pPager->dbSize, 1, - (pPager->fullSync ? pPager->sync_flags : 0) + (pPager->fullSync ? pPager->syncFlags : 0) ); } if( rc==SQLITE_OK ){ sqlite3PcacheCleanAll(pPager->pPCache); } @@ -5779,11 +5813,11 @@ if( rc!=SQLITE_OK ) goto commit_phase_one_exit; } /* Finally, sync the database file. */ if( !pPager->noSync && !noSync ){ - rc = sqlite3OsSync(pPager->fd, pPager->sync_flags); + rc = sqlite3OsSync(pPager->fd, pPager->syncFlags); } IOTRACE(("DBSYNC %p\n", pPager)) } } @@ -6525,12 +6559,11 @@ int sqlite3PagerCheckpoint(Pager *pPager, int eMode, int *pnLog, int *pnCkpt){ int rc = SQLITE_OK; if( pPager->pWal ){ rc = sqlite3WalCheckpoint(pPager->pWal, eMode, pPager->xBusyHandler, pPager->pBusyHandlerArg, - (pPager->noSync ? 0 : pPager->sync_flags), - pPager->pageSize, (u8 *)pPager->pTmpSpace, + pPager->ckptSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace, pnLog, pnCkpt ); } return rc; } @@ -6680,14 +6713,12 @@ ** the database file, the log and log-summary files will be deleted. */ if( rc==SQLITE_OK && pPager->pWal ){ rc = pagerExclusiveLock(pPager); if( rc==SQLITE_OK ){ - rc = sqlite3WalClose(pPager->pWal, - (pPager->noSync ? 0 : pPager->sync_flags), - pPager->pageSize, (u8*)pPager->pTmpSpace - ); + rc = sqlite3WalClose(pPager->pWal, pPager->ckptSyncFlags, + pPager->pageSize, (u8*)pPager->pTmpSpace); pPager->pWal = 0; } } return rc; } Index: src/pager.h ================================================================== --- src/pager.h +++ src/pager.h @@ -101,11 +101,11 @@ /* Functions used to configure a Pager object. */ void sqlite3PagerSetBusyhandler(Pager*, int(*)(void *), void *); int sqlite3PagerSetPagesize(Pager*, u32*, int); int sqlite3PagerMaxPageCount(Pager*, int); void sqlite3PagerSetCachesize(Pager*, int); -void sqlite3PagerSetSafetyLevel(Pager*,int,int); +void sqlite3PagerSetSafetyLevel(Pager*,int,int,int); int sqlite3PagerLockingMode(Pager *, int); int sqlite3PagerSetJournalMode(Pager *, int); int sqlite3PagerGetJournalMode(Pager*); int sqlite3PagerOkToChangeJournalMode(Pager*); i64 sqlite3PagerJournalSizeLimit(Pager *, i64); Index: src/pragma.c ================================================================== --- src/pragma.c +++ src/pragma.c @@ -170,10 +170,11 @@ { "short_column_names", SQLITE_ShortColNames }, { "count_changes", SQLITE_CountRows }, { "empty_result_callbacks", SQLITE_NullCallback }, { "legacy_file_format", SQLITE_LegacyFileFmt }, { "fullfsync", SQLITE_FullFSync }, + { "checkpoint_fullfsync", SQLITE_CkptFullFSync }, { "reverse_unordered_selects", SQLITE_ReverseOrder }, #ifndef SQLITE_OMIT_AUTOMATIC_INDEX { "automatic_index", SQLITE_AutoIndex }, #endif #ifdef SQLITE_DEBUG @@ -1517,14 +1518,15 @@ ** setting changed. */ #ifndef SQLITE_OMIT_PAGER_PRAGMAS if( db->autoCommit ){ sqlite3BtreeSetSafetyLevel(pDb->pBt, pDb->safety_level, - (db->flags&SQLITE_FullFSync)!=0); + (db->flags&SQLITE_FullFSync)!=0, + (db->flags&SQLITE_CkptFullFSync)!=0); } #endif pragma_out: sqlite3DbFree(db, zLeft); sqlite3DbFree(db, zRight); } #endif /* SQLITE_OMIT_PRAGMA */ Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -535,10 +535,22 @@ ** sync operation only needs to flush data to mass storage. Inode ** information need not be flushed. If the lower four bits of the flag ** equal SQLITE_SYNC_NORMAL, that means to use normal fsync() semantics. ** If the lower four bits equal SQLITE_SYNC_FULL, that means ** to use Mac OS X style fullsync instead of fsync(). +** +** Do not confuse the SQLITE_SYNC_NORMAL and SQLITE_SYNC_FULL flags +** with the [PRAGMA synchronous]=NORMAL and [PRAGMA synchronous]=FULL +** settings. The [synchronous pragma] determines when calls to the +** xSync VFS method occur and applies uniformly across all platforms. +** The SQLITE_SYNC_NORMAL and SQLITE_SYNC_FULL flags determine how +** energetic or rigorous or forceful the sync operations are and +** only make a difference on Mac OSX for the default SQLite code. +** (Third-party VFS implementations might also make the distinction +** between SQLITE_SYNC_NORMAL and SQLITE_SYNC_FULL, but among the +** operating systems natively supported by SQLite, only Mac OSX +** cares about the difference.) */ #define SQLITE_SYNC_NORMAL 0x00002 #define SQLITE_SYNC_FULL 0x00003 #define SQLITE_SYNC_DATAONLY 0x00010 @@ -703,10 +715,12 @@ #define SQLITE_GET_LOCKPROXYFILE 2 #define SQLITE_SET_LOCKPROXYFILE 3 #define SQLITE_LAST_ERRNO 4 #define SQLITE_FCNTL_SIZE_HINT 5 #define SQLITE_FCNTL_CHUNK_SIZE 6 +#define SQLITE_FCNTL_FILE_POINTER 7 + /* ** CAPI3REF: Mutex Handle ** ** The mutex module within SQLite defines [sqlite3_mutex] to be an @@ -5232,19 +5246,25 @@ ** CAPI3REF: Low-Level Control Of Database Files ** ** ^The [sqlite3_file_control()] interface makes a direct call to the ** xFileControl method for the [sqlite3_io_methods] object associated ** with a particular database identified by the second argument. ^The -** name of the database "main" for the main database or "temp" for the +** name of the database is "main" for the main database or "temp" for the ** TEMP database, or the name that appears after the AS keyword for ** databases that are added using the [ATTACH] SQL command. ** ^A NULL pointer can be used in place of "main" to refer to the ** main database file. ** ^The third and fourth parameters to this routine ** are passed directly through to the second and third parameters of ** the xFileControl method. ^The return value of the xFileControl ** method becomes the return value of this routine. +** +** ^The SQLITE_FCNTL_FILE_POINTER value for the op parameter causes +** a pointer to the underlying [sqlite3_file] object to be written into +** the space pointed to by the 4th parameter. ^The SQLITE_FCNTL_FILE_POINTER +** case is a short-circuit path which does not actually invoke the +** underlying sqlite3_io_methods.xFileControl method. ** ** ^If the second parameter (zDbName) does not match the name of any ** open database file, then SQLITE_ERROR is returned. ^This error ** code is not remembered and will not be recalled by [sqlite3_errcode()] ** or [sqlite3_errmsg()]. The underlying xFileControl method might Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -912,17 +912,18 @@ ** accessing read-only databases */ #define SQLITE_IgnoreChecks 0x00040000 /* Do not enforce check constraints */ #define SQLITE_ReadUncommitted 0x0080000 /* For shared-cache mode */ #define SQLITE_LegacyFileFmt 0x00100000 /* Create new databases in format 1 */ #define SQLITE_FullFSync 0x00200000 /* Use full fsync on the backend */ -#define SQLITE_LoadExtension 0x00400000 /* Enable load_extension */ +#define SQLITE_CkptFullFSync 0x00400000 /* Use full fsync for checkpoint */ #define SQLITE_RecoveryMode 0x00800000 /* Ignore schema errors */ #define SQLITE_ReverseOrder 0x01000000 /* Reverse unordered SELECTs */ #define SQLITE_RecTriggers 0x02000000 /* Enable recursive triggers */ #define SQLITE_ForeignKeys 0x04000000 /* Enforce foreign key constraints */ #define SQLITE_AutoIndex 0x08000000 /* Enable automatic indexes */ #define SQLITE_PreferBuiltin 0x10000000 /* Preference to built-in funcs */ +#define SQLITE_LoadExtension 0x20000000 /* Enable load_extension */ /* ** Bits of the sqlite3.flags field that are used by the ** sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,...) interface. ** These must be the low-order bits of the flags field. Index: src/tclsqlite.c ================================================================== --- src/tclsqlite.c +++ src/tclsqlite.c @@ -3578,10 +3578,11 @@ extern int Sqlitetestvfs_Init(Tcl_Interp *); extern int SqlitetestStat_Init(Tcl_Interp*); extern int Sqlitetestrtree_Init(Tcl_Interp*); extern int Sqlitequota_Init(Tcl_Interp*); extern int Sqlitemultiplex_Init(Tcl_Interp*); + extern int SqliteSuperlock_Init(Tcl_Interp*); Sqliteconfig_Init(interp); Sqlitetest1_Init(interp); Sqlitetest2_Init(interp); Sqlitetest3_Init(interp); @@ -3609,10 +3610,11 @@ Sqlitetestvfs_Init(interp); SqlitetestStat_Init(interp); Sqlitetestrtree_Init(interp); Sqlitequota_Init(interp); Sqlitemultiplex_Init(interp); + SqliteSuperlock_Init(interp); Tcl_CreateObjCommand(interp,"load_testfixture_extensions",init_all_cmd,0,0); #ifdef SQLITE_SSE Sqlitetestsse_Init(interp); ADDED src/test_superlock.c Index: src/test_superlock.c ================================================================== --- /dev/null +++ src/test_superlock.c @@ -0,0 +1,330 @@ +/* +** 2010 November 19 +** +** 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. +** +************************************************************************* +** Example code for obtaining an exclusive lock on an SQLite database +** file. This method is complicated, but works for both WAL and rollback +** mode database files. The interface to the example code in this file +** consists of the following two functions: +** +** sqlite3demo_superlock() +** sqlite3demo_superunlock() +*/ + +#include +#include /* memset(), strlen() */ +#include /* assert() */ + +/* +** A structure to collect a busy-handler callback and argument and a count +** of the number of times it has been invoked. +*/ +struct SuperlockBusy { + int (*xBusy)(void*,int); /* Pointer to busy-handler function */ + void *pBusyArg; /* First arg to pass to xBusy */ + int nBusy; /* Number of times xBusy has been invoked */ +}; +typedef struct SuperlockBusy SuperlockBusy; + +/* +** The pCtx pointer passed to this function is actually a pointer to a +** SuperlockBusy structure. Invoke the busy-handler function encapsulated +** by the structure and return the result. +*/ +static int superlockBusyHandler(void *pCtx, int UNUSED){ + SuperlockBusy *pBusy = (SuperlockBusy *)pCtx; + if( pBusy->xBusy==0 ) return 0; + return pBusy->xBusy(pBusy->pBusyArg, pBusy->nBusy++); +} + +/* +** This function is used to determine if the main database file for +** connection db is open in WAL mode or not. If no error occurs and the +** database file is in WAL mode, set *pbWal to true and return SQLITE_OK. +** If it is not in WAL mode, set *pbWal to false. +** +** If an error occurs, return an SQLite error code. The value of *pbWal +** is undefined in this case. +*/ +static int superlockIsWal(sqlite3 *db, int *pbWal){ + int rc; /* Return Code */ + sqlite3_stmt *pStmt; /* Compiled PRAGMA journal_mode statement */ + + rc = sqlite3_prepare(db, "PRAGMA main.journal_mode", -1, &pStmt, 0); + if( rc!=SQLITE_OK ) return rc; + + *pbWal = 0; + if( SQLITE_ROW==sqlite3_step(pStmt) ){ + const char *zMode = (const char *)sqlite3_column_text(pStmt, 0); + if( zMode && strlen(zMode)==3 && sqlite3_strnicmp("wal", zMode, 3)==0 ){ + *pbWal = 1; + } + } + + return sqlite3_finalize(pStmt); +} + +/* +** Obtain an exclusive shm-lock on nByte bytes starting at offset idx +** of the file fd. If the lock cannot be obtained immediately, invoke +** the busy-handler until either it is obtained or the busy-handler +** callback returns 0. +*/ +static int superlockShmLock( + sqlite3_file *fd, /* Database file handle */ + int idx, /* Offset of shm-lock to obtain */ + int nByte, /* Number of consective bytes to lock */ + SuperlockBusy *pBusy /* Busy-handler wrapper object */ +){ + int rc; + int (*xShmLock)(sqlite3_file*, int, int, int) = fd->pMethods->xShmLock; + do { + rc = xShmLock(fd, idx, nByte, SQLITE_SHM_LOCK|SQLITE_SHM_EXCLUSIVE); + }while( rc==SQLITE_BUSY && superlockBusyHandler((void *)pBusy, 0) ); + return rc; +} + +/* +** Obtain the extra locks on the database file required for WAL databases. +** Invoke the supplied busy-handler as required. +*/ +static int superlockWalLock( + sqlite3 *db, /* Database handle open on WAL database */ + SuperlockBusy *pBusy /* Busy handler wrapper object */ +){ + int rc; /* Return code */ + sqlite3_file *fd = 0; /* Main database file handle */ + void volatile *p = 0; /* Pointer to first page of shared memory */ + int nBusy = 0; /* Number of calls already made to xBusy */ + + /* Obtain a pointer to the sqlite3_file object open on the main db file. */ + rc = sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, (void *)&fd); + if( rc!=SQLITE_OK ) return rc; + + /* Obtain the "recovery" lock. Normally, this lock is only obtained by + ** clients running database recovery. + */ + rc = superlockShmLock(fd, 2, 1, pBusy); + if( rc!=SQLITE_OK ) return rc; + + /* Zero the start of the first shared-memory page. This means that any + ** clients that open read or write transactions from this point on will + ** have to run recovery before proceeding. Since they need the "recovery" + ** lock that this process is holding to do that, no new read or write + ** transactions may now be opened. Nor can a checkpoint be run, for the + ** same reason. + */ + rc = fd->pMethods->xShmMap(fd, 0, 32*1024, 1, &p); + if( rc!=SQLITE_OK ) return rc; + memset((void *)p, 0, 32); + + /* Obtain exclusive locks on all the "read-lock" slots. Once these locks + ** are held, it is guaranteed that there are no active reader, writer or + ** checkpointer clients. + */ + rc = superlockShmLock(fd, 3, SQLITE_SHM_NLOCK-3, pBusy); + return rc; +} + +/* +** Obtain a superlock on the database file identified by zPath, using the +** locking primitives provided by VFS zVfs. If successful, SQLITE_OK is +** returned and output variable *ppLock is populated with an opaque handle +** that may be used with sqlite3demo_superunlock() to release the lock. +** +** If an error occurs, *ppLock is set to 0 and an SQLite error code +** (e.g. SQLITE_BUSY) is returned. +** +** If a required lock cannot be obtained immediately and the xBusy parameter +** to this function is not NULL, then xBusy is invoked in the same way +** as a busy-handler registered with SQLite (using sqlite3_busy_handler()) +** until either the lock can be obtained or the busy-handler function returns +** 0 (indicating "give up"). +*/ +int sqlite3demo_superlock( + const char *zPath, /* Path to database file to lock */ + const char *zVfs, /* VFS to use to access database file */ + int (*xBusy)(void*,int), /* Busy handler callback */ + void *pBusyArg, /* Context arg for busy handler */ + void **ppLock /* OUT: Context to pass to superunlock() */ +){ + sqlite3 *db = 0; /* Database handle open on zPath */ + SuperlockBusy busy = {0, 0, 0}; /* Busy handler wrapper object */ + int rc; /* Return code */ + + /* Open a database handle on the file to superlock. */ + rc = sqlite3_open_v2( + zPath, &db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs + ); + + /* Install a busy-handler and execute a BEGIN EXCLUSIVE. If this is not + ** a WAL database, this is all we need to do. + ** + ** A wrapper function is used to invoke the busy-handler instead of + ** registering the busy-handler function supplied by the user directly + ** with SQLite. This is because the same busy-handler function may be + ** invoked directly later on when attempting to obtain the extra locks + ** required in WAL mode. By using the wrapper, we are able to guarantee + ** that the "nBusy" integer parameter passed to the users busy-handler + ** represents the total number of busy-handler invocations made within + ** this call to sqlite3demo_superlock(), including any made during the + ** "BEGIN EXCLUSIVE". + */ + if( rc==SQLITE_OK ){ + busy.xBusy = xBusy; + busy.pBusyArg = pBusyArg; + sqlite3_busy_handler(db, superlockBusyHandler, (void *)&busy); + rc = sqlite3_exec(db, "BEGIN EXCLUSIVE", 0, 0, 0); + } + + /* If the BEGIN EXCLUSIVE was executed successfully and this is a WAL + ** database, call superlockWalLock() to obtain the extra locks required + ** to prevent readers, writers and/or checkpointers from accessing the + ** db while this process is holding the superlock. + ** + ** Before attempting any WAL locks, commit the transaction started above + ** to drop the WAL read and write locks currently held. Otherwise, the + ** new WAL locks may conflict with the old. + */ + if( rc==SQLITE_OK ){ + int bWal; /* True for a WAL database, false otherwise */ + if( SQLITE_OK==(rc = superlockIsWal(db, &bWal)) && bWal ){ + rc = sqlite3_exec(db, "COMMIT", 0, 0, 0); + if( rc==SQLITE_OK ){ + rc = superlockWalLock(db, &busy); + } + } + } + + if( rc!=SQLITE_OK ){ + sqlite3_close(db); + *ppLock = 0; + }else{ + *ppLock = (void *)db; + } + + return rc; +} + +/* +** Release a superlock held on a database file. The argument passed to +** this function must have been obtained from a successful call to +** sqlite3demo_superlock(). +*/ +void sqlite3demo_superunlock(void *pLock){ + sqlite3_close((sqlite3 *)pLock); +} + +/* +** End of example code. Everything below here is the test harness. +************************************************************************** +************************************************************************** +*************************************************************************/ + + +#ifdef SQLITE_TEST + +#include + +struct InterpAndScript { + Tcl_Interp *interp; + Tcl_Obj *pScript; +}; +typedef struct InterpAndScript InterpAndScript; + +static void superunlock_del(ClientData cd){ + sqlite3demo_superunlock((void *)cd); +} + +static int superunlock_cmd( + ClientData cd, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + if( objc!=1 ){ + Tcl_WrongNumArgs(interp, 1, objv, ""); + return TCL_ERROR; + } + Tcl_DeleteCommand(interp, Tcl_GetString(objv[0])); + return TCL_OK; +} + +static int superlock_busy(void *pCtx, int nBusy){ + InterpAndScript *p = (InterpAndScript *)pCtx; + Tcl_Obj *pEval; /* Script to evaluate */ + int iVal = 0; /* Value to return */ + + pEval = Tcl_DuplicateObj(p->pScript); + Tcl_IncrRefCount(pEval); + Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewIntObj(nBusy)); + Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL); + Tcl_GetIntFromObj(p->interp, Tcl_GetObjResult(p->interp), &iVal); + Tcl_DecrRefCount(pEval); + + return iVal; +} + +/* +** Tclcmd: sqlite3demo_superlock CMDNAME PATH VFS BUSY-HANDLER-SCRIPT +*/ +static int superlock_cmd( + ClientData cd, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + void *pLock; /* Lock context */ + char *zPath; + char *zVfs = 0; + InterpAndScript busy = {0, 0}; + int (*xBusy)(void*,int) = 0; /* Busy handler callback */ + int rc; /* Return code from sqlite3demo_superlock() */ + + if( objc<3 || objc>5 ){ + Tcl_WrongNumArgs( + interp, 1, objv, "CMDNAME PATH ?VFS? ?BUSY-HANDLER-SCRIPT?"); + return TCL_ERROR; + } + + zPath = Tcl_GetString(objv[2]); + + if( objc>3 ){ + zVfs = Tcl_GetString(objv[3]); + if( strlen(zVfs)==0 ) zVfs = 0; + } + if( objc>4 ){ + busy.interp = interp; + busy.pScript = objv[4]; + xBusy = superlock_busy; + } + + rc = sqlite3demo_superlock(zPath, zVfs, xBusy, &busy, &pLock); + assert( rc==SQLITE_OK || pLock==0 ); + assert( rc!=SQLITE_OK || pLock!=0 ); + + if( rc!=SQLITE_OK ){ + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, sqlite3ErrStr(rc), 0); + return TCL_ERROR; + } + + Tcl_CreateObjCommand( + interp, Tcl_GetString(objv[1]), superunlock_cmd, pLock, superunlock_del + ); + Tcl_SetObjResult(interp, objv[1]); + return TCL_OK; +} + +int SqliteSuperlock_Init(Tcl_Interp *interp){ + Tcl_CreateObjCommand(interp, "sqlite3demo_superlock", superlock_cmd, 0, 0); + return TCL_OK; +} +#endif ADDED test/superlock.test Index: test/superlock.test ================================================================== --- /dev/null +++ test/superlock.test @@ -0,0 +1,98 @@ +# 2010 November 19 +# +# 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 +source $testdir/lock_common.tcl + +set testprefix superlock + +do_execsql_test 1.1 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + PRAGMA journal_mode = DELETE; +} {delete} + +do_test 1.2 { sqlite3demo_superlock unlock test.db } {unlock} +do_catchsql_test 1.3 { SELECT * FROM t1 } {1 {database is locked}} +do_test 1.4 { unlock } {} + +do_execsql_test 2.1 { + INSERT INTO t1 VALUES(3, 4); + PRAGMA journal_mode = WAL; +} {wal} + +do_test 2.2 { sqlite3demo_superlock unlock test.db } {unlock} +do_catchsql_test 2.3 { SELECT * FROM t1 } {1 {database is locked}} +do_catchsql_test 2.4 { INSERT INTO t1 VALUES(5, 6)} {1 {database is locked}} +do_catchsql_test 2.5 { PRAGMA wal_checkpoint } {1 {database is locked}} +do_test 2.6 { unlock } {} + +do_execsql_test 3.1 { INSERT INTO t1 VALUES(3, 4) } + +do_test 3.2 { sqlite3demo_superlock unlock test.db } {unlock} +do_catchsql_test 3.3 { SELECT * FROM t1 } {1 {database is locked}} +do_catchsql_test 3.4 { INSERT INTO t1 VALUES(5, 6)} {1 {database is locked}} +do_catchsql_test 3.5 { PRAGMA wal_checkpoint } {1 {database is locked}} +do_test 3.6 { unlock } {} + +do_execsql_test 4.1 { PRAGMA wal_checkpoint } {} + +do_test 4.2 { sqlite3demo_superlock unlock test.db } {unlock} +do_catchsql_test 4.3 { SELECT * FROM t1 } {1 {database is locked}} +do_catchsql_test 4.4 { INSERT INTO t1 VALUES(5, 6)} {1 {database is locked}} +do_catchsql_test 4.5 { PRAGMA wal_checkpoint } {1 {database is locked}} +do_test 4.6 { unlock } {} + +do_multiclient_test tn { + proc busyhandler {x} { + switch -- $x { + 1 { sql1 "COMMIT" } + 2 { sql2 "COMMIT" } + 3 { sql3 "COMMIT" } + } + lappend ::busylist $x + return 1 + } + set ::busylist [list] + + do_test 5.$tn.1 { + sql1 { + CREATE TABLE t1(a, b); + PRAGMA journal_mode = WAL; + INSERT INTO t1 VALUES(1, 2); + } + } {wal} + + do_test 5.$tn.2 { + sql1 { BEGIN ; SELECT * FROM t1 } + sql2 { BEGIN ; INSERT INTO t1 VALUES(3, 4) } + sql3 { BEGIN ; SELECT * FROM t1 } + } {1 2} + + do_test 5.$tn.3 { + set ::busylist [list] + sqlite3demo_superlock unlock test.db "" busyhandler + set ::busylist + } {0 1 2 3} + + do_test 5.$tn.4 { csql2 { SELECT * FROM t1 } } {1 {database is locked}} + do_test 5.$tn.5 { + csql3 { INSERT INTO t1 VALUES(5, 6) } + } {1 {database is locked}} + do_test 5.$tn.6 { csql1 "PRAGMA wal_checkpoint" } {1 {database is locked}} + + do_test 5.$tn.7 { unlock } {} +} + + +finish_test