具有不同文件的跨多个线程的 SQLite 死锁

Sqlite deadlock across multiple threads with different files

我有一个 OS X 应用程序 (Yosemite, 10.10),它执行长时间运行的作业,涉及跨多个线程大量使用 sqlite。而且我在 8 个线程中遇到了死锁,所有线程都卡在连接到不同数据库文件的 sqlite 代码中。它们之间没有明显的资源相关联系。我在新的 Mac Pro(2013 年底)上调试它。

他们中有四个人在这个堆栈中。其中,三个在相同的 table 上运行(同样,不同的数据库文件);三个正在更新,一个正在查询。

__psynch_mutexwait
_pthread_mutex_lock
unixLock
sqlite3PagerSharedLock
sqlite3BtreeBeginTrans
sqlite3VdbeExec
sqlite3_step

一个在这个堆栈中,更新与上面堆栈中的三个相同的table。

guarded_close_np
nolockClose
pager_end_transaction
sqlite3BtreeCommitPhaseTwo
sqlite3VdbeHalt
sqlite3VdbeExec
sqlite3_step

两个在此堆栈中,打开不同位置的同名数据库文件。开启模式为SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX.

__psynch_mutexwait
_pthread_mutex_lock
sqlite3ParseUri
openDatabase

一个在这个堆栈上,结束一个事务。

__psynch_mutexwait
_pthread_mutex_lock
unixLock
sqlite3VdbeHalt
sqlite3VdbeExec
sqlite3_step

那么,问题是:什么会导致 sqlite 在不涉及任何共享资源的情况下死锁?

更新: 我现在有七个线程锁定调用 sqlite3_open_v2 和一个 sqlite3_close,所有线程都在不同的数据库文件上(几个具有相同的名称,但在不同的文件夹中)。堆栈是:

__psynch_mutexwait
_pthread_mutex_lock
sqlite3ParseUri
openDatabase

关闭堆栈是:

guarded_close_np
unixClose
sqlite3PagerClose
sqlite3BtreeClose
sqlite3LeaveMutexAndCloseZombie
sqlite3Close

通过修复一些内存泄漏(这不是与 ARC 一起运行)并删除一些事务语句,我能够在锁定之前让它运行更长时间。

更新 2: 我已经通过 sqlite3_config (documentation) and I'm seeing a fair amount of logging of code 28 (sqlite_warning) 与消息 "file renamed while open:" 连接 SQLITE_LOG

更新 3: 我擦除机器并重新安装 Yosemite 以尝试排除文件系统问题。我仍然以同样的方式锁定。它会运行几分钟,然后线程一个接一个地锁定。在 guarded_close_np 处有一个,卡在指令 jae <address here> 处的汇编中,其中跳转到的地址具有指令 retq。我问了 关于重命名文件的 sqlite 日志消息,希望它是相关的。

听起来您好像卡在了 UNIX 主互斥体上,它需要在文件关闭之前获取:

/*
** Close a file.
*/
static int unixClose(sqlite3_file *id){  
  int rc = SQLITE_OK;
  unixFile *pFile = (unixFile *)id;
  verifyDbFile(pFile);
  unixUnlock(id, NO_LOCK);
  unixEnterMutex(); <- HERE
...

此互斥锁主要在低级文件操作期间持有。您必须找到持有互斥锁的线程并查看它在做什么。也许它在缓慢或损坏的文件系统上运行。

我的应用程序通过链接到动态库来使用 sqlite。我从 here (3.8.7.4 as of writing) and compiled it right into the app, and everything started working. So perhaps it was a bug in 3.8.5. Apparently compiling the source into the app directly is the recommended way 下载了最新的 SQLite 源合并,无论如何都可以使用 sqlite。

我仍然不知道究竟是什么导致了这个问题。我唯一能想到的是,这与我创建数据库文件的方式有关:我正在使用 NSFileManager createFileAtPath 创建一个空文件,然后使用 [=12] 将其传递给 sqlite3_open_v2 =] 作为标志参数的一部分。所以它是将数据库写入现有文件,而不是在指定位置创建数据库文件。