Safe Haskell | None |
---|---|
Language | Haskell2010 |
Rationale
This module offers functions to handle files that offer better durability and/or atomicity.
When to use the functions on this module?
Given the usage of this functions comes at a cost in performance, it is important to consider what are the use cases that are ideal for each of the functions.
Not Durable and not Atomic
For this use case, you want to use the regular functions:
withBinaryFile
writeFileBinary
The regular use case for this scenario happens when your program is dealing with outputs that are never going to be consumed again by your program. For example, imagine you have a program that generates sales reports for the last month, this is a report that can be generated quickly; you don't really care if the output file gets corrupted or lost at one particular execution of your program given that is cheap to execute the data export program a second time. In other words, your program doesn't rely on the data contained in this file in order to work.
Atomic but not Durable
Imagine a scenario where your program builds a temporary file that serves as an
intermediate step to a bigger task, like Object files (.o
) in a compilation
process. The program will use an existing .o
file if it is present, or it will
build one from scratch if it is not. The file is not really required, but if it
is present, it *must* be valid and consistent. In this situation, you care about
atomicity, but not durability.
There is no function exported by this module that provides only atomicity.
Durable but not Atomic
For this use case, you want to use the functions:
The regular use case for this scenario happens when your program deals with file modifications that must be guaranteed to be durable, but you don't care that changes are consistent. If you use this function, more than likely your program is ensuring consistency guarantees through other means, for example, SQLite uses the Write Ahead Log (WAL) algorithm to ensure changes are atomic at an application level.
Durable and Atomic
For this use case, you can use the functions:
The regular use case for this scenario happens when you want to ensure that after a program is executed, the modifications done to a file are guaranteed to be saved, and also that changes are rolled-back in case there is a failure (e.g. hard reboot, shutdown, etc).
Synopsis
- writeBinaryFileDurable :: FilePath -> ByteString -> IO ()
- writeBinaryFileDurableAtomic :: FilePath -> ByteString -> IO ()
- withBinaryFileDurable :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
- withBinaryFileDurableAtomic :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
- ensureFileDurable :: FilePath -> IO ()
Documentation
writeBinaryFileDurable :: FilePath -> ByteString -> IO () Source #
Similar to writeFileBinary
, but it also ensures that changes executed to
the file are guaranteed to be durable. It internally uses fsync and makes
sure it synchronizes the file on disk.
Cross-Platform support
This function behaves the same as writeFileBinary
on Windows platforms.
writeBinaryFileDurableAtomic :: FilePath -> ByteString -> IO () Source #
Similar to writeFileBinary
, but it also guarantes that changes executed
to the file are durable, also, in case of failure, the modified file is never
going to get corrupted. It internally uses fsync and makes sure it
synchronizes the file on disk.
Cross-Platform support
This function behaves the same as writeFileBinary
on Windows platforms.
withBinaryFileDurable :: FilePath -> IOMode -> (Handle -> IO r) -> IO r Source #
Opens a file with the following guarantees:
- It successfully closes the file in case of an asynchronous exception
- It reliably saves the file in the correct directory; including edge case situations like a different device being mounted to the current directory, or the current directory being renamed to some other name while the file is being used.
- It ensures durability by executing an fsync call before closing the file handle
Cross-Platform support
This function behaves the same as withBinaryFile
on Windows platforms.
withBinaryFileDurableAtomic :: FilePath -> IOMode -> (Handle -> IO r) -> IO r Source #
Opens a file with the following guarantees:
- It successfully closes the file in case of an asynchronous exception
- It reliably saves the file in the correct directory; including edge case situations like a different device being mounted to the current directory, or the current directory being renamed to some other name while the file is being used.
- It ensures durability by executing an fsync call before closing the file handle
- It keeps all changes in a temporary file, and after it is closed it atomically moves the temporal file to the original filepath, in case of catastrophic failure, the original file stays unaffected.
Performance Considerations
When using a writable but non-truncating IOMode
(i.e. ReadWriteMode
and
AppendMode
), this function performs a copy operation of the specified input
file to guarantee the original file is intact in case of a catastrophic
failure (no partial writes). This approach may be prohibitive in scenarios
where the input file is expected to be large in size.
Cross-Platform support
This function behaves the same as withBinaryFile
on Windows
platforms.
ensureFileDurable :: FilePath -> IO () Source #
After a file is closed, it opens it again and executes fsync internally on
both the file and the directory that contains it. Note this function is
intended to work around the non-durability of existing file APIs, as opposed
to being necessary for the API functions provided in Dura
module.
The effectiveness of calling this function is debatable, as it relies on internal implementation details at the Kernel level that might change. We argue that, despite this fact, calling this function may bring benefits in terms of durability.
Cross-Platform support
This function is a noop on Windows platforms.