【问题标题】:Sqlite deadlock across multiple threads with different files具有不同文件的多个线程之间的 Sqlite 死锁
【发布时间】:2015-01-09 00:10:42
【问题描述】:

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

其中四个在此堆栈中。其中,三个在同一张表上操作(同样,不同的数据库文件);三个在更新,一个在查询。

__psynch_mutexwait
_pthread_mutex_lock
unixLock
sqlite3PagerSharedLock
sqlite3BtreeBeginTrans
sqlite3VdbeExec
sqlite3_step

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

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) 连接了 SQLITE_LOG,我看到相当多的代码 28 (sqlite_warning) 与消息“文件在打开时重命名:”。

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

【问题讨论】:

  • 我怀疑你弄错了,有些人正在访问同一个文件。但请注意,SQLite 在某些配置中会创建一个“日志”(WAL)文件,即使 SQLite 文件不同,它们也有可能尝试使用相同的日志文件。
  • 不,我只是再次检查了所有线程并检查了所有传递给 sqlite 函数的路径。如果确实有多个线程访问同一个文件,那么它会以一种非常非常奇怪的方式发生。并且 DB 文件是使用 WAL 日志创建的,然后转换为 DELETE(它们将来只会用作只读)。
  • 我不是很喜欢 OSx 的东西,但是有没有工具可以显示进程中打开的文件?
  • Yes。我看到很多 sqlite-shm 文件句柄打开,但没有一个与被锁定的特定文件完全对应。 SQLite docs 声明在删除 wal 文件时应该删除 -shm 文件,但在我的情况下不会发生这种情况。当我转换为 DELETE (PRAGMA journal_mode=DELETE) 时,wal 文件消失但 shm 文件保留。
  • 我对此没有答案,但我想注册一个“我也是” - 我看到三个线程非常相似,三个线程都卡在不同数据库上的 sqlite3_step 中。其中一个线程在guarded_close_np,其他线程在__psynch_mutexwait

标签: multithreading macos sqlite deadlock


【解决方案1】:

听起来你被 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
...

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

【讨论】:

  • 我认为我已经修复了这台机器上的其他一些看似与文件相关的问题(Finder 锁定)。我会运行一些诊断工具。
  • 我在同事的机器(也是新的 Mac Pro)上调试了该应用程序,它以相同的方式锁定,除了 guarded_close_np 中没有线程。所以这不是我的机器特有的问题。当它确实锁定在我的机器上时,guarded_close_np 的程序集被锁定在 jae &lt;address here&gt;jumping 到指令为 retq 的地址。
  • 我猜这是一个特定于 Yosemite 附带的 sqlite 版本的错误。不幸的是我找不到源代码。 sqlite --version 表示 3.8.5 和 c8ade949d4a2eb3bba4702a4a0e17b405e9b6ace 但该提交在 sqlite.org 上不存在
  • @craig65535 - 我也有这个版本。我提到的 sqlite 警告消息不是由在 sqlite 版本 3.7.13 下的 iOS 上运行的相同代码生成的。
【解决方案2】:

我的应用通过链接动态库来使用 sqlite。我从here(写作时为 3.8.7.4)下载了最新的 SQLite 源合并,并将其直接编译到应用程序中,一切都开始工作了。所以也许这是 3.8.5 中的一个错误。显然将源代码直接编译到应用程序中是recommended way 无论如何都要使用sqlite。

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

【讨论】:

    猜你喜欢
    • 2015-10-29
    • 1970-01-01
    • 2018-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多