Isolate managed precompile race validation

This commit is contained in:
udo
2026-07-18 19:12:53 +00:00
parent 322ffd006f
commit d4ae51f54b
4 changed files with 48 additions and 23 deletions
+11 -1
View File
@@ -256,7 +256,17 @@ Managed restart builds and precompiles the complete candidate ABI generation
before it switches the service. Precompile uses two low-priority processes by before it switches the service. Precompile uses two low-priority processes by
default; set `PRECOMPILE_JOBS` in `/etc/uce/settings.cfg` to tune the bounded default; set `PRECOMPILE_JOBS` in `/etc/uce/settings.cfg` to tune the bounded
116 process count for the host. A failed worker, compile, serialization, or 116 process count for the host. A failed worker, compile, serialization, or
result report aborts the switch and leaves the current service running. result report aborts the switch and leaves the current service running. The
managed invocation is bounded to 900 seconds by default; set
`UCE_PRECOMPILE_TIMEOUT` on `manage-uce-service.sh restart` to select another
GNU `timeout` duration. Timeout exit 124 also aborts before the service switch.
A trusted direct `--precompile` process may set `UCE_PRECOMPILE_FILES_IN` and
`UCE_PRECOMPILE_BIN_DIRECTORY` to isolate that invocation's source scan and
artifact registry. Both overrides apply only to precompile mode; normal server
workers continue to use `/etc/uce/settings.cfg`. The parallel precompile
regression uses private roots so a running proactive compiler cannot consume
or publish its controlled race fixtures.
Equivalent manual systemd service for a source checkout (`<UCE_REPO>` = checkout root): Equivalent manual systemd service for a source checkout (`<UCE_REPO>` = checkout root):
+3 -2
View File
@@ -18,6 +18,7 @@ CONFIG_SOURCE="$REPO_ROOT/etc/uce/settings.cfg"
CONFIG_DEST="/etc/uce/settings.cfg" CONFIG_DEST="/etc/uce/settings.cfg"
action="${1:-setup}" action="${1:-setup}"
precompile_timeout="${UCE_PRECOMPILE_TIMEOUT:-900s}"
render_runtime_file() { render_runtime_file() {
local source="$1" local source="$1"
@@ -60,9 +61,9 @@ case "$action" in
cd "$REPO_ROOT" cd "$REPO_ROOT"
service_user=$(systemctl show "$UNIT_NAME" -p User --value) service_user=$(systemctl show "$UNIT_NAME" -p User --value)
if [[ -n "$service_user" && "$(id -u)" == "0" ]]; then if [[ -n "$service_user" && "$(id -u)" == "0" ]]; then
runuser -u "$service_user" -- nice -n 10 "$REPO_ROOT/bin/uce_fastcgi.linux.bin" --precompile runuser -u "$service_user" -- timeout --signal=TERM --kill-after=10s "$precompile_timeout" nice -n 10 "$REPO_ROOT/bin/uce_fastcgi.linux.bin" --precompile
else else
nice -n 10 "$REPO_ROOT/bin/uce_fastcgi.linux.bin" --precompile timeout --signal=TERM --kill-after=10s "$precompile_timeout" nice -n 10 "$REPO_ROOT/bin/uce_fastcgi.linux.bin" --precompile
fi fi
) )
systemctl restart "$UNIT_NAME" systemctl restart "$UNIT_NAME"
+28 -20
View File
@@ -3,34 +3,32 @@ set -euo pipefail
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
test_name="parallel-precompile-test-$$" test_name="parallel-precompile-test-$$"
site_directory="site" test_root="/tmp/$test_name"
bin_directory="/tmp/uce/work" source_dir="$test_root/site"
if [[ -r /etc/uce/settings.cfg ]]; then bin_directory="$test_root/work"
configured_precompile=$(awk -F= '/^[[:space:]]*PRECOMPILE_FILES_IN[[:space:]]*=/ {sub(/^[^=]*=/, ""); gsub(/^[[:space:]]+|[[:space:]]+$/, ""); print; exit}' /etc/uce/settings.cfg)
configured_site=$(awk -F= '/^[[:space:]]*SITE_DIRECTORY[[:space:]]*=/ {sub(/^[^=]*=/, ""); gsub(/^[[:space:]]+|[[:space:]]+$/, ""); print; exit}' /etc/uce/settings.cfg)
configured_bin=$(awk -F= '/^[[:space:]]*BIN_DIRECTORY[[:space:]]*=/ {sub(/^[^=]*=/, ""); gsub(/^[[:space:]]+|[[:space:]]+$/, ""); print; exit}' /etc/uce/settings.cfg)
site_directory="${configured_precompile:-${configured_site:-$site_directory}}"
bin_directory="${configured_bin:-$bin_directory}"
fi
source_dir="$site_directory/$test_name"
absolute_source_dir="" absolute_source_dir=""
artifact_dir="" artifact_dir=""
generation_log="" generation_log=""
generation_pid=""
precompile_timeout="${UCE_TEST_PRECOMPILE_TIMEOUT:-120s}" precompile_timeout="${UCE_TEST_PRECOMPILE_TIMEOUT:-120s}"
precompile() { precompile() {
timeout "$precompile_timeout" env "$@" bin/uce_fastcgi.linux.bin --precompile timeout "$precompile_timeout" env UCE_PRECOMPILE_FILES_IN="$source_dir" UCE_PRECOMPILE_BIN_DIRECTORY="$bin_directory" "$@" bin/uce_fastcgi.linux.bin --precompile
} }
cleanup() { cleanup() {
rm -rf "$source_dir" if [[ -n "$generation_pid" ]] && kill -0 "$generation_pid" 2>/dev/null; then
if [[ -x bin/uce_fastcgi.linux.bin ]]; then kill "$generation_pid" 2>/dev/null || true
precompile UCE_PRECOMPILE_JOBS=1 >/dev/null 2>&1 || true cleanup_deadline=$((SECONDS + 10))
fi while kill -0 "$generation_pid" 2>/dev/null && (( SECONDS < cleanup_deadline )); do
if [[ -n "$artifact_dir" ]]; then sleep 0.05
rm -rf "$artifact_dir" done
if kill -0 "$generation_pid" 2>/dev/null; then
kill -KILL "$generation_pid" 2>/dev/null || true
fi
wait "$generation_pid" 2>/dev/null || true
fi fi
rm -rf "$test_root"
if [[ -n "$generation_log" ]]; then if [[ -n "$generation_log" ]]; then
rm -f "$generation_log" rm -f "$generation_log"
fi fi
@@ -124,18 +122,23 @@ set +e
wait "$generation_pid" wait "$generation_pid"
generation_rc=$? generation_rc=$?
set -e set -e
generation_pid=""
if [[ $generation_rc -eq 0 ]] || ! grep -q 'source generation changed during precompile; candidate generation rejected' "$generation_log"; then if [[ $generation_rc -eq 0 ]] || ! grep -q 'source generation changed during precompile; candidate generation rejected' "$generation_log"; then
echo "parallel precompile accepted a source generation that changed during compilation" >&2 echo "parallel precompile accepted a source generation that changed during compilation" >&2
cat "$generation_log" >&2 cat "$generation_log" >&2
exit 1 exit 1
fi fi
if [[ -e "$artifact_dir/generation-new.uce.wasm" || -e "$artifact_dir/generation-new.uce.cwasm" ]]; then
echo "parallel precompile published an added unit outside the rejected candidate source set" >&2
exit 1
fi
generation_retry_output=$(precompile UCE_PRECOMPILE_JOBS=2 UCE_WASM_PCH_DIR="$artifact_dir/pch-shared") generation_retry_output=$(precompile UCE_PRECOMPILE_JOBS=2 UCE_WASM_PCH_DIR="$artifact_dir/pch-shared")
if ! grep -Eq 'with 2 jobs: .* 2 compiled, 0 failed, worker status ok' <<<"$generation_retry_output"; then if ! grep -Eq 'with 2 jobs: .* 2 compiled, 0 failed, worker status ok' <<<"$generation_retry_output"; then
echo "parallel precompile did not reconcile the changed and added units on retry" >&2 echo "parallel precompile did not reconcile the changed and added units on retry" >&2
echo "$generation_retry_output" >&2 echo "$generation_retry_output" >&2
exit 1 exit 1
fi fi
rm -f "$generation_log" "$source_dir"/generation-*.uce rm -f "$generation_log"
printf 'CLI(Request& context) { print(deliberate_parallel_precompile_failure); }\n' >"$source_dir/broken.uce" printf 'CLI(Request& context) { print(deliberate_parallel_precompile_failure); }\n' >"$source_dir/broken.uce"
set +e set +e
@@ -158,13 +161,18 @@ if [[ $repeat_failure_rc -eq 0 ]] || ! grep -Eq 'with 2 jobs: .* 1 failed, worke
fi fi
rm "$source_dir/broken.uce" rm "$source_dir/broken.uce"
rm -f "$source_dir"/unit-*.uce
for unit in $(seq -w 0 4); do
printf 'CLI(Request& context) { print("parallel-precompile-bounds-%s"); }\n' "$unit" >"$source_dir/bounds-$unit.uce"
done
negative_jobs_output=$(precompile UCE_PRECOMPILE_JOBS=-1) negative_jobs_output=$(precompile UCE_PRECOMPILE_JOBS=-1)
malformed_jobs_output=$(precompile UCE_PRECOMPILE_JOBS=invalid) malformed_jobs_output=$(precompile UCE_PRECOMPILE_JOBS=invalid)
oversized_jobs_output=$(precompile UCE_PRECOMPILE_JOBS=17) oversized_jobs_output=$(precompile UCE_PRECOMPILE_JOBS=17)
if ! grep -q 'with 1 job:' <<<"$negative_jobs_output" || ! grep -q 'with 2 jobs:' <<<"$malformed_jobs_output" || ! grep -q 'with 16 jobs:' <<<"$oversized_jobs_output"; then if ! grep -q 'with 1 job: 16 units,' <<<"$negative_jobs_output" || ! grep -q 'with 2 jobs: 16 units,' <<<"$malformed_jobs_output" || ! grep -q 'with 16 jobs: 16 units,' <<<"$oversized_jobs_output"; then
echo "precompile job bounds did not map negative/malformed/oversized input to 1/2/16" >&2 echo "precompile job bounds did not map negative/malformed/oversized input to 1/2/16" >&2
exit 1 exit 1
fi fi
rm -f "$source_dir"/bounds-*.uce "$source_dir"/generation-*.uce
printf 'parallel precompile passed: serial %.3fs, parallel %.3fs\n' \ printf 'parallel precompile passed: serial %.3fs, parallel %.3fs\n' \
"$(awk -v ns="$serial_ns" 'BEGIN { print ns / 1000000000 }')" \ "$(awk -v ns="$serial_ns" 'BEGIN { print ns / 1000000000 }')" \
+6
View File
@@ -1644,6 +1644,12 @@ int precompile_unit_generation()
{ {
f64 started_at = time_precise(); f64 started_at = time_precise();
server_state.config = make_server_settings(); server_state.config = make_server_settings();
const char* precompile_files_in = getenv("UCE_PRECOMPILE_FILES_IN");
if(precompile_files_in && precompile_files_in[0] != '\0')
server_state.config["PRECOMPILE_FILES_IN"] = precompile_files_in;
const char* precompile_bin_directory = getenv("UCE_PRECOMPILE_BIN_DIRECTORY");
if(precompile_bin_directory && precompile_bin_directory[0] != '\0')
server_state.config["BIN_DIRECTORY"] = precompile_bin_directory;
server_state.config["COMPILER_SYS_PATH"] = cwd_get(); server_state.config["COMPILER_SYS_PATH"] = cwd_get();
Request background_context; Request background_context;
background_context.server = &server_state; background_context.server = &server_state;