Expose dynamic compile failures
This commit is contained in:
+5
-1
@@ -189,6 +189,8 @@ CLI_SOCKET_MODE=0600
|
||||
SITE_DIRECTORY=/var/www/html
|
||||
HTTP_DOCUMENT_ROOT=/var/www/html
|
||||
JIT_COMPILE_ON_REQUEST=1
|
||||
SHOW_DYNAMIC_COMPILE_ERRORS=1
|
||||
SERVE_LAST_KNOWN_GOOD=0
|
||||
PROACTIVE_COMPILE_ENABLED=1
|
||||
PROACTIVE_COMPILE_JOBS=2
|
||||
PROACTIVE_COMPILE_CHECK_INTERVAL=60
|
||||
@@ -226,7 +228,9 @@ Important settings:
|
||||
- `HTTP_PORT` is the built-in HTTP/WebSocket listener used for WebSocket upgrade traffic and direct local probes. Bind/firewall it for local access only; nginx/Apache should be the public entry point.
|
||||
- `WS_BROKER_OUTBOUND_TIMEOUT_SECONDS` controls how long a forwarded WS message can remain queued in the broker before being dropped (default `30`). Set to `0` to disable the timeout.
|
||||
- `WASM_COMPILE_SCRIPT` must point to `scripts/compile_wasm_unit` unless you provide an equivalent compiler. Relative paths are resolved from the runtime root/`COMPILER_SYS_PATH`. That script calls `scripts/check_unit_wasm.py` after linking each unit and uses the pinned WASI SDK on every deployment host.
|
||||
- `PROACTIVE_COMPILE_JOBS` selects 1–16 low-priority full-site scanner processes (default `2`). Each canonical unit path has one scanner owner. The separate higher-priority demand compiler remains reserved for stale units requested over HTTP, so total background compile concurrency can reach this value plus one.
|
||||
- `SHOW_DYNAMIC_COMPILE_ERRORS=1` makes a failed dynamic `component()`, `unit_render()`, or `unit_call()` show the bounded compiler diagnostic instead of only a generic missing-handler message. Set it to `0` on deployments where source paths and compiler output must not reach HTTP responses.
|
||||
- `SERVE_LAST_KNOWN_GOOD=1` lets HTTP GET/HEAD/OPTIONS requests keep using a compatible complete unit artifact while the proactive compiler builds changed source. It defaults to `0`; CLI and mutation requests always use current code or fail closed. The option requires an enabled proactive compiler with a positive check interval. Failed background builds preserve the prior Wasm, source map, and serialized module until a successful atomic publication replaces them. Missing source or an incompatible compiler/core ABI is never served as last-known-good.
|
||||
- `PROACTIVE_COMPILE_JOBS` selects 1–16 low-priority full-site scanner processes (default `2`). Each canonical unit path has one scanner owner. When last-known-good serving is enabled, the separate higher-priority demand compiler remains reserved for stale units requested over HTTP, so total background compile concurrency can reach this value plus one.
|
||||
- `WASM_CORE_PATH` must point at the built `core.wasm` file.
|
||||
- `WASM_EPOCH_DEADLINE_TICKS` and `WASM_EPOCH_PERIOD_MS` bound one
|
||||
uninterrupted guest CPU segment. `WASM_INVOCATION_TIMEOUT_MS` is the
|
||||
|
||||
@@ -292,17 +292,19 @@ All compiler processes use the same per-unit lock, so concurrent demand and scan
|
||||
discovery cannot publish duplicate artifacts. The priority worker is idle when
|
||||
there is no demand and never scans the site on its own. Unit
|
||||
compilation writes and validates a process-unique temporary wasm file, then
|
||||
publishes it with an atomic rename. When proactive compilation is enabled,
|
||||
read-only HTTP requests keep using the last complete artifact while requesting
|
||||
that stale unit at the head of the compiler queue. Non-read requests never run
|
||||
a stale entry artifact: they return `503 Service Unavailable` with
|
||||
`Retry-After: 1`, allowing the client to retry after the priority rebuild.
|
||||
CLI and explicit compile paths remain synchronous. A failed rebuild removes
|
||||
availability and surfaces the compiler error rather than serving the old unit
|
||||
indefinitely. This freshness contract includes components resolved lazily from
|
||||
an otherwise-current CLI entry unit: if the proactive compiler already owns a
|
||||
stale child's lock, the CLI request joins that compile and waits for the fresh
|
||||
artifact. Read-only HTTP may use the child's last complete artifact instead.
|
||||
publishes it with an atomic rename. By default, requests do not execute stale
|
||||
code: a missing or changed unit is rebuilt synchronously and a failed dynamic
|
||||
build surfaces its bounded compiler diagnostic. `SERVE_LAST_KNOWN_GOOD=1`
|
||||
opts ordinary read-only HTTP requests into using the last complete,
|
||||
ABI-compatible artifact while requesting that stale unit at the head of the
|
||||
compiler queue. The proactive compiler must be enabled for this mode. Failed
|
||||
background builds preserve that artifact and its source map and serialized
|
||||
module until a later successful atomic publication replaces them. Missing
|
||||
source, incompatible ABI generations, CLI, WebSocket dispatch, and mutation
|
||||
requests never use last-known-good code. A stale mutation returns `503 Service
|
||||
Unavailable` with `Retry-After: 1`; CLI and explicit compile paths remain
|
||||
synchronous. If a proactive compiler already owns a stale child's lock, a CLI
|
||||
request joins that compile and waits for the current artifact.
|
||||
|
||||
Before preprocessing, the compiler verifies that the worker can actually read
|
||||
the source. An unreadable path is reported as a source-read failure with a
|
||||
|
||||
@@ -37,6 +37,16 @@ SITE_DIRECTORY=site
|
||||
# ENABLE JIT COMPILATION WHEN A PAGE REQUEST HITS A STALE OR MISSING UNIT
|
||||
JIT_COMPILE_ON_REQUEST=1
|
||||
|
||||
# SHOW THE BOUNDED COMPILER DIAGNOSTIC WHEN DYNAMIC DISPATCH TARGETS A UNIT
|
||||
# THAT FAILED TO COMPILE. WHEN DISABLED, THE CALL REPORTS THE GENERIC MISSING
|
||||
# COMPONENT OR UNIT ERROR.
|
||||
SHOW_DYNAMIC_COMPILE_ERRORS=1
|
||||
|
||||
# KEEP SERVING A COMPATIBLE LAST-KNOWN-GOOD ARTIFACT TO HTTP READ REQUESTS
|
||||
# WHILE THE PROACTIVE COMPILER BUILDS A CHANGED UNIT. MUTATIONS AND CLI NEVER
|
||||
# SERVE STALE CODE. REQUIRES THE PROACTIVE COMPILER AND DEFAULTS FAIL-CLOSED.
|
||||
SERVE_LAST_KNOWN_GOOD=0
|
||||
|
||||
# WASM SIDE-MODULE COMPILER USED FOR .uce UNITS
|
||||
WASM_COMPILE_SCRIPT=scripts/compile_wasm_unit
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ if [[ "$action" == "run" ]]; then
|
||||
scripts/test_unit_export_surface.sh
|
||||
timeout --signal=TERM --kill-after=5s 120s scripts/test_wasm_metadata_buffer.sh
|
||||
timeout --signal=TERM --kill-after=5s 150s scripts/test_wasm_metadata_deadline.sh
|
||||
timeout --signal=TERM --kill-after=5s 240s scripts/test_dynamic_compile_failures.sh
|
||||
scripts/test_wasm_source_locations.sh
|
||||
scripts/test_server_arguments.sh
|
||||
scripts/test_socket_activation.sh
|
||||
|
||||
Executable
+167
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if [[ "${1:-}" != "--inside" ]]; then
|
||||
exec timeout --signal=TERM --kill-after=5s 240s unshare --mount --net --fork --kill-child=TERM "$0" --inside
|
||||
fi
|
||||
ip link set lo up
|
||||
root="/tmp/uce-dynamic-compile-failures-$$"
|
||||
site="$root/site"
|
||||
work="$root/work"
|
||||
settings="$root/settings.cfg"
|
||||
log="$root/service.log"
|
||||
port=18080
|
||||
server_pid=""
|
||||
cleanup() {
|
||||
status=$?
|
||||
if [[ -n "$server_pid" ]] && kill -0 "$server_pid" 2>/dev/null; then
|
||||
kill -TERM "$server_pid" 2>/dev/null || true
|
||||
wait "$server_pid" 2>/dev/null || true
|
||||
fi
|
||||
if (( status != 0 )) && [[ -r "$log" ]]; then cat "$log" >&2; fi
|
||||
while umount /etc/uce/settings.cfg 2>/dev/null; do :; done
|
||||
rm -rf "$root"
|
||||
return "$status"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
mkdir -p "$site" "$work" "$root/run" "$root/session" "$root/upload"
|
||||
cat >"$site/driver.uce" <<'UCE'
|
||||
RENDER(Request& context) {
|
||||
print("driver:");
|
||||
if(context.get["mode"] == "render") unit_render("broken-render", context);
|
||||
else if(context.get["mode"] == "call") unit_call("broken-call", "COMPONENT");
|
||||
else print(component("child", context));
|
||||
}
|
||||
UCE
|
||||
printf '%s\n' 'COMPONENT(Request& context) { print("good-v1"); }' >"$site/child.uce"
|
||||
printf '%s\n' 'RENDER(Request& context) { print("render-ok"); }' >"$site/broken-render.uce"
|
||||
printf '%s\n' 'COMPONENT(Request& context) { print("call-ok"); }' >"$site/broken-call.uce"
|
||||
|
||||
write_settings() {
|
||||
local show_errors="$1" serve_good="$2" proactive="$3"
|
||||
sed -E '/^[[:space:]]*(BIN_DIRECTORY|PRECOMPILE_FILES_IN|SITE_DIRECTORY|FCGI_SOCKET_PATH|FCGI_PORT|CLI_SOCKET_PATH|WS_BROKER_SOCKET_PATH|HTTP_PORT|HTTP_DOCUMENT_ROOT|SESSION_PATH|TMP_UPLOAD_PATH|WASM_CORE_PATH|PROACTIVE_COMPILE_ENABLED|PROACTIVE_COMPILE_CHECK_INTERVAL|PROACTIVE_COMPILE_JOBS|WORKER_COUNT|SHOW_DYNAMIC_COMPILE_ERRORS|SERVE_LAST_KNOWN_GOOD)[[:space:]]*=/d' \
|
||||
/etc/uce/settings.cfg >"$settings"
|
||||
cat >>"$settings" <<CFG
|
||||
BIN_DIRECTORY=$work
|
||||
PRECOMPILE_FILES_IN=$site
|
||||
SITE_DIRECTORY=$site
|
||||
FCGI_SOCKET_PATH=$root/run/fastcgi.sock
|
||||
FCGI_PORT=
|
||||
CLI_SOCKET_PATH=$root/run/cli.sock
|
||||
WS_BROKER_SOCKET_PATH=$root/run/ws.sock
|
||||
HTTP_PORT=$port
|
||||
HTTP_DOCUMENT_ROOT=$site
|
||||
SESSION_PATH=$root/session
|
||||
TMP_UPLOAD_PATH=$root/upload
|
||||
WASM_CORE_PATH=$(pwd)/bin/wasm/core.wasm
|
||||
PROACTIVE_COMPILE_ENABLED=$proactive
|
||||
PROACTIVE_COMPILE_CHECK_INTERVAL=1
|
||||
PROACTIVE_COMPILE_JOBS=1
|
||||
WORKER_COUNT=1
|
||||
SHOW_DYNAMIC_COMPILE_ERRORS=$show_errors
|
||||
SERVE_LAST_KNOWN_GOOD=$serve_good
|
||||
CFG
|
||||
mount --bind "$settings" /etc/uce/settings.cfg
|
||||
}
|
||||
start_server() {
|
||||
: >"$log"
|
||||
bin/uce_fastcgi.linux.bin >"$log" 2>&1 &
|
||||
server_pid=$!
|
||||
for _ in $(seq 1 500); do
|
||||
curl -sS --max-time 1 "http://127.0.0.1:$port/driver.uce" >/dev/null 2>&1 && return
|
||||
kill -0 "$server_pid" 2>/dev/null || break
|
||||
sleep 0.02
|
||||
done
|
||||
cat "$log" >&2
|
||||
return 1
|
||||
}
|
||||
stop_server() {
|
||||
kill -TERM "$server_pid" 2>/dev/null || true
|
||||
wait "$server_pid" 2>/dev/null || true
|
||||
server_pid=""
|
||||
}
|
||||
request() {
|
||||
local method="${1:-GET}" query="${2:-}"
|
||||
if [[ "$method" == POST ]]; then
|
||||
curl -sS --max-time 30 -w $'\n%{http_code}' -X POST --data '' "http://127.0.0.1:$port/driver.uce?$query"
|
||||
else
|
||||
curl -sS --max-time 30 -w $'\n%{http_code}' "http://127.0.0.1:$port/driver.uce?$query"
|
||||
fi
|
||||
}
|
||||
body_of() { printf '%s' "${1%$'\n'*}"; }
|
||||
status_of() { printf '%s' "${1##*$'\n'}"; }
|
||||
|
||||
write_settings 1 0 0
|
||||
start_server
|
||||
printf '%s\n' 'COMPONENT(Request& context) { UCE_DYNAMIC_COMPILE_MARKER }' >"$site/child.uce"
|
||||
response=$(request GET)
|
||||
body=$(body_of "$response")
|
||||
[[ "$(status_of "$response")" == 500 ]]
|
||||
[[ "$body" == *UCE_DYNAMIC_COMPILE_MARKER* ]]
|
||||
[[ "$body" != *"component not found"* ]]
|
||||
printf '%s\n' 'RENDER(Request& context) { UCE_RENDER_COMPILE_MARKER }' >"$site/broken-render.uce"
|
||||
response=$(request GET mode=render)
|
||||
[[ "$(status_of "$response")" == 500 && "$(body_of "$response")" == *UCE_RENDER_COMPILE_MARKER* ]]
|
||||
printf '%s\n' 'COMPONENT(Request& context) { UCE_CALL_COMPILE_MARKER }' >"$site/broken-call.uce"
|
||||
response=$(request GET mode=call)
|
||||
[[ "$(body_of "$response")" == *UCE_CALL_COMPILE_MARKER* ]]
|
||||
[[ "$(body_of "$response")" != *"function 'COMPONENT' not found"* ]]
|
||||
printf '%s\n' 'COMPONENT(Request& context) { print("recovered"); }' >"$site/child.uce"
|
||||
response=$(request GET)
|
||||
[[ "$(status_of "$response")" == 200 && "$(body_of "$response")" == "driver:recovered" ]]
|
||||
stop_server
|
||||
|
||||
write_settings 0 0 0
|
||||
start_server
|
||||
printf '%s\n' 'COMPONENT(Request& context) { UCE_HIDDEN_COMPILE_MARKER }' >"$site/child.uce"
|
||||
response=$(request GET)
|
||||
body=$(body_of "$response")
|
||||
[[ "$(status_of "$response")" == 500 ]]
|
||||
[[ "$body" == *"component not found: child"* ]]
|
||||
[[ "$body" != *UCE_HIDDEN_COMPILE_MARKER* ]]
|
||||
stop_server
|
||||
|
||||
printf '%s\n' 'COMPONENT(Request& context) { print("good-v1"); }' >"$site/child.uce"
|
||||
write_settings 1 1 1
|
||||
start_server
|
||||
for _ in $(seq 1 100); do
|
||||
response=$(request GET)
|
||||
[[ "$(status_of "$response")" == 200 && "$(body_of "$response")" == "driver:good-v1" ]] && break
|
||||
sleep 0.1
|
||||
done
|
||||
[[ "$(status_of "$response")" == 200 && "$(body_of "$response")" == "driver:good-v1" ]]
|
||||
cache="$(scripts/unit_cache_directory "$work")$(realpath "$site")"
|
||||
for artifact in "$cache/child.uce.wasm" "$cache/child.uce.cwasm" "$cache/child.uce.wasm.source-map"; do [[ -s "$artifact" ]]; done
|
||||
before_hashes=$(sha256sum "$cache/child.uce.wasm" "$cache/child.uce.cwasm" "$cache/child.uce.wasm.source-map")
|
||||
printf '%s\n' 'COMPONENT(Request& context) { UCE_STALE_COMPILE_MARKER }' >"$site/child.uce"
|
||||
for _ in $(seq 1 30); do
|
||||
response=$(request GET)
|
||||
[[ "$(status_of "$response")" == 200 && "$(body_of "$response")" == "driver:good-v1" ]]
|
||||
sleep 0.1
|
||||
done
|
||||
[[ -s "$cache/child.uce.compile.txt" ]]
|
||||
[[ "$(sha256sum "$cache/child.uce.wasm" "$cache/child.uce.cwasm" "$cache/child.uce.wasm.source-map")" == "$before_hashes" ]]
|
||||
stop_server
|
||||
start_server
|
||||
response=$(request GET)
|
||||
[[ "$(status_of "$response")" == 200 && "$(body_of "$response")" == "driver:good-v1" ]] || { echo "last-known-good artifact did not survive worker restart: $response" >&2; exit 1; }
|
||||
printf '%s\n' 'COMPONENT(Request& context) { print("permission-repair"); }' >"$site/child.uce"
|
||||
chmod 000 "$site/child.uce"
|
||||
for _ in $(seq 1 100); do
|
||||
grep -q "source file is not readable" "$cache/child.uce.compile.txt" 2>/dev/null && break
|
||||
sleep 0.05
|
||||
done
|
||||
[[ "$(sha256sum "$cache/child.uce.wasm" "$cache/child.uce.cwasm" "$cache/child.uce.wasm.source-map")" == "$before_hashes" ]]
|
||||
response=$(request GET)
|
||||
[[ "$(status_of "$response")" == 200 && "$(body_of "$response")" == "driver:good-v1" ]] || { echo "unreadable source removed last-known-good service: $response" >&2; exit 1; }
|
||||
chmod 644 "$site/child.uce"
|
||||
printf '%s\n' 'COMPONENT(Request& context) { print("good-v2"); }' >"$site/child.uce"
|
||||
for _ in $(seq 1 200); do
|
||||
response=$(request GET)
|
||||
if [[ "$(status_of "$response")" == 200 && "$(body_of "$response")" == "driver:good-v2" ]]; then break; fi
|
||||
sleep 0.1
|
||||
done
|
||||
[[ "$(status_of "$response")" == 200 && "$(body_of "$response")" == "driver:good-v2" ]] || { echo "repaired unit was not published: $response" >&2; exit 1; }
|
||||
|
||||
echo "dynamic compile failure handling passed"
|
||||
+31
-14
@@ -1236,6 +1236,8 @@ String compiler_format_source_read_failure(Request* context, SharedUnit* su, Str
|
||||
void compile_shared_unit_bounded(Request* context, SharedUnit* su, CompilerDeadline* deadline)
|
||||
{
|
||||
f64 comp_start = time_precise();
|
||||
bool preserve_last_known_good = compiler_preserve_last_known_good(context, su->file_name);
|
||||
bool stage_artifacts = deadline || preserve_last_known_good;
|
||||
|
||||
if(!file_exists(su->file_name))
|
||||
{
|
||||
@@ -1261,7 +1263,8 @@ void compile_shared_unit_bounded(Request* context, SharedUnit* su, CompilerDeadl
|
||||
file_put_contents(su->compile_output_file_name, su->compiler_messages + "\n");
|
||||
file_put_contents(su->wasm_check_file_name, su->compiler_messages + "\n");
|
||||
file_put_contents(su->meta_file_name, compiler_unit_metadata_text(context, su));
|
||||
compiler_unlink_unit_wasm_artifacts(su);
|
||||
if(!preserve_last_known_good)
|
||||
compiler_unlink_unit_wasm_artifacts(su);
|
||||
compiler_record_compile_result(su, time_precise() - comp_start, false, "compile_error", su->compiler_messages);
|
||||
printf("%s \n", compiler_format_source_read_failure(context, su, su->compiler_messages).c_str());
|
||||
if(deadline)
|
||||
@@ -1281,7 +1284,7 @@ void compile_shared_unit_bounded(Request* context, SharedUnit* su, CompilerDeadl
|
||||
String staged_api_name;
|
||||
String staged_meta_name;
|
||||
bool publication_failed = false;
|
||||
if(deadline)
|
||||
if(stage_artifacts)
|
||||
{
|
||||
u64 stage_id = compiler_invocation_stage_counter.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
String stage_suffix = ".invocation-" + std::to_string((u64)getpid()) + "-" + std::to_string(stage_id);
|
||||
@@ -1316,8 +1319,8 @@ void compile_shared_unit_bounded(Request* context, SharedUnit* su, CompilerDeadl
|
||||
}
|
||||
if(deadline && (deadline->timed_out || deadline->operational_failure))
|
||||
break;
|
||||
if(!file_put_contents(deadline ? staged_pre_name : su->pre_path + "/" + su->pre_file_name, generated_source) ||
|
||||
!file_put_contents(deadline ? staged_api_name : su->api_file_name, join(su->api_declarations, "\n")))
|
||||
if(!file_put_contents(stage_artifacts ? staged_pre_name : su->pre_path + "/" + su->pre_file_name, generated_source) ||
|
||||
!file_put_contents(stage_artifacts ? staged_api_name : su->api_file_name, join(su->api_declarations, "\n")))
|
||||
{
|
||||
su->compiler_messages = "could not write generated bounded compile inputs";
|
||||
break;
|
||||
@@ -1327,8 +1330,8 @@ void compile_shared_unit_bounded(Request* context, SharedUnit* su, CompilerDeadl
|
||||
shell_escape(su->src_path)+" "+
|
||||
shell_escape(su->bin_path)+" "+
|
||||
shell_escape(su->file_name)+" "+
|
||||
shell_escape(deadline ? staged_pre_file_name : su->pre_file_name)+" "+
|
||||
shell_escape(deadline ? staged_wasm_file_name : su->wasm_file_name)+" "+
|
||||
shell_escape(stage_artifacts ? staged_pre_file_name : su->pre_file_name)+" "+
|
||||
shell_escape(stage_artifacts ? staged_wasm_file_name : su->wasm_file_name)+" "+
|
||||
shell_escape(compiler_unit_bin_directory(context));
|
||||
if(deadline)
|
||||
{
|
||||
@@ -1353,7 +1356,7 @@ void compile_shared_unit_bounded(Request* context, SharedUnit* su, CompilerDeadl
|
||||
else
|
||||
su->compiler_messages = trim(shell_exec(compile_command));
|
||||
|
||||
String compiled_wasm_name = deadline ? staged_wasm_name : su->wasm_name;
|
||||
String compiled_wasm_name = stage_artifacts ? staged_wasm_name : su->wasm_name;
|
||||
if(su->compiler_messages.length() == 0 && !file_exists(compiled_wasm_name))
|
||||
su->compiler_messages = "wasm compile script completed without creating " + compiled_wasm_name;
|
||||
if(su->compiler_messages.length() > 0)
|
||||
@@ -1361,9 +1364,9 @@ void compile_shared_unit_bounded(Request* context, SharedUnit* su, CompilerDeadl
|
||||
String current_input_signature = compiler_unit_input_signature(context, su);
|
||||
if(current_input_signature == compiled_input_signature)
|
||||
{
|
||||
if(deadline)
|
||||
if(stage_artifacts)
|
||||
{
|
||||
if(deadline->expire_if_needed())
|
||||
if(deadline && deadline->expire_if_needed())
|
||||
break;
|
||||
String source_map = file_get_contents(staged_map_name);
|
||||
if(source_map == "")
|
||||
@@ -1383,7 +1386,7 @@ void compile_shared_unit_bounded(Request* context, SharedUnit* su, CompilerDeadl
|
||||
}
|
||||
break;
|
||||
}
|
||||
if(deadline)
|
||||
if(stage_artifacts)
|
||||
{
|
||||
file_unlink(staged_wasm_name);
|
||||
file_unlink(staged_map_name);
|
||||
@@ -1426,11 +1429,14 @@ void compile_shared_unit_bounded(Request* context, SharedUnit* su, CompilerDeadl
|
||||
file_unlink(staged_api_name);
|
||||
file_unlink(staged_meta_name);
|
||||
compiler_record_compile_result(su, time_precise() - comp_start, false, "publish_error", su->compiler_messages);
|
||||
deadline->operational_failure = true;
|
||||
deadline->operational_error = su->compiler_messages;
|
||||
if(deadline)
|
||||
{
|
||||
deadline->operational_failure = true;
|
||||
deadline->operational_error = su->compiler_messages;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(deadline)
|
||||
if(stage_artifacts)
|
||||
{
|
||||
file_unlink(staged_wasm_name);
|
||||
file_unlink(staged_map_name);
|
||||
@@ -1445,7 +1451,7 @@ void compile_shared_unit_bounded(Request* context, SharedUnit* su, CompilerDeadl
|
||||
file_put_contents(su->compile_output_file_name, raw_messages + "\n");
|
||||
file_put_contents(su->wasm_check_file_name, raw_messages + "\n");
|
||||
file_put_contents(su->meta_file_name, compiler_unit_metadata_text(context, su, compiled_input_signature));
|
||||
if(!publication_failed)
|
||||
if(!publication_failed && !preserve_last_known_good)
|
||||
compiler_unlink_unit_wasm_artifacts(su);
|
||||
compiler_record_compile_result(su, time_precise() - comp_start, false, "compile_error", raw_messages);
|
||||
printf("%s \n", compiler_format_compile_failure(context, su, raw_messages).c_str());
|
||||
@@ -1691,10 +1697,21 @@ bool compiler_unit_compile_in_progress(Request* context, String file_name)
|
||||
bool compiler_request_can_serve_stale_artifact(Request* context)
|
||||
{
|
||||
return(context && context->server && !context->resources.is_cli &&
|
||||
context->params["UCE_WS"] != "1" &&
|
||||
to_bool(context->server->config["SERVE_LAST_KNOWN_GOOD"], false) &&
|
||||
to_bool(context->server->config["PROACTIVE_COMPILE_ENABLED"], true) &&
|
||||
float_val(context->server->config["PROACTIVE_COMPILE_CHECK_INTERVAL"]) > 0);
|
||||
}
|
||||
|
||||
bool compiler_preserve_last_known_good(Request* context, String file_name)
|
||||
{
|
||||
return(
|
||||
context && context->server &&
|
||||
to_bool(context->server->config["SERVE_LAST_KNOWN_GOOD"], false) &&
|
||||
compiler_unit_can_serve_stale_artifact(context, file_name)
|
||||
);
|
||||
}
|
||||
|
||||
bool compiler_unit_can_serve_stale_artifact(Request* context, String file_name)
|
||||
{
|
||||
if(!compiler_request_can_serve_stale_artifact(context))
|
||||
|
||||
@@ -34,6 +34,7 @@ String compiler_error_page_unit(Request* context, String config_key);
|
||||
bool compiler_unit_compile_pending(Request* context, String file_name);
|
||||
bool compiler_unit_compile_in_progress(Request* context, String file_name);
|
||||
bool compiler_request_can_serve_stale_artifact(Request* context);
|
||||
bool compiler_preserve_last_known_good(Request* context, String file_name);
|
||||
bool compiler_unit_can_serve_stale_artifact(Request* context, String file_name);
|
||||
void compiler_prioritize_unit(Request* context, String file_name);
|
||||
StringList compiler_take_priority_units(Request* context);
|
||||
|
||||
@@ -1925,6 +1925,8 @@ StringMap make_server_settings()
|
||||
cfg["PRECOMPILE_JOBS"] = "2";
|
||||
cfg["SITE_DIRECTORY"] = "site";
|
||||
cfg["JIT_COMPILE_ON_REQUEST"] = "1";
|
||||
cfg["SHOW_DYNAMIC_COMPILE_ERRORS"] = "1";
|
||||
cfg["SERVE_LAST_KNOWN_GOOD"] = "0";
|
||||
cfg["PROACTIVE_COMPILE_ENABLED"] = "1";
|
||||
cfg["PROACTIVE_COMPILE_JOBS"] = "2";
|
||||
cfg["COMPILE_FAILURE_RETRY_SECONDS"] = std::to_string(10);
|
||||
|
||||
+32
-11
@@ -479,6 +479,7 @@ extern "C" int32_t uce_host_component_resolve(
|
||||
// but a single workspace can render the same component many times)
|
||||
static std::map<String, s32> wasm_component_slots;
|
||||
static std::map<String, String> wasm_component_paths;
|
||||
static std::map<String, String> wasm_component_errors;
|
||||
|
||||
// These mirror small page-runtime pieces that cannot include compiler.cpp in
|
||||
// the wasm core (compiler.cpp owns parser/clang/cache bookkeeping for the host
|
||||
@@ -548,7 +549,7 @@ struct RequestPropsScope
|
||||
// handler is just a string: "render", "component:CARD", "render:VARIANT",
|
||||
// "once", "cli", "websocket", "serve_http:named" — or "exists" (an existence
|
||||
// probe that loads nothing). No per-mode kinds.
|
||||
static s32 wasm_resolve_target(String unit_target, String handler, String* resolved_out = 0)
|
||||
static s32 wasm_resolve_target(String unit_target, String handler, String* resolved_out = 0, String* error_out = 0)
|
||||
{
|
||||
String current = context ? context->resources.current_unit_file : "";
|
||||
String cache_key = current + "\t" + handler + "\t" + unit_target;
|
||||
@@ -558,21 +559,30 @@ static s32 wasm_resolve_target(String unit_target, String handler, String* resol
|
||||
{
|
||||
if(resolved_out)
|
||||
*resolved_out = wasm_component_paths[cache_key];
|
||||
if(error_out)
|
||||
*error_out = wasm_component_errors[cache_key];
|
||||
return(cached->second);
|
||||
}
|
||||
char resolved[512];
|
||||
char resolved[4096];
|
||||
s32 once_slot = 0;
|
||||
s32 slot = uce_host_component_resolve(
|
||||
unit_target.data(), unit_target.size(), handler.data(), handler.size(),
|
||||
current.data(), current.size(),
|
||||
resolved, sizeof(resolved), &once_slot);
|
||||
String resolved_path = slot ? String(resolved, strnlen(resolved, sizeof(resolved))) : String("");
|
||||
String response(resolved, strnlen(resolved, sizeof(resolved)));
|
||||
String resolve_error = slot < 0 ? response : String("");
|
||||
if(slot < 0)
|
||||
slot = 0;
|
||||
String resolved_path = slot ? response : String("");
|
||||
if(resolved_out && slot)
|
||||
*resolved_out = resolved_path;
|
||||
if(error_out)
|
||||
*error_out = resolve_error;
|
||||
if(!is_exists)
|
||||
{
|
||||
wasm_component_slots[cache_key] = slot;
|
||||
wasm_component_paths[cache_key] = resolved_path;
|
||||
wasm_component_errors[cache_key] = resolve_error;
|
||||
bool runs_once = handler == "render" || handler.rfind("render:", 0) == 0 ||
|
||||
handler == "component" || handler.rfind("component:", 0) == 0;
|
||||
if(slot && runs_once)
|
||||
@@ -636,10 +646,14 @@ DValue* unit_call(String file_name, String function_name, DValue* call_param)
|
||||
if(handler != "")
|
||||
{
|
||||
String resolved;
|
||||
s32 slot = wasm_resolve_target(file_name, handler, &resolved);
|
||||
String resolve_error;
|
||||
s32 slot = wasm_resolve_target(file_name, handler, &resolved, &resolve_error);
|
||||
if(!slot)
|
||||
{
|
||||
print("Error: unit_call() function '", function_name, "' not found");
|
||||
if(resolve_error != "")
|
||||
print("Error: ", resolve_error);
|
||||
else
|
||||
print("Error: unit_call() function '", function_name, "' not found");
|
||||
return(0);
|
||||
}
|
||||
DValue props = call_param ? *call_param : DValue();
|
||||
@@ -656,10 +670,14 @@ DValue* unit_call(String file_name, String function_name, DValue* call_param)
|
||||
}
|
||||
|
||||
String resolved;
|
||||
s32 slot = wasm_resolve_target(file_name, "export:" + function_name, &resolved);
|
||||
String resolve_error;
|
||||
s32 slot = wasm_resolve_target(file_name, "export:" + function_name, &resolved, &resolve_error);
|
||||
if(!slot)
|
||||
{
|
||||
print("Error: unit_call() function '", function_name, "' not found");
|
||||
if(resolve_error != "")
|
||||
print("Error: ", resolve_error);
|
||||
else
|
||||
print("Error: unit_call() function '", function_name, "' not found");
|
||||
return(0);
|
||||
}
|
||||
|
||||
@@ -683,11 +701,12 @@ static void component_render_with_props(String name, DValue& props, Request& req
|
||||
component_parse_target(trim(name), file_name, render_name);
|
||||
String handler = render_name == "" ? String("component") : "component:" + render_name;
|
||||
String resolved;
|
||||
s32 slot = wasm_resolve_target(file_name, handler, &resolved);
|
||||
String resolve_error;
|
||||
s32 slot = wasm_resolve_target(file_name, handler, &resolved, &resolve_error);
|
||||
if(!slot)
|
||||
{
|
||||
request.set_status(500, "Internal Server Error");
|
||||
print(component_error_banner("component not found: " + trim(name)));
|
||||
print(component_error_banner(resolve_error != "" ? resolve_error : "component not found: " + trim(name)));
|
||||
return;
|
||||
}
|
||||
wasm_run_once(resolved, request);
|
||||
@@ -724,11 +743,12 @@ void unit_render(String file_name, Request& request)
|
||||
component_parse_target(trim(file_name), unit_name, render_name);
|
||||
String handler = render_name == "" ? String("render") : "render:" + render_name;
|
||||
String resolved;
|
||||
s32 slot = wasm_resolve_target(unit_name, handler, &resolved);
|
||||
String resolve_error;
|
||||
s32 slot = wasm_resolve_target(unit_name, handler, &resolved, &resolve_error);
|
||||
if(!slot)
|
||||
{
|
||||
request.set_status(500, "Internal Server Error");
|
||||
print(component_error_banner("unit not found: " + trim(file_name)));
|
||||
print(component_error_banner(resolve_error != "" ? resolve_error : "unit not found: " + trim(file_name)));
|
||||
return;
|
||||
}
|
||||
wasm_run_once(resolved, request);
|
||||
@@ -857,6 +877,7 @@ void uce_wasm_core_reset_request()
|
||||
wasm_request.resources.current_unit_file = "";
|
||||
wasm_component_slots.clear();
|
||||
wasm_component_paths.clear();
|
||||
wasm_component_errors.clear();
|
||||
}
|
||||
|
||||
// Host pushes the worker-cached immutable configuration followed by the
|
||||
|
||||
+24
-4
@@ -2995,12 +2995,15 @@ private:
|
||||
// `handler` names the export ("render", "component:CARD", "cli",
|
||||
// "serve_http:named", "once") or is "exists" (probe only, loads nothing).
|
||||
int32_t component_resolve(const String& target, const String& handler, const String& current_unit,
|
||||
String& resolved_out, int32_t* once_slot_out = 0, bool* compile_timed_out = 0)
|
||||
String& resolved_out, int32_t* once_slot_out = 0, bool* compile_timed_out = 0,
|
||||
String* compile_error_out = 0)
|
||||
{
|
||||
if(once_slot_out)
|
||||
*once_slot_out = 0;
|
||||
if(compile_timed_out)
|
||||
*compile_timed_out = false;
|
||||
if(compile_error_out)
|
||||
compile_error_out->clear();
|
||||
auto probe_start = std::chrono::steady_clock::now();
|
||||
auto record_probe = [&]() {
|
||||
component_resolve_count += 1;
|
||||
@@ -3130,10 +3133,22 @@ private:
|
||||
{
|
||||
u64 remaining_ms = invocation_remaining_ms();
|
||||
bool timed_out = false;
|
||||
SharedUnit* compiled_unit = 0;
|
||||
if(remaining_ms == 0)
|
||||
timed_out = true;
|
||||
else
|
||||
get_shared_unit_bounded(context, resolved, remaining_ms, &timed_out);
|
||||
compiled_unit = get_shared_unit_bounded(context, resolved, remaining_ms, &timed_out);
|
||||
if(compiled_unit && compile_error_out &&
|
||||
to_bool(context->server->config["SHOW_DYNAMIC_COMPILE_ERRORS"], true))
|
||||
{
|
||||
String detail = trim(first(compiled_unit->compiler_messages, compiled_unit->compile_error_status));
|
||||
if(detail != "")
|
||||
{
|
||||
*compile_error_out = "compile failed for " + resolved + ":\n" + detail;
|
||||
if(compile_error_out->size() > 4095)
|
||||
*compile_error_out = compile_error_out->substr(0, 4064) + "\n[compile error truncated]";
|
||||
}
|
||||
}
|
||||
if(timed_out)
|
||||
{
|
||||
if(compile_timed_out)
|
||||
@@ -4521,15 +4536,20 @@ private:
|
||||
}));
|
||||
if(mod == "env" && name == "uce_host_component_resolve")
|
||||
return(add([self](Caller caller, Span<const Val> args, Span<Val> results) -> Result<std::monostate, Trap> {
|
||||
String target, handler, current, resolved;
|
||||
String target, handler, current, resolved, compile_error;
|
||||
self->hostcall_read(args[0].i32(), args[1].i32(), target);
|
||||
self->hostcall_read(args[2].i32(), args[3].i32(), handler);
|
||||
self->hostcall_read(args[4].i32(), args[5].i32(), current);
|
||||
int32_t once_slot = 0;
|
||||
bool compile_timed_out = false;
|
||||
int32_t slot = self->component_resolve(target, handler, current, resolved, &once_slot, &compile_timed_out);
|
||||
int32_t slot = self->component_resolve(target, handler, current, resolved, &once_slot, &compile_timed_out, &compile_error);
|
||||
if(compile_timed_out)
|
||||
return(Trap(self->invocation_timeout_error()));
|
||||
if(slot == 0 && compile_error != "")
|
||||
{
|
||||
resolved = compile_error;
|
||||
slot = -1;
|
||||
}
|
||||
u32 cap = (u32)args[7].i32();
|
||||
if(cap > 0)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user