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,15 +128,15 @@ 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
Export.updateExportDb db prevtree importedtree Export.updateExportDb db prevtree importedtree
liftIO $ Export.recordExportTreeCurrent db importedtree liftIO $ Export.recordExportTreeCurrent db importedtree
Export.closeDb db Export.closeDb db
updateexportlog importedtree = do updateexportlog importedtree = do
oldexport <- getExport (Remote.uuid remote) oldexport <- getExport (Remote.uuid remote)

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,12 +83,12 @@ 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
closeDb db closeDb db
-- | When the treeish is a branch like master or refs/heads/master -- | When the treeish is a branch like master or refs/heads/master
-- (but not refs/remotes/...), find the commit it points to -- (but not refs/remotes/...), find the commit it points to

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,22 +260,72 @@ 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,
old <- liftIO $ fromMaybe emptyTree -- this will block until it's done.
<$> getExportTreeCurrent db withExclusiveLock (gitAnnexExportUpdateLock u) $ do
l <- Log.getExport u -- If the database is locked by something else,
case Log.exportedTreeishes l of -- this will not run the update. But, in that case,
[] -> return ExportUpdateSuccess -- writeLockDbWhile is running, and has already
(new:[]) -- completed the update, so we don't need to do anything.
| new /= old -> do mr <- tryExclusiveLock (gitAnnexExportLock u) $
updateExportTree db old new updateExportTreeFromLog' db
liftIO $ recordExportTreeCurrent db new case mr of
liftIO $ flushDbQueue db Just r -> return r
return ExportUpdateSuccess Nothing -> do
| new == old -> return ExportUpdateSuccess old <- liftIO $ fromMaybe emptyTree
_ts -> return ExportUpdateConflict <$> 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
<$> getExportTreeCurrent db
l <- Log.getExport u
case Log.exportedTreeishes l of
[] -> return ExportUpdateSuccess
(new:[])
| new /= old -> do
updateExportTree db old new
liftIO $ recordExportTreeCurrent db new
liftIO $ flushDbQueue db
return ExportUpdateSuccess
| new == old -> return ExportUpdateSuccess
_ts -> return ExportUpdateConflict