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
+11 -1
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 Wasmtime's own trap signals are not escalated into a native fatal signal (see
`serve_via_wasm` in `handle_complete`). `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 callbacks and workspace lifetime
`task()` and `task_repeat()` are fork-backed. The `uce_host_task_spawn` hostcall `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 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 `scripts/test_dependency_invalidation.sh`. The latter changes a transitive
`#load`, then replaces a warmed worker artifact while preserving its `#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 `scripts/test_cold_component_deadline.sh` separately compiles a deliberately
cold component that exceeds the development epoch window and proves the cold component that exceeds the development epoch window and proves the
parent request still renders it. parent request still renders it.
+15 -2
View File
@@ -29,8 +29,8 @@ printf '%s\n' \
'#endif' >"$source_dir/child.uce" '#endif' >"$source_dir/child.uce"
printf '%s\n' \ printf '%s\n' \
'#load "child.uce"' \ '#load "child.uce"' \
'CLI(Request& context) { print(dependency_cache_marker()); }' \ 'CLI(Request& context) { print(dependency_cache_marker(), ":", request_perf()["worker_pid"].to_string()); }' \
'RENDER(Request& context) { print(dependency_cache_marker()); }' >"$source_dir/parent.uce" 'RENDER(Request& context) { print(dependency_cache_marker(), ":", request_perf()["worker_pid"].to_string()); }' >"$source_dir/parent.uce"
assert_marker() { assert_marker() {
local path="$1" local path="$1"
@@ -62,4 +62,17 @@ touch -d "@$parent_mtime" "$parent_wasm"
rm -f "$cache_dir/parent.uce.cwasm" rm -f "$cache_dir/parent.uce.cwasm"
for _ in {1..16}; do assert_marker parent dependency-marker-c; done 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" echo "dependency invalidation passed"
+4
View File
@@ -1352,6 +1352,10 @@ void listen_for_connections()
// Workers are uniform FastCGI/CLI renderers; the WS broker owns the HTTP/WS // Workers are uniform FastCGI/CLI renderers; the WS broker owns the HTTP/WS
// port and every connection, so workers never accept raw HTTP themselves. // port and every connection, so workers never accept raw HTTP themselves.
server.close_http_listeners(); 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_request = &handle_request;
server.on_data = &handle_data; server.on_data = &handle_data;
server.on_complete = &handle_complete; server.on_complete = &handle_complete;