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

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
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
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):

View File

@ -18,6 +18,7 @@ CONFIG_SOURCE="$REPO_ROOT/etc/uce/settings.cfg"
CONFIG_DEST="/etc/uce/settings.cfg"
action="${1:-setup}"
precompile_timeout="${UCE_PRECOMPILE_TIMEOUT:-900s}"
render_runtime_file() {
local source="$1"
@ -60,9 +61,9 @@ case "$action" in
cd "$REPO_ROOT"
service_user=$(systemctl show "$UNIT_NAME" -p User --value)
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
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
)
systemctl restart "$UNIT_NAME"

View File

@ -3,34 +3,32 @@ set -euo pipefail
cd "$(dirname "$0")/.."
test_name="parallel-precompile-test-$$"
site_directory="site"
bin_directory="/tmp/uce/work"
if [[ -r /etc/uce/settings.cfg ]]; then
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"
test_root="/tmp/$test_name"
source_dir="$test_root/site"
bin_directory="$test_root/work"
absolute_source_dir=""
artifact_dir=""
generation_log=""
generation_pid=""
precompile_timeout="${UCE_TEST_PRECOMPILE_TIMEOUT:-120s}"
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() {
rm -rf "$source_dir"
if [[ -x bin/uce_fastcgi.linux.bin ]]; then
precompile UCE_PRECOMPILE_JOBS=1 >/dev/null 2>&1 || true
fi
if [[ -n "$artifact_dir" ]]; then
rm -rf "$artifact_dir"
if [[ -n "$generation_pid" ]] && kill -0 "$generation_pid" 2>/dev/null; then
kill "$generation_pid" 2>/dev/null || true
cleanup_deadline=$((SECONDS + 10))
while kill -0 "$generation_pid" 2>/dev/null && (( SECONDS < cleanup_deadline )); do
sleep 0.05
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
rm -rf "$test_root"
if [[ -n "$generation_log" ]]; then
rm -f "$generation_log"
fi
@ -124,18 +122,23 @@ set +e
wait "$generation_pid"
generation_rc=$?
set -e
generation_pid=""
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
cat "$generation_log" >&2
exit 1
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")
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 "$generation_retry_output" >&2
exit 1
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"
set +e
@ -158,13 +161,18 @@ if [[ $repeat_failure_rc -eq 0 ]] || ! grep -Eq 'with 2 jobs: .* 1 failed, worke
fi
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)
malformed_jobs_output=$(precompile UCE_PRECOMPILE_JOBS=invalid)
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
exit 1
fi
rm -f "$source_dir"/bounds-*.uce "$source_dir"/generation-*.uce
printf 'parallel precompile passed: serial %.3fs, parallel %.3fs\n' \
"$(awk -v ns="$serial_ns" 'BEGIN { print ns / 1000000000 }')" \

View File

@ -1644,6 +1644,12 @@ int precompile_unit_generation()
{
f64 started_at = time_precise();
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();
Request background_context;
background_context.server = &server_state;