diff --git a/docs/wasm-runtime-architecture.md b/docs/wasm-runtime-architecture.md index ea677fb..c3e4b7d 100644 --- a/docs/wasm-runtime-architecture.md +++ b/docs/wasm-runtime-architecture.md @@ -159,6 +159,13 @@ suspend the native SIGSEGV/SIGILL recovery handler around the wasm call so that Wasmtime's own trap signals are not escalated into a native fatal signal (see `serve_via_wasm` in `handle_complete`). +Render workers are long-lived. The generic FastCGI transport retains a legacy +eight-connection recycle default, but `listen_for_connections()` disables it +for the UCE pool: recycling a healthy worker discards its Wasmtime engine and +module cache, adding a visible cold-start request. Request state remains bounded +by the fresh workspace and the normal per-request database/resource cleanup; +faulted workers still exit and are replaced by the parent. + ### Task callbacks and workspace lifetime `task()` and `task_repeat()` are fork-backed. The `uce_host_task_spawn` hostcall @@ -333,7 +340,10 @@ header free-functions are `inline`. The wasm backend exposes only declarations in-runtime CLI suite (`site/tests/cli_runner.uce`) plus the site test pages and `scripts/test_dependency_invalidation.sh`. The latter changes a transitive `#load`, then replaces a warmed worker artifact while preserving its - whole-second mtime to prove both compiler and worker caches invalidate it. + whole-second mtime to prove both compiler and worker caches invalidate it. It + also sends 48 requests and asserts the observed worker PID set does not exceed + `WORKER_COUNT`, guarding against accidental reintroduction of request-count + recycling. `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. diff --git a/scripts/test_dependency_invalidation.sh b/scripts/test_dependency_invalidation.sh index 5e72b38..03a7e1e 100755 --- a/scripts/test_dependency_invalidation.sh +++ b/scripts/test_dependency_invalidation.sh @@ -29,8 +29,8 @@ printf '%s\n' \ '#endif' >"$source_dir/child.uce" printf '%s\n' \ '#load "child.uce"' \ - 'CLI(Request& context) { print(dependency_cache_marker()); }' \ - 'RENDER(Request& context) { print(dependency_cache_marker()); }' >"$source_dir/parent.uce" + 'CLI(Request& context) { print(dependency_cache_marker(), ":", request_perf()["worker_pid"].to_string()); }' \ + 'RENDER(Request& context) { print(dependency_cache_marker(), ":", request_perf()["worker_pid"].to_string()); }' >"$source_dir/parent.uce" assert_marker() { local path="$1" @@ -62,4 +62,17 @@ 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 +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="" +for _ in {1..48}; do + output=$(scripts/uce-cli "/$test_name/parent.uce") + worker_pids+="${output##*:}"$'\n' +done +unique_workers=$(printf '%s' "$worker_pids" | sed '/^$/d' | sort -u | wc -l) +if (( unique_workers > worker_count )); then + echo "worker pool recycled during 48 requests: $unique_workers PIDs for $worker_count workers" >&2 + exit 1 +fi + echo "dependency invalidation passed" diff --git a/src/linux_fastcgi.cpp b/src/linux_fastcgi.cpp index 3dd16b6..53242d0 100644 --- a/src/linux_fastcgi.cpp +++ b/src/linux_fastcgi.cpp @@ -1352,6 +1352,10 @@ void listen_for_connections() // Workers are uniform FastCGI/CLI renderers; the WS broker owns the HTTP/WS // port and every connection, so workers never accept raw HTTP themselves. server.close_http_listeners(); + // The transport's legacy eight-connection recycle predates persistent + // Wasmtime engines. Recycling makes every ninth request pay engine/module + // startup; request-scoped workspaces already isolate and release user state. + server.calls_until_termination = -1; server.on_request = &handle_request; server.on_data = &handle_data; server.on_complete = &handle_complete;