Invalidate component freshness by source generation

This commit is contained in:
udo 2026-07-17 23:14:42 +00:00
parent 075ce0bc07
commit e176ff09d9
5 changed files with 68 additions and 3 deletions

View File

@ -118,6 +118,24 @@ if [[ "$http_component_output" != *"http-component-marker-b"* ]]; then
exit 1
fi
printf '%s\n' 'String http_component_dependency_marker() { return("http-component-dependency-a"); }' >"$source_dir/http-component-dependency.uce"
printf '%s\n' '#load "http-component-dependency.uce"' 'COMPONENT(Request& context) { print(http_component_dependency_marker()); }' >"$source_dir/http-transitive-child.uce"
printf '%s\n' 'RENDER(Request& context) { DValue props; print(component("http-transitive-child", props, context)); }' >"$source_dir/http-transitive-parent.uce"
http_transitive_marker() {
curl -fsS -H "Host: $http_host" "http://127.0.0.1/$test_name/http-transitive-parent.uce"
}
if [[ "$(http_transitive_marker)" != *"http-component-dependency-a"* ]]; then
echo "HTTP transitive component warm-up did not return dependency-a" >&2
exit 1
fi
sed -i 's/http-component-dependency-a/http-component-dependency-b/' "$source_dir/http-component-dependency.uce"
deadline=$((SECONDS + 20))
while [[ "$(http_transitive_marker)" != *"http-component-dependency-b"* && $SECONDS -lt $deadline ]]; do sleep 0.2; done
if [[ "$(http_transitive_marker)" != *"http-component-dependency-b"* ]]; then
echo "HTTP dynamic component did not converge after its loaded dependency changed" >&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"

View File

@ -385,6 +385,11 @@ String compiler_priority_file_name(Request* context)
return(context->server->config["BIN_DIRECTORY"] + "/proactive-priority.txt");
}
String compiler_source_generation_file_name(Request* context)
{
return(context->server->config["BIN_DIRECTORY"] + "/source-generation.txt");
}
int compiler_open_lock_file(String file_name, String purpose, bool nonblocking = false)
{
(void)purpose;
@ -906,6 +911,7 @@ void compile_shared_unit(Request* context, SharedUnit* su)
file_put_contents(su->compile_output_file_name, su->compiler_messages + "\n");
compiler_untrack_known_unit(context, su->file_name);
compiler_record_compile_result(su, time_precise() - comp_start, false, "missing_source", su->compiler_messages);
compiler_mark_source_generation(context);
return;
}
struct stat source_info;
@ -922,6 +928,7 @@ void compile_shared_unit(Request* context, SharedUnit* su)
file_unlink(su->wasm_name);
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());
compiler_mark_source_generation(context);
return;
}
@ -982,6 +989,7 @@ void compile_shared_unit(Request* context, SharedUnit* su)
(su->pre_path + "/" + su->pre_file_name).c_str(),
time_precise() - comp_start);
}
compiler_mark_source_generation(context);
}
SharedUnit* compiler_get_shared_unit_internal(Request* context, String file_name, bool force_recompile)
@ -1074,6 +1082,25 @@ SharedUnit* get_shared_unit(Request* context, String file_name)
return(compiler_get_shared_unit_internal(context, file_name, false));
}
String compiler_source_generation(Request* context)
{
if(!context || !context->server)
return("");
return(trim(file_get_contents(compiler_source_generation_file_name(context))));
}
void compiler_mark_source_generation(Request* context)
{
if(!context || !context->server)
return;
String file_name = compiler_source_generation_file_name(context);
int fdlock = compiler_open_lock_file(file_name + ".lock", "source-generation");
if(fdlock < 0)
return;
file_put_contents(file_name, std::to_string(getpid()) + ":" + std::to_string((u64)(time_precise() * 1000000.0)) + "\n");
compiler_close_lock_file(fdlock);
}
String compiler_error_page_unit(Request* context, String config_key)
{
if(!context || !context->server)

View File

@ -21,6 +21,8 @@ bool compiler_unit_compile_in_progress(Request* context, String file_name);
bool compiler_request_can_serve_stale_artifact(Request* context);
void compiler_prioritize_unit(Request* context, String file_name);
StringList compiler_take_priority_units(Request* context);
String compiler_source_generation(Request* context);
void compiler_mark_source_generation(Request* context);
String compiler_site_directory(Request* context);
StringList compiler_scan_site_units(Request* context);
StringList compiler_list_known_units(Request* context);

View File

@ -1263,6 +1263,7 @@ void run_proactive_compiler()
{
auto tracked_units = compiler_list_known_units(&background_context);
StringList existing_units;
bool source_generation_changed = false;
for(auto& file_name : tracked_units)
{
@ -1271,9 +1272,13 @@ void run_proactive_compiler()
bool retry_allowed = (retry_it == retry_after.end() || time_precise() >= retry_it->second);
bool needs_compile = compiler_unit_needs_recompile(&background_context, file_name, &source_missing);
if(needs_compile && retry_allowed)
{
proactive_compile_queue_push(compile_queue, file_name);
source_generation_changed = true;
}
if(source_missing)
{
source_generation_changed = true;
printf("(i) proactive compiler forget removed unit %s\n", file_name.c_str());
retry_after.erase(file_name);
continue;
@ -1283,6 +1288,8 @@ void run_proactive_compiler()
if(existing_units.size() != tracked_units.size())
compiler_set_known_units(&background_context, existing_units);
if(source_generation_changed)
compiler_mark_source_generation(&background_context);
next_scan_at = time_precise() + check_interval;
}

View File

@ -1121,6 +1121,7 @@ private:
{
std::chrono::steady_clock::time_point checked_at;
bool stale = false;
String source_generation;
};
std::map<String, ComponentFreshnessState> component_freshness;
@ -1528,6 +1529,8 @@ private:
std::vector<wasmtime::Func> host_funcs; // keep host imports alive
bool stale_component_mutation_blocked = false;
String stale_component_mutation_status;
bool component_source_generation_checked = false;
String component_source_generation;
wasmtime::Store::Context ctx()
{
@ -2197,6 +2200,11 @@ private:
bool can_serve_stale = compiler_request_can_serve_stale_artifact(context);
String method = context ? to_upper(trim(context->params["REQUEST_METHOD"])) : String("");
bool read_request = method == "GET" || method == "HEAD" || method == "OPTIONS";
if(can_serve_stale && read_request && !component_source_generation_checked)
{
component_source_generation = compiler_source_generation(context);
component_source_generation_checked = true;
}
bool stale = false;
if(artifact_exists)
{
@ -2204,14 +2212,17 @@ private:
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 generation_available = component_source_generation != "";
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;
(generation_available ?
cached_freshness->second.source_generation != component_source_generation :
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, can_serve_stale && read_request, true);
worker.component_freshness[resolved] = { now, stale };
worker.component_freshness[resolved] = { now, stale, component_source_generation };
}
else
stale = cached_freshness->second.stale;