Index: src/os_win.c ================================================================== --- src/os_win.c +++ src/os_win.c @@ -412,26 +412,28 @@ # define SQLITE_WIN32_IOERR_RETRY 10 #endif #ifndef SQLITE_WIN32_IOERR_RETRY_DELAY # define SQLITE_WIN32_IOERR_RETRY_DELAY 25 #endif +static int win32IoerrRetry = SQLITE_WIN32_IOERR_RETRY; +static int win32IoerrRetryDelay = SQLITE_WIN32_IOERR_RETRY_DELAY; /* ** If a ReadFile() or WriteFile() error occurs, invoke this routine ** to see if it should be retried. Return TRUE to retry. Return FALSE ** to give up with an error. */ static int retryIoerr(int *pnRetry){ DWORD e; - if( *pnRetry>=SQLITE_WIN32_IOERR_RETRY ){ + if( *pnRetry>=win32IoerrRetry ){ return 0; } e = GetLastError(); if( e==ERROR_ACCESS_DENIED || e==ERROR_LOCK_VIOLATION || e==ERROR_SHARING_VIOLATION ){ - Sleep(SQLITE_WIN32_IOERR_RETRY_DELAY*(1+*pnRetry)); + Sleep(win32IoerrRetryDelay*(1+*pnRetry)); ++*pnRetry; return 1; } return 0; } @@ -441,11 +443,11 @@ */ static void logIoerr(int nRetry){ if( nRetry ){ sqlite3_log(SQLITE_IOERR, "delayed %dms for lock/sharing conflict", - SQLITE_WIN32_IOERR_RETRY_DELAY*nRetry*(nRetry+1)/2 + win32IoerrRetryDelay*nRetry*(nRetry+1)/2 ); } } #if SQLITE_OS_WINCE @@ -1353,10 +1355,24 @@ SimulateIOErrorBenign(0); return SQLITE_OK; } case SQLITE_FCNTL_SYNC_OMITTED: { return SQLITE_OK; + } + case SQLITE_FCNTL_WIN32_AV_RETRY: { + int *a = (int*)pArg; + if( a[0]>0 ){ + win32IoerrRetry = a[0]; + }else{ + a[0] = win32IoerrRetry; + } + if( a[1]>0 ){ + win32IoerrRetryDelay = a[1]; + }else{ + a[1] = win32IoerrRetryDelay; + } + return SQLITE_OK; } } return SQLITE_NOTFOUND; } Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -734,20 +734,37 @@ ** when [PRAGMA synchronous | PRAGMA synchronous=OFF] is set, but most ** VFSes do not need this signal and should silently ignore this opcode. ** Applications should not call [sqlite3_file_control()] with this ** opcode as doing so may disrupt the operation of the specialized VFSes ** that do require it. +** +** ^The [SQLITE_FCNTL_WIN32_AV_RETRY] opcode is used to configure automatic +** retry counts and intervals for certain disk I/O operations for the +** windows [VFS] in order to work to provide robustness against +** anti-virus programs. By default, the windows VFS will retry file read, +** file write, and file delete opertions up to 10 times, with a delay +** of 25 milliseconds before the first retry and with the delay increasing +** by an additional 25 milliseconds with each subsequent retry. This +** opcode allows those to values (10 retries and 25 milliseconds of delay) +** to be adjusted. The values are changed for all database connections +** within the same process. The argument is a pointer to an array of two +** integers where the first integer i the new retry count and the second +** integer is the delay. If either integer is negative, then the setting +** is not changed but instead the prior value of that setting is written +** into the array entry, allowing the current retry settings to be +** interrogated. The zDbName parameter is ignored. +** */ #define SQLITE_FCNTL_LOCKSTATE 1 #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 #define SQLITE_FCNTL_SYNC_OMITTED 8 - +#define SQLITE_FCNTL_WIN32_AV_RETRY 9 /* ** CAPI3REF: Mutex Handle ** ** The mutex module within SQLite defines [sqlite3_mutex] to be an Index: src/test1.c ================================================================== --- src/test1.c +++ src/test1.c @@ -5094,10 +5094,43 @@ } #endif return TCL_OK; } +/* +** tclcmd: file_control_win32_av_retry DB NRETRY DELAY +** +** This TCL command runs the sqlite3_file_control interface with +** the SQLITE_FCNTL_WIN32_AV_RETRY opcode. +*/ +static int file_control_win32_av_retry( + ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + sqlite3 *db; + int rc; + int a[2]; + char z[100]; + + if( objc!=4 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", + Tcl_GetStringFromObj(objv[0], 0), " DB NRETRY DELAY", 0); + return TCL_ERROR; + } + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){ + return TCL_ERROR; + } + if( Tcl_GetIntFromObj(interp, objv[2], &a[0]) ) return TCL_ERROR; + if( Tcl_GetIntFromObj(interp, objv[3], &a[1]) ) return TCL_ERROR; + rc = sqlite3_file_control(db, NULL, SQLITE_FCNTL_WIN32_AV_RETRY, (void*)a); + sqlite3_snprintf(sizeof(z), z, "%d %d %d", rc, a[0], a[1]); + Tcl_AppendResult(interp, z, (char*)0); + return TCL_OK; +} + /* ** tclcmd: sqlite3_vfs_list ** ** Return a tcl list containing the names of all registered vfs's. @@ -5636,11 +5669,11 @@ sqlite3_snprintf(sizeof(zBuf), zBuf, "%d %d %d %d %d", x.ok, x.err, x.delay1, x.delay2, x.h); Tcl_AppendResult(interp, zBuf, (char*)0); return TCL_OK; } - while( x.h && retry<10 ){ + while( x.h && retry<30 ){ retry++; Sleep(100); } if( x.h ){ Tcl_AppendResult(interp, "busy", (char*)0); @@ -5659,10 +5692,11 @@ _beginthread(win32_file_locker, 0, (void*)&x); Sleep(0); return TCL_OK; } #endif + /* ** optimization_control DB OPT BOOLEAN ** ** Enable or disable query optimizations using the sqlite3_test_control() @@ -5890,11 +5924,12 @@ { "vfs_reregister_all", vfs_reregister_all, 0 }, { "file_control_test", file_control_test, 0 }, { "file_control_lasterrno_test", file_control_lasterrno_test, 0 }, { "file_control_lockproxy_test", file_control_lockproxy_test, 0 }, { "file_control_chunksize_test", file_control_chunksize_test, 0 }, - { "file_control_sizehint_test", file_control_sizehint_test, 0 }, + { "file_control_sizehint_test", file_control_sizehint_test, 0 }, + { "file_control_win32_av_retry", file_control_win32_av_retry, 0 }, { "sqlite3_vfs_list", vfs_list, 0 }, { "sqlite3_create_function_v2", test_create_function_v2, 0 }, /* Functions from os.h */ #ifndef SQLITE_OMIT_UTF16 Index: test/win32lock.test ================================================================== --- test/win32lock.test +++ test/win32lock.test @@ -65,10 +65,44 @@ } incr delay1 50 } set ::log {} } + +do_test win32lock-2.0 { + file_control_win32_av_retry db -1 -1 +} {0 10 25} +do_test win32lock-2.1 { + file_control_win32_av_retry db 1 1 +} {0 1 1} + +set delay1 50 +while {1} { + sqlite3_sleep 10 + lock_win32_file test.db 0 $::delay1 + set rc [catch {db eval {SELECT x, length(y) FROM t1 ORDER BY rowid}} msg] + if {$rc} { + do_test win32lock-2.2-$delay1-fin { + set ::msg + } {disk I/O error} + break + } else { + do_test win32lock-2.2-$delay1 { + set ::msg + } {1 100000 2 50000 3 25000 4 12500} + if {$::log!=""} { + do_test win32lock-2.2-$delay1-log1 { + regsub {\d+} $::log # x + set x + } {{delayed #ms for lock/sharing conflict}} + } + incr delay1 50 + } + set ::log {} +} + +file_control_win32_av_retry db 10 25 sqlite3_test_control_pending_byte $old_pending_byte sqlite3_shutdown test_sqlite3_log sqlite3_initialize finish_test