
Fix hang that could occur when using git-annex adjust on a branch with a
number of files greater than annex.queuesize. Or potentially other
commands.
When reconcileStaged is running, the database is being opened. But
restagePointerFiles closes the database, and later writes to it. So it will
deadlock if called by reconcileStaged.
The deadlock occurred when the git queue happened to be full, causing
adding a call to restagePointerFiles to it to flush the queue and
restagePointerFiles to run at the wrong time.
Fixed by making reconcileStaged, when it populates or depopulates a pointer
file, arrange for restagePointerFiles to be run as a cleanup action, rather
than from the git queue.
But, what if restagePointerFiles is already in the git queue before
reconcileStaged is run? If it adds anything else to the git queue, causing
the queue to flush, it would still deadlock. To avoid this hypothetical
situation, added a Annex.inreconcilestaged, and made restagePointerFiles
check it and not do anything.
Note that, I did consider the simpler approach of only running
restagePointerFiles as a cleanup action, rather than from the git queue.
But see commit 6a3bd283b8
for why it was made
to use the queue in the first place. I wanted to avoid tying this bug fix
to a behavior change.
Sponsored-by: mycroft
71 lines
2.2 KiB
Haskell
71 lines
2.2 KiB
Haskell
{- git-annex pointer files
|
|
-
|
|
- Copyright 2010-2018 Joey Hess <id@joeyh.name>
|
|
-
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
|
-}
|
|
|
|
{-# LANGUAGE CPP #-}
|
|
|
|
module Annex.Content.PointerFile where
|
|
|
|
import Annex.Common
|
|
import Annex.Perms
|
|
import Annex.Link
|
|
import Annex.ReplaceFile
|
|
import Annex.InodeSentinal
|
|
import Annex.Content.LowLevel
|
|
import Utility.InodeCache
|
|
import qualified Utility.RawFilePath as R
|
|
#if ! defined(mingw32_HOST_OS)
|
|
import Utility.Touch
|
|
import qualified System.Posix.Files as Posix
|
|
#endif
|
|
|
|
import System.PosixCompat.Files (fileMode)
|
|
|
|
{- Populates a pointer file with the content of a key.
|
|
-
|
|
- If the file already has some other content, it is not modified.
|
|
-
|
|
- Returns an InodeCache if it populated the pointer file.
|
|
-}
|
|
populatePointerFile :: Restage -> Key -> OsPath -> OsPath -> Annex (Maybe InodeCache)
|
|
populatePointerFile restage k obj f = go =<< liftIO (isPointerFile f)
|
|
where
|
|
go (Just k') | k == k' = do
|
|
destmode <- liftIO $ catchMaybeIO $
|
|
fileMode <$> R.getFileStatus (fromOsPath f)
|
|
liftIO $ removeWhenExistsWith removeFile f
|
|
(ic, populated) <- replaceWorkTreeFile f $ \tmp -> do
|
|
ok <- linkOrCopy k obj tmp destmode >>= \case
|
|
Just _ -> thawContent tmp >> return True
|
|
Nothing -> liftIO (writePointerFile tmp k destmode) >> return False
|
|
ic <- withTSDelta (liftIO . genInodeCache tmp)
|
|
return (ic, ok)
|
|
maybe noop (restagePointerFile restage f) ic
|
|
if populated
|
|
then return ic
|
|
else return Nothing
|
|
go _ = return Nothing
|
|
|
|
{- Removes the content from a pointer file, replacing it with a pointer.
|
|
-
|
|
- Does not check if the pointer file is modified. -}
|
|
depopulatePointerFile :: Restage -> Key -> OsPath -> Annex ()
|
|
depopulatePointerFile restage key file = do
|
|
st <- liftIO $ catchMaybeIO $ R.getFileStatus (fromOsPath file)
|
|
let mode = fmap fileMode st
|
|
secureErase file
|
|
liftIO $ removeWhenExistsWith removeFile file
|
|
ic <- replaceWorkTreeFile file $ \tmp -> do
|
|
liftIO $ writePointerFile tmp key mode
|
|
#if ! defined(mingw32_HOST_OS)
|
|
-- Don't advance mtime; this avoids unnecessary re-smudging
|
|
-- by git in some cases.
|
|
liftIO $ maybe noop
|
|
(\t -> touch (fromOsPath tmp) t False)
|
|
(fmap Posix.modificationTimeHiRes st)
|
|
#endif
|
|
withTSDelta (liftIO . genInodeCache tmp)
|
|
maybe noop (restagePointerFile restage file) ic
|