SQLite和安全分叉

z2acfund  于 2023-10-23  发布在  SQLite
关注(0)|答案(2)|浏览(106)

我有一个应用程序,其中有许多SQLite数据库的读取器并行执行,每个读取器都在自己的进程中。进程可以分叉…并且能够在SQLite连接建立后执行此操作。
我想写一个fork处理程序,这样当父进程fork时,所有的SQLite状态都会在子进程中“刷新”。这意味着所有准备好的语句、数据库连接和其他资源将立即被丢弃而不进行清理。
作为一个库,SQLite管理自己的内存和其他资源,所以在理论上是可能的。
实际上,我想做的是在分叉时复制非SQLite应用程序状态,但如果程序通过exec*而不是fork开始执行,则会出现SQLite状态。
如果可能的话,我想做的事情是这样的:

void sqlite_refresh(void)
{
     // discard all sqlite-specific state
     // make all sqlite3_db, sqlite3_stmt &c pointers NULL
     return;
}

// call this code somewhere
pthread_atfork(
     /* prepare */ NULL,
     /* parent */  NULL,
     /* child */   sqlite_refresh
);

在SQLite网站上,有一些关于不使用fork的评论,但似乎是假设程序员打算在fork之后与子进程中的SQLite库交互。
SQLite在its documentation中给出了以下关于使用fork的警告:
不要打开SQLite数据库连接,然后使用fork(),然后尝试在子进程中使用该数据库连接。所有类型的锁定问题将导致,您很容易以损坏的数据库结束。SQLite并不支持这种行为。在子进程中使用的任何数据库连接都必须在子进程中打开,而不是从父进程继承。
如果数据库连接是在父进程中打开的,甚至不要从子进程调用sqlite3_close()。关闭底层文件描述符是安全的,但是sqlite3_close()接口可能会调用清理活动,这些活动将删除父文件描述符下的内容,从而导致错误,甚至可能导致数据库损坏。
这里说
在Unix下,不应该通过fork()系统调用将打开的SQLite数据库带入子进程。

qaxu7uf2

qaxu7uf21#

子进程不能对从父进程获得的连接/语句对象做任何事情。这意味着sqlite_refresh()函数必须为空,或者它只是将所有指针设置为NULL。(这意味着内存泄漏。)
分叉时最好不要有开放的连接。

1sbrub3j

1sbrub3j2#

这不是问题的答案,而是为什么在调用fork()后使用数据库连接是不安全的:
SQLite使用文件系统锁来处理进程并发,但这些锁大多是advisory locks而不是mandatory locks,也就是说,如果进程不知道锁,那么它仍然可以访问(读或写)文件而没有任何问题。对于衍生的子进程,它不知道锁(因为连接已经在程序中创建,父进程和子进程是同一个程序,直到子进程涉及exec_*函数),因此它可以不受任何限制地对数据库文件做任何事情,这是完全不安全的。
有关advisory locksmandatory locks的详细信息,请参阅File locking in Linux

相关问题