-
Notifications
You must be signed in to change notification settings - Fork 724
Description
cabal/Cabal-syntax/src/Distribution/Utils/Generic.hs
Lines 184 to 200 in c2d8353
| writeFileAtomic :: FilePath -> LBS.ByteString -> IO () | |
| writeFileAtomic targetPath content = do | |
| let targetFile = takeFileName targetPath | |
| tmpDir <- getTemporaryDirectory | |
| Exception.bracketOnError | |
| (openBinaryTempFileWithDefaultPermissions tmpDir $ targetFile <.> "tmp") | |
| (\(tmpPath, handle) -> hClose handle >> removeFile tmpPath) | |
| ( \(tmpPath, handle) -> do | |
| LBS.hPut handle content | |
| hClose handle | |
| Exception.catch | |
| (renameFile tmpPath targetPath) | |
| ( \(_ :: Exception.SomeException) -> do | |
| copyFile tmpPath targetPath | |
| removeFile tmpPath | |
| ) | |
| ) |
So if renameFile failed we immediately try copyFile followed by removeFile. My experience however is that this is not helpful, at least on Windows. The most common reason for renameFile to fail is because another process (typically, an antivirus) opened the source file for reading. If this is the case then copyFile is likely to be too short delay for removeFile to succeed, because the source file remains locked by antivirus.
To make it worse, copyFile from directory internally makes its own attempt at atomicity, creating a temporary file and renaming it, which triggers the antivirus yet again.
In short, if renameFile fails then there is no reason to expect copyFile / removeFile to succeed.
I'd suggest to scrap copyFile / removeFile catch: just keep retrying renameFile after threadDelay 10000 a few times more.
CC @GulinSS who was looking at writeFileAtomic recently.