I have a scenario where multiple instances of MUSHclient may want to use the same sqlite database at the same time. Consider the gmcp mapper being used simultaneously in parallel worlds, main and test, for example.
Now the documentation for SQLite says that it is thread safe by default with certain caveats. Fine. So I followed some advice and tried calling db:busy_handler(myHandler) after opening the connection. Where myHandler prints "BUSY!" (so I know it got called) and then returns true (to continuously retry).
But given the dbcheck function in the mapper
function dbcheck (code)
if code ~= sqlite3.OK and -- no error
code ~= sqlite3.ROW and -- completed OK with another row of data
code ~= sqlite3.DONE then -- completed OK, no more rows
local err = db:errmsg () -- the rollback will change the error message
db:exec ("ROLLBACK") -- rollback any transaction to unlock the database
error (err, 2) -- show error in caller's context
end -- if
end -- dbcheck
I can rather easily and consistently contrive examples where a call of
dbcheck(db:exec(my_query))
in one or the other of my simultaneous sessions gets the following error
Quote: Run-time error
Plugin: Aardwolf_GMCP_Mapper (called from world: Aardwolf)
Function/Sub: OnPluginEnable called by Plugin Aardwolf_GMCP_Mapper
Reason: Executing plugin Aardwolf_GMCP_Mapper sub OnPluginEnable
[string "Plugin"]:2035: database is locked
stack traceback:
[C]: in function 'error'
[string "Plugin"]:117: in function 'dbcheck'
[string "Plugin"]:2035: in function <[string "Plugin"]:2027>
Error context in script:
With the OTHER session showing "BUSY!" one or maybe two times.
So, fine. Maybe I messed...something...up. So I try db:busy_timeout(100) instead of busy_handler. Still no luck.
I don't quite understand why the busy handler isn't getting used all the time, because it SHOULDN'T be deadlocking (the only reason I can find for sqlite to ignore the busyhandler (I wish it didn't have that inconsistent behavior, but whatever)), but maybe it is for reasons I'm not seeing.
Anyway, manually looping on busy does appear to work. Though this may not be an exactly optimal method...
function dbCheckExecute(query)
wait.make (function()
local code = db:exec(query)
while code == 5 do
wait.time(.1)
code = db:exec(query)
end
dbcheck(code)
end)
end
|