#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

Reply via email to