Cache immutable Wasm server configuration encoding
This commit is contained in:
@@ -87,9 +87,11 @@ the boundary).
|
|||||||
children yielding `const DValue&`.
|
children yielding `const DValue&`.
|
||||||
- The request context (`params`/`get`/`post`/`cookies`/`session`, the raw body
|
- The request context (`params`/`get`/`post`/`cookies`/`session`, the raw body
|
||||||
`in`, and—for WS—the connection context) is marshalled into a single `ctx`
|
`in`, and—for WS—the connection context) is marshalled into a single `ctx`
|
||||||
DValue, UCEB-encoded, and handed to the workspace. The response (body,
|
DValue and UCEB-encoded. Immutable server configuration is UCEB-encoded once
|
||||||
headers, status, and any `meta` such as `ws_commands`) comes back the same
|
per worker as a flat string map; both byte ranges are written into one guest
|
||||||
way.
|
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
|
See [`docs/wasm-phase1-dvalue-abi.md`](wasm-phase1-dvalue-abi.md) for the wire
|
||||||
format details.
|
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,
|
preventing the first request assigned to each worker from paying engine, linker,
|
||||||
or pre-instantiation startup.
|
or pre-instantiation startup.
|
||||||
Server configuration is immutable by that point. The worker therefore retains
|
Server configuration is immutable by that point. The worker therefore retains
|
||||||
one native `DValue` view of it; each request's temporary context tree references
|
one native `DValue` view and one UCEB encoding of it. Each request transfers
|
||||||
that view while UCEB is encoded, avoiding a repeated native tree copy. The guest
|
those cached bytes and the guest decodes the flat scalar map directly into its
|
||||||
still decodes the bytes into its fresh request tree. Request parameters, body,
|
fresh `Server`, avoiding both repeated native encoding and an intermediate guest
|
||||||
cookies, session, call data, and response state are never retained this way.
|
`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
|
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
|
module lives in the configured writable cache root rather than beside the
|
||||||
possibly root-owned deployed `core.wasm`; freshness still uses the deployed
|
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,
|
artifacts stay ABI-compatible. `request_perf()` subdivides birth into policy,
|
||||||
import materialization, core instantiation, export/table lookup, and initialization,
|
import materialization, core instantiation, export/table lookup, and initialization,
|
||||||
and context transfer into bytes, host encode, guest allocation/write,
|
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
|
### 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("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("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 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"];
|
String saved_query_string = context.params["QUERY_STRING"];
|
||||||
context.params["QUERY_STRING"] = "workspace/projects&theme=dark";
|
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)));
|
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);
|
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 ucb_decode(const String& encoded)
|
||||||
{
|
{
|
||||||
DValue out;
|
DValue out;
|
||||||
|
|||||||
+17
-5
@@ -68,10 +68,10 @@ static String wasm_backend_ensure_started(Request* context)
|
|||||||
}
|
}
|
||||||
|
|
||||||
g_wasm_worker = new WasmWorker(wc);
|
g_wasm_worker = new WasmWorker(wc);
|
||||||
// Server configuration is finalized before render workers start. Retain one
|
// Server configuration is finalized before render workers start. Encode it
|
||||||
// native tree so per-request UCEB encoding can reference it without rebuilding
|
// once; each fresh guest decodes the cached flat map into its own Server.
|
||||||
// dozens of identical nodes; the guest still decodes it into fresh state.
|
|
||||||
g_wasm_worker->server_config_context = cfg;
|
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();
|
g_wasm_init_error = g_wasm_worker->init();
|
||||||
if(g_wasm_init_error == "")
|
if(g_wasm_init_error == "")
|
||||||
g_wasm_init_error = wasm_worker_prepare(*g_wasm_worker);
|
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)
|
for(auto& entry : source)
|
||||||
ctx[key][entry.first] = entry.second;
|
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.params, "params");
|
||||||
copy_map(request.get, "get");
|
copy_map(request.get, "get");
|
||||||
copy_map(request.post, "post");
|
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))
|
if(to_bool(request.server->config["WASM_BACKEND_VERBOSE"], false))
|
||||||
{
|
{
|
||||||
request.header["X-UCE-Backend"] = "wasm";
|
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-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-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-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-Load-Us"] = std::to_string(response.entry_load_us);
|
||||||
|
|||||||
+12
-6
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#define __UCE_WASM_CORE__ 1
|
#define __UCE_WASM_CORE__ 1
|
||||||
#include "../lib/uce_lib.cpp"
|
#include "../lib/uce_lib.cpp"
|
||||||
|
|
||||||
#include "../lib/mysql-connector.h"
|
#include "../lib/mysql-connector.h"
|
||||||
#include "../lib/sqlite-connector.h"
|
#include "../lib/sqlite-connector.h"
|
||||||
|
|
||||||
@@ -799,19 +800,26 @@ void uce_wasm_core_reset_request()
|
|||||||
wasm_component_paths.clear();
|
wasm_component_paths.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Host pushes the UCEB2-encoded request context into a guest buffer
|
// Host pushes the worker-cached immutable configuration followed by the
|
||||||
// (uce_alloc) and applies it here; mirrors the native param population.
|
// dynamic UCEB2 request context into one guest buffer.
|
||||||
int uce_wasm_apply_context(const char* buf, size_t len)
|
int uce_wasm_apply_context(const char* config_buf, size_t config_len, const char* context_buf, size_t context_len)
|
||||||
{
|
{
|
||||||
if(context == 0)
|
if(context == 0)
|
||||||
uce_wasm_core_init();
|
uce_wasm_core_init();
|
||||||
|
StringMap decoded_config;
|
||||||
DValue decoded;
|
DValue decoded;
|
||||||
String error;
|
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());
|
uce_host_log(3, error.data(), error.size());
|
||||||
return(1);
|
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);
|
wasm_request.call = std::move(decoded);
|
||||||
DValue& applied = wasm_request.call;
|
DValue& applied = wasm_request.call;
|
||||||
auto apply_map = [](DValue* source, StringMap& dest) {
|
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();
|
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);
|
apply_map(applied.key("params"), wasm_request.params);
|
||||||
wasm_request.response_code = wasm_request.params["GATEWAY_INTERFACE"] != "" ?
|
wasm_request.response_code = wasm_request.params["GATEWAY_INTERFACE"] != "" ?
|
||||||
"Status: 200 OK" : "HTTP/1.1 200 OK";
|
"Status: 200 OK" : "HTTP/1.1 200 OK";
|
||||||
|
|||||||
+11
-4
@@ -187,6 +187,7 @@ struct WasmRequestProfile
|
|||||||
u64 context_apply_us = 0;
|
u64 context_apply_us = 0;
|
||||||
u64 context_apply_cpu_us = 0;
|
u64 context_apply_cpu_us = 0;
|
||||||
u64 context_bytes = 0;
|
u64 context_bytes = 0;
|
||||||
|
u64 server_config_bytes = 0;
|
||||||
u64 context_encode_us = 0;
|
u64 context_encode_us = 0;
|
||||||
u64 context_allocate_us = 0;
|
u64 context_allocate_us = 0;
|
||||||
u64 context_write_us = 0;
|
u64 context_write_us = 0;
|
||||||
@@ -978,6 +979,7 @@ public:
|
|||||||
std::optional<wasmtime::Linker> core_linker;
|
std::optional<wasmtime::Linker> core_linker;
|
||||||
std::unique_ptr<wasmtime_instance_pre_t, WasmInstancePreDeleter> core_instance_pre;
|
std::unique_ptr<wasmtime_instance_pre_t, WasmInstancePreDeleter> core_instance_pre;
|
||||||
DValue server_config_context;
|
DValue server_config_context;
|
||||||
|
String server_config_encoded;
|
||||||
struct CoreImport
|
struct CoreImport
|
||||||
{
|
{
|
||||||
String module;
|
String module;
|
||||||
@@ -1532,21 +1534,25 @@ public:
|
|||||||
return(elapsed);
|
return(elapsed);
|
||||||
};
|
};
|
||||||
String encoded = ucb_encode(context_tree);
|
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();
|
context_encode_us = phase_us();
|
||||||
int32_t guest_ptr = 0;
|
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();
|
context_allocate_us = phase_us();
|
||||||
if(error != "")
|
if(error != "")
|
||||||
return(error);
|
return(error);
|
||||||
if(guest_ptr == 0)
|
if(guest_ptr == 0)
|
||||||
return("guest uce_alloc failed for context buffer");
|
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();
|
context_write_us = phase_us();
|
||||||
if(error != "")
|
if(error != "")
|
||||||
return(error);
|
return(error);
|
||||||
int32_t rc = 0;
|
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();
|
context_guest_apply_us = phase_us();
|
||||||
if(error != "")
|
if(error != "")
|
||||||
return(error);
|
return(error);
|
||||||
@@ -2592,6 +2598,7 @@ private:
|
|||||||
response["context_apply_us"] = (f64)self->context_apply_us;
|
response["context_apply_us"] = (f64)self->context_apply_us;
|
||||||
response["context_apply_cpu_us"] = (f64)self->context_apply_cpu_us;
|
response["context_apply_cpu_us"] = (f64)self->context_apply_cpu_us;
|
||||||
response["context_bytes"] = (f64)self->context_bytes;
|
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_encode_us"] = (f64)self->context_encode_us;
|
||||||
response["context_allocate_us"] = (f64)self->context_allocate_us;
|
response["context_allocate_us"] = (f64)self->context_allocate_us;
|
||||||
response["context_write_us"] = (f64)self->context_write_us;
|
response["context_write_us"] = (f64)self->context_write_us;
|
||||||
|
|||||||
Reference in New Issue
Block a user