Reuse core host definitions across requests
This commit is contained in:
parent
1ebf94b135
commit
6e4ef8ed7a
@ -218,7 +218,7 @@ Each request gets a fresh **workspace** — a per-request wasm instance tree wit
|
||||
the membrane wired in. `wasm_worker_serve(worker, ctx, entry_unit, handler)`
|
||||
(`src/wasm/worker.cpp`) is the single entry point for *every* mode:
|
||||
|
||||
1. Birth a request-scoped workspace with fresh per-request state. Current production workers instantiate a fresh workspace for each request; any future snapshot/CoW optimization must preserve that request-isolation contract.
|
||||
1. Birth a request-scoped workspace with fresh per-request state. Current production workers instantiate a fresh Store, table, core instance, memory, globals, constructors, and request tree for each request. The worker retains only Wasmtime's store-independent host-function definitions and their fixed import order, avoiding reconstruction of 73 identical callback definitions. Every callback resolves the one active request workspace at invocation. Any future snapshot/CoW optimization must preserve that request-isolation contract.
|
||||
2. Resolve `entry_unit` + `handler` to an export; components referenced at
|
||||
runtime are resolved on demand via the `uce_host_component_resolve` hostcall
|
||||
(`component_resolve()` → `__uce_<...>` slot), loading dependency modules
|
||||
@ -240,6 +240,15 @@ module cache, adding a visible cold-start request. Request state remains bounded
|
||||
by the fresh workspace and the normal per-request database/resource cleanup;
|
||||
faulted workers still exit and are replaced by the parent.
|
||||
|
||||
The request context remains UCEB2 so application `context.call` semantics do
|
||||
not fork into a private transport type. Validated decode trees move into the
|
||||
request rather than being deep-copied twice; the historical by-value
|
||||
`DValue::operator=(DValue)` symbol remains exported so warmed side-module
|
||||
artifacts stay ABI-compatible. `request_perf()` subdivides birth into policy,
|
||||
import materialization, core instantiation, export lookup, and initialization,
|
||||
and context transfer into bytes, host encode, guest allocation/write,
|
||||
guest decode/application, and free.
|
||||
|
||||
### Task callbacks and workspace lifetime
|
||||
|
||||
`task()` and `task_repeat()` are fork-backed. The `uce_host_task_spawn` hostcall
|
||||
|
||||
@ -23,6 +23,8 @@ Successful first loads within the request are counted by `unit_load_count` and d
|
||||
|
||||
The request log's `wasm-ready`, `wasm`, `workspace`, `invoke`, `collect`, and `post` fields complete the timing boundary after this in-page snapshot: compiled-artifact readiness before backend entry, native backend wall time, complete workspace wall time, entry invocation, guest-output/meta collection, and native response assembly after the backend. They do not expose request data. This distinction is useful when a completed FastCGI request takes longer than the page's mid-render `request_perf()` snapshot.
|
||||
|
||||
Workspace birth is subdivided into `birth_policy_us`, `birth_import_us`, `birth_instantiate_us`, `birth_exports_us`, and `birth_initialize_us`. Request-context transfer reports `context_bytes`, `context_encode_us`, `context_allocate_us`, `context_write_us`, `context_guest_apply_us`, and `context_free_us`. These bounded aggregate fields expose sizes and timing only, never request values.
|
||||
|
||||
Wasm FastCGI workers retain up to `MYSQL_PERSISTENT_POOL_SIZE` credential-keyed MySQL connections (default `8`; set `0` to disable). UCE calls the client library's connection-reset operation before another request receives a cached connection, clearing transactions, temporary tables, session variables, and selected databases while avoiding a new authentication handshake. Same-request leases continue to share state until request cleanup.
|
||||
|
||||
:example
|
||||
|
||||
@ -86,6 +86,16 @@ RENDER(Request& context)
|
||||
DValue nav_dash_summary = nav_dash.filter({"title"});
|
||||
check("DValue collection helpers", empty_pop.to_string() == "" && app_nav.is_list() && nav_titles["0"].to_string() == "Dashboard" && join(nav_dash_summary.keys(), ",") == "title" && nav_dash.values()["0"].to_string() == "app" && nav_dash.values()["1"].to_string() == "Dashboard", json_encode(nav_titles));
|
||||
|
||||
DValue copy_source; copy_source["nested"]["value"] = "original";
|
||||
DValue copied = copy_source;
|
||||
copied["nested"]["value"] = "copy";
|
||||
DValue move_source; move_source["nested"]["value"] = "moved";
|
||||
DValue moved = std::move(move_source);
|
||||
DValue reference_target; DValue reference; reference.set_reference(&reference_target);
|
||||
DValue reference_source; reference_source["nested"]["value"] = "target";
|
||||
reference = reference_source;
|
||||
check("DValue copy/move assignment preserves value and reference semantics", copy_source["nested"]["value"].to_string() == "original" && copied["nested"]["value"].to_string() == "copy" && moved["nested"]["value"].to_string() == "moved" && reference_target["nested"]["value"].to_string() == "target", json_encode(moved) + " / " + json_encode(reference_target));
|
||||
|
||||
DValue conv;
|
||||
conv["name"] = "ada";
|
||||
conv["count"] = "12";
|
||||
|
||||
@ -162,7 +162,9 @@ RENDER(Request& context)
|
||||
bool unit_module_profile_valid = serialized_reads_bounded && unit_operation_profile_valid && perf["unit_module_cache_hit_count"].to_u64() + module_misses == perf["unit_load_count"].to_u64() && serialized_hits + perf["unit_module_compile_count"].to_u64() == module_misses && perf["unit_module_lookup_us"].type != 'S' && perf["unit_module_read_us"].type != 'S' && perf["unit_module_read_bytes"].type != 'S' && perf["unit_module_parse_us"].type != 'S' && perf["unit_module_compile_us"].type != 'S' && perf["unit_module_classify_us"].type != 'S';
|
||||
bool transport_profile_valid = perf["transport_params_us"].type != 'S' && perf["transport_input_us"].type != 'S' && perf["handler_queue_us"].type != 'S' && perf["accept_us"].to_f64() + 2 >= perf["transport_params_us"].to_f64() + perf["transport_input_us"].to_f64() + perf["handler_queue_us"].to_f64();
|
||||
u64 phase_cpu_us = perf["workspace_setup_cpu_us"].to_u64() + perf["workspace_birth_cpu_us"].to_u64() + perf["context_apply_cpu_us"].to_u64();
|
||||
bool workspace_cpu_profile_valid = perf["workspace_wall_us"].to_u64() > 0 && perf["workspace_cpu_us"].to_u64() > 0 && perf["workspace_cpu_us"].to_u64() <= perf["workspace_wall_us"].to_u64() + 2 && (perf["workspace_cpu_us"].to_u64() >= perf["workspace_wall_us"].to_u64() ? perf["workspace_wait_us"].to_u64() == 0 : perf["workspace_wait_us"].to_u64() + perf["workspace_cpu_us"].to_u64() == perf["workspace_wall_us"].to_u64()) && perf["workspace_setup_cpu_us"].to_u64() <= perf["workspace_setup_us"].to_u64() + 2 && perf["workspace_birth_cpu_us"].to_u64() <= perf["workspace_birth_us"].to_u64() + 2 && perf["context_apply_cpu_us"].to_u64() <= perf["context_apply_us"].to_u64() + 2 && phase_cpu_us + perf["execution_cpu_us"].to_u64() == perf["workspace_cpu_us"].to_u64();
|
||||
u64 birth_profile_us = perf["birth_policy_us"].to_u64() + perf["birth_import_us"].to_u64() + perf["birth_instantiate_us"].to_u64() + perf["birth_exports_us"].to_u64() + perf["birth_initialize_us"].to_u64();
|
||||
u64 context_profile_us = perf["context_encode_us"].to_u64() + perf["context_allocate_us"].to_u64() + perf["context_write_us"].to_u64() + perf["context_guest_apply_us"].to_u64() + perf["context_free_us"].to_u64();
|
||||
bool workspace_cpu_profile_valid = perf["workspace_wall_us"].to_u64() > 0 && perf["workspace_cpu_us"].to_u64() > 0 && perf["workspace_cpu_us"].to_u64() <= perf["workspace_wall_us"].to_u64() + 2 && (perf["workspace_cpu_us"].to_u64() >= perf["workspace_wall_us"].to_u64() ? perf["workspace_wait_us"].to_u64() == 0 : perf["workspace_wait_us"].to_u64() + perf["workspace_cpu_us"].to_u64() == perf["workspace_wall_us"].to_u64()) && perf["workspace_setup_cpu_us"].to_u64() <= perf["workspace_setup_us"].to_u64() + 2 && perf["workspace_birth_cpu_us"].to_u64() <= perf["workspace_birth_us"].to_u64() + 2 && perf["context_apply_cpu_us"].to_u64() <= perf["context_apply_us"].to_u64() + 2 && birth_profile_us <= perf["workspace_birth_us"].to_u64() && perf["context_bytes"].to_u64() > 0 && context_profile_us <= perf["context_apply_us"].to_u64() && phase_cpu_us + perf["execution_cpu_us"].to_u64() == perf["workspace_cpu_us"].to_u64();
|
||||
u64 operation_hostcalls = 0;
|
||||
u64 operation_hostcall_us = 0;
|
||||
u64 operation_hostcall_cpu_us = 0;
|
||||
|
||||
@ -652,7 +652,7 @@ void DValue::set_bool(bool b)
|
||||
_list_mode = false;
|
||||
}
|
||||
|
||||
void DValue::set(DValue source)
|
||||
void DValue::set(const DValue& source)
|
||||
{
|
||||
DValue* target = reference_target();
|
||||
if(target)
|
||||
@ -691,6 +691,42 @@ void DValue::set(DValue source)
|
||||
}
|
||||
}
|
||||
|
||||
void DValue::set(DValue&& source)
|
||||
{
|
||||
DValue* target = reference_target();
|
||||
if(target)
|
||||
{
|
||||
target->set(std::move(source));
|
||||
return;
|
||||
}
|
||||
set_type(source.type);
|
||||
switch(type)
|
||||
{
|
||||
case('S'):
|
||||
_String = std::move(source._String);
|
||||
_list_mode = false;
|
||||
break;
|
||||
case('F'):
|
||||
_float = source._float;
|
||||
_list_mode = false;
|
||||
break;
|
||||
case('B'):
|
||||
_bool = source._bool;
|
||||
_list_mode = false;
|
||||
break;
|
||||
case('M'):
|
||||
_map = std::move(source._map);
|
||||
_array_index = source._array_index;
|
||||
_list_mode = source._list_mode;
|
||||
break;
|
||||
case('P'):
|
||||
case('R'):
|
||||
_ptr = source._ptr;
|
||||
_list_mode = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DValue::set(StringMap source)
|
||||
{
|
||||
DValue* target = reference_target();
|
||||
@ -782,7 +818,7 @@ DValue& DValue::operator [] (String s) {
|
||||
void DValue::operator = (String v) { set(v); }
|
||||
void DValue::operator = (f64 v) { set(v); }
|
||||
void DValue::operator = (void* v) { set(v); }
|
||||
void DValue::operator = (DValue v) { set(v); }
|
||||
void DValue::operator = (DValue v) { set(std::move(v)); }
|
||||
void DValue::operator = (StringMap v) { set(v); }
|
||||
|
||||
void DValue::push(const DValue& child)
|
||||
@ -1076,7 +1112,7 @@ bool ucb_decode_node(const String& src, size_t& offset, DValue& out, String& err
|
||||
DValue child;
|
||||
if(!ucb_decode_node(src, offset, child, error, depth + 1))
|
||||
return(false);
|
||||
out[key] = child;
|
||||
out[key] = std::move(child);
|
||||
}
|
||||
return(true);
|
||||
}
|
||||
@ -1126,7 +1162,7 @@ bool ucb_decode(const String& encoded, DValue& out, String* error_out)
|
||||
DValue decoded;
|
||||
if(ucb_decode_node(encoded, offset, decoded, error) && offset == encoded.size())
|
||||
{
|
||||
out = decoded;
|
||||
out = std::move(decoded);
|
||||
if(error_out)
|
||||
*error_out = "";
|
||||
return(true);
|
||||
|
||||
@ -8,6 +8,9 @@ String json_escape(String s, char quote_char = '"');
|
||||
// JSON-decoded values, and metadata trees can be consumed without repetitive
|
||||
// manual parsing at each call site.
|
||||
struct DValue {
|
||||
DValue() = default;
|
||||
DValue(const DValue&) = default;
|
||||
DValue(DValue&&) = default;
|
||||
|
||||
char type = 'S';
|
||||
|
||||
@ -49,7 +52,8 @@ struct DValue {
|
||||
void set(void* p);
|
||||
void set(f64 f);
|
||||
void set_bool(bool b);
|
||||
void set(DValue source);
|
||||
void set(const DValue& source);
|
||||
void set(DValue&& source);
|
||||
void set(StringMap source);
|
||||
void set_array();
|
||||
void set_reference(DValue* target);
|
||||
|
||||
@ -818,7 +818,8 @@ int uce_wasm_apply_context(const char* buf, size_t len)
|
||||
uce_host_log(3, error.data(), error.size());
|
||||
return(1);
|
||||
}
|
||||
wasm_request.call = decoded;
|
||||
wasm_request.call = std::move(decoded);
|
||||
DValue& applied = wasm_request.call;
|
||||
auto apply_map = [](DValue* source, StringMap& dest) {
|
||||
dest.clear();
|
||||
if(source)
|
||||
@ -826,23 +827,23 @@ int uce_wasm_apply_context(const char* buf, size_t len)
|
||||
dest[key] = item.to_string();
|
||||
});
|
||||
};
|
||||
if(decoded.key("server_config"))
|
||||
apply_map(decoded.key("server_config"), wasm_server.config);
|
||||
apply_map(decoded.key("params"), wasm_request.params);
|
||||
apply_map(decoded.key("get"), wasm_request.get);
|
||||
apply_map(decoded.key("post"), wasm_request.post);
|
||||
apply_map(decoded.key("cookies"), wasm_request.cookies);
|
||||
apply_map(decoded.key("session"), wasm_request.session);
|
||||
if(DValue* session_id = decoded.key("session_id"))
|
||||
if(applied.key("server_config"))
|
||||
apply_map(applied.key("server_config"), wasm_server.config);
|
||||
apply_map(applied.key("params"), wasm_request.params);
|
||||
apply_map(applied.key("get"), wasm_request.get);
|
||||
apply_map(applied.key("post"), wasm_request.post);
|
||||
apply_map(applied.key("cookies"), wasm_request.cookies);
|
||||
apply_map(applied.key("session"), wasm_request.session);
|
||||
if(DValue* session_id = applied.key("session_id"))
|
||||
wasm_request.session_id = session_id->to_string();
|
||||
if(DValue* session_name = decoded.key("session_name"))
|
||||
if(DValue* session_name = applied.key("session_name"))
|
||||
wasm_request.session_name = session_name->to_string();
|
||||
if(DValue* session_loaded_hash = decoded.key("session_loaded_hash"))
|
||||
if(DValue* session_loaded_hash = applied.key("session_loaded_hash"))
|
||||
wasm_request.session_loaded_hash = session_loaded_hash->to_string();
|
||||
DValue* entry = decoded.key("entry_unit");
|
||||
DValue* entry = applied.key("entry_unit");
|
||||
if(entry)
|
||||
wasm_request.resources.current_unit_file = entry->to_string();
|
||||
DValue* raw_in = decoded.key("in");
|
||||
DValue* raw_in = applied.key("in");
|
||||
wasm_request.in = raw_in ? raw_in->to_string() : "";
|
||||
// websocket event context: ws_send()/ws_close() capture into the dispatch
|
||||
// list (the workspace owns no connections), which collect() carries back to
|
||||
@ -851,7 +852,7 @@ int uce_wasm_apply_context(const char* buf, size_t len)
|
||||
wasm_request.resources.websocket_connection_state_before = DValue();
|
||||
wasm_request.resources.websocket_dispatch_commands = DValue();
|
||||
wasm_request.resources.websocket_dispatch_capture = false;
|
||||
DValue* ws = decoded.key("ws");
|
||||
DValue* ws = applied.key("ws");
|
||||
if(ws)
|
||||
{
|
||||
wasm_request.resources.websocket_connection_id = (*ws)["connection_id"].to_string();
|
||||
|
||||
@ -172,8 +172,19 @@ struct WasmRequestProfile
|
||||
u64 workspace_setup_cpu_us = 0;
|
||||
u64 workspace_birth_us = 0;
|
||||
u64 workspace_birth_cpu_us = 0;
|
||||
u64 birth_policy_us = 0;
|
||||
u64 birth_import_us = 0;
|
||||
u64 birth_instantiate_us = 0;
|
||||
u64 birth_exports_us = 0;
|
||||
u64 birth_initialize_us = 0;
|
||||
u64 context_apply_us = 0;
|
||||
u64 context_apply_cpu_us = 0;
|
||||
u64 context_bytes = 0;
|
||||
u64 context_encode_us = 0;
|
||||
u64 context_allocate_us = 0;
|
||||
u64 context_write_us = 0;
|
||||
u64 context_guest_apply_us = 0;
|
||||
u64 context_free_us = 0;
|
||||
u64 entry_invoke_us = 0;
|
||||
u64 output_collect_us = 0;
|
||||
u64 workspace_complete_us = 0;
|
||||
@ -862,6 +873,8 @@ static bool wasm_read_metadata_file(const String& path, std::vector<u8>& metadat
|
||||
|
||||
// ---- worker (per process): engine + compiled module caches ----------------
|
||||
|
||||
class WasmWorkspace;
|
||||
|
||||
class WasmWorker
|
||||
{
|
||||
public:
|
||||
@ -951,6 +964,21 @@ public:
|
||||
|
||||
wasmtime::Engine engine;
|
||||
std::optional<wasmtime::Module> core_module;
|
||||
std::optional<wasmtime::Linker> core_linker;
|
||||
struct CoreImport
|
||||
{
|
||||
String module;
|
||||
String name;
|
||||
bool table = false;
|
||||
};
|
||||
std::vector<CoreImport> core_imports;
|
||||
u32 core_table_min = 0;
|
||||
bool core_table_imported = false;
|
||||
u64 core_host_import_count = 0;
|
||||
// A render worker dispatches one workspace at a time. Persistent host Func
|
||||
// definitions resolve their request-owned state through this pointer; forked
|
||||
// task children inherit the live workspace copy before the parent drops it.
|
||||
WasmWorkspace* active_workspace = 0;
|
||||
WasmDylinkInfo core_dylink;
|
||||
|
||||
// unit artifact path in the W2 cache: cache_root + <abs source dir> + /<file>.wasm
|
||||
@ -1257,6 +1285,7 @@ public:
|
||||
|
||||
explicit WasmWorkspace(WasmWorker& w) : worker(w), store(w.engine)
|
||||
{
|
||||
worker.active_workspace = this;
|
||||
}
|
||||
|
||||
void set_perf_snapshot(u64 worker_pid, u64 parent_pid, u64 request_count,
|
||||
@ -1287,6 +1316,8 @@ public:
|
||||
#endif
|
||||
~WasmWorkspace()
|
||||
{
|
||||
if(worker.active_workspace == this)
|
||||
worker.active_workspace = 0;
|
||||
for(auto& h : file_handles)
|
||||
{
|
||||
if(h.fd >= 0)
|
||||
@ -1341,50 +1372,82 @@ public:
|
||||
|
||||
String birth()
|
||||
{
|
||||
auto phase_start = std::chrono::steady_clock::now();
|
||||
auto phase_us = [&]() -> u64 {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
u64 elapsed = (u64)std::chrono::duration_cast<std::chrono::microseconds>(now - phase_start).count();
|
||||
phase_start = now;
|
||||
return(elapsed);
|
||||
};
|
||||
auto cx = ctx();
|
||||
store.limiter(worker.cfg.memory_limit, -1, -1, -1, -1);
|
||||
cx.set_epoch_deadline(worker.cfg.epoch_deadline_ticks);
|
||||
birth_policy_us = phase_us();
|
||||
|
||||
auto& module = *worker.core_module;
|
||||
std::vector<wasmtime::Extern> imports;
|
||||
for(auto import_type : module.imports())
|
||||
if(!worker.core_linker)
|
||||
{
|
||||
String mod(import_type.module());
|
||||
String name(import_type.name());
|
||||
auto extern_type = wasmtime::ExternType::from_import(import_type);
|
||||
if(auto* table_ref = std::get_if<wasmtime::TableType::Ref>(&extern_type))
|
||||
worker.core_linker.emplace(worker.engine);
|
||||
for(auto import_type : module.imports())
|
||||
{
|
||||
String mod(import_type.module());
|
||||
String name(import_type.name());
|
||||
auto extern_type = wasmtime::ExternType::from_import(import_type);
|
||||
if(auto* table_ref = std::get_if<wasmtime::TableType::Ref>(&extern_type))
|
||||
{
|
||||
if(name.rfind("__indirect_function_table", 0) != 0)
|
||||
return("core imports unexpected table " + mod + "." + name);
|
||||
worker.core_table_min = table_ref->min();
|
||||
worker.core_table_imported = true;
|
||||
worker.core_imports.push_back({ mod, name, true });
|
||||
continue;
|
||||
}
|
||||
auto* func_ref = std::get_if<wasmtime::FuncType::Ref>(&extern_type);
|
||||
if(!func_ref)
|
||||
return("core has unexpected non-func import " + mod + "." + name);
|
||||
wasmtime::FuncType func_type(*func_ref);
|
||||
String error = make_host_import(*worker.core_linker, mod, name, func_type);
|
||||
if(error != "")
|
||||
return(error);
|
||||
worker.core_imports.push_back({ mod, name, false });
|
||||
}
|
||||
}
|
||||
if(!worker.core_table_imported)
|
||||
return("core does not import __indirect_function_table — rebuild core with --import-table");
|
||||
std::vector<wasmtime::Extern> imports;
|
||||
u32 total = worker.core_table_min + worker.cfg.table_headroom;
|
||||
wasmtime::TableType table_type(wasmtime::ValType::funcref(), total, total);
|
||||
auto table_created = wasmtime::Table::create(cx, table_type, wasmtime::Val(std::optional<wasmtime::Func>()));
|
||||
if(!table_created)
|
||||
return("table create failed: " + String(table_created.err().message()));
|
||||
table.emplace(table_created.ok());
|
||||
table_next_free = worker.core_table_min;
|
||||
for(auto& import : worker.core_imports)
|
||||
{
|
||||
if(import.table)
|
||||
{
|
||||
if(name.rfind("__indirect_function_table", 0) != 0)
|
||||
return("core imports unexpected table " + mod + "." + name);
|
||||
u32 core_min = table_ref->min();
|
||||
u32 total = core_min + worker.cfg.table_headroom;
|
||||
wasmtime::TableType table_type(wasmtime::ValType::funcref(), total, total);
|
||||
auto created = wasmtime::Table::create(cx, table_type, wasmtime::Val(std::optional<wasmtime::Func>()));
|
||||
if(!created)
|
||||
return("table create failed: " + String(created.err().message()));
|
||||
table.emplace(created.ok());
|
||||
table_next_free = core_min;
|
||||
imports.push_back(*table);
|
||||
continue;
|
||||
}
|
||||
auto* func_ref = std::get_if<wasmtime::FuncType::Ref>(&extern_type);
|
||||
if(!func_ref)
|
||||
return("core has unexpected non-func import " + mod + "." + name);
|
||||
wasmtime::FuncType func_type(*func_ref);
|
||||
imports.push_back(make_host_import(cx, mod, name, func_type));
|
||||
auto host_import = worker.core_linker->get(cx, import.module, import.name);
|
||||
if(!host_import)
|
||||
return("core host import unavailable: " + import.module + "." + import.name);
|
||||
imports.push_back(*host_import);
|
||||
}
|
||||
if(!table)
|
||||
return("core does not import __indirect_function_table — rebuild core with --import-table");
|
||||
hostcall_operation_slots = worker.hostcall_operation_names.size();
|
||||
birth_import_us = phase_us();
|
||||
|
||||
auto created = wasmtime::Instance::create(cx, module, imports);
|
||||
if(!created)
|
||||
return("core instantiation failed: " + trap_text(created.err()));
|
||||
core.emplace(created.ok());
|
||||
birth_instantiate_us = phase_us();
|
||||
|
||||
auto exported_memory = core->get(cx, "memory");
|
||||
if(!exported_memory || !std::get_if<wasmtime::Memory>(&*exported_memory))
|
||||
return("core does not export memory");
|
||||
memory.emplace(std::get<wasmtime::Memory>(*exported_memory));
|
||||
birth_exports_us = phase_us();
|
||||
|
||||
String error = call_core("_initialize", {}, 0);
|
||||
if(error != "")
|
||||
@ -1403,6 +1466,7 @@ public:
|
||||
error = call_core("uce_wasm_request", {}, &request_ptr);
|
||||
if(error != "")
|
||||
return(error);
|
||||
birth_initialize_us = phase_us();
|
||||
if(request_ptr == 0)
|
||||
return("core returned null Request*");
|
||||
return("");
|
||||
@ -1410,21 +1474,34 @@ public:
|
||||
|
||||
String apply_context(const DValue& context_tree)
|
||||
{
|
||||
auto phase_start = std::chrono::steady_clock::now();
|
||||
auto phase_us = [&]() -> u64 {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
u64 elapsed = (u64)std::chrono::duration_cast<std::chrono::microseconds>(now - phase_start).count();
|
||||
phase_start = now;
|
||||
return(elapsed);
|
||||
};
|
||||
String encoded = ucb_encode(context_tree);
|
||||
context_bytes = encoded.size();
|
||||
context_encode_us = phase_us();
|
||||
int32_t guest_ptr = 0;
|
||||
String error = call_core("uce_alloc", { (int32_t)encoded.size() }, &guest_ptr);
|
||||
context_allocate_us = phase_us();
|
||||
if(error != "")
|
||||
return(error);
|
||||
if(guest_ptr == 0)
|
||||
return("guest uce_alloc failed for context buffer");
|
||||
error = guest_write((u32)guest_ptr, encoded);
|
||||
context_write_us = phase_us();
|
||||
if(error != "")
|
||||
return(error);
|
||||
int32_t rc = 0;
|
||||
error = call_core("uce_wasm_apply_context", { guest_ptr, (int32_t)encoded.size() }, &rc);
|
||||
context_guest_apply_us = phase_us();
|
||||
if(error != "")
|
||||
return(error);
|
||||
call_core("uce_free", { guest_ptr }, 0);
|
||||
context_free_us = phase_us();
|
||||
if(rc != 0)
|
||||
return("uce_wasm_apply_context returned " + std::to_string(rc));
|
||||
return("");
|
||||
@ -1532,7 +1609,6 @@ private:
|
||||
std::vector<LoadedUnit> units;
|
||||
std::map<String, size_t> units_by_source;
|
||||
std::map<String, u32> handler_slots; // source + ":" + symbol → table slot
|
||||
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;
|
||||
@ -2315,18 +2391,22 @@ private:
|
||||
|
||||
// ---- host imports for the core -----------------------------------------
|
||||
|
||||
wasmtime::Extern make_host_import(wasmtime::Store::Context cx, const String& mod, const String& name, const wasmtime::FuncType& func_type)
|
||||
String make_host_import(wasmtime::Linker& linker, const String& mod, const String& name, const wasmtime::FuncType& func_type)
|
||||
{
|
||||
using namespace wasmtime;
|
||||
WasmWorkspace* self = this;
|
||||
struct ActiveWorkspace
|
||||
{
|
||||
WasmWorker* worker;
|
||||
WasmWorkspace* operator->() const { return(worker->active_workspace); }
|
||||
};
|
||||
ActiveWorkspace self = { &worker };
|
||||
|
||||
auto add = [&](auto&& callback) -> Extern {
|
||||
u64 profile_index = self->hostcall_operation_slots;
|
||||
auto add = [&](auto&& callback) -> String {
|
||||
u64 profile_index = worker.core_host_import_count++;
|
||||
if(profile_index < WasmRequestProfile::HOSTCALL_OPERATION_MAX)
|
||||
{
|
||||
self->hostcall_operation_slots++;
|
||||
if(self->worker.hostcall_operation_names.size() <= profile_index)
|
||||
self->worker.hostcall_operation_names.push_back(name);
|
||||
if(worker.hostcall_operation_names.size() <= profile_index)
|
||||
worker.hostcall_operation_names.push_back(name);
|
||||
}
|
||||
bool profile_enabled = name != "uce_host_request_perf";
|
||||
bool profile_mysql = name == "uce_host_mysql";
|
||||
@ -2368,15 +2448,16 @@ private:
|
||||
}
|
||||
return(result);
|
||||
};
|
||||
Func func(cx, func_type, profiled);
|
||||
host_funcs.push_back(func);
|
||||
return(host_funcs.back());
|
||||
auto defined = linker.func_new(mod, name, func_type, profiled);
|
||||
if(!defined)
|
||||
return("core host import failed: " + mod + "." + name + ": " + String(defined.err().message()));
|
||||
return("");
|
||||
};
|
||||
|
||||
// Hostcall blocklist (UCE_HOSTCALL_BLOCKLIST): a sysadmin-disabled hostcall
|
||||
// resolves to a trap stub instead of its real implementation, so a unit
|
||||
// invoking it fails at runtime into the configurable error page. The
|
||||
// decision is made once per import at workspace birth — no per-call cost,
|
||||
// decision is made once per worker when imports are defined — no per-call cost,
|
||||
// and zero cost when nothing is blocked. A small core set stays exempt so
|
||||
// the runtime itself cannot be bricked.
|
||||
if(mod == "env" && !worker.cfg.hostcall_blocklist.empty() && name.rfind("uce_host_", 0) == 0)
|
||||
@ -2437,8 +2518,19 @@ private:
|
||||
response["workspace_setup_cpu_us"] = (f64)self->workspace_setup_cpu_us;
|
||||
response["workspace_birth_us"] = (f64)self->workspace_birth_us;
|
||||
response["workspace_birth_cpu_us"] = (f64)self->workspace_birth_cpu_us;
|
||||
response["birth_policy_us"] = (f64)self->birth_policy_us;
|
||||
response["birth_import_us"] = (f64)self->birth_import_us;
|
||||
response["birth_instantiate_us"] = (f64)self->birth_instantiate_us;
|
||||
response["birth_exports_us"] = (f64)self->birth_exports_us;
|
||||
response["birth_initialize_us"] = (f64)self->birth_initialize_us;
|
||||
response["context_apply_us"] = (f64)self->context_apply_us;
|
||||
response["context_apply_cpu_us"] = (f64)self->context_apply_cpu_us;
|
||||
response["context_bytes"] = (f64)self->context_bytes;
|
||||
response["context_encode_us"] = (f64)self->context_encode_us;
|
||||
response["context_allocate_us"] = (f64)self->context_allocate_us;
|
||||
response["context_write_us"] = (f64)self->context_write_us;
|
||||
response["context_guest_apply_us"] = (f64)self->context_guest_apply_us;
|
||||
response["context_free_us"] = (f64)self->context_free_us;
|
||||
u64 phase_cpu_us = self->workspace_setup_cpu_us + self->workspace_birth_cpu_us + self->context_apply_cpu_us;
|
||||
response["execution_cpu_us"] = (f64)(workspace_cpu_us > phase_cpu_us ? workspace_cpu_us - phase_cpu_us : 0);
|
||||
response["hostcall_count"] = (f64)self->hostcall_count;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user