diff options
| author | Ian Johnson <ian@ianjohnson.dev> | 2025-05-26 19:23:42 +0000 |
|---|---|---|
| committer | Matthew Lugg <mlugg@mlugg.co.uk> | 2025-08-26 12:02:50 +0100 |
| commit | d4df65e35504bc4a2fa500e03fbbaf95910035c8 (patch) | |
| tree | 23528356b265434f0ec24e0220991880054db138 /lib/std/Build/Step/Compile.zig | |
| parent | d57b1e3552bae31c535a39d165608127579b9b08 (diff) | |
| download | zig-d4df65e35504bc4a2fa500e03fbbaf95910035c8.tar.gz zig-d4df65e35504bc4a2fa500e03fbbaf95910035c8.zip | |
std.Build.Step.Compile: fix race condition in args file creation
Fixes #23993
Previously, if multiple build processes tried to create the same args file, there was a race condition with the use of the non-atomic `writeFile` function which could cause a spawned compiler to read an empty or incomplete args file. This commit avoids the race condition by first writing to a temporary file with a random path and renaming it to the desired path.
Diffstat (limited to 'lib/std/Build/Step/Compile.zig')
| -rw-r--r-- | lib/std/Build/Step/Compile.zig | 21 |
1 files changed, 20 insertions, 1 deletions
diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index 79d3694c02..b64ce59778 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -1827,7 +1827,26 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { _ = try std.fmt.bufPrint(&args_hex_hash, "{x}", .{&args_hash}); const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash; - try b.cache_root.handle.writeFile(.{ .sub_path = args_file, .data = args }); + if (b.cache_root.handle.access(args_file, .{})) |_| { + // The args file is already present from a previous run. + } else |err| switch (err) { + error.FileNotFound => { + try b.cache_root.handle.makePath("tmp"); + const rand_int = std.crypto.random.int(u64); + const tmp_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); + try b.cache_root.handle.writeFile(.{ .sub_path = tmp_path, .data = args }); + defer b.cache_root.handle.deleteFile(tmp_path) catch { + // It's fine if the temporary file can't be cleaned up. + }; + b.cache_root.handle.rename(tmp_path, args_file) catch |rename_err| switch (rename_err) { + error.PathAlreadyExists => { + // The args file was created by another concurrent build process. + }, + else => |other_err| return other_err, + }; + }, + else => |other_err| return other_err, + } const resolved_args_file = try mem.concat(arena, u8, &.{ "@", |
