Browse Source

New busy timeout stuff - going to test it out on a branch for a bit before mainline commit.

August Mueller 12 years ago
parent
commit
019eebe879
5 changed files with 135 additions and 206 deletions
  1. 2 2
      src/FMDatabase.h
  2. 102 133
      src/FMDatabase.m
  3. 10 23
      src/FMDatabaseAdditions.m
  4. 20 47
      src/FMResultSet.m
  5. 1 1
      src/fmdb.m

+ 2 - 2
src/FMDatabase.h

@@ -87,7 +87,7 @@
     BOOL                _shouldCacheStatements;
     BOOL                _isExecutingStatement;
     BOOL                _inTransaction;
-    int                 _busyRetryTimeout;
+    NSTimeInterval      _busyTimeout;
     
     NSMutableDictionary *_cachedStatements;
     NSMutableSet        *_openResultSets;
@@ -110,7 +110,7 @@
 
 /** Busy retry timeout */
 
-@property (atomic, assign) int busyRetryTimeout;
+@property (atomic, assign) NSTimeInterval busyTimeout;
 
 /** Crash on errors */
 

+ 102 - 133
src/FMDatabase.m

@@ -12,7 +12,7 @@ @implementation FMDatabase
 @synthesize cachedStatements=_cachedStatements;
 @synthesize logsErrors=_logsErrors;
 @synthesize crashOnErrors=_crashOnErrors;
-@synthesize busyRetryTimeout=_busyRetryTimeout;
+@synthesize busyTimeout=_busyTimeout;
 @synthesize checkedOut=_checkedOut;
 @synthesize traceExecution=_traceExecution;
 
@@ -41,7 +41,7 @@ - (instancetype)initWithPath:(NSString*)aPath {
         _db                 = nil;
         _logsErrors         = YES;
         _crashOnErrors      = NO;
-        _busyRetryTimeout   = 0;
+        _busyTimeout        = 0;
     }
     
     return self;
@@ -98,6 +98,11 @@ - (BOOL)open {
         return NO;
     }
     
+    if (_busyTimeout > 0.0) {
+        sqlite3_busy_timeout(_db, (int)(_busyTimeout * 1000));
+    }
+    
+    
     return YES;
 }
 
@@ -108,6 +113,11 @@ - (BOOL)openWithFlags:(int)flags {
         NSLog(@"error opening!: %d", err);
         return NO;
     }
+    
+    if (_busyTimeout > 0.0) {
+        sqlite3_busy_timeout(_db, (int)(_busyTimeout * 1000));
+    }
+    
     return YES;
 }
 #endif
@@ -124,30 +134,19 @@ - (BOOL)close {
     
     int  rc;
     BOOL retry;
-    int numberOfRetries = 0;
     BOOL triedFinalizingOpenStatements = NO;
     
     do {
         retry   = NO;
         rc      = sqlite3_close(_db);
-        
         if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
-            
-            retry = YES;
-            usleep(FMDatabaseSQLiteBusyMicrosecondsTimeout);
-            
-            if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) {
-                NSLog(@"%s:%d", __FUNCTION__, __LINE__);
-                NSLog(@"Database busy, unable to close");
-                return NO;
-            }
-            
             if (!triedFinalizingOpenStatements) {
                 triedFinalizingOpenStatements = YES;
                 sqlite3_stmt *pStmt;
-                while ((pStmt = sqlite3_next_stmt(_db, 0x00)) !=0) {
+                while ((pStmt = sqlite3_next_stmt(_db, nil)) !=0) {
                     NSLog(@"Closing leaked statement");
                     sqlite3_finalize(pStmt);
+                    retry = YES;
                 }
             }
         }
@@ -161,6 +160,36 @@ - (BOOL)close {
     return YES;
 }
 
+
+- (void)setRetryTimeout:(NSTimeInterval)timeout {
+    _busyTimeout = timeout;
+    if (_db) {
+        sqlite3_busy_timeout(_db, (int)(timeout * 1000));
+    }
+}
+
+- (NSTimeInterval)retryTimeout {
+    return _busyTimeout;
+}
+
+
+// we no longer make busyRetryTimeout public
+// but for folks who don't bother noticing that the interface to FMDatabase changed,
+// we'll still implement the method so they don't get suprise crashes
+- (int)busyRetryTimeout {
+    NSLog(@"%s:%d", __FUNCTION__, __LINE__);
+    NSLog(@"FMDB: busyRetryTimeout no longer works, please use retryTimeout");
+    return -1;
+}
+
+- (void)setBusyRetryTimeout:(int)i {
+    NSLog(@"%s:%d", __FUNCTION__, __LINE__);
+    NSLog(@"FMDB: setBusyRetryTimeout does nothing, please use setRetryTimeout:");
+}
+
+
+
+
 - (void)clearCachedStatements {
     
     for (NSMutableSet *statements in [_cachedStatements objectEnumerator]) {
@@ -605,46 +634,28 @@ - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arr
         [statement reset];
     }
     
-    int numberOfRetries = 0;
-    BOOL retry          = NO;
-    
     if (!pStmt) {
-        do {
-            retry   = NO;
-            rc      = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
+    
+        rc      = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
+        
+        if (SQLITE_OK != rc) {
             
-            if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
-                retry = YES;
-                usleep(FMDatabaseSQLiteBusyMicrosecondsTimeout);
-                
-                if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) {
-                    NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
-                    NSLog(@"Database busy");
-                    sqlite3_finalize(pStmt);
-                    _isExecutingStatement = NO;
-                    return nil;
-                }
-            }
-            else if (SQLITE_OK != rc) {
-                
-                if (_logsErrors) {
-                    NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
-                    NSLog(@"DB Query: %@", sql);
-                    NSLog(@"DB Path: %@", _databasePath);
+            if (_logsErrors) {
+                NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
+                NSLog(@"DB Query: %@", sql);
+                NSLog(@"DB Path: %@", _databasePath);
 #ifndef NS_BLOCK_ASSERTIONS
-                    if (_crashOnErrors) {
-                        abort();
-                        NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
-                    }
-#endif
+                if (_crashOnErrors) {
+                    abort();
+                    NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
                 }
-                
-                sqlite3_finalize(pStmt);
-                _isExecutingStatement = NO;
-                return nil;
+#endif
             }
+            
+            sqlite3_finalize(pStmt);
+            _isExecutingStatement = NO;
+            return nil;
         }
-        while (retry);
     }
     
     id obj;
@@ -797,51 +808,32 @@ - (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArra
         [cachedStmt reset];
     }
     
-    int numberOfRetries = 0;
-    BOOL retry          = NO;
-    
     if (!pStmt) {
+        rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
         
-        do {
-            retry   = NO;
-            rc      = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
-            if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
-                retry = YES;
-                usleep(FMDatabaseSQLiteBusyMicrosecondsTimeout);
-                
-                if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) {
-                    NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
-                    NSLog(@"Database busy");
-                    sqlite3_finalize(pStmt);
-                    _isExecutingStatement = NO;
-                    return NO;
-                }
-            }
-            else if (SQLITE_OK != rc) {
-                
-                if (_logsErrors) {
-                    NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
-                    NSLog(@"DB Query: %@", sql);
-                    NSLog(@"DB Path: %@", _databasePath);
+        if (SQLITE_OK != rc) {
+            
+            if (_logsErrors) {
+                NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
+                NSLog(@"DB Query: %@", sql);
+                NSLog(@"DB Path: %@", _databasePath);
 #ifndef NS_BLOCK_ASSERTIONS
-                    if (_crashOnErrors) {
-                        abort();
-                        NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
-                    }
-#endif
-                }
-                
-                sqlite3_finalize(pStmt);
-                
-                if (outErr) {
-                    *outErr = [self errorWithMessage:[NSString stringWithUTF8String:sqlite3_errmsg(_db)]];
+                if (_crashOnErrors) {
+                    abort();
+                    NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
                 }
-                
-                _isExecutingStatement = NO;
-                return NO;
+#endif
+            }
+            
+            sqlite3_finalize(pStmt);
+            
+            if (outErr) {
+                *outErr = [self errorWithMessage:[NSString stringWithUTF8String:sqlite3_errmsg(_db)]];
             }
+            
+            _isExecutingStatement = NO;
+            return NO;
         }
-        while (retry);
     }
     
     id obj;
@@ -914,55 +906,32 @@ - (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArra
     /* Call sqlite3_step() to run the virtual machine. Since the SQL being
      ** executed is not a SELECT statement, we assume no data will be returned.
      */
-    numberOfRetries = 0;
     
-    do {
-        rc      = sqlite3_step(pStmt);
-        retry   = NO;
-        
-        if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
-            // this will happen if the db is locked, like if we are doing an update or insert.
-            // in that case, retry the step... and maybe wait just 10 milliseconds.
-            retry = YES;
-            if (SQLITE_LOCKED == rc) {
-                rc = sqlite3_reset(pStmt);
-                if (rc != SQLITE_LOCKED) {
-                    NSLog(@"Unexpected result from sqlite3_reset (%d) eu", rc);
-                }
-            }
-            usleep(FMDatabaseSQLiteBusyMicrosecondsTimeout);
-            
-            if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) {
-                NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
-                NSLog(@"Database busy");
-                retry = NO;
-            }
-        }
-        else if (SQLITE_DONE == rc) {
-            // all is well, let's return.
-        }
-        else if (SQLITE_ERROR == rc) {
-            if (_logsErrors) {
-                NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_ERROR", rc, sqlite3_errmsg(_db));
-                NSLog(@"DB Query: %@", sql);
-            }
+    rc      = sqlite3_step(pStmt);
+    
+    if (SQLITE_DONE == rc) {
+        // all is well, let's return.
+    }
+    else if (SQLITE_ERROR == rc) {
+        if (_logsErrors) {
+            NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_ERROR", rc, sqlite3_errmsg(_db));
+            NSLog(@"DB Query: %@", sql);
         }
-        else if (SQLITE_MISUSE == rc) {
-            // uh oh.
-            if (_logsErrors) {
-                NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_MISUSE", rc, sqlite3_errmsg(_db));
-                NSLog(@"DB Query: %@", sql);
-            }
+    }
+    else if (SQLITE_MISUSE == rc) {
+        // uh oh.
+        if (_logsErrors) {
+            NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_MISUSE", rc, sqlite3_errmsg(_db));
+            NSLog(@"DB Query: %@", sql);
         }
-        else {
-            // wtf?
-            if (_logsErrors) {
-                NSLog(@"Unknown error calling sqlite3_step (%d: %s) eu", rc, sqlite3_errmsg(_db));
-                NSLog(@"DB Query: %@", sql);
-            }
+    }
+    else {
+        // wtf?
+        if (_logsErrors) {
+            NSLog(@"Unknown error calling sqlite3_step (%d: %s) eu", rc, sqlite3_errmsg(_db));
+            NSLog(@"DB Query: %@", sql);
         }
-        
-    } while (retry);
+    }
     
     if (rc == SQLITE_ROW) {
         NSAssert1(NO, @"A executeUpdate is being called with a query string '%@'", sql);

+ 10 - 23
src/FMDatabaseAdditions.m

@@ -180,32 +180,19 @@ - (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName __att
 
 #pragma clang diagnostic pop
 
+
 - (BOOL)validateSQL:(NSString*)sql error:(NSError**)error {
     sqlite3_stmt *pStmt = NULL;
     BOOL validationSucceeded = YES;
-    BOOL keepTrying = YES;
-    int numberOfRetries = 0;
-    
-    while (keepTrying == YES) {
-        keepTrying = NO;
-        int rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
-        if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {
-            keepTrying = YES;
-            usleep(FMDatabaseSQLiteBusyMicrosecondsTimeout);
-            
-            if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) {
-                NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
-                NSLog(@"Database busy");
-            }          
-        } 
-        else if (rc != SQLITE_OK) {
-            validationSucceeded = NO;
-            if (error) {
-                *error = [NSError errorWithDomain:NSCocoaErrorDomain 
-                                             code:[self lastErrorCode]
-                                         userInfo:[NSDictionary dictionaryWithObject:[self lastErrorMessage] 
-                                                                              forKey:NSLocalizedDescriptionKey]];
-            }
+    
+    int rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
+    if (rc != SQLITE_OK) {
+        validationSucceeded = NO;
+        if (error) {
+            *error = [NSError errorWithDomain:NSCocoaErrorDomain
+                                         code:[self lastErrorCode]
+                                     userInfo:[NSDictionary dictionaryWithObject:[self lastErrorMessage]
+                                                                          forKey:NSLocalizedDescriptionKey]];
         }
     }
     

+ 20 - 47
src/FMResultSet.m

@@ -146,55 +146,28 @@ - (NSDictionary*)resultDictionary {
 
 
 
-
 - (BOOL)next {
     
-    int rc;
-    BOOL retry;
-    int numberOfRetries = 0;
-    do {
-        retry = NO;
-        
-        rc = sqlite3_step([_statement statement]);
-        
-        if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
-            // this will happen if the db is locked, like if we are doing an update or insert.
-            // in that case, retry the step... and maybe wait just 10 milliseconds.
-            retry = YES;
-            if (SQLITE_LOCKED == rc) {
-                rc = sqlite3_reset([_statement statement]);
-                if (rc != SQLITE_LOCKED) {
-                    NSLog(@"Unexpected result from sqlite3_reset (%d) rs", rc);
-                }
-            }
-            usleep(FMDatabaseSQLiteBusyMicrosecondsTimeout);
-            
-            if ([_parentDB busyRetryTimeout] && (numberOfRetries++ > [_parentDB busyRetryTimeout])) {
-                
-                NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [_parentDB databasePath]);
-                NSLog(@"Database busy");
-                break;
-            }
-        }
-        else if (SQLITE_DONE == rc || SQLITE_ROW == rc) {
-            // all is well, let's return.
-        }
-        else if (SQLITE_ERROR == rc) {
-            NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle]));
-            break;
-        } 
-        else if (SQLITE_MISUSE == rc) {
-            // uh oh.
-            NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle]));
-            break;
-        }
-        else {
-            // wtf?
-            NSLog(@"Unknown error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle]));
-            break;
-        }
-        
-    } while (retry);
+    int rc = sqlite3_step([_statement statement]);
+    
+    if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
+        NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [_parentDB databasePath]);
+        NSLog(@"Database busy");
+    }
+    else if (SQLITE_DONE == rc || SQLITE_ROW == rc) {
+        // all is well, let's return.
+    }
+    else if (SQLITE_ERROR == rc) {
+        NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle]));
+    }
+    else if (SQLITE_MISUSE == rc) {
+        // uh oh.
+        NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle]));
+    }
+    else {
+        // wtf?
+        NSLog(@"Unknown error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle]));
+    }
     
     
     if (rc != SQLITE_ROW) {

+ 1 - 1
src/fmdb.m

@@ -343,7 +343,7 @@ int main (int argc, const char * argv[]) {
     
     // test the busy rety timeout schtuff.
     
-    [db setBusyRetryTimeout:500];
+    [db setBusyTimeout:5];
     
     FMDatabase *newDb = [FMDatabase databaseWithPath:dbPath];
     [newDb open];