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
requests and asserts the observed worker PID set does not exceed
`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
cold component that exceeds the development epoch window and proves the
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_headers="/tmp/uce-dependency-post-headers-$$"
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}"
cleanup() {
@ -22,7 +25,7 @@ cleanup() {
if [[ -n "$cache_dir" ]]; then
rm -rf "$cache_dir"
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
mkdir -p "$source_dir"
@ -179,6 +182,56 @@ touch -d "@$parent_mtime" "$parent_wasm"
rm -f "$cache_dir/parent.uce.cwasm"
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="${worker_count:-4}"
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(
"format=uce-unit-metadata-v1\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"
);
}
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 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));
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"));
String compiled_input_signature;
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))+" "+
shell_escape(su->src_path)+" "+
shell_escape(su->bin_path)+" "+
shell_escape(su->file_name)+" "+
shell_escape(su->pre_file_name)+" "+
shell_escape(su->wasm_file_name)
));
su->compiler_messages = trim(shell_exec(shell_escape(compiler_wasm_compile_script(context))+" "+
shell_escape(su->src_path)+" "+
shell_escape(su->bin_path)+" "+
shell_escape(su->file_name)+" "+
shell_escape(su->pre_file_name)+" "+
shell_escape(su->wasm_file_name)
));
if(su->compiler_messages.length() == 0 && !file_exists(su->wasm_name))
su->compiler_messages = "wasm compile script completed without creating " + 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;
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)
{
String raw_messages = su->compiler_messages;
file_put_contents(su->compile_output_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);
printf("%s \n", compiler_format_compile_failure(su, raw_messages).c_str());
}
else
{
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->wasm_check_file_name);
compiler_record_compile_result(