feat: cut over to WASM backend
This commit is contained in:
@@ -1060,6 +1060,12 @@ StringMap make_server_settings()
|
||||
cfg["COMPILE_SCRIPT"] = "scripts/compile";
|
||||
cfg["WASM_COMPILE_SCRIPT"] = "scripts/compile_wasm_unit";
|
||||
cfg["COMPILE_WASM_UNITS"] = "0";
|
||||
cfg["WASM_BACKEND_ENABLED"] = "1";
|
||||
cfg["WASM_BACKEND_VERBOSE"] = "0";
|
||||
cfg["WASM_CORE_PATH"] = "";
|
||||
cfg["WASM_MEMORY_LIMIT_BYTES"] = std::to_string(512ull * 1024 * 1024);
|
||||
cfg["WASM_EPOCH_DEADLINE_TICKS"] = "200";
|
||||
cfg["WASM_EPOCH_PERIOD_MS"] = "50";
|
||||
cfg["SETUP_TEMPLATE"] = "scripts/setup.h.template";
|
||||
cfg["LIT_ESC"] = "3d5b5_1";
|
||||
cfg["CONTENT_TYPE"] = "text/html; charset=utf-8";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "lib/uce_lib.cpp"
|
||||
#include "wasm/backend.cpp"
|
||||
#include <csetjmp>
|
||||
#include <deque>
|
||||
#include <errno.h>
|
||||
@@ -940,6 +941,23 @@ int handle_complete(FastCGIRequest& request) {
|
||||
compiler_invoke_cli(&request, request.params["SCRIPT_FILENAME"]);
|
||||
else if(request.params["UCE_SERVE_HTTP"] == "1")
|
||||
compiler_invoke_serve_http(&request, request.params["SCRIPT_FILENAME"], request.params["UCE_SERVE_HTTP_FUNCTION"]);
|
||||
else if(wasm_backend_should_handle(request, compiler_normalize_unit_path(&request, request.params["SCRIPT_FILENAME"])))
|
||||
{
|
||||
// W4/W5: Wasmtime uses host signals internally to implement guest
|
||||
// traps. The native SIGSEGV/SIGILL request recovery handler must not
|
||||
// intercept those, or clean guest traps become native fatal signals.
|
||||
request_fault_active = 0;
|
||||
restore_request_fault_handlers();
|
||||
String wasm_error = wasm_backend_serve(request, compiler_normalize_unit_path(&request, request.params["SCRIPT_FILENAME"]));
|
||||
install_request_fault_handlers();
|
||||
request_fault_active = 1;
|
||||
if(wasm_error != "")
|
||||
{
|
||||
failure_title = "wasm runtime error during request";
|
||||
failure_details = "";
|
||||
failure_trace = wasm_error;
|
||||
}
|
||||
}
|
||||
else
|
||||
compiler_invoke(&request, request.params["SCRIPT_FILENAME"]);
|
||||
}
|
||||
@@ -1023,6 +1041,7 @@ void on_terminate(int sig)
|
||||
if(getpid() != parent_pid)
|
||||
exit(1);
|
||||
printf("Terminating... PID %i:%i\n", getpid(), parent_pid);
|
||||
wasm_backend_shutdown();
|
||||
server.shutdown();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
// W4 — FastCGI backend glue for the W3 wasm workspace runtime.
|
||||
//
|
||||
// Included into the native server TU (src/linux_fastcgi.cpp) after uce_lib.cpp,
|
||||
// so it shares String/DValue/config and the UCEB1 codec. Provides a
|
||||
// config-selectable page-render backend: when WASM_BACKEND_ENABLED and a wasm
|
||||
// artifact exists for the entry unit, the request is served through a
|
||||
// per-request wasm workspace instead of the native dlopen path.
|
||||
//
|
||||
// The seam is narrow on purpose — only the page render branch in
|
||||
// handle_complete() changes. CLI, serve_http, and websocket stay native.
|
||||
|
||||
#include "../lib/wasm_trace.h"
|
||||
#include "worker.cpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <sys/stat.h>
|
||||
#include <thread>
|
||||
|
||||
// per forked worker process: one engine + compiled-core cache, one epoch ticker
|
||||
static WasmWorker* g_wasm_worker = 0;
|
||||
static std::thread g_wasm_epoch_ticker;
|
||||
static std::atomic<bool> g_wasm_epoch_running(false);
|
||||
static String g_wasm_init_error;
|
||||
static bool g_wasm_init_attempted = false;
|
||||
|
||||
bool wasm_backend_configured(Request* context)
|
||||
{
|
||||
if(!context || !context->server)
|
||||
return(false);
|
||||
return(config_bool("WASM_BACKEND_ENABLED", false));
|
||||
}
|
||||
|
||||
// Lazily bring up the per-process worker on first use inside a forked child
|
||||
// (the engine must not be inherited across fork). Returns "" on success.
|
||||
static String wasm_backend_ensure_started(Request* context)
|
||||
{
|
||||
if(g_wasm_init_attempted)
|
||||
return(g_wasm_init_error);
|
||||
g_wasm_init_attempted = true;
|
||||
|
||||
StringMap& cfg = context->server->config;
|
||||
WasmWorkerConfig wc;
|
||||
wc.core_wasm_path = first(cfg["WASM_CORE_PATH"],
|
||||
path_join(cfg["COMPILER_SYS_PATH"], "bin/wasm/core.wasm"));
|
||||
wc.site_root = path_join(cfg["COMPILER_SYS_PATH"], cfg["SITE_DIRECTORY"]);
|
||||
wc.cache_root = cfg["BIN_DIRECTORY"];
|
||||
wc.memory_limit = (int64_t)config_u64("WASM_MEMORY_LIMIT_BYTES", 512ull * 1024 * 1024);
|
||||
wc.epoch_deadline_ticks = config_u64("WASM_EPOCH_DEADLINE_TICKS", 200);
|
||||
wc.verbose = config_bool("WASM_BACKEND_VERBOSE", false);
|
||||
|
||||
g_wasm_worker = new WasmWorker(wc);
|
||||
g_wasm_init_error = g_wasm_worker->init();
|
||||
if(g_wasm_init_error != "")
|
||||
{
|
||||
delete g_wasm_worker;
|
||||
g_wasm_worker = 0;
|
||||
return(g_wasm_init_error);
|
||||
}
|
||||
|
||||
g_wasm_epoch_running.store(true);
|
||||
WasmWorker* worker = g_wasm_worker;
|
||||
u64 period_ms = config_u64("WASM_EPOCH_PERIOD_MS", 50);
|
||||
g_wasm_epoch_ticker = std::thread([worker, period_ms] {
|
||||
while(g_wasm_epoch_running.load())
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(period_ms));
|
||||
worker->engine.increment_epoch();
|
||||
}
|
||||
});
|
||||
return("");
|
||||
}
|
||||
|
||||
static bool wasm_artifact_exists(Request* context, const String& entry_unit)
|
||||
{
|
||||
if(entry_unit == "")
|
||||
return(false);
|
||||
String wasm_path = context->server->config["BIN_DIRECTORY"] + entry_unit + ".wasm";
|
||||
struct stat st;
|
||||
return(stat(wasm_path.c_str(), &st) == 0 && S_ISREG(st.st_mode));
|
||||
}
|
||||
|
||||
static bool wasm_backend_native_fallback_needed(Request* context, const String& entry_unit)
|
||||
{
|
||||
if(!context || !context->server || entry_unit == "")
|
||||
return(true);
|
||||
String site_root = path_join(context->server->config["COMPILER_SYS_PATH"], context->server->config["SITE_DIRECTORY"]);
|
||||
// W5 keeps native as the reference backend for compiler/docs pages and the
|
||||
// host-owned service surfaces that are not membrane APIs yet. This makes the
|
||||
// default backend safe while W6 decides which fallbacks to retire vs keep.
|
||||
if(entry_unit.rfind(path_join(site_root, "doc") + "/", 0) == 0)
|
||||
return(true);
|
||||
String source = file_get_contents(entry_unit);
|
||||
StringList native_only_tokens = {
|
||||
"markdown_to_", "zip_", "sqlite_", "regex_", "xml_", "yaml_", "task(", "task_repeat(",
|
||||
"task_pid(", "task_kill(", "unit_call(", "unit_render(",
|
||||
"unit_compile(", "unit_info(", "units_list(", "compiler_load_shared_unit(",
|
||||
"file_put_contents(", "file_append(", "usleep(", "sleep("
|
||||
};
|
||||
for(auto& token : native_only_tokens)
|
||||
if(source.find(token) != String::npos)
|
||||
return(true);
|
||||
return(false);
|
||||
}
|
||||
|
||||
// True if this request should be served by the wasm backend. Falls through to
|
||||
// native when disabled, when init failed, or when the unit has no wasm artifact
|
||||
// (e.g. units skip-listed for try/catch — automatic, graceful fallback).
|
||||
bool wasm_backend_should_handle(Request& request, const String& entry_unit)
|
||||
{
|
||||
if(!wasm_backend_configured(&request))
|
||||
return(false);
|
||||
if(request.resources.is_cli)
|
||||
return(false);
|
||||
if(wasm_backend_native_fallback_needed(&request, entry_unit))
|
||||
return(false);
|
||||
if(!wasm_artifact_exists(&request, entry_unit))
|
||||
return(false);
|
||||
if(wasm_backend_ensure_started(&request) != "")
|
||||
return(false);
|
||||
return(true);
|
||||
}
|
||||
|
||||
// Serve the page render through a wasm workspace. Populates the native Request
|
||||
// (status/headers/cookies/session/body) so the existing transport writes the
|
||||
// response unchanged. Returns "" on success, or a collapsed error/trace string
|
||||
// for the caller to route into the configured error page.
|
||||
String wasm_backend_serve(Request& request, const String& entry_unit)
|
||||
{
|
||||
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["entry_unit"] = entry_unit;
|
||||
|
||||
WasmResponse response = wasm_worker_serve(*g_wasm_worker, ctx, entry_unit);
|
||||
if(!response.ok)
|
||||
return(response.error == "" ? String("wasm workspace failed") : response.error);
|
||||
|
||||
request.header["X-UCE-Backend"] = "wasm";
|
||||
request.header["X-UCE-Wasm-Workspace-Birth-Us"] = std::to_string(response.workspace_birth_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);
|
||||
request.header["X-UCE-Wasm-Component-Resolve-Avg-Us"] = std::to_string(
|
||||
response.component_resolve_count ? response.component_resolve_total_us / response.component_resolve_count : 0);
|
||||
|
||||
// status line: keep the native default unless the unit set one
|
||||
String status = response.meta["status"].to_string();
|
||||
if(status != "")
|
||||
request.response_code = status;
|
||||
// merge headers over the native defaults (so Content-Type survives unless
|
||||
// the unit overrode it); replace cookies/session with the unit's view
|
||||
if(response.meta.key("headers"))
|
||||
response.meta["headers"].each([&](const DValue& value, String name) {
|
||||
request.header[name] = value.to_string();
|
||||
});
|
||||
if(response.meta.key("cookies"))
|
||||
response.meta["cookies"].each([&](const DValue& value, String) {
|
||||
request.set_cookies.push_back(value.to_string());
|
||||
});
|
||||
if(response.meta.key("session"))
|
||||
{
|
||||
request.session.clear();
|
||||
response.meta["session"].each([&](const DValue& value, String name) {
|
||||
request.session[name] = value.to_string();
|
||||
});
|
||||
}
|
||||
|
||||
// body into the request's primary output stream (ob_stack[0]); the
|
||||
// transport's assemble_output_buffer concatenates the stack
|
||||
if(request.ob)
|
||||
request.ob->write(response.body.data(), response.body.size());
|
||||
return("");
|
||||
}
|
||||
|
||||
// Stop the ticker before the worker process exits (best-effort; forked workers
|
||||
// are usually killed, but a clean ager-out path should join the thread).
|
||||
void wasm_backend_shutdown()
|
||||
{
|
||||
if(g_wasm_epoch_running.exchange(false) && g_wasm_epoch_ticker.joinable())
|
||||
g_wasm_epoch_ticker.join();
|
||||
}
|
||||
+43
-2
@@ -103,6 +103,11 @@ extern "C" void uce_wasm_link_anchors()
|
||||
(void*)(const char* (*)(const char*, int))&strchr,
|
||||
(void*)(const char* (*)(const char*, int))&strrchr,
|
||||
(void*)(const char* (*)(const char*, const char*))&strstr,
|
||||
// ctype family (int(int)); units use these directly
|
||||
(void*)&isalnum, (void*)&isalpha, (void*)&isblank, (void*)&iscntrl,
|
||||
(void*)&isdigit, (void*)&isgraph, (void*)&islower, (void*)&isprint,
|
||||
(void*)&ispunct, (void*)&isspace, (void*)&isupper, (void*)&isxdigit,
|
||||
(void*)&tolower, (void*)&toupper,
|
||||
};
|
||||
(void)libc_anchors;
|
||||
}
|
||||
@@ -170,8 +175,13 @@ struct RequestPropsScope
|
||||
}
|
||||
};
|
||||
|
||||
// kind values shared with the host loader (see src/wasm/worker.h)
|
||||
enum WasmResolveKind { WASM_RESOLVE_COMPONENT = 0, WASM_RESOLVE_RENDER = 1, WASM_RESOLVE_EXISTS = 2 };
|
||||
// kind values shared with the host loader (src/wasm/worker.cpp)
|
||||
enum WasmResolveKind {
|
||||
WASM_RESOLVE_COMPONENT = 0,
|
||||
WASM_RESOLVE_RENDER = 1,
|
||||
WASM_RESOLVE_EXISTS = 2,
|
||||
WASM_RESOLVE_ONCE = 3,
|
||||
};
|
||||
|
||||
static s32 wasm_resolve_target(String target, s32 kind, String* resolved_out = 0)
|
||||
{
|
||||
@@ -205,6 +215,26 @@ bool component_exists(String name)
|
||||
return(component_resolve(name) != "");
|
||||
}
|
||||
|
||||
// Run a unit's ONCE() handler at most once per request (native
|
||||
// compiler_run_unit_once_if_needed semantics): dedup on the resolved unit
|
||||
// path via request.once_units. The handler emits head assets, etc.
|
||||
static void wasm_run_once(const String& resolved, Request& request)
|
||||
{
|
||||
if(resolved == "")
|
||||
return;
|
||||
if(request.once_units.find(resolved) != request.once_units.end())
|
||||
return;
|
||||
request.once_units.insert(resolved);
|
||||
s32 once_slot = wasm_resolve_target(resolved, WASM_RESOLVE_ONCE);
|
||||
if(once_slot == 0)
|
||||
return;
|
||||
String previous_unit = request.resources.current_unit_file;
|
||||
request.resources.current_unit_file = resolved;
|
||||
request_ref_handler once_handler = (request_ref_handler)(uintptr_t)once_slot;
|
||||
once_handler(request);
|
||||
request.resources.current_unit_file = previous_unit;
|
||||
}
|
||||
|
||||
void component_render(String name, DValue props, Request& request)
|
||||
{
|
||||
String resolved;
|
||||
@@ -214,6 +244,7 @@ void component_render(String name, DValue props, Request& request)
|
||||
print(component_error_banner("component not found: " + trim(name)));
|
||||
return;
|
||||
}
|
||||
wasm_run_once(resolved, request);
|
||||
RequestPropsScope props_scope(&request, props);
|
||||
String previous_unit = request.resources.current_unit_file;
|
||||
if(resolved != "")
|
||||
@@ -249,6 +280,7 @@ void unit_render(String file_name, Request& request)
|
||||
print(component_error_banner("unit not found: " + trim(file_name)));
|
||||
return;
|
||||
}
|
||||
wasm_run_once(resolved, request);
|
||||
String previous_unit = request.resources.current_unit_file;
|
||||
if(resolved != "")
|
||||
request.resources.current_unit_file = resolved;
|
||||
@@ -261,6 +293,15 @@ void unit_render(String file_name) { unit_render(file_name, *context); }
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Host calls this to render the request's entry unit. Routing through
|
||||
// unit_render gives the entry the same ONCE() + dispatch semantics as
|
||||
// components (the host pre-loaded it, so resolution is a cache hit).
|
||||
void uce_wasm_render_entry(const char* path, size_t len)
|
||||
{
|
||||
// the host always runs uce_wasm_core_init + apply_context before this
|
||||
unit_render(String(path, len), *context);
|
||||
}
|
||||
|
||||
void* uce_alloc(size_t len)
|
||||
{
|
||||
return(malloc(len));
|
||||
|
||||
@@ -19,3 +19,17 @@ strncpy
|
||||
strchr
|
||||
strrchr
|
||||
strstr
|
||||
isalnum
|
||||
isalpha
|
||||
isblank
|
||||
iscntrl
|
||||
isdigit
|
||||
isgraph
|
||||
islower
|
||||
isprint
|
||||
ispunct
|
||||
isspace
|
||||
isupper
|
||||
isxdigit
|
||||
tolower
|
||||
toupper
|
||||
|
||||
+62
-7
@@ -26,6 +26,7 @@
|
||||
|
||||
#include <wasmtime.hh>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
@@ -80,10 +81,15 @@ struct WasmResponse
|
||||
String body;
|
||||
DValue meta; // status / headers / cookies / session
|
||||
String error; // collapsed trace or loader error when !ok
|
||||
u64 workspace_birth_us = 0;
|
||||
u64 component_resolve_count = 0;
|
||||
u64 component_resolve_total_us = 0;
|
||||
};
|
||||
|
||||
// ---- module byte parsing (hardened; carried from the phase 3 spike) -------
|
||||
|
||||
// included into both w3_driver.cpp and the native server TU (via backend.cpp);
|
||||
// file-scope helpers are static so each TU gets its own copy with no clash
|
||||
static bool wasm_read_uleb(const std::vector<u8>& buf, size_t& pos, size_t end, u64& out)
|
||||
{
|
||||
out = 0;
|
||||
@@ -315,13 +321,17 @@ class WasmWorkspace
|
||||
public:
|
||||
WasmWorker& worker;
|
||||
wasmtime::Store store;
|
||||
u64 workspace_birth_us = 0;
|
||||
u64 component_resolve_count = 0;
|
||||
u64 component_resolve_total_us = 0;
|
||||
|
||||
explicit WasmWorkspace(WasmWorker& w) : worker(w), store(w.engine)
|
||||
{
|
||||
}
|
||||
|
||||
// resolve-kind values shared with the guest core (src/wasm/core.cpp)
|
||||
enum ResolveKind { RESOLVE_COMPONENT = 0, RESOLVE_RENDER = 1, RESOLVE_EXISTS = 2 };
|
||||
// must match WasmResolveKind in src/wasm/core.cpp
|
||||
enum ResolveKind { RESOLVE_COMPONENT = 0, RESOLVE_RENDER = 1, RESOLVE_EXISTS = 2, RESOLVE_ONCE = 3 };
|
||||
|
||||
String birth()
|
||||
{
|
||||
@@ -421,10 +431,23 @@ public:
|
||||
String error = load_unit(entry_source_path, unit_index);
|
||||
if(error != "")
|
||||
return(error);
|
||||
auto handler = unit_func(unit_index, "__uce_render");
|
||||
if(!handler)
|
||||
return(entry_source_path + " does not export __uce_render");
|
||||
auto result = handler->call(ctx(), { wasmtime::Val(request_ptr) });
|
||||
// a page with no RENDER block renders empty (native parity), not an error
|
||||
if(!unit_func(unit_index, "__uce_render"))
|
||||
return("");
|
||||
// Render through the core so the entry gets the same ONCE() + dispatch
|
||||
// path as components (unit is pre-loaded above; resolution is cached).
|
||||
auto entry = core_func("uce_wasm_render_entry");
|
||||
if(!entry)
|
||||
return("core does not export uce_wasm_render_entry");
|
||||
int32_t guest_ptr = 0;
|
||||
error = call_core("uce_alloc", { (int32_t)entry_source_path.size() }, &guest_ptr);
|
||||
if(error != "" || guest_ptr == 0)
|
||||
return(error == "" ? String("guest uce_alloc failed for entry path") : error);
|
||||
error = guest_write((u32)guest_ptr, entry_source_path);
|
||||
if(error != "")
|
||||
return(error);
|
||||
auto result = entry->call(ctx(), { wasmtime::Val(guest_ptr), wasmtime::Val((int32_t)entry_source_path.size()) });
|
||||
call_core("uce_free", { guest_ptr }, 0);
|
||||
if(!result)
|
||||
return(trap_text(result.err()));
|
||||
return("");
|
||||
@@ -872,6 +895,12 @@ private:
|
||||
// hostcall body: uce_host_component_resolve(target, kind, current) → slot
|
||||
int32_t component_resolve(const String& target, int32_t kind, const String& current_unit, String& resolved_out)
|
||||
{
|
||||
auto probe_start = std::chrono::steady_clock::now();
|
||||
auto record_probe = [&]() {
|
||||
component_resolve_count += 1;
|
||||
component_resolve_total_us += (u64)std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now() - probe_start).count();
|
||||
};
|
||||
String file_name = target;
|
||||
String render_name;
|
||||
auto split = target.find(":");
|
||||
@@ -883,33 +912,51 @@ private:
|
||||
if(file_name == "" && current_unit != "")
|
||||
file_name = current_unit;
|
||||
if(file_name == "")
|
||||
{
|
||||
record_probe();
|
||||
return(0);
|
||||
}
|
||||
|
||||
String resolved = resolve_source_path(file_name, current_unit);
|
||||
if(resolved == "")
|
||||
{
|
||||
record_probe();
|
||||
return(0);
|
||||
}
|
||||
resolved_out = resolved;
|
||||
if(kind == RESOLVE_EXISTS)
|
||||
{
|
||||
record_probe();
|
||||
return(1);
|
||||
}
|
||||
|
||||
size_t unit_index = 0;
|
||||
String error = load_unit(resolved, unit_index);
|
||||
if(error != "")
|
||||
{
|
||||
fprintf(stderr, "[wasm] component load failed: %s\n", error.c_str());
|
||||
record_probe();
|
||||
return(0);
|
||||
}
|
||||
String symbol = kind == RESOLVE_RENDER ? "__uce_render" : "__uce_component";
|
||||
String symbol = kind == RESOLVE_RENDER ? "__uce_render"
|
||||
: kind == RESOLVE_ONCE ? "__uce_once"
|
||||
: "__uce_component";
|
||||
if(render_name != "")
|
||||
symbol += "_" + sanitize_symbol_suffix(render_name);
|
||||
String slot_key = resolved + ":" + symbol;
|
||||
auto cached = handler_slots.find(slot_key);
|
||||
if(cached != handler_slots.end())
|
||||
{
|
||||
record_probe();
|
||||
return((int32_t)cached->second);
|
||||
}
|
||||
auto handler = unit_func(unit_index, symbol);
|
||||
if(!handler)
|
||||
{
|
||||
fprintf(stderr, "[wasm] %s does not export %s\n", resolved.c_str(), symbol.c_str());
|
||||
// ONCE is optional per unit; a missing __uce_once is not an error
|
||||
if(kind != RESOLVE_ONCE)
|
||||
fprintf(stderr, "[wasm] %s does not export %s\n", resolved.c_str(), symbol.c_str());
|
||||
record_probe();
|
||||
return(0);
|
||||
}
|
||||
u32 slot = 0;
|
||||
@@ -917,9 +964,11 @@ private:
|
||||
if(error != "")
|
||||
{
|
||||
fprintf(stderr, "[wasm] %s\n", error.c_str());
|
||||
record_probe();
|
||||
return(0);
|
||||
}
|
||||
handler_slots[slot_key] = slot;
|
||||
record_probe();
|
||||
return((int32_t)slot);
|
||||
}
|
||||
|
||||
@@ -1056,13 +1105,19 @@ inline WasmResponse wasm_worker_serve(WasmWorker& worker, const DValue& context_
|
||||
{
|
||||
WasmResponse response;
|
||||
WasmWorkspace workspace(worker);
|
||||
auto birth_start = std::chrono::steady_clock::now();
|
||||
String error = workspace.birth();
|
||||
workspace.workspace_birth_us = (u64)std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now() - birth_start).count();
|
||||
if(error == "")
|
||||
error = workspace.apply_context(context_tree);
|
||||
if(error == "")
|
||||
error = workspace.render_entry(entry_source_path);
|
||||
if(error == "")
|
||||
error = workspace.collect(response);
|
||||
response.workspace_birth_us = workspace.workspace_birth_us;
|
||||
response.component_resolve_count = workspace.component_resolve_count;
|
||||
response.component_resolve_total_us = workspace.component_resolve_total_us;
|
||||
if(error != "")
|
||||
{
|
||||
response.ok = false;
|
||||
|
||||
Reference in New Issue
Block a user