Skip to content

[Basic] Add FileLock class #720

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 1 commit into from
Oct 20, 2016
Merged

[Basic] Add FileLock class #720

merged 1 commit into from
Oct 20, 2016

Conversation

aciidgh
Copy link
Contributor

@aciidgh aciidgh commented Oct 4, 2016

This class is a wrapper on POSIX's flock() and is part of fix towards:

  • rdar://problem/28448023 Prevent concurrent package workspace
    manipulations

@aciidgh aciidgh force-pushed the file-lock branch 2 times, most recently from 5714ca6 to 6cc0896 Compare October 4, 2016 06:51
Copy link
Contributor

@abertelrud abertelrud left a comment

Choose a reason for hiding this comment

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

Looks good in general. Some comments inline.

/// Create an instance of process lock with a name and the path where
/// the lock file can be created.
public init(name: String, cachePath: AbsolutePath) {
assert(localFileSystem.isDirectory(cachePath))
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure it's really appropriate to use assertions to check the state of file system entities. In most cases that should be a runtime error. One could argue that in this case, it's the caller's responsibility. In that case, the documentation should state that.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's also inconsistent with the lock() function (below), which throws an error.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, an assertion based on the filesystem state is going to be prone to race conditions too, which is another argument against.

try fileLockTest(Array(arguments.dropFirst()))
}
} catch {
print(error)
Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably get printed to stderr instead of stdout.

let cachePath: AbsolutePath

/// File descriptor to the lock file.
private var fd: Int32 = -1
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems that this could be an optional instead of having to use -1 (which is of course common in C because it's all that's available).

@aciidgh aciidgh force-pushed the file-lock branch 2 times, most recently from cf5fb6f to e4950bb Compare October 4, 2016 07:40
@aciidgh
Copy link
Contributor Author

aciidgh commented Oct 4, 2016

Thanks @abertelrud, updated PR

// Save the fd to close and remove lock later.
fd = fileno(fp)
// Aquire lock on the file.
guard flock(fd!, LOCK_EX) == 0 else {
Copy link
Contributor

@ddunbar ddunbar Oct 4, 2016

Choose a reason for hiding this comment

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

This should have an errno == EINTR loop.

We may want to consider implementing which has the semantics of a file lock, when exclusively used by the SwiftPM class which creates it, but which doesn't actually use flock. The typical implementation here is to atomic write the process info to a file and then point lock file (as a symbolic link) to it, if that fails, then check the contents of the file to see if that PID is alive, and then wait for that file to be removed (along with a timeout/retry loop in case that process dies).

A couple refs:

Note that the first ref talks about how the approach I describe isn't reliable on places where you can't effectively do an atomic FS write (an NFS scenario), so this may night be the right approach. It is however the approach LLVM is currently taking, for better or worse.

This advantage of this approach is that it is more amenable to reuse of our other APIs (for example, the work we would need to do to support timeouts, which is something we should certainly have support for).

Disclaimer: I don't have much experience with actually using file system locking. I know enough to be paranoid. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As discussed on slack, there is no good solution to file locking which works with NFS too so we're going to start with flock() and see how it goes. The underlying mechanism can be changed anytime and the current usecase is just a protective measure for now.

@@ -0,0 +1,56 @@
import Basic
import POSIX
import libc
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we have a usage or documentation comment or something in this file which explains all the operations it supports? I imagine it will grow over time, and it would be nice to be able to easily survey what it can do...

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd still like to see a "usage" style description somewhere...

Copy link
Contributor

Choose a reason for hiding this comment

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

... and I'd probably make the sole existing command a subcommand " lock-test 3" so it is obvious how to extend.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I forgot to comment here, I think the new parser will automatically solve this and generate usage text. We then probably only need minimal comments here, I already have a FIXME for moving to new parser once we have it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is a subcommand right now or do you mean something else?

@aciidgh
Copy link
Contributor Author

aciidgh commented Oct 5, 2016

Depends on swiftlang/swift#5131

Copy link
Contributor

@ddunbar ddunbar left a comment

Choose a reason for hiding this comment

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

LGTM, I agree we can start with flock() and adapt as needed.

public init(name: String, cachePath: AbsolutePath) {
self.name = name
self.cachePath = cachePath
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Should deinit assert the lock has been released?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The lock is per-process so OS will release the lock once the process ends, do think assert is needed?

Copy link
Contributor

Choose a reason for hiding this comment

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

What if client tries to open it again?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That works (even across threads) because it will be the same process

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok!

@@ -0,0 +1,56 @@
import Basic
import POSIX
import libc
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd still like to see a "usage" style description somewhere...

@@ -0,0 +1,56 @@
import Basic
import POSIX
import libc
Copy link
Contributor

Choose a reason for hiding this comment

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

... and I'd probably make the sole existing command a subcommand " lock-test 3" so it is obvious how to extend.

@ddunbar ddunbar assigned aciidgh and unassigned ddunbar Oct 6, 2016
@aciidgh aciidgh force-pushed the file-lock branch 2 times, most recently from 613b6a4 to b9f0292 Compare October 20, 2016 05:21
This class is a wrapper on POSIX's flock() and is part of fix towards:
- <rdar://problem/28448023> Prevent concurrent package workspace
manipulations
@aciidgh
Copy link
Contributor Author

aciidgh commented Oct 20, 2016

@swift-ci please test

@aciidgh
Copy link
Contributor Author

aciidgh commented Oct 20, 2016

@swift-ci please test OS X

@aciidgh aciidgh merged commit bdc4a3d into swiftlang:master Oct 20, 2016
@aciidgh aciidgh deleted the file-lock branch October 20, 2016 06:40
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