summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Joey Hess <joey@kitenet.net>2012-06-04 17:32:46 -0400
committerGravatar Joey Hess <joey@kitenet.net>2012-06-04 18:09:18 -0400
commit5b4e5ce7e5141eac7f0bc81033d7d7676ff0008f (patch)
tree85ee183972451657f4a8b10cf9692da101dc8c9a
parent659e6b13249ec9d8f3c01607b0eb819b8a5690fe (diff)
deletion
When a new file is annexed, a deletion event occurs when it's moved away to be replaced by a symlink. Most of the time, there is no problimatic race, because the same thread runs the add event as the deletion event. So, once the symlink is in place, the deletion code won't run at all, due to existing checks that a deleted file is really gone. But there is a race at startup, as then the inotify thread is running at the same time as the main thread, which does the initial tree walking and annexing. It would be possible for the deletion inotify to run in a perfect race with the addition, and remove the newly added symlink from the git cache. To solve this race, added event serialization via a MVar. We putMVar before running each event, which blocks if an event is already running. And when an event finishes (or crashes!), we takeMVar to free the lock. Also, make rm -rf not spew warnings by passing --ignore-unmatch when deleting directories.
-rw-r--r--Command/Watch.hs16
-rw-r--r--Utility/Inotify.hs32
2 files changed, 32 insertions, 16 deletions
diff --git a/Command/Watch.hs b/Command/Watch.hs
index b38c04d2c..046777685 100644
--- a/Command/Watch.hs
+++ b/Command/Watch.hs
@@ -44,8 +44,11 @@ start = notBareRepo $ do
gitdir dir = takeFileName dir /= ".git"
{- Inotify events are run in separate threads, and so each is a
- - self-contained Annex monad. Exceptions by the handlers are ignored,
- - otherwise a whole watcher thread could be crashed. -}
+ - self-contained Annex monad.
+ -
+ - Exceptions by the handlers are ignored,
+ - otherwise a whole watcher thread could be crashed.
+ -}
run :: Annex.AnnexState -> (FilePath -> Annex a) -> FilePath -> IO ()
run startstate a f = do
r <- E.try go :: IO (Either E.SomeException ())
@@ -89,10 +92,11 @@ onAddSymlink file = go =<< Backend.lookupFile file
[Params "--force --", File file]
onDel :: FilePath -> Annex ()
-onDel file = liftIO $ print $ "del " ++ file
+onDel file = inRepo $ Git.Command.run "rm"
+ [Params "--quiet --cached --", File file]
-{- A directory has been deleted, so tell git to remove anything that
- was inside it from its cache. -}
+{- A directory has been deleted, or moved, so tell git to remove anything
+ - that was inside it from its cache. -}
onDelDir :: FilePath -> Annex ()
onDelDir dir = inRepo $ Git.Command.run "rm"
- [Params "--quiet -r --cached --", File dir]
+ [Params "--quiet -r --cached --ignore-unmatch --", File dir]
diff --git a/Utility/Inotify.hs b/Utility/Inotify.hs
index 2dcc1ed64..ff3de81b1 100644
--- a/Utility/Inotify.hs
+++ b/Utility/Inotify.hs
@@ -15,6 +15,7 @@ import qualified System.Posix.Files as Files
import System.Posix.Terminal
import Control.Concurrent.MVar
import System.Posix.Signals
+import Control.Exception as E
type Hook = Maybe (FilePath -> IO ())
@@ -51,10 +52,13 @@ type Hook = Maybe (FilePath -> IO ())
watchDir :: INotify -> FilePath -> (FilePath -> Bool) -> Hook -> Hook -> Hook -> Hook -> IO ()
watchDir i dir ignored add addsymlink del deldir
| ignored dir = noop
- | otherwise = void $ do
- _ <- addWatch i watchevents dir go
- mapM walk =<< filter (not . dirCruft) <$>
- getDirectoryContents dir
+ | otherwise = do
+ mvar <- newEmptyMVar
+ void $ addWatch i watchevents dir $ \event ->
+ serialized mvar (void $ go event)
+ serialized mvar $
+ mapM_ walk =<< filter (not . dirCruft) <$>
+ getDirectoryContents dir
where
recurse d = watchDir i d ignored add addsymlink del deldir
@@ -117,14 +121,22 @@ watchDir i dir ignored add addsymlink del deldir
filetype t f = catchBoolIO $ t <$> getSymbolicLinkStatus (indir f)
+{- Uses an MVar to serialize an action, so that only one thread at a time
+ - runs it. -}
+serialized :: MVar () -> IO () -> IO ()
+serialized mvar a = void $ do
+ putMVar mvar () -- blocks if action already running
+ _ <- E.try a :: IO (Either E.SomeException ())
+ takeMVar mvar -- allow next action to run
+
{- Pauses the main thread, letting children run until program termination. -}
waitForTermination :: IO ()
waitForTermination = do
- mv <- newEmptyMVar
- check softwareTermination mv
+ mvar <- newEmptyMVar
+ check softwareTermination mvar
whenM (queryTerminal stdInput) $
- check keyboardSignal mv
- takeMVar mv
+ check keyboardSignal mvar
+ takeMVar mvar
where
- check sig mv = void $
- installHandler sig (CatchOnce $ putMVar mv ()) Nothing
+ check sig mvar = void $
+ installHandler sig (CatchOnce $ putMVar mvar ()) Nothing