add debugLocks around database operations

to track down a blocked indefinitely on MVar that seems to occur after
sqlite throws ErrorBusy but that I have not been able to reproduce when
I made commits synthetically throw ErrorBusy.

Sponsored-by: Dartmouth College's Datalad project
This commit is contained in:
Joey Hess 2022-06-03 14:10:24 -04:00
parent d6dfaa8d0f
commit 09edb07ac5
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
5 changed files with 32 additions and 15 deletions

View file

@ -19,6 +19,7 @@ module Database.Handle (
import Utility.Exception import Utility.Exception
import Utility.FileSystemEncoding import Utility.FileSystemEncoding
import Utility.DebugLocks
import Database.Persist.Sqlite import Database.Persist.Sqlite
import qualified Database.Sqlite as Sqlite import qualified Database.Sqlite as Sqlite
@ -57,7 +58,7 @@ openDb db tablename = do
- auto-close. -} - auto-close. -}
closeDb :: DbHandle -> IO () closeDb :: DbHandle -> IO ()
closeDb (DbHandle worker jobs) = do closeDb (DbHandle worker jobs) = do
putMVar jobs CloseJob debugLocks $ putMVar jobs CloseJob
wait worker wait worker
{- Makes a query using the DbHandle. This should not be used to make {- Makes a query using the DbHandle. This should not be used to make
@ -74,8 +75,8 @@ queryDb :: DbHandle -> SqlPersistM a -> IO a
queryDb (DbHandle _ jobs) a = do queryDb (DbHandle _ jobs) a = do
res <- newEmptyMVar res <- newEmptyMVar
putMVar jobs $ QueryJob $ putMVar jobs $ QueryJob $
liftIO . putMVar res =<< tryNonAsync a debugLocks $ liftIO . putMVar res =<< tryNonAsync a
(either throwIO return =<< takeMVar res) debugLocks $ (either throwIO return =<< takeMVar res)
`catchNonAsync` (\e -> error $ "sqlite query crashed: " ++ show e) `catchNonAsync` (\e -> error $ "sqlite query crashed: " ++ show e)
{- Writes a change to the database. {- Writes a change to the database.
@ -101,8 +102,8 @@ commitDb' :: DbHandle -> SqlPersistM () -> IO (Either SomeException ())
commitDb' (DbHandle _ jobs) a = do commitDb' (DbHandle _ jobs) a = do
res <- newEmptyMVar res <- newEmptyMVar
putMVar jobs $ ChangeJob $ putMVar jobs $ ChangeJob $
liftIO . putMVar res =<< tryNonAsync a debugLocks $ liftIO . putMVar res =<< tryNonAsync a
takeMVar res debugLocks $ takeMVar res
data Job data Job
= QueryJob (SqlPersistM ()) = QueryJob (SqlPersistM ())

View file

@ -16,6 +16,7 @@ module Database.Keys.Handle (
import qualified Database.Queue as H import qualified Database.Queue as H
import Utility.Exception import Utility.Exception
import Utility.DebugLocks
import Control.Concurrent import Control.Concurrent
import Control.Monad.IO.Class (liftIO, MonadIO) import Control.Monad.IO.Class (liftIO, MonadIO)
@ -42,16 +43,16 @@ withDbState
-> (DbState -> m (v, DbState)) -> (DbState -> m (v, DbState))
-> m v -> m v
withDbState (DbHandle mvar) a = do withDbState (DbHandle mvar) a = do
st <- liftIO $ takeMVar mvar st <- liftIO $ debugLocks $ takeMVar mvar
go st `onException` (liftIO $ putMVar mvar st) go st `onException` (liftIO $ debugLocks $ putMVar mvar st)
where where
go st = do go st = do
(v, st') <- a st (v, st') <- a st
liftIO $ putMVar mvar st' liftIO $ debugLocks $ putMVar mvar st'
return v return v
flushDbQueue :: DbHandle -> IO () flushDbQueue :: DbHandle -> IO ()
flushDbQueue (DbHandle mvar) = go =<< readMVar mvar flushDbQueue (DbHandle mvar) = go =<< debugLocks (readMVar mvar)
where where
go (DbOpen qh) = H.flushDbQueue qh go (DbOpen qh) = H.flushDbQueue qh
go _ = return () go _ = return ()

View file

@ -19,6 +19,7 @@ module Database.Queue (
import Utility.Monad import Utility.Monad
import Utility.RawFilePath import Utility.RawFilePath
import Utility.DebugLocks
import Database.Handle import Database.Handle
import Database.Persist.Sqlite import Database.Persist.Sqlite
@ -51,12 +52,12 @@ closeDbQueue h@(DQ hdl _) = do
{- Blocks until all queued changes have been written to the database. -} {- Blocks until all queued changes have been written to the database. -}
flushDbQueue :: DbQueue -> IO () flushDbQueue :: DbQueue -> IO ()
flushDbQueue (DQ hdl qvar) = do flushDbQueue (DQ hdl qvar) = do
q@(Queue sz _ qa) <- takeMVar qvar q@(Queue sz _ qa) <- debugLocks $ takeMVar qvar
if sz > 0 if sz > 0
then do then do
commitDb hdl qa commitDb hdl qa
putMVar qvar =<< emptyQueue debugLocks $ putMVar qvar =<< emptyQueue
else putMVar qvar q else debugLocks $ putMVar qvar q
{- Makes a query using the DbQueue's database connection. {- Makes a query using the DbQueue's database connection.
- This should not be used to make changes to the database! - This should not be used to make changes to the database!
@ -95,10 +96,10 @@ queueDb
-> SqlPersistM () -> SqlPersistM ()
-> IO () -> IO ()
queueDb (DQ hdl qvar) commitchecker a = do queueDb (DQ hdl qvar) commitchecker a = do
Queue sz lastcommittime qa <- takeMVar qvar Queue sz lastcommittime qa <- debugLocks $ takeMVar qvar
let !sz' = sz + 1 let !sz' = sz + 1
let qa' = qa >> a let qa' = qa >> a
let enqueue = putMVar qvar let enqueue = debugLocks . putMVar qvar
ifM (commitchecker sz' lastcommittime) ifM (commitchecker sz' lastcommittime)
( do ( do
r <- commitDb' hdl qa' r <- commitDb' hdl qa'

View file

@ -35,7 +35,7 @@ install-home:
tmp/configure-stamp: Build/TestConfig.hs Build/Configure.hs tmp/configure-stamp: Build/TestConfig.hs Build/Configure.hs
if [ "$(BUILDER)" = ./Setup ]; then $(GHC) --make Setup; fi if [ "$(BUILDER)" = ./Setup ]; then $(GHC) --make Setup; fi
if [ "$(BUILDER)" != stack ]; then \ if [ "$(BUILDER)" != stack ]; then \
$(BUILDER) configure $(BUILDERCOMMONOPTIONS) --ghc-options="$(shell Build/collect-ghc-options.sh)"; \ $(BUILDER) configure -f debuglocks $(BUILDERCOMMONOPTIONS) --ghc-options="$(shell Build/collect-ghc-options.sh)"; \
else \ else \
$(BUILDER) setup $(BUILDERCOMMONOPTIONS); \ $(BUILDER) setup $(BUILDERCOMMONOPTIONS); \
fi fi

View file

@ -0,0 +1,14 @@
[[!comment format=mdwn
username="joey"
subject="""comment 13"""
date="2022-06-03T17:52:12Z"
content="""
To hopefully find out what MVar operation is blocking, I have added lock
debugging instrumentation to all database calls. This will need a special
build of git-annex with the DebugLocks build flag enabled.
I have made a standalone tarball built that way available here:
<https://downloads.kitenet.net/git-annex/linux/debuglocks/>
It should display a backtrace on stderr when the MVar deadlock happens.
"""]]