Isolate CLI workers and module serialization
This commit is contained in:
Executable
+153
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if [[ "${1:-}" != "--inside" ]]; then
|
||||
exec timeout --signal=TERM --kill-after=5s 120s unshare --mount --fork --kill-child=TERM "$0" --inside
|
||||
fi
|
||||
|
||||
root="/tmp/uce-cli-worker-isolation-$$"
|
||||
site="$root/site"
|
||||
work="$root/work"
|
||||
settings="$root/settings.cfg"
|
||||
log="$root/service.log"
|
||||
cli_socket="$root/run/cli.sock"
|
||||
fastcgi_socket="$root/run/fastcgi.sock"
|
||||
server_pid=""
|
||||
|
||||
cleanup() {
|
||||
status=$?
|
||||
if [[ -n "$server_pid" ]] && kill -0 "$server_pid" 2>/dev/null; then
|
||||
kill -TERM "$server_pid" 2>/dev/null || true
|
||||
deadline=$((SECONDS + 10))
|
||||
while kill -0 "$server_pid" 2>/dev/null && (( SECONDS < deadline )); do sleep 0.05; done
|
||||
if kill -0 "$server_pid" 2>/dev/null; then kill -KILL "$server_pid" 2>/dev/null || true; fi
|
||||
wait "$server_pid" 2>/dev/null || true
|
||||
fi
|
||||
if (( status != 0 )) && [[ -r "$log" ]]; then cat "$log" >&2; fi
|
||||
rm -rf "$root"
|
||||
return "$status"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
mkdir -p "$site" "$work" "$root/run" "$root/session" "$root/upload"
|
||||
cp /etc/uce/settings.cfg "$settings"
|
||||
cat >>"$settings" <<CFG
|
||||
BIN_DIRECTORY=$work
|
||||
PRECOMPILE_FILES_IN=$site
|
||||
SITE_DIRECTORY=$site
|
||||
FCGI_SOCKET_PATH=$fastcgi_socket
|
||||
FCGI_PORT=
|
||||
CLI_SOCKET_PATH=$cli_socket
|
||||
CLI_WORKER_COUNT=2
|
||||
CLI_WORKER_MAX_REQUESTS=3
|
||||
WS_BROKER_SOCKET_PATH=$root/run/ws.sock
|
||||
HTTP_PORT=
|
||||
HTTP_DOCUMENT_ROOT=$site
|
||||
SESSION_PATH=$root/session
|
||||
TMP_UPLOAD_PATH=$root/upload
|
||||
WASM_CORE_PATH=$(pwd)/bin/wasm/core.wasm
|
||||
WORKER_COUNT=2
|
||||
PROACTIVE_COMPILE_ENABLED=0
|
||||
CFG
|
||||
mount --bind "$settings" /etc/uce/settings.cfg
|
||||
|
||||
cat >"$site/isolation.uce" <<'UCE'
|
||||
RENDER(Request& context)
|
||||
{
|
||||
print(request_perf()["worker_pid"].to_string());
|
||||
}
|
||||
|
||||
CLI(Request& context)
|
||||
{
|
||||
print(request_perf()["worker_pid"].to_string());
|
||||
}
|
||||
UCE
|
||||
|
||||
cat >"$site/nested.uce" <<UCE
|
||||
CLI(Request& context)
|
||||
{
|
||||
print(shell_exec("cd $(pwd) && timeout 10s scripts/uce-cli --socket $cli_socket /isolation.uce"));
|
||||
}
|
||||
UCE
|
||||
|
||||
bin/uce_fastcgi.linux.bin >"$log" 2>&1 &
|
||||
server_pid=$!
|
||||
deadline=$((SECONDS + 20))
|
||||
while [[ ! -S "$cli_socket" ]] && (( SECONDS < deadline )); do sleep 0.05; done
|
||||
[[ -S "$cli_socket" ]] || { echo "private UCE CLI socket was not ready" >&2; exit 1; }
|
||||
timeout --signal=TERM --kill-after=1s 30s scripts/uce-cli --socket "$cli_socket" /isolation.uce >/dev/null
|
||||
timeout --signal=TERM --kill-after=1s 30s scripts/uce-cli --socket "$cli_socket" /nested.uce | grep -Eq '^[0-9]+([.]0+)?$'
|
||||
|
||||
fastcgi_request() {
|
||||
SCRIPT_FILENAME="$site/isolation.uce" SCRIPT_NAME=/isolation.uce REQUEST_URI=/isolation.uce REQUEST_METHOD=GET DOCUMENT_ROOT="$site" \
|
||||
timeout --signal=TERM --kill-after=1s 10s cgi-fcgi -bind -connect "$fastcgi_socket" | tr -d '\r' | awk 'NF{last=$0} END{print last}'
|
||||
}
|
||||
fastcgi_request >/dev/null
|
||||
|
||||
cli_request() {
|
||||
local output
|
||||
local deadline=$((SECONDS + 20))
|
||||
while (( SECONDS < deadline )); do
|
||||
if output=$(timeout --signal=TERM --kill-after=1s 10s scripts/uce-cli --socket "$cli_socket" /isolation.uce 2>/dev/null); then
|
||||
printf '%s\n' "$output"
|
||||
return 0
|
||||
fi
|
||||
sleep 0.05
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
cli_pids=()
|
||||
http_pids=()
|
||||
|
||||
# Concurrent completions must count individually and retire both initial CLI
|
||||
# workers despite neither connection pool necessarily becoming idle first.
|
||||
concurrent_jobs=()
|
||||
for n in $(seq 1 12); do
|
||||
cli_request >"$root/concurrent-$n" & concurrent_jobs+=("$!")
|
||||
done
|
||||
for job in "${concurrent_jobs[@]}"; do wait "$job"; done
|
||||
mapfile -t concurrent_cli < <(cat "$root"/concurrent-* | sort -u)
|
||||
cli_pids+=("${concurrent_cli[@]}")
|
||||
|
||||
for _ in $(seq 1 12); do
|
||||
cli_pids+=("$(cli_request)")
|
||||
http_pids+=("$(fastcgi_request)")
|
||||
done
|
||||
mapfile -t unique_cli < <(printf '%s\n' "${cli_pids[@]}" | sort -u)
|
||||
mapfile -t unique_http < <(printf '%s\n' "${http_pids[@]}" | sort -u)
|
||||
[[ ${#unique_cli[@]} -ge 4 ]] || { echo "CLI workers did not recycle after three requests: ${unique_cli[*]}" >&2; exit 1; }
|
||||
[[ ${#unique_http[@]} -ge 1 ]] || { echo "HTTP requests reached no public worker" >&2; exit 1; }
|
||||
for cli_pid in "${unique_cli[@]}"; do
|
||||
if printf '%s\n' "${unique_http[@]}" | grep -Fxq "$cli_pid"; then
|
||||
echo "CLI worker also served public HTTP: $cli_pid" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
[[ $(grep -c 'wasm FastCGI worker ready' "$log") -eq 2 ]]
|
||||
[[ $(grep -c 'wasm CLI worker ready' "$log") -ge 4 ]]
|
||||
|
||||
echo "CLI worker isolation and recycling passed: CLI ${unique_cli[*]}, public ${unique_http[*]}"
|
||||
|
||||
# A CLI-only runtime must not allocate a listener-less public Wasmtime worker.
|
||||
kill -TERM "$server_pid"
|
||||
wait "$server_pid"
|
||||
server_pid=""
|
||||
rm -f "$cli_socket" "$fastcgi_socket"
|
||||
cat >>"$settings" <<CFG
|
||||
FCGI_SOCKET_PATH=
|
||||
CLI_WORKER_COUNT=1
|
||||
CLI_WORKER_MAX_REQUESTS=0
|
||||
CFG
|
||||
: >"$log"
|
||||
bin/uce_fastcgi.linux.bin >"$log" 2>&1 &
|
||||
server_pid=$!
|
||||
deadline=$((SECONDS + 20))
|
||||
while [[ ! -S "$cli_socket" ]] && (( SECONDS < deadline )); do sleep 0.05; done
|
||||
[[ -S "$cli_socket" ]] || { echo "CLI-only socket was not ready" >&2; exit 1; }
|
||||
cli_request >/dev/null
|
||||
[[ $(grep -c 'wasm CLI worker ready' "$log") -eq 1 ]]
|
||||
[[ $(grep -c 'wasm FastCGI worker ready' "$log") -eq 0 ]]
|
||||
|
||||
echo "CLI-only worker allocation passed"
|
||||
Reference in New Issue
Block a user