Close sqlite databases more robustly.

Had a report of close throwing ErrorBusy on CIFS.

Retrying up to 16 seconds is a balance between hopefully waiting long
enough for the problem to clear up and waiting so long that git-annex seems
to hang.

The new dependency is free; persistent depends on unliftio-core.
This commit is contained in:
Joey Hess 2019-09-26 12:24:03 -04:00
parent ab8a6a82e1
commit 9628ae2e67
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
3 changed files with 51 additions and 2 deletions

View file

@ -5,6 +5,7 @@ git-annex (7.20190913) UNRELEASED; urgency=medium
* git-lfs: Added support for http basic auth.
* git-lfs: Only do endpoint discovery once when concurrency is enabled.
* Test: Use more robust directory removal when built with directory-1.2.7.
* Close sqlite databases more robustly.
-- Joey Hess <id@joeyh.name> Thu, 19 Sep 2019 11:11:19 -0400

View file

@ -1,10 +1,12 @@
{- Persistent sqlite database handles.
-
- Copyright 2015-2018 Joey Hess <id@joeyh.name>
- Copyright 2015-2019 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU AGPL version 3 or higher.
-}
{-# LANGUAGE TypeFamilies, FlexibleContexts #-}
module Database.Handle (
DbHandle,
DbConcurrency(..),
@ -23,6 +25,8 @@ import Database.Persist.Sqlite
import qualified Database.Sqlite as Sqlite
import Control.Monad
import Control.Monad.IO.Class (liftIO)
import Control.Monad.IO.Unlift (MonadUnliftIO, withRunInIO)
import Control.Monad.Logger (MonadLogger)
import Control.Concurrent
import Control.Concurrent.Async
import Control.Exception (throwIO, BlockedIndefinitelyOnMVar(..))
@ -193,7 +197,7 @@ runSqliteRobustly tablename db a = do
go conn retries = do
r <- try $ runResourceT $ runNoLoggingT $
withSqlConn (wrapConnection conn) $
withSqlConnRobustly (wrapConnection conn) $
runSqlConn a
case r of
Right v -> return v
@ -237,3 +241,46 @@ runSqliteRobustly tablename db a = do
nullselect = T.pack $ "SELECT null from " ++ tablename ++ " limit 1"
briefdelay = threadDelay 1000 -- 1/1000th second
-- Like withSqlConn, but more robust.
withSqlConnRobustly
:: (MonadUnliftIO m, MonadLogger m, IsPersistBackend backend, BaseBackend backend ~ SqlBackend)
=> (LogFunc -> IO backend)
-> (backend -> m a)
-> m a
withSqlConnRobustly open f = do
logFunc <- askLogFunc
withRunInIO $ \run -> bracket
(open logFunc)
closeRobustly
(run . f)
-- Sqlite can throw ErrorBusy while closing a database; this catches
-- the exception and retries.
closeRobustly
:: (IsPersistBackend backend, BaseBackend backend ~ SqlBackend)
=> backend
-> IO ()
closeRobustly conn = go maxretries briefdelay
where
briefdelay = 1000 -- 1/1000th second
-- Try up to 14 times; with the delay doubling each time,
-- the maximum delay before giving up is 16 seconds.
maxretries = 14 :: Int
go retries delay = do
r <- try $ close' conn
case r of
Right () -> return ()
Left ex@(Sqlite.SqliteException { Sqlite.seError = e })
| e == Sqlite.ErrorBusy -> do
threadDelay delay
let delay' = delay * 2
let retries' = retries - 1
if retries' < 1
then rethrow "while closing database connection" ex
else go retries' delay'
| otherwise -> rethrow "while closing database connection" ex
rethrow msg e = throwIO $ userError $ show e ++ "(" ++ msg ++ ")"

View file

@ -344,6 +344,7 @@ Executable git-annex
persistent-sqlite (>= 2.8.1),
persistent (>= 2.8.1),
persistent-template,
unliftio-core,
microlens,
aeson,
vector,