EXC_BAD_ACCESS when using SQLite (FMDB) and streams in iOS 4.0

I use FMDB to work with my database, which works great. The application uses a background thread that does some work and needs access to the database. At the same time, the main thread must run some queries in the same database. FMDB itself has a small locking system, however I added one more of my classes.

Each query is only executed if my class indicates that the database is not in use. After completing the actions, the database is unlocked. This works as expected until the load is too high. When I access a lot of data with a thread running in the main thread, an EXC_BAD_ACCESS error occurs.

Here it looks:

- (BOOL)isDatabaseLocked { return isDatabaseLocked; } - (Pile *)lockDatabase { isDatabaseLocked = YES; return self; } - (FMDatabase *)lockedDatabase { @synchronized(self) { while ([self isDatabaseLocked]) { usleep(20); //NSLog(@"Waiting until database gets unlocked..."); } isDatabaseLocked = YES; return self.database; } } - (Pile *)unlockDatabase { isDatabaseLocked = NO; return self; } 

Debugger says error occurs when [FMResultSet next] in line

 rc = sqlite3_step(statement.statement); 

I double-checked all hold values ​​and all objects exist at this time. Again, this only happens when the main thread launches a lot of requests while the background thread is running (which always creates a lot of load). The error is always generated by the main thread, not the background thread.

My last idea would be that both threads simultaneously start lockDatabase so that they can get the database object. So I added a mutex lock through "@synchronized (self)". However, this did not help.

Does anyone have a key?

+6
sqlite objective-c iphone exc-bad-access fmdb
source share
4 answers

You must add a synchronized shell around your unlockDatabase and lockDatabase functions, as well as isDatabaseLocked - it does not always guarantee that the storage or retrieval of a variable is atomic. Of course, if you do this, you will want to move your sleep outside the synchronized block, otherwise you are at a standstill. This, in fact, direct locking is not the most efficient method.

 - (FMDatabase *)lockedDatabase { do { @synchronized(self) { if (![self isDatabaseLocked]) { isDatabaseLocked = YES; return self.database; } } usleep(20); }while(true); // continue until we get a lock } 

Make sure you are not using the FMDatabase object after calling unlockDatabase? You might want to consider a handle template - create an object that wraps an FMDatabase object, and as long as it exists, contains a database lock. In init, you request a lock, and in dealloc you can release this lock. Then your client code does not need to worry about calling various lock / unlock functions, and you will not accidentally mess up. Try using NSMutex instead of @synchronized blocks, see http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/Multithreading/ThreadSafety/ThreadSafety.html#//apple_ref/doc/uid/10000057i-CH8- SW16

+2
source share

SQLite provides much simpler serialization. By simply setting the sqlite_config () parameter to SQLITE_CONFIG_SERIALIZED, you are likely to avoid most of these headaches. I found this to be a difficult path after a long struggle with streaming issues.

Here, how you use it, you can put it in the init method of FMDatabase ...

  if (sqlite3_config(SQLITE_CONFIG_SERIALIZED) == SQLITE_ERROR) { NSLog(@"couldn't set serialized mode"); } 

See the SQLite threadsafety and serialized docs for more details.

+6
source share

You can also try FMDatabaseQueue - I created it specifically for such situations. I haven't tried it, but I'm sure it will work for iOS 4.

0
source share

I had this problem, and I was able to fix the problem by simply turning on the caching of prepared statements.

 FMDatabase *myDatabase = [FMDatabase databaseWithPath: pathToDatabase]; myDatabase.shouldCacheStatements = YES; 
0
source share

All Articles