diff --git a/docs/wasm-runtime-architecture.md b/docs/wasm-runtime-architecture.md index 78c8436..86c6a79 100644 --- a/docs/wasm-runtime-architecture.md +++ b/docs/wasm-runtime-architecture.md @@ -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 diff --git a/scripts/test_dependency_invalidation.sh b/scripts/test_dependency_invalidation.sh index a81fe4c..504228b 100755 --- a/scripts/test_dependency_invalidation.sh +++ b/scripts/test_dependency_invalidation.sh @@ -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="" diff --git a/src/lib/compiler.cpp b/src/lib/compiler.cpp index 35e6a34..ae9a3d7 100644 --- a/src/lib/compiler.cpp +++ b/src/lib/compiler.cpp @@ -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(