Okay, I put in some instrumentation. Basically I print out all database queries as they happen, along with all calls to sqlite3OsLock() and sqlite3OsUnlock() (including their lockType argument and any abnormal return code). Also I print out how many times sqlite3InvokeBusyHandler() has to call the busy handler.
For the moment, the solution that is working for me is to disable syncing with PRAGMA synchronous = OFF. This is acceptable in this particular application because a power failure or OS crash will necessitate restarting the data gathering process anyway. I'll explain later why this change helps. In looking at the logs, I'm seeing several unlucky locking patterns. Let's just pick one. Process A gets starved by process B since B does many write transactions in a row, each time successfully getting an EXCLUSIVE lock. When B calls fdatasync() (actually fsync()), it blocks while holding EXCLUSIVE. A wakes up, but A can't immediately get a SHARED lock because B holds EXCLUSIVE. A goes to sleep while holding no locks, then B wakes up when the fsync() completes. B then releases locks but grabs EXCLUSIVE again to complete its next transaction, and the cycle repeats. A still can't get its SHARED lock, so it goes to sleep again, and then B continues to monopolize the lock. This goes on long enough to exhaust A's patience, at which point SQLITE_BUSY propagates to the application. If A tries to get SHARED while B holds EXCLUSIVE, I'd like to see A's desire for SHARED to be registered somehow so that A is granted SHARED the moment B drops EXCLUSIVE. Then when B tries to get EXCLUSIVE a second time, it is forced to wait in PENDING until A releases SHARED. I imagine one way to do this would be for A to block inside fcntl() rather than usleep(), so that the OS can atomically switch the lock over to A the very instant B releases it. But as far as I know, the only API that can give fcntl() locking a timeout is setitimer() and SIGALRM. I wouldn't want SQLite to mess with that, not without being given permission by a special preprocessor #define. SQLITE_ENABLE_SETLK_TIMEOUT, perhaps? In the above case, A is just trying to get SHARED. This is a difference from the test program I posted, which only needs write transactions. My real application needs to do a read transaction as part of database initialization in order to confirm the schema version is correct. That's probably a waste of time, but it's in there for now. But I think the more important difference is that B is doing many transactions in a tight loop. I guess I could put a sleep in the loop to give other processes a chance. I saw another pattern that I'm having a harder time explaining. It looked almost like the process holding EXCLUSIVE got stuck waiting for the process trying to get EXCLUSIVE. The latter would eventually timeout, at which point the former would proceed. Shrug! I should investigate this further, but look at the time. Okay, why does disabling syncing help? Taking fdatasync() out of the picture means B doesn't have to go to sleep holding EXCLUSIVE. It grabs EXCLUSIVE, tells the OS what it wants written to disk, then releases EXCLUSIVE without waiting to confirm that all the bits landed on the platters. ------------------------------------------- CONFIDENTIALITY NOTICE: This email and any attachments are for the sole use of the intended recipient and may contain material that is proprietary, confidential, privileged or otherwise legally protected or restricted under applicable government laws. Any review, disclosure, distributing or other use without expressed permission of the sender is strictly prohibited. If you are not the intended recipient, please contact the sender and delete all copies without reading, printing, or saving.. _______________________________________________ sqlite-users mailing list sqlite-users@mailinglists.sqlite.org http://mailinglists.sqlite.org/cgi-bin/mailman/listinfo/sqlite-users