Cache immutable Wasm server configuration encoding
This commit is contained in:
parent
a043fb8fc7
commit
516ed94032
@ -87,9 +87,11 @@ the boundary).
|
||||
children yielding `const DValue&`.
|
||||
- The request context (`params`/`get`/`post`/`cookies`/`session`, the raw body
|
||||
`in`, and—for WS—the connection context) is marshalled into a single `ctx`
|
||||
DValue, UCEB-encoded, and handed to the workspace. The response (body,
|
||||
headers, status, and any `meta` such as `ws_commands`) comes back the same
|
||||
way.
|
||||
DValue and UCEB-encoded. Immutable server configuration is UCEB-encoded once
|
||||
per worker as a flat string map; both byte ranges are written into one guest
|
||||
buffer. The guest decodes the flat map directly into its fresh `Server` and
|
||||
the dynamic tree into its fresh `Request`. The response (body, headers,
|
||||
status, and any `meta` such as `ws_commands`) comes back as UCEB.
|
||||
|
||||
See [`docs/wasm-phase1-dvalue-abi.md`](wasm-phase1-dvalue-abi.md) for the wire
|
||||
format details.
|
||||
@ -160,10 +162,11 @@ drops one empty workspace. This preserves Wasmtime's fork boundary while
|
||||
preventing the first request assigned to each worker from paying engine, linker,
|
||||
or pre-instantiation startup.
|
||||
Server configuration is immutable by that point. The worker therefore retains
|
||||
one native `DValue` view of it; each request's temporary context tree references
|
||||
that view while UCEB is encoded, avoiding a repeated native tree copy. The guest
|
||||
still decodes the bytes into its fresh request tree. Request parameters, body,
|
||||
cookies, session, call data, and response state are never retained this way.
|
||||
one native `DValue` view and one UCEB encoding of it. Each request transfers
|
||||
those cached bytes and the guest decodes the flat scalar map directly into its
|
||||
fresh `Server`, avoiding both repeated native encoding and an intermediate guest
|
||||
`DValue` tree. Request parameters, body, cookies, session, call data, and
|
||||
response state are never retained this way.
|
||||
Startup duration or failure is written to the service log. The serialized core
|
||||
module lives in the configured writable cache root rather than beside the
|
||||
possibly root-owned deployed `core.wasm`; freshness still uses the deployed
|
||||
@ -285,7 +288,8 @@ request rather than being deep-copied twice; the historical by-value
|
||||
artifacts stay ABI-compatible. `request_perf()` subdivides birth into policy,
|
||||
import materialization, core instantiation, export/table lookup, and initialization,
|
||||
and context transfer into bytes, host encode, guest allocation/write,
|
||||
guest decode/application, and free.
|
||||
guest decode/application, and free. The byte profile separately reports the
|
||||
worker-cached server-configuration portion.
|
||||
|
||||
### Task callbacks and workspace lifetime
|
||||
|
||||
|
||||
@ -51,6 +51,7 @@ RENDER(Request& context)
|
||||
check("str_ends_with()", str_ends_with("component.uce", ".uce"), "component.uce ends with .uce");
|
||||
check("to_lower() / to_upper()", to_lower("MiXeD") == "mixed" && to_upper("MiXeD") == "MIXED", to_lower("MiXeD") + " / " + to_upper("MiXeD"));
|
||||
check("request context params", context.params["SCRIPT_URL"] != "" && context.params["BASE_URL"] != "" && context.params["ROUTE_PATH"] != "" && context.params["ROUTE_PAGE"] != "" && context.params["ROUTE_VALID"] == "1", "script=" + context.params["SCRIPT_URL"] + " base=" + context.params["BASE_URL"] + " route=" + context.params["ROUTE_PATH"] + " page=" + context.params["ROUTE_PAGE"] + " valid=" + context.params["ROUTE_VALID"]);
|
||||
check("request server configuration", context.server && context.server->config["BIN_DIRECTORY"] != "" && context.server->config["SITE_DIRECTORY"] != "" && context.server->config["SESSION_PATH"] != "" && context.server->config["WASM_CORE_PATH"] != "", "bin=" + context.server->config["BIN_DIRECTORY"] + " site=" + context.server->config["SITE_DIRECTORY"] + " session=" + context.server->config["SESSION_PATH"] + " core=" + context.server->config["WASM_CORE_PATH"]);
|
||||
String saved_query_string = context.params["QUERY_STRING"];
|
||||
context.params["QUERY_STRING"] = "workspace/projects&theme=dark";
|
||||
check("request_query_path() delegates to request_query_route()", request_query_path(context) == request_query_route(context)["l_path"].to_string() && request_query_path(context) == "workspace/projects", request_query_path(context) + " / " + json_encode(request_query_route(context)));
|
||||
|
||||
@ -1175,6 +1175,87 @@ bool ucb_decode(const String& encoded, DValue& out, String* error_out)
|
||||
return(false);
|
||||
}
|
||||
|
||||
#ifdef __UCE_WASM_CORE__
|
||||
static bool ucb_decode_flat_string_map(const String& encoded, StringMap& out, String* error_out)
|
||||
{
|
||||
String error;
|
||||
StringMap decoded;
|
||||
size_t offset = 0;
|
||||
auto fail = [&](String message) {
|
||||
error = message;
|
||||
return(false);
|
||||
};
|
||||
if(encoded.size() < 5 || encoded.compare(0, 4, UCEB_MAGIC) != 0)
|
||||
fail("missing UCEB magic header");
|
||||
else if((u8)encoded[4] != UCEB_VERSION)
|
||||
fail("unsupported UCEB version");
|
||||
else
|
||||
{
|
||||
offset = 5;
|
||||
if(encoded.size() - offset < 2)
|
||||
fail("unexpected end of UCEB2 string map");
|
||||
else
|
||||
{
|
||||
u8 flags = (u8)encoded[offset++];
|
||||
char type = encoded[offset++];
|
||||
u64 scalar_len = 0, child_count = 0;
|
||||
if(flags != 0 || type != 'M')
|
||||
fail("UCEB2 string map root must be a map");
|
||||
else if(!ucb_read_varint(encoded, offset, scalar_len) || scalar_len != 0)
|
||||
fail("UCEB2 string map root must have no scalar");
|
||||
else if(!ucb_read_varint(encoded, offset, child_count))
|
||||
fail("invalid UCEB2 string map child count");
|
||||
else
|
||||
{
|
||||
for(u64 i = 0; error == "" && i < child_count; i++)
|
||||
{
|
||||
u64 key_len = 0, value_len = 0, value_children = 0;
|
||||
if(!ucb_read_varint(encoded, offset, key_len) || key_len > encoded.size() - offset)
|
||||
{
|
||||
fail("invalid UCEB2 string map key");
|
||||
break;
|
||||
}
|
||||
String key(encoded.data() + offset, (size_t)key_len);
|
||||
offset += (size_t)key_len;
|
||||
if(encoded.size() - offset < 2)
|
||||
{
|
||||
fail("unexpected end of UCEB2 string map value");
|
||||
break;
|
||||
}
|
||||
u8 value_flags = (u8)encoded[offset++];
|
||||
char value_type = encoded[offset++];
|
||||
if(value_flags != 0 || value_type != 'S' || !ucb_read_varint(encoded, offset, value_len) || value_len > encoded.size() - offset)
|
||||
{
|
||||
fail("UCEB2 string map value must be a string scalar");
|
||||
break;
|
||||
}
|
||||
String value(encoded.data() + offset, (size_t)value_len);
|
||||
offset += (size_t)value_len;
|
||||
if(!ucb_read_varint(encoded, offset, value_children) || value_children != 0)
|
||||
{
|
||||
fail("UCEB2 string map value cannot have children");
|
||||
break;
|
||||
}
|
||||
decoded[std::move(key)] = std::move(value);
|
||||
}
|
||||
if(error == "" && offset != encoded.size())
|
||||
fail("trailing bytes after UCEB2 string map");
|
||||
}
|
||||
}
|
||||
}
|
||||
if(error != "")
|
||||
{
|
||||
if(error_out)
|
||||
*error_out = error;
|
||||
return(false);
|
||||
}
|
||||
out = std::move(decoded);
|
||||
if(error_out)
|
||||
*error_out = "";
|
||||
return(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
DValue ucb_decode(const String& encoded)
|
||||
{
|
||||
DValue out;
|
||||
|
||||
@ -68,10 +68,10 @@ static String wasm_backend_ensure_started(Request* context)
|
||||
}
|
||||
|
||||
g_wasm_worker = new WasmWorker(wc);
|
||||
// Server configuration is finalized before render workers start. Retain one
|
||||
// native tree so per-request UCEB encoding can reference it without rebuilding
|
||||
// dozens of identical nodes; the guest still decodes it into fresh state.
|
||||
// Server configuration is finalized before render workers start. Encode it
|
||||
// once; each fresh guest decodes the cached flat map into its own Server.
|
||||
g_wasm_worker->server_config_context = cfg;
|
||||
g_wasm_worker->server_config_encoded = ucb_encode(g_wasm_worker->server_config_context);
|
||||
g_wasm_init_error = g_wasm_worker->init();
|
||||
if(g_wasm_init_error == "")
|
||||
g_wasm_init_error = wasm_worker_prepare(*g_wasm_worker);
|
||||
@ -159,8 +159,6 @@ String wasm_backend_serve(Request& request, const String& entry_unit, const Stri
|
||||
for(auto& entry : source)
|
||||
ctx[key][entry.first] = entry.second;
|
||||
};
|
||||
if(request.server)
|
||||
ctx["server_config"].set_reference(&g_wasm_worker->server_config_context);
|
||||
copy_map(request.params, "params");
|
||||
copy_map(request.get, "get");
|
||||
copy_map(request.post, "post");
|
||||
@ -240,7 +238,21 @@ String wasm_backend_serve(Request& request, const String& entry_unit, const Stri
|
||||
if(to_bool(request.server->config["WASM_BACKEND_VERBOSE"], false))
|
||||
{
|
||||
request.header["X-UCE-Backend"] = "wasm";
|
||||
request.header["X-UCE-Wasm-Workspace-Setup-Us"] = std::to_string(response.workspace_setup_us);
|
||||
request.header["X-UCE-Wasm-Workspace-Birth-Us"] = std::to_string(response.workspace_birth_us);
|
||||
request.header["X-UCE-Wasm-Birth-Policy-Us"] = std::to_string(response.birth_policy_us);
|
||||
request.header["X-UCE-Wasm-Birth-Import-Us"] = std::to_string(response.birth_import_us);
|
||||
request.header["X-UCE-Wasm-Birth-Instantiate-Us"] = std::to_string(response.birth_instantiate_us);
|
||||
request.header["X-UCE-Wasm-Birth-Exports-Us"] = std::to_string(response.birth_exports_us);
|
||||
request.header["X-UCE-Wasm-Birth-Initialize-Us"] = std::to_string(response.birth_initialize_us);
|
||||
request.header["X-UCE-Wasm-Context-Apply-Us"] = std::to_string(response.context_apply_us);
|
||||
request.header["X-UCE-Wasm-Context-Bytes"] = std::to_string(response.context_bytes);
|
||||
request.header["X-UCE-Wasm-Server-Config-Bytes"] = std::to_string(response.server_config_bytes);
|
||||
request.header["X-UCE-Wasm-Context-Encode-Us"] = std::to_string(response.context_encode_us);
|
||||
request.header["X-UCE-Wasm-Context-Allocate-Us"] = std::to_string(response.context_allocate_us);
|
||||
request.header["X-UCE-Wasm-Context-Write-Us"] = std::to_string(response.context_write_us);
|
||||
request.header["X-UCE-Wasm-Context-Guest-Apply-Us"] = std::to_string(response.context_guest_apply_us);
|
||||
request.header["X-UCE-Wasm-Context-Free-Us"] = std::to_string(response.context_free_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);
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
|
||||
#define __UCE_WASM_CORE__ 1
|
||||
#include "../lib/uce_lib.cpp"
|
||||
|
||||
#include "../lib/mysql-connector.h"
|
||||
#include "../lib/sqlite-connector.h"
|
||||
|
||||
@ -799,19 +800,26 @@ void uce_wasm_core_reset_request()
|
||||
wasm_component_paths.clear();
|
||||
}
|
||||
|
||||
// Host pushes the UCEB2-encoded request context into a guest buffer
|
||||
// (uce_alloc) and applies it here; mirrors the native param population.
|
||||
int uce_wasm_apply_context(const char* buf, size_t len)
|
||||
// Host pushes the worker-cached immutable configuration followed by the
|
||||
// dynamic UCEB2 request context into one guest buffer.
|
||||
int uce_wasm_apply_context(const char* config_buf, size_t config_len, const char* context_buf, size_t context_len)
|
||||
{
|
||||
if(context == 0)
|
||||
uce_wasm_core_init();
|
||||
StringMap decoded_config;
|
||||
DValue decoded;
|
||||
String error;
|
||||
if(!ucb_decode(String(buf, len), decoded, &error))
|
||||
if(!ucb_decode_flat_string_map(String(config_buf, config_len), decoded_config, &error))
|
||||
{
|
||||
uce_host_log(3, error.data(), error.size());
|
||||
return(1);
|
||||
}
|
||||
if(!ucb_decode(String(context_buf, context_len), decoded, &error))
|
||||
{
|
||||
uce_host_log(3, error.data(), error.size());
|
||||
return(2);
|
||||
}
|
||||
wasm_server.config = std::move(decoded_config);
|
||||
wasm_request.call = std::move(decoded);
|
||||
DValue& applied = wasm_request.call;
|
||||
auto apply_map = [](DValue* source, StringMap& dest) {
|
||||
@ -821,8 +829,6 @@ int uce_wasm_apply_context(const char* buf, size_t len)
|
||||
dest[key] = item.to_string();
|
||||
});
|
||||
};
|
||||
if(applied.key("server_config"))
|
||||
apply_map(applied.key("server_config"), wasm_server.config);
|
||||
apply_map(applied.key("params"), wasm_request.params);
|
||||
wasm_request.response_code = wasm_request.params["GATEWAY_INTERFACE"] != "" ?
|
||||
"Status: 200 OK" : "HTTP/1.1 200 OK";
|
||||
|
||||
@ -187,6 +187,7 @@ struct WasmRequestProfile
|
||||
u64 context_apply_us = 0;
|
||||
u64 context_apply_cpu_us = 0;
|
||||
u64 context_bytes = 0;
|
||||
u64 server_config_bytes = 0;
|
||||
u64 context_encode_us = 0;
|
||||
u64 context_allocate_us = 0;
|
||||
u64 context_write_us = 0;
|
||||
@ -978,6 +979,7 @@ public:
|
||||
std::optional<wasmtime::Linker> core_linker;
|
||||
std::unique_ptr<wasmtime_instance_pre_t, WasmInstancePreDeleter> core_instance_pre;
|
||||
DValue server_config_context;
|
||||
String server_config_encoded;
|
||||
struct CoreImport
|
||||
{
|
||||
String module;
|
||||
@ -1532,21 +1534,25 @@ public:
|
||||
return(elapsed);
|
||||
};
|
||||
String encoded = ucb_encode(context_tree);
|
||||
context_bytes = encoded.size();
|
||||
server_config_bytes = worker.server_config_encoded.size();
|
||||
context_bytes = server_config_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);
|
||||
String error = call_core("uce_alloc", { (int32_t)context_bytes }, &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);
|
||||
error = guest_write((u32)guest_ptr, worker.server_config_encoded);
|
||||
if(error == "")
|
||||
error = guest_write((u32)guest_ptr + (u32)server_config_bytes, 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);
|
||||
error = call_core("uce_wasm_apply_context", { guest_ptr, (int32_t)server_config_bytes,
|
||||
guest_ptr + (int32_t)server_config_bytes, (int32_t)encoded.size() }, &rc);
|
||||
context_guest_apply_us = phase_us();
|
||||
if(error != "")
|
||||
return(error);
|
||||
@ -2592,6 +2598,7 @@ private:
|
||||
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["server_config_bytes"] = (f64)self->server_config_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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user