Commit d7418fff authored by Petr Pudlak's avatar Petr Pudlak
Browse files

After closing a file, reopen it and call fsync(2) on it

The problem is that (at least) GHC 7.6.3 has a bug that leaks memory for
'handleToFd' calls. Hence as a workaround we open the file again using
just 'openFd'. The additional benefit is that it also works if the file
has been already close.
Signed-off-by: default avatarPetr Pudlak <>
Reviewed-by: default avatarKlaus Aehlig <>
parent 252607b5
......@@ -44,8 +44,9 @@ import System.Posix.Types
import Ganeti.BasicTypes
import Ganeti.Errors
import Ganeti.Logging (logAlert)
import Ganeti.Utils
import Ganeti.Utils.UniStd (hCloseAndFsync)
import Ganeti.Utils.UniStd (fsyncFile)
-- | Atomically write a file, by first writing the contents into a temporary
-- file and then renaming it to the old position.
......@@ -53,6 +54,15 @@ atomicWriteFile :: FilePath -> String -> IO ()
atomicWriteFile path contents = atomicUpdateFile path
(\_ fh -> hPutStr fh contents)
-- | Calls fsync(2) on a given file.
-- If the operation fails, issue an alert log message and continue.
-- Doesn't throw an exception.
fsyncFileChecked :: FilePath -> IO ()
fsyncFileChecked path =
runResultT (fsyncFile path) >>= genericResult logMsg return
logMsg e = logAlert $ "Can't fsync file '" ++ path ++ "': " ++ e
-- | Atomically update a file, by first creating a temporary file, running the
-- given action on it, and then renaming it to the old position.
-- Usually the action will write to the file and update its permissions.
......@@ -64,7 +74,7 @@ atomicUpdateFile path action = do
(tmppath, tmphandle) <- liftBase $ openBinaryTempFile (takeDirectory path)
(takeBaseName path)
r <- L.finally (action tmppath tmphandle)
(liftBase $ hCloseAndFsync tmphandle)
(liftBase (hClose tmphandle >> fsyncFileChecked tmppath))
-- if all went well, rename the file
liftBase $ renameFile tmppath path
return r
......@@ -28,23 +28,25 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
module Ganeti.Utils.UniStd
( hCloseAndFsync
( fsyncFile
) where
import Control.Exception (bracket)
import Foreign.C
import System.IO
import System.IO.Error
import System.Posix.IO
import System.Posix.Types
foreign import ccall "fsync" fsync :: CInt -> IO CInt
import Ganeti.BasicTypes
foreign import ccall "fsync" fsync :: CInt -> IO CInt
-- | Flush, close and fsync(2) a file handle.
hCloseAndFsync :: Handle -> IO ()
hCloseAndFsync handle = do
Fd fd <- handleToFd handle -- side effect of closing the handle and flushing
-- its write buffer, if necessary.
_ <- fsync fd
_ <- tryIOError $ closeFd (Fd fd)
return ()
-- Opens a file and calls fsync(2) on the file descriptor.
-- Because of a bug in GHC 7.6.3 (at least), calling 'hIsClosed' on a handle
-- to get the file descriptor leaks memory. Therefore we open a given file
-- just to sync it and close it again.
fsyncFile :: (Error e) => FilePath -> ResultT e IO ()
fsyncFile path = liftIO
$ bracket (openFd path ReadOnly Nothing defaultFileFlags) closeFd callfsync
callfsync (Fd fd) = throwErrnoPathIfMinus1_ "fsyncFile" path $ fsync fd
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment