Stage ABI-scoped unit generations

This commit is contained in:
udo
2026-07-18 14:09:38 +00:00
parent 810c19b042
commit 1952c7d762
27 changed files with 302 additions and 53 deletions
+28 -17
View File
@@ -1,4 +1,5 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")"
cd ..
@@ -6,11 +7,14 @@ BUILDMODE=${2:-"debug"}
OPT_FLAG="O0"
GF="uce_fastcgi"
mkdir bin > /dev/null 2>&1
mkdir bin/tmp > /dev/null 2>&1
mkdir bin/assets > /dev/null 2>&1
mkdir bin/wasm > /dev/null 2>&1
mkdir work > /dev/null 2>&1
mkdir -p bin/tmp bin/assets bin/wasm work
exec 9>bin/.build.lock
flock 9
build_tmp_files=()
cleanup_build_tmp() {
((${#build_tmp_files[@]} == 0)) || rm -f "${build_tmp_files[@]}"
}
trap cleanup_build_tmp EXIT
COMPILER="clang++"
# -rdynamic is a link-time flag; the -c compiles below do not need it.
@@ -52,13 +56,16 @@ fi
# SQLite: vendored C, depends only on its own source (not our headers).
if needs_rebuild bin/sqlite3.o src/3rdparty/sqlite/sqlite3.c src/3rdparty/sqlite/sqlite3.h; then
echo "Compiling SQLite..."
tmp="bin/sqlite3.o.tmp.$$"
build_tmp_files+=("$tmp")
clang -g -O2 -fPIC \
-DSQLITE_THREADSAFE=1 \
-DSQLITE_OMIT_LOAD_EXTENSION=1 \
-DSQLITE_DQS=0 \
-DSQLITE_DEFAULT_FOREIGN_KEYS=1 \
-DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \
-c src/3rdparty/sqlite/sqlite3.c -o bin/sqlite3.o 2>&1 || exit 1
-c src/3rdparty/sqlite/sqlite3.c -o "$tmp" 2>&1
mv "$tmp" bin/sqlite3.o
else
echo "Reusing bin/sqlite3.o"
fi
@@ -67,7 +74,10 @@ fi
# declarations (not the lib .cpp — those are compiled into main.o).
if needs_rebuild bin/wasm.o src/wasm src/lib/*.h; then
echo "Compiling wasm backend..."
time -p $COMPILER -c src/wasm/wasm_module.cpp $SRCFLAGS $FLAGS $WASM_FLAGS -o bin/wasm.o 2>&1 || exit 1
tmp="bin/wasm.o.tmp.$$"
build_tmp_files+=("$tmp")
time -p $COMPILER -c src/wasm/wasm_module.cpp $SRCFLAGS $FLAGS $WASM_FLAGS -o "$tmp" 2>&1
mv "$tmp" bin/wasm.o
else
echo "Reusing bin/wasm.o"
fi
@@ -77,18 +87,19 @@ fi
# (its only view of the wasm object) — but not the wasm .cpp sources.
if needs_rebuild bin/main.o src/linux_fastcgi.cpp src/lib src/fastcgi src/wasm/backend.h; then
echo "Compiling main..."
time -p $COMPILER -c src/linux_fastcgi.cpp $SRCFLAGS $FLAGS -o bin/main.o 2>&1 || exit 1
tmp="bin/main.o.tmp.$$"
build_tmp_files+=("$tmp")
time -p $COMPILER -c src/linux_fastcgi.cpp $SRCFLAGS $FLAGS -o "$tmp" 2>&1
mv "$tmp" bin/main.o
else
echo "Reusing bin/main.o"
fi
echo "Linking..."
$COMPILER -rdynamic bin/main.o bin/wasm.o bin/sqlite3.o $FLAGS $LIBS -o bin/$GF.linux.bin 2>&1
if [ $? -eq 0 ]
then
ls -lh bin/ | grep $GF
exit 0
else
exit 1
fi
binary_tmp="bin/$GF.linux.bin.tmp.$$"
build_tmp_files+=("$binary_tmp")
$COMPILER -rdynamic bin/main.o bin/wasm.o bin/sqlite3.o $FLAGS $LIBS -o "$binary_tmp" 2>&1
test -s "$binary_tmp"
chmod 0755 "$binary_tmp"
mv "$binary_tmp" "bin/$GF.linux.bin"
ls -lh "bin/$GF.linux.bin"
+7 -1
View File
@@ -160,7 +160,13 @@ def defined_symbols(path: Path, llvm_nm: str) -> list[str]:
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("wasm", type=Path)
ap.add_argument("--abi-version", default="7")
default_abi = "7"
abi_header = Path(__file__).resolve().parents[1] / "src" / "wasm" / "abi.h"
for line in abi_header.read_text().splitlines():
if line.startswith("#define UCE_WASM_CORE_ABI_VERSION "):
default_abi = line.rsplit(" ", 1)[1]
break
ap.add_argument("--abi-version", default=default_abi)
ap.add_argument("--llvm-nm", default=None)
ap.add_argument("--verbose", action="store_true")
args = ap.parse_args()
+1 -1
View File
@@ -11,7 +11,7 @@ PP_FN="$4"
WASM_FN="$5"
SDK=${WASI_SDK:-/opt/wasi-sdk}
ABI_VERSION=${UCE_UNIT_ABI_VERSION:-7}
ABI_VERSION=${UCE_UNIT_ABI_VERSION:-$(awk '/^#define UCE_WASM_CORE_ABI_VERSION / {print $3; exit}' src/wasm/abi.h)}
ROOT=$(pwd)
OBJ_FN="$DEST_DIR/$PP_FN.wasm.o"
ABI_TMP="$DEST_DIR/$PP_FN.uce-abi.txt"
+1
View File
@@ -67,6 +67,7 @@ if [[ "$action" == "run" ]]; then
curl -sS --max-time "$curl_timeout" --fail-with-body --unix-socket "$socket_path" "${base_url}&group=${group}"
done
scripts/test_dependency_invalidation.sh
scripts/test_abi_generation_rollout.sh
scripts/test_cold_component_deadline.sh
scripts/test_nested_component_props.sh
scripts/test_component_once_prefetch.sh
+9
View File
@@ -56,6 +56,15 @@ case "$action" in
if [[ "$REPO_ROOT" != "/usr/lib/uce" && "$REPO_ROOT" != "/opt/uce" ]]; then
"$REPO_ROOT/scripts/build_linux.sh"
fi
(
cd "$REPO_ROOT"
service_user=$(systemctl show "$UNIT_NAME" -p User --value)
if [[ -n "$service_user" && "$(id -u)" == "0" ]]; then
runuser -u "$service_user" -- "$REPO_ROOT/bin/uce_fastcgi.linux.bin" --precompile
else
"$REPO_ROOT/bin/uce_fastcgi.linux.bin" --precompile
fi
)
systemctl restart "$UNIT_NAME"
;;
start|stop|status)
+1 -1
View File
@@ -10,7 +10,7 @@ if [[ -r /etc/uce/settings.cfg ]]; then
fi
for ((attempt = 0; attempt < 200; attempt++)); do
if [[ -S "$socket_path" ]]; then
if [[ -S "$socket_path" ]] && [[ "$(curl -sS --max-time 0.2 --unix-socket "$socket_path" http://localhost/ping 2>/dev/null || true)" == "uce-cli: ok" ]]; then
exit 0
fi
sleep 0.05
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
test_name="abi-generation-test-$$"
site_directory="${UCE_TEST_SITE_DIRECTORY:-site}"
if [[ -z "${UCE_TEST_SITE_DIRECTORY:-}" && -r /etc/uce/settings.cfg ]]; then
configured_site_directory=$(awk -F= '/^[[:space:]]*SITE_DIRECTORY[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' /etc/uce/settings.cfg)
[[ -z "${configured_site_directory:-}" ]] || site_directory="$configured_site_directory"
fi
source_dir="$site_directory/$test_name"
http_host="${UCE_TEST_HTTP_HOST:-uce.openfu.com}"
body_file="/tmp/uce-abi-generation-body-$$"
ready_file="/tmp/uce-abi-generation-ready-$$"
cleanup() {
rm -rf "$source_dir"
rm -f "$body_file" "$ready_file"
[[ -z "${artifact_dir:-}" ]] || rm -rf "$artifact_dir"
}
trap cleanup EXIT
mkdir -p "$source_dir"
printf '%s\n' \
'RENDER(Request& context) { print("entry-current"); }' \
'CLI(Request& context) {' \
' if(context.get["artifact"] == "entry") print(unit_info(context.params["SCRIPT_FILENAME"])["wasm_name"].to_string());' \
' else print("entry-current");' \
'}' >"$source_dir/entry.uce"
printf '%s\n' \
'COMPONENT(Request& context) { print("component-current"); }' >"$source_dir/child.uce"
printf '%s\n' \
'RENDER(Request& context) { DValue props; print(component("child", props, context)); }' \
'CLI(Request& context) {' \
' DValue props; component("child", props, context);' \
' print(unit_info("child.uce")["wasm_name"].to_string());' \
'}' >"$source_dir/component-parent.uce"
entry_url="http://127.0.0.1/$test_name/entry.uce"
component_url="http://127.0.0.1/$test_name/component-parent.uce"
[[ "$(curl -fsS -H "Host: $http_host" "$entry_url")" == "entry-current" ]]
entry_wasm=$(scripts/uce-cli --get "/$test_name/entry.uce" artifact=entry)
component_wasm=$(scripts/uce-cli "/$test_name/component-parent.uce")
artifact_dir=$(dirname "$entry_wasm")
generation_name=$(basename "$(scripts/unit_cache_directory /tmp/uce-generation-probe)")
if [[ "$entry_wasm" != *"/$generation_name/"* || "$component_wasm" != *"/$generation_name/"* ]]; then
echo "unit artifacts are not isolated by compiler/core ABI generation: $entry_wasm $component_wasm" >&2
exit 1
fi
publish_incompatible_artifact_while_locked() {
local source_file="$1"
local wasm_file="$2"
local marker="$3"
local destination
destination=$(dirname "$wasm_file")
(
exec 8>"$wasm_file.lock"
flock 8
UCE_UNIT_ABI_VERSION=6 scripts/compile_wasm_unit \
"$(dirname "$source_file")" "$destination" "$source_file" \
"$(basename "$source_file").cpp" "$(basename "$wasm_file")"
sed -i \
-e 's/^unit_abi_version=.*/unit_abi_version=0/' \
-e 's/^wasm_core_abi_version=.*/wasm_core_abi_version=0/' \
"${wasm_file%.wasm}.meta.txt"
rm -f "${wasm_file%.wasm}.cwasm"
printf '%s\n' "$marker" >"$ready_file"
sleep 2
) &
lock_pid=$!
deadline=$((SECONDS + 20))
while [[ ! -e "$ready_file" && $SECONDS -lt $deadline ]]; do sleep 0.05; done
if [[ ! -e "$ready_file" || "$(cat "$ready_file")" != "$marker" ]]; then
echo "incompatible artifact was not published under lock: $marker" >&2
exit 1
fi
}
publish_incompatible_artifact_while_locked "$source_dir/entry.uce" "$entry_wasm" entry
started_at=$(date +%s%N)
entry_status=$(curl -sS --max-time 30 -o "$body_file" -w '%{http_code}' -H "Host: $http_host" "$entry_url")
entry_elapsed_ms=$(( ($(date +%s%N) - started_at) / 1000000 ))
wait "$lock_pid"
if [[ "$entry_status" != "200" || "$(cat "$body_file")" != "entry-current" || $entry_elapsed_ms -lt 1500 ]]; then
echo "ABI-incompatible entry artifact was served instead of joining its rebuild: status=$entry_status elapsed=${entry_elapsed_ms}ms body=$(cat "$body_file")" >&2
exit 1
fi
rm -f "$ready_file"
publish_incompatible_artifact_while_locked "$source_dir/child.uce" "$component_wasm" component
started_at=$(date +%s%N)
component_status=$(curl -sS --max-time 30 -o "$body_file" -w '%{http_code}' -H "Host: $http_host" "$component_url")
component_elapsed_ms=$(( ($(date +%s%N) - started_at) / 1000000 ))
wait "$lock_pid"
if [[ "$component_status" != "200" || "$(cat "$body_file")" != "component-current" || $component_elapsed_ms -lt 1500 ]]; then
echo "ABI-incompatible component artifact was served instead of joining its rebuild: status=$component_status elapsed=${component_elapsed_ms}ms body=$(cat "$body_file")" >&2
exit 1
fi
echo "ABI generation rollout passed"
+1 -1
View File
@@ -26,7 +26,7 @@ cleanup() {
}
trap cleanup EXIT
mkdir -p "$source_dir"
cache_dir="$bin_directory$(realpath "$source_dir")"
cache_dir="$(scripts/unit_cache_directory "$bin_directory")$(realpath "$source_dir")"
printf '%s\n' \
'CLI(Request& context) { DValue props; print(component("child", props, context)); }' >"$source_dir/parent.uce"
+1 -1
View File
@@ -40,7 +40,7 @@ cleanup() {
}
trap cleanup EXIT
mkdir -p "$source_dir"
cache_dir="$bin_directory$(realpath "$source_dir")"
cache_dir="$(scripts/unit_cache_directory "$bin_directory")$(realpath "$source_dir")"
printf '%s\n' \
'#ifndef UCE_DEPENDENCY_CACHE_CHILD' \
+1 -1
View File
@@ -26,7 +26,7 @@ cleanup() {
}
trap cleanup EXIT
mkdir -p "$source_dir"
cache_dir="$bin_directory$(realpath "$source_dir")"
cache_dir="$(scripts/unit_cache_directory "$bin_directory")$(realpath "$source_dir")"
printf '%s\n' 'CLI(Request& context) { deliberate_log_timeliness_compile_failure }' >"$source_dir/probe.uce"
rm -rf "$cache_dir"
+1 -1
View File
@@ -38,7 +38,7 @@ cleanup() {
}
trap cleanup EXIT
mkdir -p "$source_dir"
cache_dir="$bin_directory$(realpath "$source_dir")"
cache_dir="$(scripts/unit_cache_directory "$bin_directory")$(realpath "$source_dir")"
mariadb -e "DROP USER IF EXISTS '$test_user'@'127.0.0.1'; CREATE USER '$test_user'@'127.0.0.1' IDENTIFIED BY '$test_password'"
test_user_created=true
+1 -1
View File
@@ -30,7 +30,7 @@ cleanup() {
}
trap cleanup EXIT
mkdir -p "$source_dir"
cache_dir="$bin_directory$(realpath "$source_dir")"
cache_dir="$(scripts/unit_cache_directory "$bin_directory")$(realpath "$source_dir")"
mariadb -e "DROP DATABASE IF EXISTS \`$test_database\`; DROP DATABASE IF EXISTS \`$test_database_other\`; DROP USER IF EXISTS '$test_user'@'127.0.0.1'; CREATE DATABASE \`$test_database\`; CREATE DATABASE \`$test_database_other\`; CREATE USER '$test_user'@'127.0.0.1' IDENTIFIED BY '$test_password'; GRANT ALL ON \`$test_database\`.* TO '$test_user'@'127.0.0.1'; GRANT ALL ON \`$test_database_other\`.* TO '$test_user'@'127.0.0.1'; CREATE TABLE \`$test_database\`.pool_identity (label VARCHAR(16) NOT NULL); INSERT INTO \`$test_database\`.pool_identity VALUES ('primary'); CREATE TABLE \`$test_database_other\`.pool_identity (label VARCHAR(16) NOT NULL); INSERT INTO \`$test_database_other\`.pool_identity VALUES ('other')"
printf '%s\n' \
+1 -1
View File
@@ -26,7 +26,7 @@ cleanup() {
}
trap cleanup EXIT
mkdir -p "$source_dir"
cache_dir="$bin_directory$(realpath "$source_dir")"
cache_dir="$(scripts/unit_cache_directory "$bin_directory")$(realpath "$source_dir")"
printf '%s\n' \
'CLI(Request& context) {' \
+1 -1
View File
@@ -27,7 +27,7 @@ cleanup() {
trap cleanup EXIT
mkdir -p "$source_dir"
absolute_source_dir=$(realpath "$source_dir")
artifact_dir="$bin_directory$absolute_source_dir"
artifact_dir="$(scripts/unit_cache_directory "$bin_directory")$absolute_source_dir"
printf '%s\n' \
'String visibility_used() { return("private-used"); }' \
+1 -1
View File
@@ -22,7 +22,7 @@ cleanup() {
trap cleanup EXIT
mkdir -p "$source_dir"
absolute_source_dir=$(realpath "$source_dir")
artifact_dir="$bin_directory$absolute_source_dir"
artifact_dir="$(scripts/unit_cache_directory "$bin_directory")$absolute_source_dir"
printf '%s\n' 'CLI(Request& context) { __builtin_trap(); }' >"$source_dir/entry.uce"
set +e
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
base="${1:-/tmp/uce/work}"
compiler_abi=$(awk '/^#define UCE_COMPILER_UNIT_ABI_VERSION / {print $3; exit}' src/wasm/abi.h)
core_abi=$(awk '/^#define UCE_WASM_CORE_ABI_VERSION / {print $3; exit}' src/wasm/abi.h)
printf '%s/units-c%s-w%s\n' "${base%/}" "$compiler_abi" "$core_abi"