Precompile native modules off request path

This commit is contained in:
udo
2026-07-16 22:51:16 +00:00
parent a247c2b520
commit 8dd307d316
6 changed files with 84 additions and 8 deletions
+2
View File
@@ -178,6 +178,8 @@ When a current serialized module exists, the worker scans wasm section headers
and reads only `dylink.0` and `uce.abi`; it does not fault the multi-megabyte code and reads only `dylink.0` and `uce.abi`; it does not fault the multi-megabyte code
and data bodies into every new worker. A missing/stale/invalid serialized module and data bodies into every new worker. A missing/stale/invalid serialized module
still reads, validates, compiles, and republishes the complete wasm artifact. still reads, validates, compiles, and republishes the complete wasm artifact.
The proactive compiler also creates that serialization immediately after source
compilation, keeping first-worker native compilation off the request path.
Cold module compilation and deserialization are host work, so `load_unit()` Cold module compilation and deserialization are host work, so `load_unit()`
refreshes the epoch deadline before its first guest call. Otherwise a component refreshes the epoch deadline before its first guest call. Otherwise a component
whose compilation outlasted the guest CPU budget would immediately trap in the whose compilation outlasted the guest CPU budget would immediately trap in the
+18 -1
View File
@@ -181,9 +181,26 @@ wait "$rebuild_lock_pid"
# parent CLI request must wait and render the new child, never its stale wasm. # parent CLI request must wait and render the new child, never its stale wasm.
printf '%s\n' 'COMPONENT(Request& context) { print("nested-component-marker-a"); }' >"$source_dir/nested-child.uce" printf '%s\n' 'COMPONENT(Request& context) { print("nested-component-marker-a"); }' >"$source_dir/nested-child.uce"
printf '%s\n' \ printf '%s\n' \
'CLI(Request& context) { DValue props; print(component("nested-child", props, context)); }' >"$source_dir/nested-parent.uce" 'CLI(Request& context) { DValue props; print(component("nested-child", props, context)); }' \
'RENDER(Request& context) { DValue props; print(component("nested-child", props, context)); }' >"$source_dir/nested-parent.uce"
assert_marker nested-parent nested-component-marker-a assert_marker nested-parent nested-component-marker-a
nested_child_wasm="$cache_dir/nested-child.uce.wasm" nested_child_wasm="$cache_dir/nested-child.uce.wasm"
# A proactive source rebuild must publish the native serialized module before
# any request worker has to load the changed unit.
nested_parent_cwasm="$cache_dir/nested-parent.uce.cwasm"
rm -f "$nested_parent_cwasm"
printf '%s\n' \
'CLI(Request& context) { DValue props; print(component("nested-child", props, context)); /* proactive-serialization-v2 */ }' \
'RENDER(Request& context) { DValue props; print(component("nested-child", props, context)); /* proactive-serialization-v2 */ }' >"$source_dir/nested-parent.uce"
curl -fsS -H "Host: $http_host" "http://127.0.0.1/$test_name/nested-parent.uce" >/dev/null
deadline=$((SECONDS + 10))
while [[ ! -s "$nested_parent_cwasm" && $SECONDS -lt $deadline ]]; do sleep 0.1; done
if [[ ! -s "$nested_parent_cwasm" ]]; then
echo "proactive compiler did not serialize rebuilt module" >&2
exit 1
fi
( (
exec 8>"$nested_child_wasm.lock" exec 8>"$nested_child_wasm.lock"
flock 8 flock 8
+19 -7
View File
@@ -1266,7 +1266,8 @@ void run_proactive_compiler()
bool source_missing = false; bool source_missing = false;
auto retry_it = retry_after.find(file_name); auto retry_it = retry_after.find(file_name);
bool retry_allowed = (retry_it == retry_after.end() || time_precise() >= retry_it->second); bool retry_allowed = (retry_it == retry_after.end() || time_precise() >= retry_it->second);
if(compiler_unit_needs_recompile(&background_context, file_name, &source_missing) && retry_allowed) 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); proactive_compile_queue_push(compile_queue, file_name);
if(source_missing) if(source_missing)
{ {
@@ -1291,14 +1292,15 @@ void run_proactive_compiler()
auto retry_it = retry_after.find(file_name); auto retry_it = retry_after.find(file_name);
if(retry_it != retry_after.end() && time_precise() < retry_it->second) if(retry_it != retry_after.end() && time_precise() < retry_it->second)
continue; continue;
bool failed = false;
String wasm_path = server_state.config["BIN_DIRECTORY"] + file_name + ".wasm";
if(compiler_unit_needs_recompile(&background_context, file_name, &source_missing)) if(compiler_unit_needs_recompile(&background_context, file_name, &source_missing))
{ {
printf("(i) proactive compile %s\n", file_name.c_str()); printf("(i) proactive compile %s\n", file_name.c_str());
auto su = get_shared_unit(&background_context, file_name); auto su = get_shared_unit(&background_context, file_name);
if(su && su->compiler_messages == "") failed = !su || su->compiler_messages != "";
retry_after.erase(file_name); if(su)
else wasm_path = su->wasm_name;
retry_after[file_name] = time_precise() + failure_retry_interval;
} }
else if(source_missing) else if(source_missing)
{ {
@@ -1306,10 +1308,20 @@ void run_proactive_compiler()
compiler_untrack_known_unit(&background_context, file_name); compiler_untrack_known_unit(&background_context, file_name);
retry_after.erase(file_name); retry_after.erase(file_name);
} }
else if(!source_missing && !failed && wasm_serialized_module_needs_refresh(wasm_path))
{ {
retry_after.erase(file_name); printf("(i) proactive serialize %s\n", file_name.c_str());
String serialize_error = wasm_serialize_module_artifact(wasm_path);
if(serialize_error != "")
{
printf("(!) proactive serialize failed for %s: %s\n", file_name.c_str(), serialize_error.c_str());
failed = true;
}
} }
if(!source_missing && failed)
retry_after[file_name] = time_precise() + failure_retry_interval;
else if(!source_missing)
retry_after.erase(file_name);
background_context.session.clear(); background_context.session.clear();
background_context.session_loaded_hash = ""; background_context.session_loaded_hash = "";
clear_shared_unit_cache(server_state); clear_shared_unit_cache(server_state);
+10
View File
@@ -93,6 +93,16 @@ String wasm_backend_start(Request& request)
return(wasm_backend_ensure_started(&request)); return(wasm_backend_ensure_started(&request));
} }
bool wasm_serialized_module_needs_refresh(const String& wasm_path)
{
return(WasmWorker::serialized_module_needs_refresh(wasm_path));
}
String wasm_serialize_module_artifact(const String& wasm_path)
{
return(WasmWorker::serialize_module_artifact(wasm_path));
}
static bool wasm_artifact_exists(Request* context, const String& entry_unit) static bool wasm_artifact_exists(Request* context, const String& entry_unit)
{ {
if(entry_unit == "") if(entry_unit == "")
+5
View File
@@ -14,6 +14,11 @@ bool wasm_backend_should_handle(Request& request, const String& entry_unit);
// requests. Returns "" on success or the retained backend initialization error. // requests. Returns "" on success or the retained backend initialization error.
String wasm_backend_start(Request& request); String wasm_backend_start(Request& request);
// Proactive compiler hooks for keeping Wasmtime's native serialization newer
// than a compiled unit artifact before any request worker loads it.
bool wasm_serialized_module_needs_refresh(const String& wasm_path);
String wasm_serialize_module_artifact(const String& wasm_path);
// Serve a request through a wasm workspace by invoking a named unit handler — // Serve a request through a wasm workspace by invoking a named unit handler —
// "render", "cli", "websocket", "serve_http", "serve_http:named" — and populate // "render", "cli", "websocket", "serve_http", "serve_http:named" — and populate
// the native Request. Returns "" on success or a collapsed error. The handler is // the native Request. Returns "" on success or a collapsed error. The handler is
+30
View File
@@ -1049,6 +1049,36 @@ public:
return(unit); return(unit);
} }
static bool serialized_module_needs_refresh(const String& wasm_path)
{
struct stat wasm_stat;
struct stat cached_stat;
String cached_path = cached_wasm_path(wasm_path);
if(stat(wasm_path.c_str(), &wasm_stat) != 0)
return(false);
if(stat(cached_path.c_str(), &cached_stat) != 0)
return(true);
return(cached_stat.st_mtim.tv_sec < wasm_stat.st_mtim.tv_sec ||
(cached_stat.st_mtim.tv_sec == wasm_stat.st_mtim.tv_sec && cached_stat.st_mtim.tv_nsec <= wasm_stat.st_mtim.tv_nsec));
}
static String serialize_module_artifact(const String& wasm_path)
{
if(!serialized_module_needs_refresh(wasm_path))
return("");
std::vector<u8> bytes;
if(!wasm_read_file(wasm_path, bytes))
return("cannot read " + wasm_path);
wasmtime::Engine engine = make_engine();
String error;
auto module = compile_and_cache_module(engine, cached_wasm_path(wasm_path), bytes, error);
if(!module)
return(error);
if(serialized_module_needs_refresh(wasm_path))
return("cannot publish serialized module for " + wasm_path);
return("");
}
private: private:
static String cached_wasm_path(const String& wasm_path) static String cached_wasm_path(const String& wasm_path)
{ {