I have a multithreaded application which is exhibiting some unexpected
behaviour in the form of SQLITE_BUSY api return codes. I've traced
this back to a fairly simple way to model the problem, but I can't
seem to find any documentation which explicitly describes this issue.
I'll summarise the problem below, and I also have provided a short
console program which can be used to demonstrate the issue.
To my understanding, the behaviour is:
I have two threads, A and B.
1. A opens the database, and gets an sqlite3*
2. A runs a SELECT, which puts the database in a SHARED lock state
3. The OS interrupts A and allows B to run
4. B opens the database, gets an sqlite3*
5. B attempts an INSERT, but is blocked by the SHARED lock state.
6. B puts the database in a PENDING lock state.
7. The OS interrupts B and allows A to continue
8. A calls some function foo()
9. foo() opens the database, and gets an sqlite3*
10. foo() runs a SELECT, which blocks because the database is in a
PENDING lock state.
11. Because foo() is part of thread A, the system is deadlocked.
In my example code, disabling foo() and replacing it with
bar(sqlite3*), which has a pointer to A's sqlite3* will allow the
application to run normally.
So my questions are:
Is this a known feature?
Should functions called by a thread, which has its own DB handle, be
forbidden from opening a new DB handle?
Any answers much appreciated! This is also my first post here so
scathing criticisms and allegations of being a newbie are also welcome
>.<
Example code follows (beware, it's quick and dirty!); this version
will compile as a windows console app (remember to set OS_WIN and
THREADSAFE in compile options):
__________________________________________________
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <assert.h>
#include "sqlite/sqlite3.h"
#define ALONGTIME 1000
void dotdotdot() { //simulate 'other' stuff
volatile int stupid = 0;
while(stupid < 10000) stupid++;
}
//use an open connection and select
void bar(sqlite3 *pdb) {
char sql[] = "SELECT * FROM B";
sqlite3_stmt* pstmt;
const char* szTail=0;
int ret;
printf("B");
assert(SQLITE_OK == (ret = sqlite3_prepare(pdb, sql, -1, &pstmt, &szTail)));
ret = sqlite3_step(pstmt);
assert(ret == SQLITE_DONE || ret == SQLITE_ROW);
sqlite3_finalize(pstmt);
}
//open a new connection and select
void foo() {
sqlite3 *pdb;
int ret;
assert(SQLITE_OK == (ret = sqlite3_open("testdb",&pdb)));
sqlite3_busy_timeout(pdb,ALONGTIME);
bar(pdb);
sqlite3_close(pdb);
}
//open a connection and select
DWORD WINAPI ThreadA(LPVOID) {
while(1) {
Sleep(7);
sqlite3 *pdb = 0;
char sql[] = "SELECT * FROM A";
sqlite3_stmt* pstmt;
const char* szTail=0;
int ret;
printf("A");
assert(SQLITE_OK == (ret = sqlite3_open("testdb",&pdb)));
sqlite3_busy_timeout(pdb,ALONGTIME);
assert(SQLITE_OK == (ret = sqlite3_prepare(pdb, sql, -1,
&pstmt, &szTail)));
ret = sqlite3_step(pstmt);
assert(ret == SQLITE_DONE || ret == SQLITE_ROW);
dotdotdot(); //simulate 'other code' that occurs here and may
use the query
foo(); //call a function that may query the db
//bar(pdb);
dotdotdot(); //simulate 'other code' that occurs here and may
use the query
sqlite3_finalize(pstmt);
sqlite3_close(pdb);
}
}
void TouchDB() {
static int counter = 0;
sqlite3 *pdb = 0;
char *sql;
char *err = 0;
int ret;
assert(SQLITE_OK == (ret = sqlite3_open("testdb",&pdb)));
sqlite3_busy_timeout(pdb,ALONGTIME);
printf("C(%d)", counter);
sql = sqlite3_mprintf("INSERT INTO C VALUES(%d);", counter++);
assert(SQLITE_OK == (ret = sqlite3_exec(pdb,sql,0,0,&err)));
sqlite3_close(pdb);
}
DWORD ThreadB(LPVOID) {
while(!kbhit()) {
Sleep(3);
TouchDB(); //touch the db
}
return 0;
}
void MakeDB()
{
sqlite3 *pdb = 0;
char sql[] ="DROP TABLE IF EXISTS A;"
"DROP TABLE IF EXISTS B;"
"DROP TABLE IF EXISTS C;"
"CREATE TABLE A(ID INTEGER PRIMARY KEY);"
"CREATE TABLE B(ID INTEGER PRIMARY KEY);"
"CREATE TABLE C(ID INTEGER PRIMARY KEY);"
"INSERT INTO A VALUES(1);"
"INSERT INTO B VALUES(2);";
char *err = 0;
int ret;
if (SQLITE_OK == (ret = sqlite3_open("testdb",&pdb))) {
sqlite3_busy_timeout(pdb,ALONGTIME);
sqlite3_exec(pdb,sql,0,0,&err);
sqlite3_close(pdb);
}
}
int main(int argc, char* argv[]) {
MakeDB(); //make the database the first time
CreateThread(NULL,0,&ThreadA,0,0,NULL);
ThreadB(NULL);
return 0;
}
-----------------------------------------------------------------------------
To unsubscribe, send email to [EMAIL PROTECTED]
-----------------------------------------------------------------------------