fix parallel build race condition with content-hashed shim files

The platform shim generation was writing to fixed filenames (platform_shim.bc
and platform_shim.o) in the shared cache directory, causing race conditions
when multiple builds ran in parallel.

Fix: Include a CRC32 hash of the serialized module content in the shim
filenames. Each unique module content now gets its own shim files, allowing
parallel builds to proceed without interfering with each other.

Also:
- Remove obsolete wasm-elem test platform from build.zig
- Update CLI tests to run in parallel instead of sequentially

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Luke Boswell 2025-12-16 17:16:13 +11:00
parent cd6a5900c6
commit e9ecd840c5
No known key found for this signature in database
GPG key ID: 54A7324B1B975757
2 changed files with 27 additions and 25 deletions

View file

@ -1443,22 +1443,6 @@ fn setupTestPlatforms(
clear_cache_step.dependOn(&copy_step.step);
}
// Build the wasm-elem test platform host for wasm32-freestanding (tests parameterized opaque types with requires clause)
{
const wasm_target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding, .abi = .none });
const copy_step = buildAndCopyTestPlatformHostLib(
b,
"wasm-elem",
wasm_target,
"wasm32",
optimize,
roc_modules,
strip,
omit_frame_pointer,
);
clear_cache_step.dependOn(&copy_step.step);
}
b.getInstallStep().dependOn(clear_cache_step);
test_platforms_step.dependOn(clear_cache_step);
}
@ -1680,8 +1664,7 @@ pub fn build(b: *std.Build) void {
b.installArtifact(test_runner_exe);
// CLI integration tests - run actual roc programs like CI does
// NOTE: These tests must run sequentially to avoid race conditions on the Roc cache
// Order: test_runner int -> test_runner str -> roc_subcommands_test
// These tests can run in parallel since each build uses content-hashed shim files
if (!no_bin) {
const install = b.addInstallArtifact(roc_exe, .{});
const install_runner = b.addInstallArtifact(test_runner_exe, .{});
@ -1694,16 +1677,19 @@ pub fn build(b: *std.Build) void {
run_int_tests.step.dependOn(&install.step);
run_int_tests.step.dependOn(&install_runner.step);
run_int_tests.step.dependOn(test_platforms_step);
test_cli_step.dependOn(&run_int_tests.step);
// Test str platform (native mode only for now)
// Run after int tests to avoid cache race conditions
const run_str_tests = b.addRunArtifact(test_runner_exe);
run_str_tests.addArg("zig-out/bin/roc");
run_str_tests.addArg("str");
run_str_tests.addArg("--mode=native");
run_str_tests.step.dependOn(&run_int_tests.step);
run_str_tests.step.dependOn(&install.step);
run_str_tests.step.dependOn(&install_runner.step);
run_str_tests.step.dependOn(test_platforms_step);
test_cli_step.dependOn(&run_str_tests.step);
// Roc subcommands integration test - runs after test_runner tests
// Roc subcommands integration test
const roc_subcommands_test = b.addTest(.{
.name = "roc_subcommands_test",
.root_module = b.createModule(.{
@ -1718,8 +1704,8 @@ pub fn build(b: *std.Build) void {
if (run_args.len != 0) {
run_roc_subcommands_test.addArgs(run_args);
}
run_roc_subcommands_test.step.dependOn(&run_str_tests.step); // Run after test_runner
run_roc_subcommands_test.step.dependOn(&install.step);
run_roc_subcommands_test.step.dependOn(test_platforms_step);
test_cli_step.dependOn(&run_roc_subcommands_test.step);
}

View file

@ -827,12 +827,28 @@ fn generatePlatformHostShim(allocs: *Allocators, cache_dir: []const u8, entrypoi
};
// Generate paths for temporary files
const bitcode_path = std.fs.path.join(allocs.arena, &.{ cache_dir, "platform_shim.bc" }) catch |err| {
// Use a hash of the serialized module content to avoid race conditions when multiple
// builds run in parallel. Each unique module content gets its own shim files.
const content_hash = if (serialized_module) |module_bytes|
std.hash.Crc32.hash(module_bytes)
else
0; // For IPC mode (roc run), use a fixed name since there's no embedded data
const bitcode_filename = std.fmt.allocPrint(allocs.arena, "platform_shim_{x}.bc", .{content_hash}) catch |err| {
std.log.err("Failed to create bitcode filename: {}", .{err});
return err;
};
const object_filename = std.fmt.allocPrint(allocs.arena, "platform_shim_{x}.o", .{content_hash}) catch |err| {
std.log.err("Failed to create object filename: {}", .{err});
return err;
};
const bitcode_path = std.fs.path.join(allocs.arena, &.{ cache_dir, bitcode_filename }) catch |err| {
std.log.err("Failed to create bitcode path: {}", .{err});
return err;
};
const object_path = std.fs.path.join(allocs.arena, &.{ cache_dir, "platform_shim.o" }) catch |err| {
const object_path = std.fs.path.join(allocs.arena, &.{ cache_dir, object_filename }) catch |err| {
std.log.err("Failed to create object path: {}", .{err});
return err;
};