Is my data on the disk? Safety properties of OS.File.writeAtomic
February 5, 2014 § 1 Comment
If you have been writing front-end or add-on code recently, chances are that you have been using library OS.File and, in particular, OS.File.writeAtomic to write files. (Note: If you have been writing files without using OS.File.writeAtomic, chances are that you are doing something wrong that will cause Firefox to jank – please don’t.) As the name implies, OS.File.writeAtomic will make efforts to write your data atomically, so as to ensure its survivability in case of crash, power loss, etc.
However, you should not trust this function blindly, because it has its limitations. Let us take a look at exactly what the guarantees provided by writeAtomic.
Algorithm: just write
Snippet OS.File.writeAtomic(path, data)
What it does
- reduce the size of the file at
path
to 0; - send
data
to the operating system kernel for writing; - close the file.
Worst case scenarios
- if the process crashes between 1. and 2. (a few microseconds), the full content of
path
may be lost; - if the operating system crashes or the computer loses power suddenly before the kernel flushes its buffers (which may happen at any point up to 30 seconds after 1.), the full content of
path
may be lost; - if the operating system crashes or the computer loses power suddenly while the operating system kernel is flushing (which may happen at any point after 1., typically up to 30 seconds), and if your data is larger than one sector (typically 32kb),
data
may be written incompletely, resulting in a corrupted file atpath
.
Performance very good.
Algorithm: write and rename
Snippet OS.File.writeAtomic(path, data, { tmpPath: path + ".tmp" })
What it does
- create a new file at
tmpPath
; - send
data
to the operating system kernel for writing totmpPath
; - close the file;
- rename
tmpPath
on top ofpath
.
Worst case scenarios
- if the process crashes at any moment, nothing is lost, but a file
tmpPath
may be left on the disk; - if the operating system crashes or the computer loses power suddenly while the operating system kernel is flushing metadata (which may happen at any point after 1., typically up to 30 seconds), the full content of
path
may be lost; - if the operating system crashes or the computer loses power suddenly while the operating system kernel is flushing (which may happen at any point after 1., typically up to 30 seconds), and if your data is larger than one sector (typically 32kb),
data
may be written incompletely, resulting in a corrupted file atpath
.
Performance almost as good as Just Write.
Side-note On the ext4fs file system, the kernel automatically adds a flush, which transparently transforms the safety properties of this operation into those of the algorithm detailed next.
Native equivalent In XPCOM/C++, the mostly-equivalent solution is the atomic-file-output-stream.
Algorithm: write, flush and rename
Use OS.File.writeAtomic(path, data, { tmpPath: path + ".tmp", flush: true })
What it does
- create a new file at
tmpPath
; - send
data
to the operating system kernel for writing totmpPath
; - close the file;
- flush the writing of
data
totmpPath
; - rename
tmpPath
on top ofpath
.
Worst case scenarios
- if the process crashes at any moment, nothing is lost, but a file
tmpPath
may be left on the disk; - if the operating system crashes, nothing is lost, but a file
tmpPath
may be left on the disk; - if the computer loses power suddenly while the hard drive is flushing its internal hardware buffers (which is very hard to predict), nothing is lost, but an incomplete file
tmpPath
may be left on the disk;.
Performance some operating systems (Windows) or file systems (ext3fs) cannot flush a single file and rather need to flush all the files on the device, which considerably slows down the full operating system. On some others (ext4fs) this operation is essentially free. On some versions of MacOS X, flushing actually doesn’t do anything.
Native equivalent In XPCOM/C++, the mostly-equivalent solution is the safe-file-output-stream.
Algorithm: write, backup, rename
(not landed yet)
Snippet OS.File.writeAtomic(path, data, { tmpPath: path + ".tmp", backupTo: path + ".backup"})
What it does
- create a new file at
tmpPath
; - send
data
to the operating system kernel for writing totmpPath
; - close the file;
- rename the file at
path
tobackupTo
; - rename the file at
tmpPath
on top ofpath
;
Worst case scenarios
- if the process crashes between 4. and 5, file
path
may be lost andbackupTo
should be used instead for recovery; - if the operating system crashes or the computer loses power suddenly while the operating system kernel is flushing metadata (which may happen at any point after 1., typically up to 30 seconds), the file at
path
may be empty andbackupTo
should be used instead for recovery; - if the operating system crashes or the computer loses power suddenly while the operating system kernel is flushing (which may happen at any point after 1., typically up to 30 seconds), and if your data is larger than one sector (typically 32kb),
data
may be written incompletely, resulting in a corrupted file atpath
andbackupTo
should be used instead for recovery;
Performance almost as good as Write and Rename.
[…] the write during its execution. While the APIs we use to write the data ensure that shutdown will never cause a file to be partially written, it may cause us to lose the final write, i.e. 15 seconds of browsing, working, etc. To make things […]