Fix wasm compile source race

This commit is contained in:
udo 2026-07-13 22:47:48 +00:00
parent 36ea0cd2a5
commit 096138d6a1
3 changed files with 103 additions and 17 deletions

View File

@ -365,7 +365,10 @@ header free-functions are `inline`. The wasm backend exposes only declarations
its permissions and proves the next CLI request compiles it, then sends 48 its permissions and proves the next CLI request compiles it, then sends 48
requests and asserts the observed worker PID set does not exceed requests and asserts the observed worker PID set does not exceed
`WORKER_COUNT`, guarding against accidental reintroduction of request-count `WORKER_COUNT`, guarding against accidental reintroduction of request-count
recycling. recycling. It also races a source edit against an active unit compile after
the generated C++ snapshot has been written. The compiler must either retry
and serve the post-edit source or fail closed; it must never stamp current
source metadata onto wasm produced from an older snapshot.
`scripts/test_cold_component_deadline.sh` separately compiles a deliberately `scripts/test_cold_component_deadline.sh` separately compiles a deliberately
cold component that exceeds the development epoch window and proves the cold component that exceeds the development epoch window and proves the
parent request still renders it. The dependency-invalidation gate also holds parent request still renders it. The dependency-invalidation gate also holds

View File

@ -15,6 +15,9 @@ mutation_file="/tmp/uce-dependency-mutation-$$"
post_body="/tmp/uce-dependency-post-body-$$" post_body="/tmp/uce-dependency-post-body-$$"
post_headers="/tmp/uce-dependency-post-headers-$$" post_headers="/tmp/uce-dependency-post-headers-$$"
lock_ready_file="/tmp/uce-dependency-lock-ready-$$" lock_ready_file="/tmp/uce-dependency-lock-ready-$$"
race_output="/tmp/uce-source-race-output-$$"
race_error="/tmp/uce-source-race-error-$$"
race_status="/tmp/uce-source-race-status-$$"
http_host="${UCE_TEST_HTTP_HOST:-uce.openfu.com}" http_host="${UCE_TEST_HTTP_HOST:-uce.openfu.com}"
cleanup() { cleanup() {
@ -22,7 +25,7 @@ cleanup() {
if [[ -n "$cache_dir" ]]; then if [[ -n "$cache_dir" ]]; then
rm -rf "$cache_dir" rm -rf "$cache_dir"
fi fi
rm -f "$mutation_file" "$post_body" "$post_headers" "$lock_ready_file" rm -f "$mutation_file" "$post_body" "$post_headers" "$lock_ready_file" "$race_output" "$race_error" "$race_status"
} }
trap cleanup EXIT trap cleanup EXIT
mkdir -p "$source_dir" mkdir -p "$source_dir"
@ -179,6 +182,56 @@ touch -d "@$parent_mtime" "$parent_wasm"
rm -f "$cache_dir/parent.uce.cwasm" rm -f "$cache_dir/parent.uce.cwasm"
for _ in {1..16}; do assert_marker parent dependency-marker-c; done for _ in {1..16}; do assert_marker parent dependency-marker-c; done
race_unit="$source_dir/source-race.uce"
race_cpp="$cache_dir/source-race.uce.cpp"
{
printf '%s\n' 'String source_race_marker() { return("source-race-marker-a"); }'
for i in $(seq 1 3500); do
printf 'int source_race_padding_%s() { return(%s); }\n' "$i" "$i"
done
printf '%s\n' 'CLI(Request& context) { print(source_race_marker()); }'
} >"$race_unit"
rm -f "$cache_dir/source-race.uce.cpp" "$cache_dir/source-race.uce.wasm" "$cache_dir/source-race.uce.cwasm" "$cache_dir/source-race.uce.meta.txt"
(
set +e
scripts/uce-cli "/$test_name/source-race.uce" >"$race_output" 2>"$race_error"
echo "$?" >"$race_status"
) &
race_pid=$!
deadline=$((SECONDS + 10))
while [[ ! -e "$race_cpp" && $SECONDS -lt $deadline ]]; do
if ! kill -0 "$race_pid" 2>/dev/null; then
echo "source-race compile finished before publishing generated C++" >&2
cat "$race_output" "$race_error" >&2 || true
exit 1
fi
sleep 0.02
done
if [[ ! -e "$race_cpp" ]]; then
echo "source-race compile did not publish generated C++ before deadline" >&2
exit 1
fi
if ! grep -q 'source-race-marker-a' "$race_cpp"; then
echo "source-race generated C++ did not contain the initial marker" >&2
exit 1
fi
if ! kill -0 "$race_pid" 2>/dev/null; then
echo "source-race compile finished before source mutation hook" >&2
cat "$race_output" "$race_error" >&2 || true
exit 1
fi
sed -i 's/source-race-marker-a/source-race-marker-b/' "$race_unit"
wait "$race_pid"
if [[ "$(cat "$race_status" 2>/dev/null || echo 1)" != "0" ]]; then
echo "source-race CLI request failed" >&2
cat "$race_output" "$race_error" >&2 || true
exit 1
fi
if [[ "$(cat "$race_output")" != *"source-race-marker-b"* ]]; then
echo "source changed during compile but UCE served stale wasm: $(cat "$race_output")" >&2
exit 1
fi
worker_count=$(awk -F= '/^[[:space:]]*WORKER_COUNT[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' /etc/uce/settings.cfg 2>/dev/null || true) worker_count=$(awk -F= '/^[[:space:]]*WORKER_COUNT[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' /etc/uce/settings.cfg 2>/dev/null || true)
worker_count="${worker_count:-4}" worker_count="${worker_count:-4}"
worker_pids="" worker_pids=""

View File

@ -215,16 +215,31 @@ String compiler_unit_build_token()
); );
} }
String compiler_unit_metadata_text(Request* context, SharedUnit* su) String compiler_unit_metadata_text(Request* context, SharedUnit* su, String input_signature = "")
{ {
if(input_signature == "")
input_signature = compiler_unit_input_signature(context, su);
return( return(
"format=uce-unit-metadata-v1\n" "format=uce-unit-metadata-v1\n"
"unit_abi_version=" + std::to_string(UCE_UNIT_ABI_VERSION) + "\n" "unit_abi_version=" + std::to_string(UCE_UNIT_ABI_VERSION) + "\n"
"input_signature=" + compiler_unit_input_signature(context, su) + "\n" "input_signature=" + input_signature + "\n"
"build_token=" + compiler_unit_build_token() + "\n" "build_token=" + compiler_unit_build_token() + "\n"
); );
} }
String compiler_cached_wasm_path(String wasm_path)
{
if(wasm_path.size() >= 5 && wasm_path.rfind(".wasm", wasm_path.size() - 5) == wasm_path.size() - 5)
return(wasm_path.substr(0, wasm_path.size() - 5) + ".cwasm");
return(wasm_path + ".cwasm");
}
void compiler_unlink_unit_wasm_artifacts(SharedUnit* su)
{
file_unlink(su->wasm_name);
file_unlink(compiler_cached_wasm_path(su->wasm_name));
}
String compiler_wasm_compile_script(Request* context) String compiler_wasm_compile_script(Request* context)
{ {
String script = "scripts/compile_wasm_unit"; String script = "scripts/compile_wasm_unit";
@ -832,33 +847,48 @@ void compile_shared_unit(Request* context, SharedUnit* su)
} }
shell_exec("mkdir -p " + shell_escape(su->pre_path)); shell_exec("mkdir -p " + shell_escape(su->pre_path));
file_put_contents(su->pre_path + "/" + su->pre_file_name, preprocess_shared_unit(context, su)); String compiled_input_signature;
file_put_contents(su->api_file_name, join(su->api_declarations, "\n")); for(u64 attempt = 0; attempt < 2; attempt++)
{
su->api_declarations.clear();
compiled_input_signature = compiler_unit_input_signature(context, su);
file_put_contents(su->pre_path + "/" + su->pre_file_name, preprocess_shared_unit(context, su));
file_put_contents(su->api_file_name, join(su->api_declarations, "\n"));
su->compiler_messages = trim(shell_exec(shell_escape(compiler_wasm_compile_script(context))+" "+ su->compiler_messages = trim(shell_exec(shell_escape(compiler_wasm_compile_script(context))+" "+
shell_escape(su->src_path)+" "+ shell_escape(su->src_path)+" "+
shell_escape(su->bin_path)+" "+ shell_escape(su->bin_path)+" "+
shell_escape(su->file_name)+" "+ shell_escape(su->file_name)+" "+
shell_escape(su->pre_file_name)+" "+ shell_escape(su->pre_file_name)+" "+
shell_escape(su->wasm_file_name) shell_escape(su->wasm_file_name)
)); ));
if(su->compiler_messages.length() == 0 && !file_exists(su->wasm_name)) if(su->compiler_messages.length() == 0 && !file_exists(su->wasm_name))
su->compiler_messages = "wasm compile script completed without creating " + su->wasm_name; su->compiler_messages = "wasm compile script completed without creating " + su->wasm_name;
if(su->compiler_messages.length() > 0)
break;
String current_input_signature = compiler_unit_input_signature(context, su);
if(current_input_signature == compiled_input_signature)
break;
compiler_unlink_unit_wasm_artifacts(su);
if(attempt == 1)
su->compiler_messages = "source changed during wasm compile; retry required";
}
if(su->compiler_messages.length() > 0) if(su->compiler_messages.length() > 0)
{ {
String raw_messages = su->compiler_messages; String raw_messages = su->compiler_messages;
file_put_contents(su->compile_output_file_name, raw_messages + "\n"); file_put_contents(su->compile_output_file_name, raw_messages + "\n");
file_put_contents(su->wasm_check_file_name, raw_messages + "\n"); file_put_contents(su->wasm_check_file_name, raw_messages + "\n");
file_unlink(su->wasm_name); compiler_unlink_unit_wasm_artifacts(su);
compiler_record_compile_result(su, time_precise() - comp_start, false, "compile_error", raw_messages); compiler_record_compile_result(su, time_precise() - comp_start, false, "compile_error", raw_messages);
printf("%s \n", compiler_format_compile_failure(su, raw_messages).c_str()); printf("%s \n", compiler_format_compile_failure(su, raw_messages).c_str());
} }
else else
{ {
su->last_compiled = file_mtime(su->wasm_name); su->last_compiled = file_mtime(su->wasm_name);
file_put_contents(su->meta_file_name, compiler_unit_metadata_text(context, su)); file_unlink(compiler_cached_wasm_path(su->wasm_name));
file_put_contents(su->meta_file_name, compiler_unit_metadata_text(context, su, compiled_input_signature));
file_unlink(su->compile_output_file_name); file_unlink(su->compile_output_file_name);
file_unlink(su->wasm_check_file_name); file_unlink(su->wasm_check_file_name);
compiler_record_compile_result( compiler_record_compile_result(