Skip to content

Add atomic writes to FileSystem and implement it for LocalFileSystem #1617

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 1, 2018
Merged

Add atomic writes to FileSystem and implement it for LocalFileSystem #1617

merged 9 commits into from
Jul 1, 2018

Conversation

czechboy0
Copy link
Member

@czechboy0 czechboy0 commented Jun 22, 2018

This adds a new customization point to the FileSystem protocol. It also provides a default implementation that forwards nonatomic writes back to the existing implementation, and throws when atomic writes are implemented.

An atomic write implementation is added for LocalFileSystem.

Tests for both have been adapted.

Depends on #1625

/cc @aciidb0mb3r @vlm

}
let temp = try TemporaryFile(deleteOnClose: false)
try writeFileContents(temp.path, bytes: bytes)
try rename(temp.path, to: path)
Copy link
Contributor

Choose a reason for hiding this comment

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

If rename didn't work, need to attempt to delete the temporary file.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

return try writeFileContents(path, bytes: bytes)
}
let temp = try TemporaryFile(deleteOnClose: false)
try writeFileContents(temp.path, bytes: bytes)
Copy link
Contributor

Choose a reason for hiding this comment

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

If write didn't work, need to attempt to delete the temporary file.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

self.suffix = suffix
self.prefix = prefix
self.deleteOnClose = deleteOnClose
// Determine in which directory to create the temporary file.
self.dir = determineTempDirectory(dir)
Copy link
Contributor

Choose a reason for hiding this comment

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

Would you please measure the typical cost of this determineTempDirectory on a local filesystem, in microseconds? I have a bad suspicion.

Copy link
Member Author

Choose a reason for hiding this comment

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

The first time it actually needs to get the environment variables, it takes about 1ms. From then on, each call takes around 12 microseconds.


// Atomic writes
// In-memory file system doesn't have atomic writes implemented, so it throws.
XCTAssertThrows(FileSystemError.unsupported, { try Basic.InMemoryFileSystem(files: [:]).writeFileContents(filePath, bytes: ByteString(testData), atomically: true) })
Copy link
Contributor

Choose a reason for hiding this comment

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

With the in-memory file system it should trivial to implement atomic writes, even without going through the temporary file. I think if you write as non-atomic, it'd appear atomic.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done, it's now treating the base implementation of in-memory file system's writeFileContents as atomic.

@czechboy0
Copy link
Member Author

@vlm Updated the PR with your feedback, let me know if there's anything else.


func writeFileContents(_ path: AbsolutePath, bytes: ByteString, atomically: Bool) throws {
// Perform non-atomic writes using the fast path
guard atomically else {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I would prefer using if here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, changed to if !atomically {

@@ -351,6 +365,24 @@ private class LocalFileSystem: FileSystem {
break
}
}

func writeFileContents(_ path: AbsolutePath, bytes: ByteString, atomically: Bool) throws {
// Perform non-atomic writes using the fast path
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: end comments with a punctuation.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

guard atomically else {
return try writeFileContents(path, bytes: bytes)
}
let temp = try TemporaryFile(deleteOnClose: false)
Copy link
Contributor

Choose a reason for hiding this comment

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

We should create the temporary file in the directory where the actual file needs to be written as it could be expensive to move files from OS's temp directory to final location (which could be a network drive).

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay, I've made that change

@@ -74,6 +74,9 @@ public final class TemporaryFile {

/// FileHandle of the temporary file, can be used to read/write data.
public let fileHandle: FileHandle

/// Whether the file should be deleted on dealloc.
public let deleteOnClose: Bool
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you propose this as its own PR?

Copy link
Member Author

Choose a reason for hiding this comment

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

@czechboy0
Copy link
Member Author

@aciidb0mb3r Okay, updated.

@aciidgh aciidgh added the WIP Work in progress label Jun 28, 2018
@czechboy0
Copy link
Member Author

Okay, rebased from master so this PR now only has the atomic write changes.

@aciidgh
Copy link
Contributor

aciidgh commented Jun 29, 2018

@swift-ci please smoke test

@aciidgh aciidgh removed the WIP Work in progress label Jun 29, 2018
@aciidgh
Copy link
Contributor

aciidgh commented Jun 29, 2018

Thanks @czechboy0 and @vlm!

@aciidgh aciidgh merged commit 32b8d90 into swiftlang:master Jul 1, 2018
@czechboy0 czechboy0 deleted the atomic-fs-writes branch July 2, 2018 08:49
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.

3 participants