wasm runtime: central WS broker, unified handlers, W7d holdouts, membrane completeness
- WS: a dedicated broker process owns HTTP_PORT + every connection; it forwards renders to the worker pool over uce.sock (non-blocking) and applies ws_* command batches flushed back at workspace teardown. Removes the now-dead per-worker websocket executor (-509 lines). - Dispatch: unify CLI / WebSocket / serve_http / page render through one serve_via_wasm(entry_unit, handler) path; handler string -> __uce_<handler> export symbol. - W7d: rewrite zip.uce to the membrane return-value error contract (no C++ try/catch), error-reporting.uce to genuine wasm traps instead of throw, and sharedunit.uce to unit_info(); empty the native-only token gate. - Membrane: wire ls / mkdir / file_mtime through new uce_host_file_list / uce_host_file_mkdir / uce_host_file_mtime hostcalls (resolve_guest_file gains directory support). Fixes /doc/index.uce listing nothing; adds a regression assertion that the index enumerates items. - Docs: add docs/wasm-runtime-architecture.md; record the W7e staged native- deletion plan in WASM-PROPOSAL.md. Verified: scripts/run_cli_tests.sh --include-wasm-kill -> 87 passed, 0 failed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+59
-24
@@ -19,6 +19,8 @@
|
||||
#include <atomic>
|
||||
#include <sys/stat.h>
|
||||
#include <thread>
|
||||
// Worker flushes ws_* dispatch batches to the broker via the FastCGI client.
|
||||
#include "../lib/fcgi_forward.h"
|
||||
|
||||
// per forked worker process: one engine + compiled-core cache, one epoch ticker
|
||||
static WasmWorker* g_wasm_worker = 0;
|
||||
@@ -116,18 +118,13 @@ static bool wasm_backend_native_fallback_uncached(Request* context, const String
|
||||
// Supported by the wasm core, intentionally NOT in this list:
|
||||
// - regex_* (host PCRE2 hostcall), xml_*/yaml_*/markdown_* (compiled in),
|
||||
// - unit_render()/component() (host resolver).
|
||||
// What remains is genuinely host-owned / native-only for now:
|
||||
// - zip pages that use C++ try/catch around zip_* error cases;
|
||||
// - direct compiler_load_shared_unit() access to native SharedUnit internals.
|
||||
// Background tasks, sleep/usleep, sockets/custom servers, memcache, mysql,
|
||||
// and unit_call/unit_compile/unit_info/units_list are host-owned but now have
|
||||
// membrane hostcalls, so they intentionally do not fallback here.
|
||||
StringList native_only_tokens = {
|
||||
"zip_", "compiler_load_shared_unit("
|
||||
};
|
||||
for(auto& token : native_only_tokens)
|
||||
if(source.find(token) != String::npos)
|
||||
return(true);
|
||||
// W7d: every former native-only surface now goes through the membrane —
|
||||
// zip_* (uce_host_zip), unit introspection (uce_host_units: unit_info/
|
||||
// unit_call/units_list), regex/xml/yaml/markdown, filesystem, sqlite,
|
||||
// background tasks, sleep, sockets/custom servers, memcache, mysql. No unit
|
||||
// source token forces native anymore, so there is nothing left to scan for.
|
||||
// (The native fallback now only covers cold/stale artifacts; W7e removes it.)
|
||||
(void)source;
|
||||
return(false);
|
||||
}
|
||||
|
||||
@@ -168,11 +165,12 @@ bool wasm_backend_should_handle(Request& request, const String& entry_unit)
|
||||
}
|
||||
|
||||
// Serve a request through a wasm workspace using the unit handler selected by
|
||||
// `kind` (page render or cli). 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, int32_t kind = WasmWorkspace::RESOLVE_RENDER)
|
||||
// `kind` (page render / cli / serve_http, with an optional named serve_http
|
||||
// handler). 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, const String& handler = "render")
|
||||
{
|
||||
DValue ctx;
|
||||
auto copy_map = [&](const StringMap& source, const char* key) {
|
||||
@@ -186,19 +184,56 @@ String wasm_backend_serve(Request& request, const String& entry_unit, int32_t ki
|
||||
copy_map(request.session, "session");
|
||||
ctx["entry_unit"] = entry_unit;
|
||||
// Raw request body: cli_input() parses a JSON CLI payload from context.in,
|
||||
// carried into the workspace, not just the form-decoded post map.
|
||||
// and serve_http handlers read it as req->in; carried into the workspace.
|
||||
ctx["in"] = request.in;
|
||||
// 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, kind);
|
||||
WasmResponse response = wasm_worker_serve(*g_wasm_worker, ctx, entry_unit, handler);
|
||||
if(!response.ok)
|
||||
return(response.error == "" ? String("wasm workspace failed") : response.error);
|
||||
|
||||
// A cli unit that does not export __uce_cli is a 404, matching native
|
||||
// compiler_invoke_cli ("CLI Entry Point Not Found"). For page render a
|
||||
// missing handler is simply an empty body (native parity).
|
||||
if(!response.handler_present && kind == WasmWorkspace::RESOLVE_CLI)
|
||||
// Any handler may have called ws_send/ws_close (not just WS handlers). If it
|
||||
// left dispatch commands, flush the batch to the central WS broker, which owns
|
||||
// all connections and resolves each command's target (id/scope/broadcast)
|
||||
// against the full registry. This is the only path WS data takes out — the
|
||||
// workspace owns no connections.
|
||||
if(DValue* cmds = response.meta.key("ws_commands"))
|
||||
{
|
||||
request.set_status(404, "CLI Entry Point Not Found");
|
||||
DValue batch;
|
||||
batch["commands"] = *cmds;
|
||||
if(DValue* cstate = response.meta.key("ws_connection_state"))
|
||||
{
|
||||
batch["connection_id"] = request.resources.websocket_connection_id;
|
||||
batch["connection_state"] = *cstate;
|
||||
}
|
||||
StringMap dispatch_params;
|
||||
dispatch_params["UCE_WS_DISPATCH"] = "1";
|
||||
String broker_socket = first(request.server ? request.server->config["WS_BROKER_SOCKET_PATH"] : String(),
|
||||
"/run/uce/ws-broker.sock");
|
||||
fcgi_forward_request(broker_socket, dispatch_params, ucb_encode(batch), 5);
|
||||
}
|
||||
|
||||
// A cli/serve_http unit that does not export the requested handler is a 404,
|
||||
// matching native compiler_invoke_cli. For page render a missing handler is
|
||||
// simply an empty body (native parity).
|
||||
if(!response.handler_present && handler != "render")
|
||||
{
|
||||
request.set_status(404, handler == "cli" ? "CLI Entry Point Not Found" : "Handler Not Found");
|
||||
return("");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user