Skip to content

Implementation of durable file rename#60

Open
clkamp wants to merge 1 commit intofpco:masterfrom
clkamp:renameFileDurable
Open

Implementation of durable file rename#60
clkamp wants to merge 1 commit intofpco:masterfrom
clkamp:renameFileDurable

Conversation

@clkamp
Copy link

@clkamp clkamp commented May 2, 2020

For some use cases, one does not only need atomic and or durable writing of files, but also durable rename of files. A standard use case for this is delivery of mails to a Maildir (especially on a unix that is not linux, so that anonymous temp files are not available), where a new mail is first delivered to a temporary directory and then renamed to its final location.

This pull request implements a durable renaming of files.

@clkamp clkamp force-pushed the renameFileDurable branch from d9f367f to 09e05f1 Compare May 2, 2020 09:23
-- === Cross-Platform support
--
-- This function is a noop on Windows platforms.
--
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should include a @since, as well as a changelog entry and minor version bump. Also: @nh2 or @lehins, could one of you take a look?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback, added those.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could one of you take a look?

Will do!

@clkamp clkamp force-pushed the renameFileDurable branch from 09e05f1 to e78e8b1 Compare May 3, 2020 12:01
--
-- === Cross-Platform support
--
-- This function is a noop on Windows platforms.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this text needs to be adjusted: On ensureFileDurable it's right because there it does nothing, but for renameFileDurable it certainly still does the rename, it just doens't make it durable.

-- is because on an async filesystem the write of the old directory might
-- already written to disk and the change on the new directory is not. It the
-- function call returns, the change is durable. Nevertheless, this will not
-- happen on filesystems using journaling, that is, allmost all modern filesystems.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it not a bit confusing to say "This is also atomic"? Because it should be only atomic on the same filesystem -- rename() does not work at all across different file systems (on Linux), where we get from System.Directory.renameFile:

Prelude System.Directory> renameFile "testfile" "mnt/testfile"
*** Exception: testfile: renameFile:renamePath:rename: unsupported operation (Invalid cross-device link)

@@ -1,5 +1,5 @@
name: unliftio
version: 0.2.12.1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@snoyberg FYI Looks like the 0.2.13 went missing.

withDirectory (takeDirectory newName) $ \newDirFd -> do
renameFile oldName newName
fsyncDirectoryFd "renameFileDurable" newDirFd
fsyncDirectoryFd "renameFileDurable" oldDirFd
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible optimisation:

For the common case that oldName and newName are in the same directory, do only 1 withDirectory and do only 1 fsync().

withDirectory (takeDirectory newName) $ \newDirFd -> do
renameFile oldName newName
fsyncDirectoryFd "renameFileDurable" newDirFd
fsyncDirectoryFd "renameFileDurable" oldDirFd
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another possible improvement could be to use renameat(), with is directory-FD based renaming.

That would be even better than what we have here, because then the fsync() would operate on the same FD as the rename does, giving even better guarantees (see also https://stackoverflow.com/questions/37288453/calling-fsync2-after-close2/50158433#50158433 for a related topic on files).

That is not necessary for this PR though, having a renameFileDurable implemented this way is already much better than not having it.

Copy link
Contributor

@lehins lehins left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am sceptical that a simple renameFile will be enough for durability.

In order for the fsync to work in order to guarantee durability on the actual file change we need to open that file with openat in the directory and then do the atomic renameat followed by an fsync on the file AND the containing directory Fd. In the case of rename we don't need to open the file, but renameat might still be necessary. I didn't find anything online about it, so I might be wrong and a simple renameFile followed by fsync on directories might be sufficient.

Another side note is that renameFile only works on files and because of that performs extra checks that the file path is not a directory, which are themselves not atomic.

Just to be safe what I recommend is adding an actual renameFilePathAtomicDurable (renames are guaranteed to be atomic, since they aren't possible across devices, as @nh2 noted in his comment: https://github.com/fpco/unliftio/pull/60/files#r419109086) That function will work on both files and directories alike. I started on this approach here: lehins@d98f605
If @nh2, @snoyberg and @clkamp you guys agree that this is the right approach @clkamp feel free to pick up from that commit and merge it into you work here.

Here is a pretty useful SO question on the topic: https://stackoverflow.com/questions/3764822/how-to-durably-rename-a-file-in-posix

I also agree with all of the comments @nh2 made and want to add:

  • I'd appreciate if formatting and indentation was kept consistent with the rest of the code
  • At least a couple of tests added to the test suite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants