Explorar el Código

Discovered sqlite3_busy_handler, and got to kill some ivars and code. busyTimeout has been replaced by setMaxBusyRetryTimeInterval: - this is a _lot_ better than adding loops everywhere.

August Mueller hace 12 años
padre
commit
484b046a4a
Se han modificado 4 ficheros con 89 adiciones y 99 borrados
  1. 1 1
      Tests/FMDatabasePoolTests.m
  2. 1 1
      Tests/FMDatabaseTests.m
  3. 4 7
      src/FMDatabase.h
  4. 83 90
      src/FMDatabase.m

+ 1 - 1
Tests/FMDatabasePoolTests.m

@@ -254,7 +254,7 @@ - (void)testStressTest
 
 
 - (BOOL)databasePool:(FMDatabasePool*)pool shouldAddDatabaseToPool:(FMDatabase*)database {
-    [database setRetryTimeout:.1];
+    [database setMaxBusyRetryTimeInterval:10];
     // [database setCrashOnErrors:YES];
     #pragma message "FIXME: Gus - we need to check for a SQLITE_BUSY when we call sqlite3_step.  sqlite will sleep for the retry amount - BUT, it won't just try the step again.  We'll have to put the old loops back in.  testReadWriteStressTest shows this mistake.  Maybe add a private method to FMDatabase that does the stepping and loop for us, and replace sqlite3_step with that?"
     return YES;

+ 1 - 1
Tests/FMDatabaseTests.m

@@ -176,7 +176,7 @@ - (void)testBusyRetryTimeout
     [self.db executeUpdate:@"create table t1 (a integer)"];
     [self.db executeUpdate:@"insert into t1 values (?)", [NSNumber numberWithInt:5]];
     
-    [self.db setRetryTimeout:2];
+    [self.db setMaxBusyRetryTimeInterval:2];
     
     FMDatabase *newDB = [FMDatabase databaseWithPath:self.databasePath];
     [newDB open];

+ 4 - 7
src/FMDatabase.h

@@ -74,7 +74,8 @@
     BOOL                _shouldCacheStatements;
     BOOL                _isExecutingStatement;
     BOOL                _inTransaction;
-    NSTimeInterval      _busyTimeout;
+    NSTimeInterval      _maxBusyRetryTimeInterval;
+    NSTimeInterval      _startBusyRetryTime;
     
     NSMutableDictionary *_cachedStatements;
     NSMutableSet        *_openResultSets;
@@ -95,10 +96,6 @@
 
 @property (atomic, assign) BOOL checkedOut;
 
-/** Busy retry timeout */
-
-@property (atomic, assign) NSTimeInterval busyTimeout;
-
 /** Crash on errors */
 
 @property (atomic, assign) BOOL crashOnErrors;
@@ -692,8 +689,8 @@
 
 
 // description forthcoming
-- (void)setRetryTimeout:(NSTimeInterval)timeout;
-- (NSTimeInterval)retryTimeout;
+- (void)setMaxBusyRetryTimeInterval:(NSTimeInterval)timeoutInSeconds;
+- (NSTimeInterval)maxBusyRetryTimeInterval;
 
 
 #if SQLITE_VERSION_NUMBER >= 3007000

+ 83 - 90
src/FMDatabase.m

@@ -12,7 +12,6 @@ @implementation FMDatabase
 @synthesize cachedStatements=_cachedStatements;
 @synthesize logsErrors=_logsErrors;
 @synthesize crashOnErrors=_crashOnErrors;
-@synthesize busyTimeout=_busyTimeout;
 @synthesize checkedOut=_checkedOut;
 @synthesize traceExecution=_traceExecution;
 
@@ -40,12 +39,12 @@ - (instancetype)initWithPath:(NSString*)aPath {
     self = [super init];
     
     if (self) {
-        _databasePath       = [aPath copy];
-        _openResultSets     = [[NSMutableSet alloc] init];
-        _db                 = nil;
-        _logsErrors         = YES;
-        _crashOnErrors      = NO;
-        _busyTimeout        = 0;
+        _databasePath               = [aPath copy];
+        _openResultSets             = [[NSMutableSet alloc] init];
+        _db                         = nil;
+        _logsErrors                 = YES;
+        _crashOnErrors              = NO;
+        _maxBusyRetryTimeInterval   = 2;
     }
     
     return self;
@@ -102,8 +101,9 @@ - (BOOL)open {
         return NO;
     }
     
-    if (_busyTimeout > 0.0) {
-        sqlite3_busy_timeout(_db, (int)(_busyTimeout * 1000));
+    if (_maxBusyRetryTimeInterval > 0.0) {
+        // set the handler
+        [self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
     }
     
     
@@ -122,8 +122,9 @@ - (BOOL)openWithFlags:(int)flags {
         return NO;
     }
     
-    if (_busyTimeout > 0.0) {
-        sqlite3_busy_timeout(_db, (int)(_busyTimeout * 1000));
+    if (_maxBusyRetryTimeInterval > 0.0) {
+        // set the handler
+        [self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
     }
     
     return YES;
@@ -169,15 +170,44 @@ - (BOOL)close {
 }
 
 
-- (void)setRetryTimeout:(NSTimeInterval)timeout {
-    _busyTimeout = timeout;
-    if (_db) {
-        sqlite3_busy_timeout(_db, (int)(timeout * 1000));
+static int FMDatabaseBusyHandler(void *f, int count) {
+    
+    FMDatabase *self = (__bridge FMDatabase*)f;
+    
+    if (count == 0) {
+        self->_startBusyRetryTime = [NSDate timeIntervalSinceReferenceDate];
+        return 1;
+    }
+    
+    NSTimeInterval delta = [NSDate timeIntervalSinceReferenceDate] - (self->_startBusyRetryTime);
+    
+    if (delta < [self maxBusyRetryTimeInterval]) {
+        sqlite3_sleep(50); // milliseconds
+        return 1;
+    }
+    
+	return 0;
+}
+
+- (void)setMaxBusyRetryTimeInterval:(NSTimeInterval)timeout {
+    
+    _maxBusyRetryTimeInterval = timeout;
+    
+    if (!_db) {
+        return;
+    }
+    
+    if (timeout > 0) {
+        sqlite3_busy_handler(_db, &FMDatabaseBusyHandler, (__bridge void *)(self));
+    }
+    else {
+        // turn it off otherwise
+        sqlite3_busy_handler(_db, nil, nil);
     }
 }
 
-- (NSTimeInterval)retryTimeout {
-    return _busyTimeout;
+- (NSTimeInterval)maxBusyRetryTimeInterval {
+    return _maxBusyRetryTimeInterval;
 }
 
 
@@ -660,37 +690,26 @@ - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arr
         [statement reset];
     }
     
-    
-    
     if (!pStmt) {
+    
+        rc      = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
         
-        BOOL retry;
-        
-        do {
-            retry   = NO;
-            rc      = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
-            
-            /*if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
-                retry = YES;
+        if (SQLITE_OK != rc) {
+            if (_logsErrors) {
+                NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
+                NSLog(@"DB Query: %@", sql);
+                NSLog(@"DB Path: %@", _databasePath);
             }
-            else */if (SQLITE_OK != rc) {
-                if (_logsErrors) {
-                    NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
-                    NSLog(@"DB Query: %@", sql);
-                    NSLog(@"DB Path: %@", _databasePath);
-                }
-                
-                if (_crashOnErrors) {
-                    NSAssert(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
-                    abort();
-                }
-                
-                sqlite3_finalize(pStmt);
-                _isExecutingStatement = NO;
-                return nil;
+            
+            if (_crashOnErrors) {
+                NSAssert(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
+                abort();
             }
             
-        } while (retry);
+            sqlite3_finalize(pStmt);
+            _isExecutingStatement = NO;
+            return nil;
+        }
     }
     
     id obj;
@@ -846,8 +865,6 @@ - (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArra
     if (!pStmt) {
         rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
         
-        #pragma message "FIXME: need the busy loop here."
-        
         if (SQLITE_OK != rc) {
             if (_logsErrors) {
                 NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
@@ -942,55 +959,31 @@ - (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArra
      ** executed is not a SELECT statement, we assume no data will be returned.
      */
     
-    BOOL retry;
-    NSInteger numberOfRetries = 0;
+    rc      = sqlite3_step(pStmt);
     
-    do {
-        retry = NO;
-        rc    = sqlite3_step(pStmt);
-        
-        if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
-            retry = YES;
-            
-            if (SQLITE_LOCKED == rc) {
-                rc = sqlite3_reset(pStmt);
-                if (rc != SQLITE_LOCKED) {
-                    NSLog(@"Unexpected result from sqlite3_reset (%d) eu", rc);
-                }
-            }
-            
-#pragma message "FIXME: HOW DO WE KNOW HOW TO BREAK OUT OF THIS?  10 is kind of a magic number?"
-            
-            if ((numberOfRetries++ > 10)) {
-                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);
-            }
+    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) {
         NSAssert(NO, @"A executeUpdate is being called with a query string '%@'", sql);