Reputation: 1019
I've read other answers on how to detect whether the SQLite database is locked, and it suggests to use sqlite3_busy_handler
/sqlite3_busy_timeout
. I'm using Qt which has support of SQLite databases but that doesn't matter here.
Because in the use case of my application another application might access the same database, I need to handle this case.
Here's the options I set to the database when opening it:
auto db = QSqlDatabase::database();
db.setDatabaseName(m_sDatabasePath);
db.setConnectOptions("QSQLITE_BUSY_TIMEOUT=0");
if (!db.open())
return false;
I have a function which should determine whether database is locked or not:
int callback(void *data, int)
{
std::cout << "callback" << std::endl;
return 0;
}
bool isDatabaseLocked(const QSqlDatabase &db)
{
if (auto driver = db.driver())
{
// get driver handler
QVariant v = driver->handle();
if (v.isValid() && qstrcmp(v.typeName(), "sqlite3*") == 0)
{
// v.data() returns a pointer to the handle
auto handle = *static_cast<sqlite3 **>(v.data());
if (handle)
{
std::cout << sqlite3_busy_handler(handle, callback, nullptr) << std::endl;
std::cout << sqlite3_busy_timeout(handle, 0) << std::endl;
}
}
}
return true;
}
When the database is lcoked, I execute this function and get printed two 0s (SQLITE_OK) while I'd expect to get 5 (SQLITE_BUSY). And the callback function isn't called either.
What am I doing wrong?
Upvotes: 3
Views: 4291
Reputation: 10047
As clearly stated in the documentation, the sqlite3_busy_handler
function sets a callback function as the busy handler for the connection. Such callback might be invoked whenever a lock is met. In your code, the call to sqlite3_busy_handler
returns SQLITE_OK
, which is perfectly fine: it means the attempt to set the callback succeeded, no reason to return SQLITE_BUSY
at this time, since the call only affects the connection, not the database.
About the same applies to the sqlite3_busy_timeout
function, which in turn sets a different kind of busy handler (which replace the callback you just set, by the way) which simply sleeps for the specified amount of milliseconds until the lock is released.
Now, generally speaking, how to detect if a resource is locked? The usual way is: try to lock it.
So you could use a function like this:
bool isDatabaseLocked(const QSqlDatabase & db)
{
QSqlQuery q(db);
if(q.exec("BEGIN EXCLUSIVE")) //tries to acquire the lock
{
q.exec("COMMIT"); //releases the lock immediately
return false; //db is not locked
}
return true; //failed to acquire the lock: returns true (db is locked)
}
To make it return immediately when the lock cannot be acquired, clear the busy handler, setting the busy timeout to zero for this connection (before opening it):
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setConnectOptions("QSQLITE_BUSY_TIMEOUT=0");
To test the function, open a command line sqlite3
session on the database, and lock it by typing:
sqlite> BEGIN EXCLUSIVE;
When you want to release the lock, just type
sqlite> COMMIT;
Upvotes: 3