Avoid resolving loaded request entries twice
This commit is contained in:
parent
339031b6c3
commit
a043fb8fc7
@ -133,6 +133,13 @@ __uce_<base>[_<sanitize(suffix)>]
|
||||
path and looks up the export's funcref slot; `exists` lets callers probe a unit
|
||||
without instantiating it.
|
||||
|
||||
The request entry unit is already loaded by the host, so its selected handler
|
||||
and optional `ONCE` export are placed directly into the workspace table and
|
||||
passed to `uce_wasm_invoke_loaded_entry()`. Dynamic component and unit calls
|
||||
continue through `wasm_resolve_target()`, where relative-path resolution is
|
||||
required. Opt-in verbose response headers split entry invocation into load,
|
||||
presence lookup, table linking, and core dispatch time.
|
||||
|
||||
`wasm_backend_should_handle(request, entry_unit)` checks whether the wasm
|
||||
backend is initialized and the requested artifact/handler is currently
|
||||
available. If an artifact is cold or stale, dispatch compiles it on demand via
|
||||
|
||||
@ -243,6 +243,10 @@ String wasm_backend_serve(Request& request, const String& entry_unit, const Stri
|
||||
request.header["X-UCE-Wasm-Workspace-Birth-Us"] = std::to_string(response.workspace_birth_us);
|
||||
request.header["X-UCE-Wasm-Workspace-Complete-Us"] = std::to_string(response.workspace_complete_us);
|
||||
request.header["X-UCE-Wasm-Entry-Invoke-Us"] = std::to_string(response.entry_invoke_us);
|
||||
request.header["X-UCE-Wasm-Entry-Load-Us"] = std::to_string(response.entry_load_us);
|
||||
request.header["X-UCE-Wasm-Entry-Presence-Us"] = std::to_string(response.entry_presence_us);
|
||||
request.header["X-UCE-Wasm-Entry-Link-Us"] = std::to_string(response.entry_link_us);
|
||||
request.header["X-UCE-Wasm-Entry-Dispatch-Us"] = std::to_string(response.entry_dispatch_us);
|
||||
request.header["X-UCE-Wasm-Output-Collect-Us"] = std::to_string(response.output_collect_us);
|
||||
request.header["X-UCE-Wasm-Component-Resolve-Count"] = std::to_string(response.component_resolve_count);
|
||||
request.header["X-UCE-Wasm-Component-Resolve-Total-Us"] = std::to_string(response.component_resolve_total_us);
|
||||
|
||||
@ -724,26 +724,20 @@ void unit_render(String file_name) { unit_render(file_name, *context); }
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Host calls this to invoke the request's entry unit through a named handler
|
||||
// ("render"/"cli"/"websocket"/"serve_http:named"). It is the same resolve +
|
||||
// ONCE() + dispatch path as components — the entry handler is just another
|
||||
// export. The host pre-checks the export exists, so a missing slot is a no-op.
|
||||
void uce_wasm_invoke_entry(const char* path, size_t path_len, const char* handler, size_t handler_len)
|
||||
// The host has already loaded the request's entry unit and can place its
|
||||
// exports directly in the shared table. This avoids resolving that same unit
|
||||
// back through a hostcall while preserving per-request ONCE deduplication.
|
||||
void uce_wasm_invoke_loaded_entry(s32 handler_slot, s32 once_slot)
|
||||
{
|
||||
// the host always runs uce_wasm_core_init + apply_context before this
|
||||
String file_name(path, path_len);
|
||||
String handler_name(handler, handler_len);
|
||||
String resolved;
|
||||
s32 slot = wasm_resolve_target(trim(file_name), handler_name, &resolved);
|
||||
if(!slot)
|
||||
return;
|
||||
wasm_run_once(resolved, *context);
|
||||
String previous_unit = context->resources.current_unit_file;
|
||||
if(resolved != "")
|
||||
context->resources.current_unit_file = resolved;
|
||||
WasmRequestHandler handler_fn = (WasmRequestHandler)(uintptr_t)slot;
|
||||
String resolved = context->resources.current_unit_file;
|
||||
if(once_slot && resolved != "" && context->once_units.find(resolved) == context->once_units.end())
|
||||
{
|
||||
context->once_units.insert(resolved);
|
||||
WasmRequestHandler once_handler = (WasmRequestHandler)(uintptr_t)once_slot;
|
||||
once_handler(*context);
|
||||
}
|
||||
WasmRequestHandler handler_fn = (WasmRequestHandler)(uintptr_t)handler_slot;
|
||||
handler_fn(*context);
|
||||
context->resources.current_unit_file = previous_unit;
|
||||
}
|
||||
|
||||
void* uce_alloc(size_t len)
|
||||
|
||||
@ -193,6 +193,10 @@ struct WasmRequestProfile
|
||||
u64 context_guest_apply_us = 0;
|
||||
u64 context_free_us = 0;
|
||||
u64 entry_invoke_us = 0;
|
||||
u64 entry_load_us = 0;
|
||||
u64 entry_presence_us = 0;
|
||||
u64 entry_link_us = 0;
|
||||
u64 entry_dispatch_us = 0;
|
||||
u64 output_collect_us = 0;
|
||||
u64 workspace_complete_us = 0;
|
||||
u64 component_resolve_count = 0;
|
||||
@ -1560,45 +1564,61 @@ public:
|
||||
// empty body, a missing cli/serve handler to a 404).
|
||||
String invoke_entry(const String& entry_source_path, const String& handler, bool* handler_present = 0)
|
||||
{
|
||||
auto phase_started = std::chrono::steady_clock::now();
|
||||
auto phase_us = [&]() {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
u64 elapsed = (u64)std::chrono::duration_cast<std::chrono::microseconds>(now - phase_started).count();
|
||||
phase_started = now;
|
||||
return(elapsed);
|
||||
};
|
||||
entry_dir = dir_of(entry_source_path);
|
||||
size_t unit_index = 0;
|
||||
String error = load_unit(entry_source_path, "entry", unit_index);
|
||||
entry_load_us = phase_us();
|
||||
if(error != "")
|
||||
return(error);
|
||||
bool present = (bool)unit_func(unit_index, handler_export_symbol(handler));
|
||||
String handler_symbol = handler_export_symbol(handler);
|
||||
auto handler_fn = unit_func(unit_index, handler_symbol);
|
||||
bool present = (bool)handler_fn;
|
||||
entry_presence_us = phase_us();
|
||||
if(handler_present)
|
||||
*handler_present = present;
|
||||
if(!present)
|
||||
return("");
|
||||
// Invoke through the core: it resolves the same handler and runs ONCE +
|
||||
// dispatch (unit is pre-loaded above; resolution is cached). The core
|
||||
// entry takes two guest buffers — the unit path and the handler name.
|
||||
auto entry = core_func("uce_wasm_invoke_entry");
|
||||
// The entry unit is already loaded above. Place its handler and optional
|
||||
// ONCE export directly in the shared table, then let the core retain ONCE
|
||||
// deduplication and dispatch without resolving the same unit via hostcall.
|
||||
auto entry = core_func("uce_wasm_invoke_loaded_entry");
|
||||
if(!entry)
|
||||
return("core does not export uce_wasm_invoke_entry");
|
||||
int32_t path_ptr = 0, handler_ptr = 0;
|
||||
error = call_core("uce_alloc", { (int32_t)entry_source_path.size() }, &path_ptr);
|
||||
if(error != "" || path_ptr == 0)
|
||||
return(error == "" ? String("guest uce_alloc failed for entry path") : error);
|
||||
error = guest_write((u32)path_ptr, entry_source_path);
|
||||
return("core does not export uce_wasm_invoke_loaded_entry");
|
||||
auto link_handler = [&](const String& symbol, const std::optional<wasmtime::Func>& func, u32& slot) {
|
||||
if(!func)
|
||||
{
|
||||
slot = 0;
|
||||
return(String(""));
|
||||
}
|
||||
String slot_key = entry_source_path + ":" + symbol;
|
||||
auto cached = handler_slots.find(slot_key);
|
||||
if(cached != handler_slots.end())
|
||||
{
|
||||
slot = cached->second;
|
||||
return(String(""));
|
||||
}
|
||||
String link_error = place_funcref(*func, slot);
|
||||
if(link_error == "")
|
||||
handler_slots[slot_key] = slot;
|
||||
return(link_error);
|
||||
};
|
||||
u32 handler_slot = 0, once_slot = 0;
|
||||
error = link_handler(handler_symbol, handler_fn, handler_slot);
|
||||
String once_symbol = handler_export_symbol("once");
|
||||
if(error == "")
|
||||
{
|
||||
error = call_core("uce_alloc", { (int32_t)handler.size() }, &handler_ptr);
|
||||
if(error == "" && handler_ptr == 0)
|
||||
error = "guest uce_alloc failed for handler";
|
||||
}
|
||||
if(error == "")
|
||||
error = guest_write((u32)handler_ptr, handler);
|
||||
error = link_handler(once_symbol, unit_func(unit_index, once_symbol), once_slot);
|
||||
if(error != "")
|
||||
{
|
||||
if(path_ptr) call_core("uce_free", { path_ptr }, 0);
|
||||
if(handler_ptr) call_core("uce_free", { handler_ptr }, 0);
|
||||
return(error);
|
||||
}
|
||||
auto result = entry->call(ctx(), { wasmtime::Val(path_ptr), wasmtime::Val((int32_t)entry_source_path.size()),
|
||||
wasmtime::Val(handler_ptr), wasmtime::Val((int32_t)handler.size()) });
|
||||
call_core("uce_free", { path_ptr }, 0);
|
||||
call_core("uce_free", { handler_ptr }, 0);
|
||||
entry_link_us = phase_us();
|
||||
auto result = entry->call(ctx(), { wasmtime::Val((int32_t)handler_slot), wasmtime::Val((int32_t)once_slot) });
|
||||
entry_dispatch_us = phase_us();
|
||||
if(!result)
|
||||
return(trap_text(result.err()));
|
||||
return("");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user