Prioritize stale dynamic components
This commit is contained in:
@@ -18,6 +18,10 @@ fi
|
|||||||
bin_directory="${bin_directory:-/tmp/uce/work}"
|
bin_directory="${bin_directory:-/tmp/uce/work}"
|
||||||
cache_dir=""
|
cache_dir=""
|
||||||
mutation_file="/tmp/uce-dependency-mutation-$$"
|
mutation_file="/tmp/uce-dependency-mutation-$$"
|
||||||
|
component_mutation_file="/tmp/uce-component-mutation-$$"
|
||||||
|
component_post_body="/tmp/uce-component-post-body-$$"
|
||||||
|
component_post_headers="/tmp/uce-component-post-headers-$$"
|
||||||
|
component_lock_ready_file="/tmp/uce-component-lock-ready-$$"
|
||||||
post_body="/tmp/uce-dependency-post-body-$$"
|
post_body="/tmp/uce-dependency-post-body-$$"
|
||||||
post_headers="/tmp/uce-dependency-post-headers-$$"
|
post_headers="/tmp/uce-dependency-post-headers-$$"
|
||||||
lock_ready_file="/tmp/uce-dependency-lock-ready-$$"
|
lock_ready_file="/tmp/uce-dependency-lock-ready-$$"
|
||||||
@@ -32,7 +36,7 @@ cleanup() {
|
|||||||
if [[ -n "$cache_dir" ]]; then
|
if [[ -n "$cache_dir" ]]; then
|
||||||
rm -rf "$cache_dir"
|
rm -rf "$cache_dir"
|
||||||
fi
|
fi
|
||||||
rm -f "$mutation_file" "$post_body" "$post_headers" "$lock_ready_file" "$nested_lock_ready_file" "$race_output" "$race_error" "$race_status"
|
rm -f "$mutation_file" "$component_mutation_file" "$component_post_body" "$component_post_headers" "$component_lock_ready_file" "$post_body" "$post_headers" "$lock_ready_file" "$nested_lock_ready_file" "$race_output" "$race_error" "$race_status"
|
||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
mkdir -p "$source_dir"
|
mkdir -p "$source_dir"
|
||||||
@@ -69,6 +73,80 @@ if [[ "$(http_marker)" != *"dependency-marker-a"* ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# HTTP entry units can resolve route/components dynamically. A changed dynamic
|
||||||
|
# component must enter the demand-priority queue just like a changed entry unit;
|
||||||
|
# otherwise a common dependency rebuild can leave the requested page stale for
|
||||||
|
# the duration of the full proactive queue.
|
||||||
|
printf '%s\n' "COMPONENT(Request& context) { if(context.params[\"REQUEST_METHOD\"] == \"POST\") file_put_contents(\"$component_mutation_file\", \"executed\"); print(\"http-component-marker-a\"); }" >"$source_dir/http-component-child.uce"
|
||||||
|
printf '%s\n' 'RENDER(Request& context) { DValue props; print(component("http-component-child", props, context)); }' >"$source_dir/http-component-parent.uce"
|
||||||
|
http_component_marker() {
|
||||||
|
curl -fsS -H "Host: $http_host" "http://127.0.0.1/$test_name/http-component-parent.uce"
|
||||||
|
}
|
||||||
|
if [[ "$(http_component_marker)" != *"http-component-marker-a"* ]]; then
|
||||||
|
echo "HTTP component warm-up did not return marker-a" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sed -i 's/http-component-marker-a/http-component-marker-b/' "$source_dir/http-component-child.uce"
|
||||||
|
started_at=$(date +%s%N)
|
||||||
|
http_component_output=$(http_component_marker)
|
||||||
|
elapsed_ms=$(( ($(date +%s%N) - started_at) / 1000000 ))
|
||||||
|
if [[ "$http_component_output" != *"http-component-marker-a"* && "$http_component_output" != *"http-component-marker-b"* ]]; then
|
||||||
|
echo "stale HTTP component request returned neither complete artifact: $http_component_output" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if (( elapsed_ms >= 2000 )); then
|
||||||
|
echo "HTTP component request spent ${elapsed_ms}ms rebuilding a stale artifact" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
deadline=$((SECONDS + 15))
|
||||||
|
while [[ "$http_component_output" != *"http-component-marker-b"* && $SECONDS -lt $deadline ]]; do
|
||||||
|
sleep 0.2
|
||||||
|
http_component_output=$(http_component_marker)
|
||||||
|
done
|
||||||
|
if [[ "$http_component_output" != *"http-component-marker-b"* ]]; then
|
||||||
|
echo "requested stale HTTP component did not receive a demand-priority rebuild" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mutations must never execute a stale dynamically resolved component, even
|
||||||
|
# inside the short read-only freshness memo window.
|
||||||
|
http_component_wasm="$cache_dir/http-component-child.uce.wasm"
|
||||||
|
(
|
||||||
|
exec 8>"$http_component_wasm.lock"
|
||||||
|
flock 8
|
||||||
|
: >"$component_lock_ready_file"
|
||||||
|
sleep 2
|
||||||
|
) &
|
||||||
|
component_lock_pid=$!
|
||||||
|
deadline=$((SECONDS + 5))
|
||||||
|
while [[ ! -e "$component_lock_ready_file" && $SECONDS -lt $deadline ]]; do sleep 0.05; done
|
||||||
|
if [[ ! -e "$component_lock_ready_file" ]]; then
|
||||||
|
echo "HTTP component mutation lock was not acquired before deadline" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sed -i 's/http-component-marker-b/http-component-marker-c/' "$source_dir/http-component-child.uce"
|
||||||
|
rm -f "$component_mutation_file"
|
||||||
|
component_post_status=$(curl -sS -o "$component_post_body" -D "$component_post_headers" -w '%{http_code}' -X POST -H "Host: $http_host" "http://127.0.0.1/$test_name/http-component-parent.uce")
|
||||||
|
if [[ "$component_post_status" != "503" ]]; then
|
||||||
|
echo "stale HTTP component mutation did not fail closed: status=$component_post_status body=$(cat "$component_post_body")" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! grep -qi '^Retry-After: 1' "$component_post_headers"; then
|
||||||
|
echo "stale HTTP component mutation omitted Retry-After" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ -e "$component_mutation_file" ]]; then
|
||||||
|
echo "stale HTTP component mutation executed application code" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
wait "$component_lock_pid"
|
||||||
|
deadline=$((SECONDS + 15))
|
||||||
|
while [[ "$(http_component_marker)" != *"http-component-marker-c"* && $SECONDS -lt $deadline ]]; do sleep 0.2; done
|
||||||
|
if [[ "$(http_component_marker)" != *"http-component-marker-c"* ]]; then
|
||||||
|
echo "HTTP component did not recover after the guarded mutation rebuild" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# A failed parent build must remember the exact dependency graph that failed.
|
# A failed parent build must remember the exact dependency graph that failed.
|
||||||
# If a dependency is restored byte-for-byte to the last working source, the
|
# If a dependency is restored byte-for-byte to the last working source, the
|
||||||
# old successful metadata must not make that transient failure permanent.
|
# old successful metadata must not make that transient failure permanent.
|
||||||
|
|||||||
+51
-5
@@ -1117,6 +1117,12 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
friend class WasmWorkspace;
|
friend class WasmWorkspace;
|
||||||
|
struct ComponentFreshnessState
|
||||||
|
{
|
||||||
|
std::chrono::steady_clock::time_point checked_at;
|
||||||
|
bool stale = false;
|
||||||
|
};
|
||||||
|
std::map<String, ComponentFreshnessState> component_freshness;
|
||||||
|
|
||||||
static String cached_wasm_path(const String& wasm_path)
|
static String cached_wasm_path(const String& wasm_path)
|
||||||
{
|
{
|
||||||
@@ -1491,6 +1497,13 @@ public:
|
|||||||
String decode_error;
|
String decode_error;
|
||||||
if(!ucb_decode(meta_encoded, response.meta, &decode_error))
|
if(!ucb_decode(meta_encoded, response.meta, &decode_error))
|
||||||
return("response meta decode failed: " + decode_error);
|
return("response meta decode failed: " + decode_error);
|
||||||
|
if(stale_component_mutation_blocked)
|
||||||
|
{
|
||||||
|
response.body = "The requested code is being updated. Retry this request shortly.\n";
|
||||||
|
response.meta["status"] = stale_component_mutation_status;
|
||||||
|
response.meta["headers"]["Content-Type"] = "text/plain; charset=utf-8";
|
||||||
|
response.meta["headers"]["Retry-After"] = "1";
|
||||||
|
}
|
||||||
return("");
|
return("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1513,6 +1526,8 @@ private:
|
|||||||
std::map<String, size_t> units_by_source;
|
std::map<String, size_t> units_by_source;
|
||||||
std::map<String, u32> handler_slots; // source + ":" + symbol → table slot
|
std::map<String, u32> handler_slots; // source + ":" + symbol → table slot
|
||||||
std::vector<wasmtime::Func> host_funcs; // keep host imports alive
|
std::vector<wasmtime::Func> host_funcs; // keep host imports alive
|
||||||
|
bool stale_component_mutation_blocked = false;
|
||||||
|
String stale_component_mutation_status;
|
||||||
|
|
||||||
wasmtime::Store::Context ctx()
|
wasmtime::Store::Context ctx()
|
||||||
{
|
{
|
||||||
@@ -2180,11 +2195,42 @@ private:
|
|||||||
auto artifact_start = std::chrono::steady_clock::now();
|
auto artifact_start = std::chrono::steady_clock::now();
|
||||||
bool artifact_exists = file_exists_host(worker.unit_wasm_path(resolved));
|
bool artifact_exists = file_exists_host(worker.unit_wasm_path(resolved));
|
||||||
bool can_serve_stale = compiler_request_can_serve_stale_artifact(context);
|
bool can_serve_stale = compiler_request_can_serve_stale_artifact(context);
|
||||||
// The proactive compiler owns freshness while stale HTTP artifacts may be
|
String method = context ? to_upper(trim(context->params["REQUEST_METHOD"])) : String("");
|
||||||
// served. Keep source-graph scans off that request path; CLI/non-stale
|
bool read_request = method == "GET" || method == "HEAD" || method == "OPTIONS";
|
||||||
// execution still checks synchronously and cold artifacts still compile.
|
bool stale = false;
|
||||||
bool stale = !can_serve_stale && artifact_exists && compiler_unit_needs_recompile(context, resolved, 0);
|
if(artifact_exists)
|
||||||
if(!artifact_exists || stale)
|
{
|
||||||
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
auto cached_freshness = worker.component_freshness.find(resolved);
|
||||||
|
// HTTP reads may serve a complete stale artifact, so bound their graph
|
||||||
|
// stat work; CLI and mutations always check the current graph.
|
||||||
|
bool check_freshness = !can_serve_stale || !read_request ||
|
||||||
|
cached_freshness == worker.component_freshness.end() ||
|
||||||
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
now - cached_freshness->second.checked_at).count() >= 1000;
|
||||||
|
if(check_freshness)
|
||||||
|
{
|
||||||
|
stale = compiler_unit_needs_recompile(context, resolved, 0);
|
||||||
|
worker.component_freshness[resolved] = { now, stale };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
stale = cached_freshness->second.stale;
|
||||||
|
}
|
||||||
|
if(stale && can_serve_stale)
|
||||||
|
{
|
||||||
|
compiler_prioritize_unit(context, resolved);
|
||||||
|
if(!read_request)
|
||||||
|
{
|
||||||
|
stale_component_mutation_blocked = true;
|
||||||
|
stale_component_mutation_status = context->params["GATEWAY_INTERFACE"] != "" ?
|
||||||
|
"Status: 503 Service Unavailable" : "HTTP/1.1 503 Service Unavailable";
|
||||||
|
component_artifact_total_us += (u64)std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
std::chrono::steady_clock::now() - artifact_start).count();
|
||||||
|
record_probe();
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!artifact_exists || (stale && !can_serve_stale))
|
||||||
get_shared_unit(context, resolved);
|
get_shared_unit(context, resolved);
|
||||||
component_artifact_total_us += (u64)std::chrono::duration_cast<std::chrono::microseconds>(
|
component_artifact_total_us += (u64)std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
std::chrono::steady_clock::now() - artifact_start).count();
|
std::chrono::steady_clock::now() - artifact_start).count();
|
||||||
|
|||||||
Reference in New Issue
Block a user