#2808: createDirectoryIfMissing should be atomic
------------------------------------+---------------------------------------
Reporter: EricKow | Owner: igloo
Type: merge | Status: reopened
Priority: normal | Milestone: 6.10.2
Component: libraries/directory | Version: 6.10.1
Severity: normal | Resolution:
Keywords: | Difficulty: Unknown
Testcase: | Os: Unknown/Multiple
Architecture: Unknown/Multiple |
------------------------------------+---------------------------------------
Changes (by duncan):
* status: closed => reopened
* resolution: fixed =>
Comment:
There are a few question marks with the new code:
The code is now race-free but the error codes are not accurate.
`createDirectoryIfMissing True "foo"` will succeed if a file `foo` exists.
It is not entirely clear that it guarantees not to recurse infinitely.
The termination condition relies on `dropFileName .
dropTrailingPathSeparator` making the path shorter, on some base directory
existing or being created or on an exception other than
`isDoesNotExistError` being generated by `createDirectory`.
It could create the same directory again and again if some other thread
or process deleted the directories it had already created. There is the
danger of it getting into a fight with another process doing `rm -r` in a
dir where `createDirectoryIfMissing` is creating dirs.
A fix for the third issue would be to change the second
`createDirectoryIfMissing` call to use `False`.
Here is an alternative implementation for review. I think it addresses the
three points above.
{{{
import System.Directory (createDirectory, doesDirectoryExist)
import System.FilePath
import System.IO.Error hiding (try)
import Control.Exception
createDirectoryIfMissing :: Bool -> FilePath -> IO ()
createDirectoryIfMissing _ "" = return ()
createDirectoryIfMissing create_parents dir0
| create_parents = createDirs (parents dir0)
| otherwise = createDir dir0 throw
where
parents = reverse . scanl1 (++) . splitPath . normalise
createDirs [] = return ()
createDirs (dir:dirs) =
createDir dir $ \_ -> do
createDirs dirs
createDir dir throw
createDir :: FilePath -> (IOException -> IO ()) -> IO ()
createDir dir notExistHandler = do
r <- try $ createDirectory dir
case (r :: Either IOException ()) of
Right () -> return ()
Left e
| isDoesNotExistError e -> notExistHandler e
-- createDirectory (and indeed POSIX mkdir) does not distinguish
-- between a dir already existing and a file already existing.
So we
-- check for it here. Unfortunately there is a slight race
condition
-- here, but we think it is benign. It could report an exeption
in
-- the case that the dir did exist but another process deletes
it
-- before we can check that it did indeed exist.
| isAlreadyExistsError e -> do exists <- doesDirectoryExist dir
if exists then return ()
else throw e
| otherwise -> throw e
}}}
--
Ticket URL: <http://hackage.haskell.org/trac/ghc/ticket/2808#comment:7>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler_______________________________________________
Glasgow-haskell-bugs mailing list
[email protected]
http://www.haskell.org/mailman/listinfo/glasgow-haskell-bugs