dura-0.1: durable/atomic file system writes (from rio package)

Safe HaskellNone
LanguageHaskell2010

Dura

Description

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:

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

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.