auto-close database connections when MVar is GCed

This commit is contained in:
Joey Hess 2015-12-23 16:11:36 -04:00
parent 6d38f54db4
commit d43ac8056b
Failed to extract signature

View file

@ -26,7 +26,7 @@ import Control.Monad
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import Control.Concurrent import Control.Concurrent
import Control.Concurrent.Async import Control.Concurrent.Async
import Control.Exception (throwIO) import Control.Exception (throwIO, BlockedIndefinitelyOnMVar(..))
import qualified Data.Text as T import qualified Data.Text as T
import Control.Monad.Trans.Resource (runResourceT) import Control.Monad.Trans.Resource (runResourceT)
import Control.Monad.Logger (runNoLoggingT) import Control.Monad.Logger (runNoLoggingT)
@ -68,6 +68,8 @@ openDb db tablename = do
worker <- async (workerThread (T.pack db) tablename jobs) worker <- async (workerThread (T.pack db) tablename jobs)
return $ DbHandle worker jobs return $ DbHandle worker jobs
{- This is optional; when the DbHandle gets garbage collected it will
- auto-close. -}
closeDb :: DbHandle -> IO () closeDb :: DbHandle -> IO ()
closeDb (DbHandle worker jobs) = do closeDb (DbHandle worker jobs) = do
putMVar jobs CloseJob putMVar jobs CloseJob
@ -123,29 +125,38 @@ data Job
| CloseJob | CloseJob
workerThread :: T.Text -> TableName -> MVar Job -> IO () workerThread :: T.Text -> TableName -> MVar Job -> IO ()
workerThread db tablename jobs = catchNonAsync (run loop) showerr workerThread db tablename jobs =
catchNonAsync (runSqliteRobustly tablename db loop) showerr
where where
showerr e = liftIO $ hPutStrLn stderr $ showerr e = hPutStrLn stderr $
"sqlite worker thread crashed: " ++ show e "sqlite worker thread crashed: " ++ show e
getjob :: IO (Either BlockedIndefinitelyOnMVar Job)
getjob = try $ takeMVar jobs
loop = do loop = do
job <- liftIO $ takeMVar jobs job <- liftIO getjob
case job of case job of
QueryJob a -> a >> loop -- Exception is thrown when the MVar is garbage
-- collected, which means the whole DbHandle
-- is not used any longer. Shutdown cleanly.
Left BlockedIndefinitelyOnMVar -> return ()
Right CloseJob -> return ()
Right (QueryJob a) -> a >> loop
-- change is run in a separate database connection -- change is run in a separate database connection
-- since sqlite only supports a single writer at a -- since sqlite only supports a single writer at a
-- time, and it may crash the database connection -- time, and it may crash the database connection
ChangeJob a -> liftIO (a run) >> loop Right (ChangeJob a) -> liftIO (a (runSqliteRobustly tablename db)) >> loop
CloseJob -> return ()
-- like runSqlite, but calls settle on the raw sql Connection. -- like runSqlite, but calls settle on the raw sql Connection.
run a = do runSqliteRobustly :: TableName -> T.Text -> (SqlPersistM a) -> IO a
conn <- Sqlite.open db runSqliteRobustly tablename db a = do
settle conn conn <- Sqlite.open db
runResourceT $ runNoLoggingT $ settle conn
withSqlConn (wrapConnection conn) $ runResourceT $ runNoLoggingT $
runSqlConn a withSqlConn (wrapConnection conn) $
runSqlConn a
where
-- Work around a bug in sqlite: New database connections can -- Work around a bug in sqlite: New database connections can
-- sometimes take a while to become usable; select statements will -- sometimes take a while to become usable; select statements will
-- fail with ErrorBusy for some time. So, loop until a select -- fail with ErrorBusy for some time. So, loop until a select