-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Add lock option to File.OpenFlags and File.CreateFlags #4711
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
Conversation
As an example of the differences between windows and linux: const std = @import("std");
pub fn main() !void {
const filename = "text.txt";
const threads = [_]*std.Thread{
try std.Thread.spawn(Context{ .thread_name = "one", .filename = filename }, lock_file),
try std.Thread.spawn(Context{ .thread_name = "two", .filename = filename }, lock_file),
};
for (threads[0..]) |thread| {
thread.wait();
}
}
const Context = struct {
thread_name: []const u8,
filename: []const u8,
};
pub fn lock_file(ctx: Context) void {
std.debug.warn("[{}] Open file\n", .{ctx.thread_name});
const file = std.fs.cwd().createFile(ctx.filename, .{ .lock = true }) catch unreachable;
std.debug.warn("[{}] Get lock\n", .{ctx.thread_name});
std.time.sleep(1 * std.time.ns_per_s);
std.debug.warn("[{}] Release lock\n", .{ctx.thread_name});
file.close();
} With WINE it produces the following:
On Linux it produces:
|
File locks on Linux may be mandatory as well [1] if the file system where the file resides is mounted with the [1] https://www.kernel.org/doc/Documentation/filesystems/mandatory-locking.txt |
@LemonBoy Yes? I'm not sure what you're trying to say. That's not something that can be controlled by the std library. The user has to make sure their filesystem is mounted with the correct flag, and that any files that need mandatory locking have the correct bits set. And then we don't have to change anything about the locking API; advisory locks are simply upgraded to mandatory locks. Are you saying that I should add that to the documentation? |
Yeah, just a small note to say that your locks may become exclusive if certain conditions are met. |
Feel free to omit the async I/O integration in this PR. That's an important piece to this but it can be done as a separate change, and there may be some other prerequisite tasks |
Unit tests would be glorious |
Okay, I added a unit test that checks if the two file openings overlapped; if they did, that means that the lock didn't block the other processes. There are a few more things that could be done:
On the topic of blocking vs returning an error: Windows only returns an error, so to make it blocking I simply check the file in a loop with a delay. Linux supports a blocking API natively. Should we make both nonblocking, or just leave as it is right now? With async we'll obviously want to use the nonblocking API, but I'm not sure in the blocking case. |
Also, replace `os.fcntlFlockBlocking` with `os.fcntlFlock`
569310c
to
43ccc2d
Compare
I added a test that checks that two read locks are able to run at the same time. I've also decided against checking for locks on before opening a file on Linux, because as I was implementing it, I got an error from |
Awesome, now the behavior is consistent across all platforms!
The access is serialized, that's what I meant. The PR is good as-is, we can always refine the tests at a later time if they become flaky. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work. I have some requested adjustments.
I'll try to pay close attention to this if you push any more commits, so we can try to get this merged in before the release on Monday (if you want to, of course. No pressure).
For some reason, this breaks file locking on windows. Not sure if this is a problem with wine.
The share_access bitfield was being ORed with what was supposed to be parts of the default value, meaning that the share_access would be more permissive than expected.
Also, make windows share delete access. Rationale: this is how it works on Unix systems, mostly because locks are (usually) advisory on Unix.
@andrewrk The requested changes have been implemented, and there is one more test for nonblocking locks. Users can now choose whether the lock will be exclusive or shared by passing Note that BSD systems that are using the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making the requested changes! I think this is ready to merge (pending CI tests passing).
That removes the other switch cases (`error.WouldBlock` here) from the error set, I think.
It hung on:
Might need to examine that test case for race conditions. |
That test is supposed to be testing the I'm trying to find a way to develop/test for MacOS . Do you know of any good services that provide a MacOS environment? The best thing I've found so far is Macincloud. Looking at the manual page for open on MacOS, it appears that, while it supports the |
I'm sorry I don't know of any good options. Personally, I have a macos laptop for testing. If you need me to run any specific tests I can do that for you and report back. |
The tests were put into a deadlock, and it seems that darwin doesn't support `O_SYNC`, though it supports `O_NONBLOCK`. It shouldn't block even with that, but I'm not sure why else it would fail.
6341a3c
to
772bb1a
Compare
Could you try running the following test? https://github.com/ziglang/zig/pull/4711/files#diff-76cb704531736c8a6ea118029a935865R1689-R1703 I've forced the darwin systems to use the |
The tests all pass on my macOS laptop, with this PR merged. However, I noticed that some of the fs lock tests take a few seconds to complete. Do you think we could reduce the sleep() times to just be a few milliseconds instead of seconds? |
Even better would be: no sleeping at all. Instead of sleeping, a thread would wait on a |
Reducing the time is reasonable, so I've gone ahead and done that. I don't think With a different type of test, one that doesn't try to make the locks overlap, I could get rid of the call to sleep. But I'm sure if it's worth the effort. |
Actually, another option is to remove threads entirely, and only run tests with |
I think that is reasonable test coverage. It would be nice to have a test that tests the blocking behavior, with no race conditions possible and no unconditional sleep(), but I don't consider that to be a merge blocker. I'll probably end up doing that as part of integration with async I/O. Also I think the tests that use sleep() (with a low number) are fine for now. If the test ends up being flaky, the path forward will be to disable it and file an issue to track it. |
This pull request makes it possible to gain exclusive access to a file by passing in a lock option when opening a file.
For example, this program:
Produces this output:
Todo
[ ] Integrate with async IO[ ] Make every file open on Linux check for locks?Some notes
fcntl
after the file has been openedNtCreateFile
takes a list of what other file handles are allowed to do, like a lock with an inverted API