Decode request maps directly into Wasm workspaces

This commit is contained in:
udo 2026-07-18 04:21:28 +00:00
parent 516ed94032
commit 5e140d6a5a
7 changed files with 218 additions and 111 deletions

View File

@ -281,9 +281,12 @@ then checked again before execution. The second check belongs only to that
state-changing branch; repeating it immediately after a successful warm check
adds no freshness guarantee.
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
The internal request envelope carries application `context.call` as UCEB2,
each request `StringMap` as a flat UCEB2 map, scalar session/input metadata as
bounded byte segments, and optional WebSocket state as UCEB2. The guest
validates the complete envelope before moving those values into a fresh
`Request`; transport-only params, cookies, session data, and entry metadata no
longer become duplicate `context.call` children. 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/table lookup, and initialization,

View File

@ -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 transport stays out of context.call", !context.call.has("params") && !context.call.has("get") && !context.call.has("post") && !context.call.has("cookies") && !context.call.has("session") && !context.call.has("entry_unit") && !context.call.has("in"), json_encode(context.call));
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";
@ -141,6 +142,11 @@ RENDER(Request& context)
DValue uceb_decoded;
bool uceb_ok = ucb_decode(uceb_encoded, uceb_decoded, &uceb_error);
check("UCEB2 DValue codec round-trip preserves scalar types", uceb_ok && uceb_decoded["name"].to_string() == "phase1" && uceb_decoded["name"].get_type_name() == "String" && uceb_decoded["nested"]["answer"].to_string() == "42" && uceb_decoded["float"].get_type_name() == "f64" && uceb_decoded["float"].to_f64() > 0.00000009 && uceb_decoded["float"].to_f64() < 0.00000011 && uceb_decoded["bool"].get_type_name() == "bool" && uceb_decoded["bool"].to_bool() && uceb_decoded["items"].is_list() && uceb_decoded["items"]["1"].to_string() == "second" && uceb_decoded["empty_list"].is_list() && uceb_decoded["binary"].to_string().size() == uceb_binary.size() && uceb_decoded["binary"].to_string() == uceb_binary && uceb_decoded["pointer"].to_string() == "", "bytes=" + std::to_string((u64)uceb_encoded.size()) + " error=" + uceb_error + " float=" + uceb_decoded["float"].to_string() + " types=" + uceb_decoded["name"].get_type_name() + "/" + uceb_decoded["float"].get_type_name() + "/" + uceb_decoded["bool"].get_type_name());
StringMap uceb_flat_source = {{"alpha", "one"}, {"binary", uceb_binary}, {"empty", ""}};
DValue uceb_flat_decoded;
String uceb_flat_error;
bool uceb_flat_ok = ucb_decode(ucb_encode_flat_string_map(uceb_flat_source), uceb_flat_decoded, &uceb_flat_error);
check("UCEB2 flat string-map codec", uceb_flat_ok && uceb_flat_decoded.to_stringmap() == uceb_flat_source, uceb_flat_error + " / " + json_encode(uceb_flat_decoded));
DValue uceb_invalid;
String uceb_bad_magic = "NOPE";

View File

@ -921,11 +921,11 @@ bool ucb_append_varint(String& out, u64 value)
return(true);
}
bool ucb_read_varint(const String& src, size_t& offset, u64& value_out)
bool ucb_read_varint(const char* src, size_t src_size, size_t& offset, u64& value_out)
{
value_out = 0;
u32 shift = 0;
while(offset < src.size() && shift <= 63)
while(offset < src_size && shift <= 63)
{
u8 byte = (u8)src[offset++];
value_out |= ((u64)(byte & 0x7f) << shift);
@ -936,6 +936,11 @@ bool ucb_read_varint(const String& src, size_t& offset, u64& value_out)
return(false);
}
bool ucb_read_varint(const String& src, size_t& offset, u64& value_out)
{
return(ucb_read_varint(src.data(), src.size(), offset, value_out));
}
char ucb_node_type(const DValue& value)
{
const DValue& target = value.deref();
@ -1149,6 +1154,30 @@ String ucb_encode(const DValue& value)
return(out);
}
// Internal request/config transport uses the same UCEB2 map representation
// without first building a duplicate DValue tree.
String ucb_encode_flat_string_map(const StringMap& value)
{
String out;
out.append(UCEB_MAGIC, 4);
out.push_back((char)UCEB_VERSION);
out.push_back(0);
out.push_back('M');
ucb_append_varint(out, 0);
ucb_append_varint(out, value.size());
for(auto& child : value)
{
ucb_append_varint(out, child.first.size());
out.append(child.first.data(), child.first.size());
out.push_back(0);
out.push_back('S');
ucb_append_varint(out, child.second.size());
out.append(child.second.data(), child.second.size());
ucb_append_varint(out, 0);
}
return(out);
}
bool ucb_decode(const String& encoded, DValue& out, String* error_out)
{
String error;
@ -1176,7 +1205,7 @@ bool ucb_decode(const String& encoded, DValue& out, String* error_out)
}
#ifdef __UCE_WASM_CORE__
static bool ucb_decode_flat_string_map(const String& encoded, StringMap& out, String* error_out)
static bool ucb_decode_flat_string_map(const char* encoded, size_t encoded_size, StringMap& out, String* error_out)
{
String error;
StringMap decoded;
@ -1185,14 +1214,14 @@ static bool ucb_decode_flat_string_map(const String& encoded, StringMap& out, St
error = message;
return(false);
};
if(encoded.size() < 5 || encoded.compare(0, 4, UCEB_MAGIC) != 0)
if(encoded_size < 5 || memcmp(encoded, UCEB_MAGIC, 4) != 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)
if(encoded_size - offset < 2)
fail("unexpected end of UCEB2 string map");
else
{
@ -1201,44 +1230,44 @@ static bool ucb_decode_flat_string_map(const String& encoded, StringMap& out, St
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)
else if(!ucb_read_varint(encoded, encoded_size, offset, scalar_len) || scalar_len != 0)
fail("UCEB2 string map root must have no scalar");
else if(!ucb_read_varint(encoded, offset, child_count))
else if(!ucb_read_varint(encoded, encoded_size, 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)
if(!ucb_read_varint(encoded, encoded_size, offset, key_len) || key_len > encoded_size - offset)
{
fail("invalid UCEB2 string map key");
break;
}
String key(encoded.data() + offset, (size_t)key_len);
String key(encoded + offset, (size_t)key_len);
offset += (size_t)key_len;
if(encoded.size() - offset < 2)
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)
if(value_flags != 0 || value_type != 'S' || !ucb_read_varint(encoded, encoded_size, 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);
String value(encoded + offset, (size_t)value_len);
offset += (size_t)value_len;
if(!ucb_read_varint(encoded, offset, value_children) || value_children != 0)
if(!ucb_read_varint(encoded, encoded_size, 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())
if(error == "" && offset != encoded_size)
fail("trailing bytes after UCEB2 string map");
}
}

View File

@ -78,6 +78,7 @@ String to_String(DValue t);
String var_dump(const DValue& map, String prefix = "", String postfix = "\n");
String ucb_encode(const DValue& value);
String ucb_encode_flat_string_map(const StringMap& value);
DValue ucb_decode(const String& encoded);
bool ucb_decode(const String& encoded, DValue& out, String* error_out = 0);

View File

@ -154,47 +154,7 @@ bool wasm_backend_should_handle(Request& request, const String& entry_unit)
// configured error page.
String wasm_backend_serve(Request& request, const String& entry_unit, const String& handler = "render")
{
DValue ctx;
auto copy_map = [&](const StringMap& source, const char* key) {
for(auto& entry : source)
ctx[key][entry.first] = entry.second;
};
copy_map(request.params, "params");
copy_map(request.get, "get");
copy_map(request.post, "post");
copy_map(request.cookies, "cookies");
copy_map(request.session, "session");
ctx["session_id"] = request.session_id;
ctx["session_name"] = request.session_name;
ctx["session_loaded_hash"] = request.session_loaded_hash;
ctx["entry_unit"] = entry_unit;
// Raw request body: cli_input() parses a JSON CLI payload from context.in,
// and serve_http handlers read it as req->in; carried into the workspace.
ctx["in"] = request.in;
// Configurable error pages read context.call["error"] (the error_info DValue
// set natively in render_wasm_error_page). apply_context binds context.call to
// the decoded ctx, so marshal the native request.call children across the
// membrane — otherwise the error page renders with no error data.
for(auto& entry : request.call._map)
ctx[entry.first] = entry.second;
// WebSocket event context: the workspace owns no connections, so the frame's
// connection identity goes in and the handler's ws_send/ws_close dispatch
// commands come back out (below) for the native broker to apply.
if(handler == "websocket")
{
ctx["ws"]["connection_id"] = request.resources.websocket_connection_id;
ctx["ws"]["scope"] = request.resources.websocket_scope;
ctx["ws"]["opcode"] = (f64)request.resources.websocket_opcode;
ctx["ws"]["binary"].set_bool(request.resources.websocket_is_binary);
for(auto& id : request.resources.websocket_scope_connection_ids)
{
DValue v; v = id;
ctx["ws"]["connections"].push(v);
}
ctx["ws"]["connection_state"] = request.connection;
}
WasmResponse response = wasm_worker_serve(*g_wasm_worker, ctx, entry_unit, handler, &request);
WasmResponse response = wasm_worker_serve(*g_wasm_worker, request, entry_unit, handler);
request.stats.wasm_dispatch_us = response.dispatch_us;
request.stats.wasm_workspace_complete_us = response.workspace_complete_us;
request.stats.wasm_entry_invoke_us = response.entry_invoke_us;

View File

@ -723,6 +723,46 @@ void unit_render(String file_name, Request& request)
void unit_render(String file_name) { unit_render(file_name, *context); }
struct WasmRequestEnvelopeSegment
{
const char* data = 0;
size_t size = 0;
};
static bool wasm_decode_request_envelope(const char* encoded, size_t encoded_size,
WasmRequestEnvelopeSegment (&segments)[12], String& error)
{
if(encoded_size < 6 || memcmp(encoded, "UCER", 4) != 0)
{
error = "missing UCE request-envelope header";
return(false);
}
if((u8)encoded[4] != 1 || (u8)encoded[5] != 12)
{
error = "unsupported UCE request-envelope version or segment count";
return(false);
}
size_t offset = 6;
for(u32 i = 0; i < 12; i++)
{
u64 segment_size = 0;
if(!ucb_read_varint(encoded, encoded_size, offset, segment_size) || segment_size > encoded_size - offset)
{
error = "invalid UCE request-envelope segment " + std::to_string(i);
return(false);
}
segments[i].data = encoded + offset;
segments[i].size = (size_t)segment_size;
offset += (size_t)segment_size;
}
if(offset != encoded_size)
{
error = "trailing bytes after UCE request envelope";
return(false);
}
return(true);
}
extern "C" {
// The host has already loaded the request's entry unit and can place its
@ -807,46 +847,66 @@ int uce_wasm_apply_context(const char* config_buf, size_t config_len, const char
if(context == 0)
uce_wasm_core_init();
StringMap decoded_config;
DValue decoded;
StringMap decoded_params;
StringMap decoded_get;
StringMap decoded_post;
StringMap decoded_cookies;
StringMap decoded_session;
DValue decoded_call;
DValue decoded_ws;
WasmRequestEnvelopeSegment segments[12];
String error;
if(!ucb_decode_flat_string_map(String(config_buf, config_len), decoded_config, &error))
if(!ucb_decode_flat_string_map(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))
if(!wasm_decode_request_envelope(context_buf, context_len, segments, 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) {
dest.clear();
if(source)
source->each([&](const DValue& item, String key) {
dest[key] = item.to_string();
});
auto decode_tree = [&](u32 index, DValue& target, const char* name) {
if(ucb_decode(String(segments[index].data, segments[index].size), target, &error))
return(true);
error = String(name) + ": " + error;
return(false);
};
apply_map(applied.key("params"), wasm_request.params);
auto decode_map = [&](u32 index, StringMap& target, const char* name) {
if(ucb_decode_flat_string_map(segments[index].data, segments[index].size, target, &error))
return(true);
error = String(name) + ": " + error;
return(false);
};
if(!decode_tree(0, decoded_call, "request call") ||
!decode_map(1, decoded_params, "request params") ||
!decode_map(2, decoded_get, "request get") ||
!decode_map(3, decoded_post, "request post") ||
!decode_map(4, decoded_cookies, "request cookies") ||
!decode_map(5, decoded_session, "request session"))
{
uce_host_log(3, error.data(), error.size());
return(3);
}
if(segments[11].size && !decode_tree(11, decoded_ws, "request websocket"))
{
uce_host_log(3, error.data(), error.size());
return(4);
}
wasm_server.config = std::move(decoded_config);
wasm_request.call = std::move(decoded_call);
wasm_request.params = std::move(decoded_params);
wasm_request.get = std::move(decoded_get);
wasm_request.post = std::move(decoded_post);
wasm_request.cookies = std::move(decoded_cookies);
wasm_request.session = std::move(decoded_session);
wasm_request.response_code = wasm_request.params["GATEWAY_INTERFACE"] != "" ?
"Status: 200 OK" : "HTTP/1.1 200 OK";
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 = applied.key("session_name"))
wasm_request.session_name = session_name->to_string();
if(DValue* session_loaded_hash = applied.key("session_loaded_hash"))
wasm_request.session_loaded_hash = session_loaded_hash->to_string();
DValue* entry = applied.key("entry_unit");
if(entry)
wasm_request.resources.current_unit_file = entry->to_string();
DValue* raw_in = applied.key("in");
wasm_request.in = raw_in ? raw_in->to_string() : "";
wasm_request.session_id.assign(segments[6].data, segments[6].size);
wasm_request.session_name.assign(segments[7].data, segments[7].size);
wasm_request.session_loaded_hash.assign(segments[8].data, segments[8].size);
wasm_request.resources.current_unit_file.assign(segments[9].data, segments[9].size);
wasm_request.in.assign(segments[10].data, segments[10].size);
// websocket event context: ws_send()/ws_close() capture into the dispatch
// list (the workspace owns no connections), which collect() carries back to
// the broker. Reset per invocation.
@ -854,20 +914,19 @@ int uce_wasm_apply_context(const char* config_buf, size_t config_len, const char
wasm_request.resources.websocket_connection_state_before = DValue();
wasm_request.resources.websocket_dispatch_commands = DValue();
wasm_request.resources.websocket_dispatch_capture = false;
DValue* ws = applied.key("ws");
if(ws)
if(segments[11].size)
{
wasm_request.resources.websocket_connection_id = (*ws)["connection_id"].to_string();
wasm_request.resources.websocket_scope = (*ws)["scope"].to_string();
wasm_request.resources.websocket_opcode = (u8)(*ws)["opcode"].to_u64();
wasm_request.resources.websocket_is_binary = (*ws)["binary"].to_bool();
wasm_request.resources.websocket_connection_id = decoded_ws["connection_id"].to_string();
wasm_request.resources.websocket_scope = decoded_ws["scope"].to_string();
wasm_request.resources.websocket_opcode = (u8)decoded_ws["opcode"].to_u64();
wasm_request.resources.websocket_is_binary = decoded_ws["binary"].to_bool();
wasm_request.resources.websocket_scope_connection_ids.clear();
if(DValue* conns = ws->key("connections"))
if(DValue* conns = decoded_ws.key("connections"))
conns->each([&](const DValue& v, String) {
wasm_request.resources.websocket_scope_connection_ids.push_back(v.to_string());
});
wasm_request.resources.websocket_dispatch_capture = true;
if(DValue* cstate = ws->key("connection_state"))
if(DValue* cstate = decoded_ws.key("connection_state"))
wasm_request.connection = *cstate;
wasm_request.resources.websocket_connection_state_before = wasm_request.connection;
}

View File

@ -1266,6 +1266,58 @@ private:
std::vector<String> hostcall_operation_names;
};
// The request envelope keeps app scratch state separate from transport fields.
// Maps use UCEB2 directly so the guest can populate Request StringMaps without
// constructing and then copying a duplicate DValue subtree.
static void wasm_request_envelope_append(String& envelope, const String& value)
{
u64 size = value.size();
while(size >= 0x80)
{
envelope.push_back((char)((size & 0x7f) | 0x80));
size >>= 7;
}
envelope.push_back((char)size);
envelope.append(value.data(), value.size());
}
static String wasm_encode_request_envelope(const Request& request, const String& entry_unit, const String& handler)
{
String envelope = "UCER";
envelope.push_back(1);
envelope.push_back(12);
wasm_request_envelope_append(envelope, ucb_encode(request.call));
wasm_request_envelope_append(envelope, ucb_encode_flat_string_map(request.params));
wasm_request_envelope_append(envelope, ucb_encode_flat_string_map(request.get));
wasm_request_envelope_append(envelope, ucb_encode_flat_string_map(request.post));
wasm_request_envelope_append(envelope, ucb_encode_flat_string_map(request.cookies));
wasm_request_envelope_append(envelope, ucb_encode_flat_string_map(request.session));
wasm_request_envelope_append(envelope, request.session_id);
wasm_request_envelope_append(envelope, request.session_name);
wasm_request_envelope_append(envelope, request.session_loaded_hash);
wasm_request_envelope_append(envelope, entry_unit);
wasm_request_envelope_append(envelope, request.in);
String websocket;
if(handler == "websocket")
{
DValue ws;
ws["connection_id"] = request.resources.websocket_connection_id;
ws["scope"] = request.resources.websocket_scope;
ws["opcode"] = (f64)request.resources.websocket_opcode;
ws["binary"].set_bool(request.resources.websocket_is_binary);
for(auto& id : request.resources.websocket_scope_connection_ids)
{
DValue value;
value = id;
ws["connections"].push(value);
}
ws["connection_state"] = request.connection;
websocket = ucb_encode(ws);
}
wasm_request_envelope_append(envelope, websocket);
return(envelope);
}
// ---- workspace (per request) ----------------------------------------------
class WasmWorkspace : public WasmRequestProfile
@ -1524,7 +1576,7 @@ public:
return("");
}
String apply_context(const DValue& context_tree)
String apply_context(const Request& request, const String& entry_unit, const String& handler)
{
auto phase_start = std::chrono::steady_clock::now();
auto phase_us = [&]() -> u64 {
@ -1533,7 +1585,7 @@ public:
phase_start = now;
return(elapsed);
};
String encoded = ucb_encode(context_tree);
String encoded = wasm_encode_request_envelope(request, entry_unit, handler);
server_config_bytes = worker.server_config_encoded.size();
context_bytes = server_config_bytes + encoded.size();
context_encode_us = phase_us();
@ -3787,15 +3839,15 @@ inline String wasm_worker_prepare(WasmWorker& worker)
// ---- public entry: one request through one workspace -----------------------
inline WasmResponse wasm_worker_serve(WasmWorker& worker, const DValue& context_tree, const String& entry_source_path,
const String& handler = "render", const Request* request = 0)
inline WasmResponse wasm_worker_serve(WasmWorker& worker, const Request& request, const String& entry_source_path,
const String& handler = "render")
{
WasmResponse response;
f64 serve_started = time_precise();
auto workspace_start = std::chrono::steady_clock::now();
f64 cpu_started = wasm_thread_cpu_time();
struct rusage thread_runtime_start = {};
bool thread_runtime_profiled = request && worker.cfg.profile_thread_runtime && getrusage(RUSAGE_THREAD, &thread_runtime_start) == 0;
bool thread_runtime_profiled = worker.cfg.profile_thread_runtime && getrusage(RUSAGE_THREAD, &thread_runtime_start) == 0;
int thread_cpu_start = thread_runtime_profiled ? sched_getcpu() : -1;
WasmWorkspace workspace(worker);
f64 setup_cpu_finished = wasm_thread_cpu_time();
@ -3803,17 +3855,14 @@ inline WasmResponse wasm_worker_serve(WasmWorker& worker, const DValue& context_
std::chrono::steady_clock::now() - workspace_start).count();
workspace.workspace_setup_cpu_us = cpu_started > 0 && setup_cpu_finished > cpu_started ?
(u64)((setup_cpu_finished - cpu_started) * 1000000.0) : 0;
if(request)
{
workspace.set_perf_snapshot(my_pid, (u64)parent_pid, request->server ? request->server->request_count : 0,
request->stats.time_init, request->stats.time_params, request->stats.time_input, request->stats.time_start,
serve_started, cpu_started);
workspace.request_perf.thread_runtime_start = thread_runtime_start;
workspace.request_perf.thread_cpu_start = thread_cpu_start;
workspace.request_perf.thread_runtime_profiled = thread_runtime_profiled;
if(request->stats.time_start > 0 && serve_started > request->stats.time_start)
workspace.dispatch_us = (u64)((serve_started - request->stats.time_start) * 1000000.0);
}
workspace.set_perf_snapshot(my_pid, (u64)parent_pid, request.server ? request.server->request_count : 0,
request.stats.time_init, request.stats.time_params, request.stats.time_input, request.stats.time_start,
serve_started, cpu_started);
workspace.request_perf.thread_runtime_start = thread_runtime_start;
workspace.request_perf.thread_cpu_start = thread_cpu_start;
workspace.request_perf.thread_runtime_profiled = thread_runtime_profiled;
if(request.stats.time_start > 0 && serve_started > request.stats.time_start)
workspace.dispatch_us = (u64)((serve_started - request.stats.time_start) * 1000000.0);
auto birth_start = std::chrono::steady_clock::now();
f64 birth_cpu_start = wasm_thread_cpu_time();
String error = workspace.birth();
@ -3826,7 +3875,7 @@ inline WasmResponse wasm_worker_serve(WasmWorker& worker, const DValue& context_
{
auto context_start = std::chrono::steady_clock::now();
f64 context_cpu_start = wasm_thread_cpu_time();
error = workspace.apply_context(context_tree);
error = workspace.apply_context(request, entry_source_path, handler);
f64 context_cpu_finished = wasm_thread_cpu_time();
workspace.context_apply_us = (u64)std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - context_start).count();