fix export db locking deadlock

This commit is contained in:
Joey Hess 2019-03-07 15:59:44 -04:00
parent 7e35c81ada
commit e3a704224f
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
5 changed files with 85 additions and 27 deletions

View file

@ -128,9 +128,9 @@ buildImportCommit remote importtreeconfig importcommitconfig importable =
importedtree importedtree
return (Just commit) return (Just commit)
updateexportdb importedtree = updateexportdb importedtree = do
withExclusiveLock (gitAnnexExportLock (Remote.uuid remote)) $ do
db <- Export.openDb (Remote.uuid remote) db <- Export.openDb (Remote.uuid remote)
Export.writeLockDbWhile db $ do
prevtree <- liftIO $ fromMaybe emptyTree prevtree <- liftIO $ fromMaybe emptyTree
<$> Export.getExportTreeCurrent db <$> Export.getExportTreeCurrent db
when (importedtree /= prevtree) $ do when (importedtree /= prevtree) $ do

View file

@ -48,6 +48,7 @@ module Annex.Locations (
gitAnnexSmudgeLock, gitAnnexSmudgeLock,
gitAnnexExportDbDir, gitAnnexExportDbDir,
gitAnnexExportLock, gitAnnexExportLock,
gitAnnexExportUpdateLock,
gitAnnexContentIdentifierDbDir, gitAnnexContentIdentifierDbDir,
gitAnnexContentIdentifierLock, gitAnnexContentIdentifierLock,
gitAnnexScheduleState, gitAnnexScheduleState,
@ -350,6 +351,10 @@ gitAnnexExportDbDir u r = gitAnnexExportDir u r </> "db"
gitAnnexExportLock :: UUID -> Git.Repo -> FilePath gitAnnexExportLock :: UUID -> Git.Repo -> FilePath
gitAnnexExportLock u r = gitAnnexExportDbDir u r ++ ".lck" gitAnnexExportLock u r = gitAnnexExportDbDir u r ++ ".lck"
{- Lock file for updating the export state for a special remote. -}
gitAnnexExportUpdateLock :: UUID -> Git.Repo -> FilePath
gitAnnexExportUpdateLock u r = gitAnnexExportDbDir u r ++ ".upl"
{- Directory containing database used to record remote content ids. -} {- Directory containing database used to record remote content ids. -}
gitAnnexContentIdentifierDbDir :: Git.Repo -> FilePath gitAnnexContentIdentifierDbDir :: Git.Repo -> FilePath
gitAnnexContentIdentifierDbDir r = gitAnnexDir r </> "cid" gitAnnexContentIdentifierDbDir r = gitAnnexDir r </> "cid"

View file

@ -24,7 +24,6 @@ import Annex.Export
import Annex.Content import Annex.Content
import Annex.Transfer import Annex.Transfer
import Annex.CatFile import Annex.CatFile
import Annex.LockFile
import Annex.RemoteTrackingBranch import Annex.RemoteTrackingBranch
import Logs.Location import Logs.Location
import Logs.Export import Logs.Export
@ -84,8 +83,8 @@ seek o = do
tree <- fromMaybe (giveup "unknown tree") <$> tree <- fromMaybe (giveup "unknown tree") <$>
inRepo (Git.Ref.tree (fromMaybe (exportTreeish o) (fmap snd mtbcommitsha))) inRepo (Git.Ref.tree (fromMaybe (exportTreeish o) (fmap snd mtbcommitsha)))
withExclusiveLock (gitAnnexExportLock (uuid r)) $ do
db <- openDb (uuid r) db <- openDb (uuid r)
writeLockDbWhile db $ do
changeExport r db tree changeExport r db tree
unlessM (Annex.getState Annex.fast) $ do unlessM (Annex.getState Annex.fast) $ do
void $ fillExport r db tree mtbcommitsha void $ fillExport r db tree mtbcommitsha

View file

@ -59,7 +59,6 @@ import Annex.Ssh
import Annex.BloomFilter import Annex.BloomFilter
import Annex.UpdateInstead import Annex.UpdateInstead
import Annex.Export import Annex.Export
import Annex.LockFile
import Annex.TaggedPush import Annex.TaggedPush
import Annex.CurrentBranch import Annex.CurrentBranch
import qualified Database.Export as Export import qualified Database.Export as Export
@ -691,8 +690,11 @@ syncFile ebloom rs af k = onlyActionOn' k $ do
seekExportContent :: [Remote] -> CurrBranch -> Annex Bool seekExportContent :: [Remote] -> CurrBranch -> Annex Bool
seekExportContent rs (currbranch, _) = or <$> forM rs go seekExportContent rs (currbranch, _) = or <$> forM rs go
where where
go r = withExclusiveLock (gitAnnexExportLock (Remote.uuid r)) $ do go r = bracket
db <- Export.openDb (Remote.uuid r) (Export.openDb (Remote.uuid r))
Export.closeDb
(\db -> Export.writeLockDbWhile db (go' r db))
go' r db = do
(exported, mtbcommitsha) <- case remoteAnnexTrackingBranch (Remote.gitconfig r) of (exported, mtbcommitsha) <- case remoteAnnexTrackingBranch (Remote.gitconfig r) of
Nothing -> nontracking r Nothing -> nontracking r
Just b -> do Just b -> do
@ -706,7 +708,7 @@ seekExportContent rs (currbranch, _) = or <$> forM rs go
Just cur -> do Just cur -> do
Command.Export.changeExport r db cur Command.Export.changeExport r db cur
return ([mkExported cur []], mtbcommitsha) return ([mkExported cur []], mtbcommitsha)
Export.closeDb db `after` fillexport r db (exportedTreeishes exported) mtbcommitsha fillexport r db (exportedTreeishes exported) mtbcommitsha
nontracking r = do nontracking r = do
exported <- getExport (Remote.uuid r) exported <- getExport (Remote.uuid r)

View file

@ -15,6 +15,7 @@ module Database.Export (
ExportHandle, ExportHandle,
openDb, openDb,
closeDb, closeDb,
writeLockDbWhile,
flushDbQueue, flushDbQueue,
addExportedLocation, addExportedLocation,
removeExportedLocation, removeExportedLocation,
@ -48,6 +49,7 @@ import Types.Export
import Annex.Export import Annex.Export
import qualified Logs.Export as Log import qualified Logs.Export as Log
import Annex.LockFile import Annex.LockFile
import Annex.LockPool
import Git.Types import Git.Types
import Git.Sha import Git.Sha
import Git.FilePath import Git.FilePath
@ -258,12 +260,62 @@ updateExportDb = runExportDiffUpdater $ mkExportDiffUpdater removeold addnew
addExportTree h k loc addExportTree h k loc
addExportedLocation h k loc addExportedLocation h k loc
{- Runs an action with the database locked for write. Waits for any other
- writers to finish first. The queue is flushed at the end.
-
- This first updates the ExportTree table with any new information
- from the git-annex branch export log.
-}
writeLockDbWhile :: ExportHandle -> Annex a -> Annex a
writeLockDbWhile db@(ExportHandle _ u) a = do
updatelck <- takeExclusiveLock (gitAnnexExportUpdateLock u)
withExclusiveLock (gitAnnexExportLock u) $ do
bracket_ (setup updatelck) cleanup a
where
setup updatelck = do
void $ updateExportTreeFromLog' db
-- flush the update so it's available immediately to
-- anything waiting on the updatelck
liftIO $ flushDbQueue db
liftIO $ dropLock updatelck
cleanup = liftIO $ flushDbQueue db
data ExportUpdateResult = ExportUpdateSuccess | ExportUpdateConflict data ExportUpdateResult = ExportUpdateSuccess | ExportUpdateConflict
deriving (Eq) deriving (Eq)
{- Updates the ExportTree table with information from the
- git-annex branch export log.
-
- This can safely be called whether the database is locked for write or
- not. Either way, it will block until the update is complete.
-}
updateExportTreeFromLog :: ExportHandle -> Annex ExportUpdateResult updateExportTreeFromLog :: ExportHandle -> Annex ExportUpdateResult
updateExportTreeFromLog db@(ExportHandle _ u) = updateExportTreeFromLog db@(ExportHandle _ u) =
withExclusiveLock (gitAnnexExportLock u) $ do -- If another process or thread is performing the update,
-- this will block until it's done.
withExclusiveLock (gitAnnexExportUpdateLock u) $ do
-- If the database is locked by something else,
-- this will not run the update. But, in that case,
-- writeLockDbWhile is running, and has already
-- completed the update, so we don't need to do anything.
mr <- tryExclusiveLock (gitAnnexExportLock u) $
updateExportTreeFromLog' db
case mr of
Just r -> return r
Nothing -> do
old <- liftIO $ fromMaybe emptyTree
<$> getExportTreeCurrent db
l <- Log.getExport u
return $ case Log.exportedTreeishes l of
[] -> ExportUpdateSuccess
(new:[])
| new /= old -> ExportUpdateSuccess
| new == old -> ExportUpdateSuccess
_ts -> ExportUpdateConflict
{- The database should be locked when calling this. -}
updateExportTreeFromLog' :: ExportHandle -> Annex ExportUpdateResult
updateExportTreeFromLog' db@(ExportHandle _ u) = do
old <- liftIO $ fromMaybe emptyTree old <- liftIO $ fromMaybe emptyTree
<$> getExportTreeCurrent db <$> getExportTreeCurrent db
l <- Log.getExport u l <- Log.getExport u