-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
#12474: std.fs.Dir.makeOpenPath: optimize case, if path already exists #14833
#12474: std.fs.Dir.makeOpenPath: optimize case, if path already exists #14833
Conversation
lib/std/fs.zig
Outdated
fn openDirAccessMaskW(self: Dir, sub_path_w: [*:0]const u16, access_mask: u32, no_follow: bool) OpenError!Dir { | ||
const OpenDirWithAccessAndCreationWOptions = struct { | ||
no_follow: bool, | ||
createDisposition: u32 |
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.
createDisposition: u32 | |
create_disposition: u32 |
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.
@squeek502 Thank you for the feedback, I have updated the PR to match your suggestion.
6e6032e
to
d26f53f
Compare
@Vexu Linux jobs are failing due to a formatting issue, I've updated the PR to fix that. |
lib/std/fs.zig
Outdated
const sub_path_w = try w.sliceToPrefixedFileW(sub_path[0..end_index]); | ||
const result = self.openDirWithAccessAndCreationW(sub_path_w.span().ptr, access_mask, .{ | ||
.no_follow = no_follow, | ||
.create_disposition = if (end_index == sub_path.len) w.FILE_OPEN_IF else w.FILE_CREATE, | ||
}) catch |err| switch (err) { | ||
error.FileNotFound => { | ||
// march end_index backward until next path component | ||
while (true) { | ||
if (end_index == 0) return err; | ||
end_index -= 1; | ||
if (path.isSep(sub_path[end_index])) break; | ||
} | ||
continue; | ||
}, | ||
else => return err, | ||
}; | ||
|
||
if (end_index == sub_path.len) return result; | ||
// march end_index forward until next path component | ||
while (true) { | ||
end_index += 1; | ||
if (end_index == sub_path.len or path.isSep(sub_path[end_index])) break; | ||
} |
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.
It's not obvious to me what this logic is intending, or why it's necessary. Some explanatory comments would be nice, and a new test case that exercises this code added to std/fs/test.zig
would be even better (ideally a test case that would fail if this logic wasn't included).
lib/std/fs.zig
Outdated
return self.openDir(sub_path, open_dir_options) catch { | ||
try self.makePath(sub_path); | ||
return self.openDir(sub_path, open_dir_options); | ||
}; |
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.
I think switching on the caught error might make sense here, since for some errors it wouldn't make sense to makePath
and retry. I think actually it might make sense to only retry on error.FileNotFound
. Something like:
return self.openDir(sub_path, open_dir_options) catch { | |
try self.makePath(sub_path); | |
return self.openDir(sub_path, open_dir_options); | |
}; | |
return self.openDir(sub_path, open_dir_options) catch |err| switch (err) { | |
error.FileNotFound => { | |
try self.makePath(sub_path); | |
return self.openDir(sub_path, open_dir_options); | |
}, | |
else => |e| return e, | |
}; |
(and same goes for makeOpenPathIterable
)
6556eec
to
00acf66
Compare
lib/std/fs.zig
Outdated
/// already exists and is a directory. | ||
/// This function is not atomic, and if it returns an error, the file system may | ||
/// have been modified regardless. | ||
fn openDirAccessMaskW(self: Dir, sub_path: []const u8, access_mask: u32, no_follow: bool) OpenError!Dir { |
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.
In light of the doc comments, I think a better name for this function would be something like makeOpenPathAccessMaskW
.
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.
I think so
63a666c
to
8da822d
Compare
8da822d
to
835ee98
Compare
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.
The only issues I see here are doc comment issues, which I can take care of for you.
@squeek502 does this look OK to you? If so, I'll fix up the doc comments while merging.
lib/std/fs.zig
Outdated
/// This function performs `makePath`, followed by `openDir`. | ||
/// If supported by the OS, this operation is atomic. It is not atomic on | ||
/// all operating systems. | ||
/// On Windows, this function preforms `makeOpenPathAccessMaskW`. |
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.
This is not useful documentation since makeOpenPathAccessMaskW
is (appropriately) private. The behavior of makeOpenPath
must be fully documented here.
lib/std/fs.zig
Outdated
@@ -1490,22 +1490,91 @@ pub const Dir = struct { | |||
} | |||
} | |||
|
|||
/// Calls makeOpenDirAccessMaskW recursively to make an entire path |
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.
Please avoid recursion in this function. Recursion is problematic because it has unbounded stack usage that potentially depends on user input. It also makes the compiler unable to perform many kinds of optimizations.
Related: #1006
It does not look like you actually used recursion here, so perhaps the only change needed is that this doc comment is incorrect.
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.
Thank you for the feedback!
Yes, It isn't an actual recursion. The call stack size is O(1) and doesn't depend on the user input.
Could you please take care of the incorrect doc comment?
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.
@andrewrk note that this 'recursively' language matches the doc comment of makePath
above which shares the same logic.
lib/std/fs/test.zig
Outdated
test "makeOpenPath parent dirs do not exist" { | ||
if (builtin.os.tag == .wasi) return error.SkipZigTest; | ||
|
||
var dir = try fs.cwd().makeOpenPath("root_dir/parent_dir/some_dir", .{}); | ||
defer dir.close(); | ||
} |
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.
This test should use std.testing.tmpDir
to avoid polluting the cwd (and to allow it to work when targeting WASI). Might also be good to double check that the full path was created (this is a bit redundant since in theory makeOpenPath
returning a Dir
means that the directory was created, but the double checking would potentially catch a bug where e.g. only ./some_dir
is created and its fd returned when passed a path like ./root_dir/parent_dir/some_dir
).
Something like this (untested):
test "makeOpenPath parent dirs do not exist" {
var tmp_dir = tmpDir(.{});
defer tmp_dir.cleanup();
var dir = try tmp_dir.dir.makeOpenPath("root_dir/parent_dir/some_dir", .{});
dir.close();
// double check that the full directory structure was created
var dir_verification = try tmp_dir.dir.openDir("root_dir/parent_dir/some_dir", .{});
dir_verification.close();
}
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.
Thank you for the feedback!
I have updated the PR and reflected your suggestion.
…case See this discussion ziglang#14833 (comment) for more Info
@QusaiHroub would you mind rebasing so that there is not a merge commit in this PR? |
…case See this discussion ziglang#14833 (comment) for more Info
4702da4
to
d2ef663
Compare
Done |
Thanks! |
…case See this discussion ziglang#14833 (comment) for more Info
Since #16570 has been merged, this can also use I've made all the necessary changes in my fork here: master...squeek502:zig:optimize_std.fs.Dir.makeOpenPath_12474 as well as a few other fixes (fixed leaking intermediate dir handles, reworded the doc comments) There are a few ways to go here:
Let me know what you'd like to do. |
…case See this discussion ziglang#14833 (comment) for more Info
d2ef663
to
16000ae
Compare
Thank you for your work, I chose to go the first way, and I did. |
23fff77
to
364f68f
Compare
be6432d
to
364f68f
Compare
…case See this discussion ziglang#14833 (comment) for more Info
364f68f
to
0363c52
Compare
Uses a single NtCreateFile syscall on windows. Closes ziglang#12474. Thanks to @joedavis and @matu3ba.
0363c52
to
595f1f1
Compare
See 49053cb for details Also, fix leaking the intermediate directory handles.
These are not recursive functions, so 'recursively' could be misleading.
595f1f1
to
fb5f69a
Compare
Thanks for working on this @QusaiHroub! |
Uses a single NtCreateFile syscall on windows, falling back if the parent directory does not exist.
Closes #12474. Thanks to @joedavis and @matu3ba.