Keep warm wasm workers alive

This commit is contained in:
udo 2026-07-13 08:59:11 +00:00
parent f7e9c5a4ec
commit fe88d4143e
3 changed files with 30 additions and 3 deletions

View File

@ -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.

View File

@ -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"

View File

@ -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;