{"name":"runtime_ffi.c","source":"/*\n * NURL runtime — stdlib/runtime_ffi.c   (stdlib FFI shims)\n *\n * Copyright (c) 2026 The NURL Project Developers\n * SPDX-License-Identifier: MIT OR Apache-2.0\n *\n * The \"stdlib FFI shim\" half of the runtime (A9 split). Everything here\n * is an external-world bridge that a bootstrap/`no_std` target does NOT\n * need: HTTP response ABI, process exec/spawn, crypto entropy, TCP/TLS\n * + UDP sockets, DNS, thread trampolines, signal handling, and the\n * async fiber runtime + I/O reactor.\n *\n * NOT a standalone translation unit: it is #included by stdlib/runtime.c\n * AFTER stdlib/runtime_core.c, and relies on the core's system-header\n * includes, feature macros, and internal helpers (nurl__xmalloc,\n * nurl__jrnl_*, nurl_alloc/free, …) already being in scope. The core\n * half has zero references back into this file, so runtime_core.c alone\n * defines the symbol set a `no_std` target must provide (ROADMAP D2).\n */\n\n#include <limits.h>\n\n/* ── §14  HTTP client response ABI ────────────────────────────── */\n/*\n * The HTTP client itself is now pure NURL (stdlib/ext/http_pure.nu over\n * the libc TCP socket + the pure TLS stack in stdlib/std/tls.nu) — no\n * libcurl, no WinHTTP. What remains C-side is purely the response value\n * type and its destructor:\n *\n *   NurlHttpResponse  — the heap struct hp_perform fills and http.nu's\n *                       accessors nurl_peek as 6 i64 slots.\n *   nurl_http_response_free  — frees that struct (body + headers[] + each\n *                       name/value), called once per Ok result by\n *                       stdlib/ext/http.nu's response_free.\n *\n * The nurl_http_perform_full[_to] / nurl_http_stream_* symbols below are\n * inert stubs kept only so the compiler's historical `declare`s still\n * resolve; the pure NURL path never calls them.\n */\n\ntypedef struct NurlHttpHeader {\n    char *name;\n    char *value;\n} NurlHttpHeader;\n\ntypedef struct NurlHttpResponse {\n    long long          status;\n    long long          err_kind;\n    long long          header_count;\n    NurlHttpHeader    *headers;\n    char              *body;          /* NUL-terminated; \"\" when empty */\n    long long          body_len;\n} NurlHttpResponse;\n\n/* Pure-NURL accessors in stdlib/ext/http.nu nurl_peek this struct as\n * 6 i64 slots; NurlHttpHeader as 2 i64 slots (16-byte stride). Static-\n * assert so a future field reorder breaks the build instead of silently\n * miscompiling NURL reads. wasm32 is exempt — its stub backend always\n * returns HttpOther and NURL dispatch short-circuits before any\n * pointer-bearing slot is read. */\n#if !defined(__wasi__)\n_Static_assert(sizeof(NurlHttpResponse) == 48, \"NurlHttpResponse layout\");\n_Static_assert(sizeof(NurlHttpHeader)   == 16, \"NurlHttpHeader layout\");\n#endif\n\n/* Tags must match `HttpErr` in stdlib/ext/http.nu. */\n#define NURL_HTTP_ERR_OK         0\n#define NURL_HTTP_ERR_CONNECT    1\n#define NURL_HTTP_ERR_TIMEOUT    2\n#define NURL_HTTP_ERR_TLS        3\n#define NURL_HTTP_ERR_DNS        4\n#define NURL_HTTP_ERR_INVALID    5\n#define NURL_HTTP_ERR_OTHER      6\n\n/* Inert stub — the live path is hp_perform in stdlib/ext/http_pure.nu. */\nlong long nurl_http_perform_full_to(const char *url, const char *method,\n                                    const char *body, const char *headers_blob,\n                                    long long timeout_ms,\n                                    long long connect_timeout_ms) {\n    (void)method; (void)body; (void)headers_blob;\n    (void)timeout_ms; (void)connect_timeout_ms;\n    NurlHttpResponse *r = (NurlHttpResponse*)calloc(1, sizeof(NurlHttpResponse));\n    if (!r) return 0;\n    r->body = strdup(\"\");\n    r->err_kind = (!url || !*url) ? NURL_HTTP_ERR_INVALID\n                                  : NURL_HTTP_ERR_OTHER;\n    return (long long)(uintptr_t)r;\n}\n\n\n/* 4-arg alias using the historical 30 s / 10 s budget. */\nlong long nurl_http_perform_full(const char *url, const char *method,\n                                 const char *body, const char *headers_blob) {\n    return nurl_http_perform_full_to(url, method, body, headers_blob,\n                                     30000, 10000);\n}\n\n/* ── §14b  HTTP streaming — inert stubs ───────────────────────── */\n/* The live streaming path is hp_stream_* in stdlib/ext/http_pure.nu\n * (incremental chunked decoding over the pure transport). These stubs\n * remain only so the compiler's historical `declare`s resolve. */\nlong long nurl_http_stream_open_to(const char *method, const char *url,\n                                   const char *body, const char *headers_blob,\n                                   long long timeout_ms,\n                                   long long connect_timeout_ms) {\n    (void)method; (void)url; (void)body; (void)headers_blob;\n    (void)timeout_ms; (void)connect_timeout_ms;\n    return 0;  /* signals open failure; NURL maps to HttpOther */\n}\n\nchar *nurl_http_stream_next(long long handle)     { (void)handle; return NULL; }\nlong long nurl_http_stream_pump_headers(long long h)  { (void)h; return 0; }\nvoid      nurl_http_stream_close(long long h)     { (void)h; }\n\n\n\n/* The response accessors (status / err_kind / body / body_len /\n * header_count / header_name / header_value) are pure-NURL @-fns in\n * `stdlib/ext/http.nu` that read NurlHttpResponse via `nurl_peek(p, slot)`\n * over the i64-slot layout. `_free` stays C because it walks the\n * headers[] array freeing every name/value pair plus the body — the\n * pure perform path (hp_perform) allocates the struct in this exact\n * shape so this destructor frees it correctly. */\nvoid nurl_http_response_free(long long resp) {\n    NurlHttpResponse *r = (NurlHttpResponse*)(uintptr_t)resp;\n    if (!r) return;\n    if (r->headers) {\n        for (long long i = 0; i < r->header_count; i++) {\n            free(r->headers[i].name);\n            free(r->headers[i].value);\n        }\n        free(r->headers);\n    }\n    free(r->body);\n    free(r);\n}\n\n/* ── §16  Process execution (synchronous, Win32 only) ─────────── */\n/*\n * Win32-only synchronous subprocess runner. POSIX runs through pure\n * NURL (stdlib/std/process.nu over stdlib/core/posix.nu); the C symbol\n * here is a link-time stub on those targets. ABI:\n *   nurl_proc_run(cmd, argv_buf, argc, stdin_blob) → heap NurlProcResult*\n * argv_buf is a char *const argv[argc] view of NUL-terminated entries;\n * stdin_blob is fed verbatim to the child's stdin (NULL/\"\" → empty).\n * Blocks until child exits and stdout+stderr have drained. Accessors\n * (exit_code / err_kind / stdout / stderr / *_len) live below.\n *\n * ProcessErr tags (must match stdlib/std/process.nu):\n *   0 ok  1 NotFound  2 ExecFailed  3 Io  4 Other\n */\n\n#define NURL_PROC_ERR_OK            0\n#define NURL_PROC_ERR_NOTFOUND      1\n#define NURL_PROC_ERR_EXEC_FAILED   2\n#define NURL_PROC_ERR_IO            3\n#define NURL_PROC_ERR_OTHER         4\n\ntypedef struct NurlProcResult {\n    long long  exit_code;\n    long long  err_kind;\n    char      *stdout_buf;\n    long long  stdout_len;\n    char      *stderr_buf;\n    long long  stderr_len;\n} NurlProcResult;\n\ntypedef struct NurlProcBuf { char *data; size_t len; size_t cap; } NurlProcBuf;\nstatic int nurl__proc_buf_append(NurlProcBuf *b, const char *src, size_t n) {\n    if (b->len + n + 1 > b->cap) {\n        size_t newcap = b->cap ? b->cap : 256;\n        while (newcap < b->len + n + 1) newcap *= 2;\n        char *p = (char*)realloc(b->data, newcap);\n        if (!p) return 0;\n        b->data = p; b->cap = newcap;\n    }\n    memcpy(b->data + b->len, src, n);\n    b->len += n;\n    b->data[b->len] = 0;\n    return 1;\n}\n\n\n#if (defined(_WIN32) && !defined(__wasi__)) || defined(NURL_PROCESS_COMMANDLINE_TEST)\n/* Quote one argv entry per CommandLineToArgvW rules (Colascione 2011). */\nstatic int nurl__proc_quote_arg(const char *arg, NurlProcBuf *out) {\n    if (!arg) arg = \"\";\n    int needs_quote = (*arg == 0);\n    for (const char *p = arg; *p && !needs_quote; p++) {\n        if (*p == ' ' || *p == '\\t' || *p == '\\n' || *p == '\\v' || *p == '\"') {\n            needs_quote = 1;\n        }\n    }\n    if (!needs_quote) {\n        return nurl__proc_buf_append(out, arg, strlen(arg));\n    }\n    if (!nurl__proc_buf_append(out, \"\\\"\", 1)) return 0;\n    const char *p = arg;\n    while (*p) {\n        size_t bs = 0;\n        while (*p == '\\\\') { bs++; p++; }\n        if (*p == 0) {\n            for (size_t i = 0; i < 2 * bs; i++)\n                if (!nurl__proc_buf_append(out, \"\\\\\", 1)) return 0;\n            break;\n        } else if (*p == '\"') {\n            for (size_t i = 0; i < 2 * bs + 1; i++)\n                if (!nurl__proc_buf_append(out, \"\\\\\", 1)) return 0;\n            if (!nurl__proc_buf_append(out, \"\\\"\", 1)) return 0;\n            p++;\n        } else {\n            for (size_t i = 0; i < bs; i++)\n                if (!nurl__proc_buf_append(out, \"\\\\\", 1)) return 0;\n            if (!nurl__proc_buf_append(out, p, 1)) return 0;\n            p++;\n        }\n    }\n    return nurl__proc_buf_append(out, \"\\\"\", 1);\n}\n\n\n/* Native executables consume CRT quoting. Batch files are parsed by\n * cmd.exe first and need a different encoder, with delayed expansion off.\n * A literal '%' is followed by an empty substring of cmd's built-in CD\n * variable, preventing the remainder of the argument from becoming %VAR%.\n * This is the same documented cmd substring mechanism used by Rust std's\n * Windows batch-argument encoder. Batch quotes/newlines are rejected because\n * they cannot preserve the ordinary argv contract; native argv is unaffected.\n * https://github.com/rust-lang/rust/blob/master/library/std/src/sys/args/windows.rs */\ntypedef struct NurlProcLaunch {\n    NurlProcBuf command;\n    const char *application;  /* borrowed trusted system cmd.exe, or NULL */\n} NurlProcLaunch;\nstatic int nurl__proc_ascii_lower(int c) {\n    return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c;\n}\nstatic int nurl__proc_batch_file(const char *cmd) {\n    size_t n = strlen(cmd);\n    if (n < 4 || cmd[n - 4] != '.') return 0;\n    const char *e = cmd + n - 3;\n    return (nurl__proc_ascii_lower(e[0]) == 'b' &&\n            nurl__proc_ascii_lower(e[1]) == 'a' && nurl__proc_ascii_lower(e[2]) == 't') ||\n           (nurl__proc_ascii_lower(e[0]) == 'c' &&\n            nurl__proc_ascii_lower(e[1]) == 'm' && nurl__proc_ascii_lower(e[2]) == 'd');\n}\nstatic void nurl__proc_launch_free(NurlProcLaunch *launch) {\n    free(launch->command.data);\n    memset(launch, 0, sizeof(*launch));\n}\nstatic int nurl__proc_batch_quote(const char *arg, NurlProcBuf *out) {\n    if (!arg) arg = \"\";\n    if (!nurl__proc_buf_append(out, \"\\\"\", 1)) return 0;\n    size_t trailing = 0;\n    for (const char *p = arg; *p; ++p) {\n        if (*p == '\"' || *p == '\\r' || *p == '\\n') return 0;\n        trailing = *p == '\\\\' ? trailing + 1 : 0;\n        if (*p == '%') {\n            if (!nurl__proc_buf_append(out, \"%%cd:~,%\", 8)) return 0;\n        } else if (!nurl__proc_buf_append(out, p, 1)) return 0;\n    }\n    for (size_t i = 0; i < trailing; ++i)\n        if (!nurl__proc_buf_append(out, \"\\\\\", 1)) return 0;\n    return nurl__proc_buf_append(out, \"\\\"\", 1);\n}\nstatic int nurl__proc_build_launch(const char *cmd, const char *const *args,\n                                  long long argc, const char *command_prompt,\n                                  NurlProcLaunch *out) {\n    int batch = nurl__proc_batch_file(cmd);\n    if (batch) {\n        if (!command_prompt || !*command_prompt) return 0;\n        out->application = command_prompt;\n        if (!nurl__proc_quote_arg(command_prompt, &out->command) ||\n            !nurl__proc_buf_append(&out->command, \" /D /E:ON /V:OFF /S /C \\\"\", 24) ||\n            !nurl__proc_batch_quote(cmd, &out->command)) return 0;\n    } else if (!nurl__proc_quote_arg(cmd, &out->command)) return 0;\n    for (long long i = 0; i < argc; ++i) {\n        const char *arg = args && args[i] ? args[i] : \"\";\n        if (!nurl__proc_buf_append(&out->command, \" \", 1)) return 0;\n        if (batch ? !nurl__proc_batch_quote(arg, &out->command)\n                  : !nurl__proc_quote_arg(arg, &out->command)) return 0;\n    }\n    return !batch || nurl__proc_buf_append(&out->command, \"\\\"\", 1);\n}\n#endif\n\n#if !defined(_WIN32) && !defined(__wasi__)\n/* POSIX path is in pure NURL; this stub is link-time only. */\nlong long nurl_proc_run(const char *cmd, const char *argv_buf,\n                        long long argc, const char *stdin_blob) {\n    (void)cmd; (void)argv_buf; (void)argc; (void)stdin_blob;\n    return 0;\n}\n\n/* POSIX headers shared with the §16b accessors below. */\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <fcntl.h>\n#include <poll.h>\n#include <signal.h>\n\nstatic void nurl__proc_close_pair(int p[2]) {\n    if (p[0] >= 0) close(p[0]);\n    if (p[1] >= 0) close(p[1]);\n    p[0] = p[1] = -1;\n}\n\n#elif defined(_WIN32) && !defined(__wasi__)\n/* ── Win32 backend (CreateProcess + reader threads) ─────────── */\n\n#include <process.h>\n\ntypedef struct NurlProcReadCtx {\n    HANDLE       h;\n    NurlProcBuf  buf;\n    int          io_err;\n} NurlProcReadCtx;\n\nstatic unsigned __stdcall nurl__proc_reader_thread(void *p) {\n    NurlProcReadCtx *ctx = (NurlProcReadCtx*)p;\n    char tmp[4096];\n    DWORD rd = 0;\n    for (;;) {\n        BOOL ok = ReadFile(ctx->h, tmp, sizeof(tmp), &rd, NULL);\n        if (!ok) {\n            DWORD le = GetLastError();\n            if (le == ERROR_BROKEN_PIPE || le == ERROR_HANDLE_EOF) break;\n            ctx->io_err = 1;\n            break;\n        }\n        if (rd == 0) break;\n        if (!nurl__proc_buf_append(&ctx->buf, tmp, (size_t)rd)) {\n            ctx->io_err = 1;\n            break;\n        }\n    }\n    return 0;\n}\n\n\nstatic int nurl__proc_prepare_launch(const char *cmd, const char *const *args,\n                                    long long argc, NurlProcLaunch *out,\n                                    char *command_prompt, size_t prompt_size) {\n    char *resolved = NULL;\n    if (!nurl__proc_batch_file(cmd)) {\n        const char *base = cmd;\n        for (const char *p = cmd; *p; ++p)\n            if (*p == '/' || *p == '\\\\') base = p + 1;\n        /* CreateProcess appends .exe, but does not search for PATH batch\n         * shims. Preserve native precedence, then resolve extensionless\n         * tools such as the installed `nurl` -> bin\\nurl.bat. */\n        if (strchr(base, '.') || SearchPathA(NULL, cmd, \".exe\", 0, NULL, NULL))\n            return nurl__proc_build_launch(cmd, args, argc, NULL, out);\n        const char *extension = \".bat\";\n        DWORD needed = SearchPathA(NULL, cmd, extension, 0, NULL, NULL);\n        if (!needed) {\n            extension = \".cmd\";\n            needed = SearchPathA(NULL, cmd, extension, 0, NULL, NULL);\n        }\n        if (!needed) return nurl__proc_build_launch(cmd, args, argc, NULL, out);\n        resolved = (char *)malloc((size_t)needed + 1);\n        if (!resolved) return 0;\n        DWORD length = SearchPathA(NULL, cmd, extension, needed + 1, resolved, NULL);\n        if (!length || length > needed) { free(resolved); return 0; }\n        cmd = resolved;\n    }\n    UINT length = GetSystemDirectoryA(command_prompt, (UINT)prompt_size);\n    if (!length || length + sizeof(\"\\\\cmd.exe\") > prompt_size) {\n        free(resolved);\n        return 0;\n    }\n    memcpy(command_prompt + length, \"\\\\cmd.exe\", sizeof(\"\\\\cmd.exe\"));\n    int result = nurl__proc_build_launch(cmd, args, argc, command_prompt, out);\n    free(resolved);\n    return result;\n}\n\nstatic int nurl__proc_prepare_shell(const char *command, NurlProcLaunch *out,\n                                    char *command_prompt, size_t prompt_size) {\n    UINT length = GetSystemDirectoryA(command_prompt, (UINT)prompt_size);\n    if (!length || length + sizeof(\"\\\\cmd.exe\") > prompt_size) return 0;\n    memcpy(command_prompt + length, \"\\\\cmd.exe\", sizeof(\"\\\\cmd.exe\"));\n    out->application = command_prompt;\n    /* This entry point deliberately accepts shell syntax. /S strips just\n     * our outer quote pair, leaving the caller's quotes/operators intact. */\n    return nurl__proc_quote_arg(command_prompt, &out->command) &&\n           nurl__proc_buf_append(&out->command, \" /D /E:ON /V:OFF /S /C \\\"\", 24) &&\n           nurl__proc_buf_append(&out->command, command, strlen(command)) &&\n           nurl__proc_buf_append(&out->command, \"\\\"\", 1);\n}\n\nstatic long long nurl__proc_run_mode(const char *cmd, const char *argv_buf,\n                                    long long argc, const char *stdin_blob,\n                                    int shell) {\n    NurlProcResult *r = (NurlProcResult*)calloc(1, sizeof(NurlProcResult));\n    if (!r) return 0;\n    if (!cmd || (!*cmd && !shell)) {\n        r->err_kind   = NURL_PROC_ERR_NOTFOUND;\n        r->stdout_buf = strdup(\"\");\n        r->stderr_buf = strdup(\"\");\n        return (long long)(uintptr_t)r;\n    }\n    if (argc < 0) argc = 0;\n    const char *const *argv_user = (const char *const *)argv_buf;\n\n    NurlProcLaunch launch = {0};\n    char command_prompt[MAX_PATH + 16];\n    int build_ok = shell\n        ? nurl__proc_prepare_shell(cmd, &launch, command_prompt, sizeof(command_prompt))\n        : nurl__proc_prepare_launch(cmd, argv_user, argc, &launch,\n                                    command_prompt, sizeof(command_prompt));\n    if (!build_ok || !launch.command.data) {\n        nurl__proc_launch_free(&launch);\n        r->err_kind   = NURL_PROC_ERR_OTHER;\n        r->stdout_buf = strdup(\"\");\n        r->stderr_buf = strdup(\"\");\n        return (long long)(uintptr_t)r;\n    }\n\n    SECURITY_ATTRIBUTES sa = {0};\n    sa.nLength              = sizeof(sa);\n    sa.bInheritHandle       = TRUE;\n    sa.lpSecurityDescriptor = NULL;\n\n    HANDLE in_r = NULL, in_w = NULL;\n    HANDLE out_r = NULL, out_w = NULL;\n    HANDLE err_r = NULL, err_w = NULL;\n    if (!CreatePipe(&in_r, &in_w, &sa, 0) ||\n        !CreatePipe(&out_r, &out_w, &sa, 0) ||\n        !CreatePipe(&err_r, &err_w, &sa, 0)) {\n        if (in_r)  CloseHandle(in_r);\n        if (in_w)  CloseHandle(in_w);\n        if (out_r) CloseHandle(out_r);\n        if (out_w) CloseHandle(out_w);\n        if (err_r) CloseHandle(err_r);\n        if (err_w) CloseHandle(err_w);\n        nurl__proc_launch_free(&launch);\n        r->err_kind   = NURL_PROC_ERR_IO;\n        r->stdout_buf = strdup(\"\");\n        r->stderr_buf = strdup(\"\");\n        return (long long)(uintptr_t)r;\n    }\n    /* Parent-side ends must not be inherited. */\n    SetHandleInformation(in_w,  HANDLE_FLAG_INHERIT, 0);\n    SetHandleInformation(out_r, HANDLE_FLAG_INHERIT, 0);\n    SetHandleInformation(err_r, HANDLE_FLAG_INHERIT, 0);\n\n    STARTUPINFOA si = {0};\n    si.cb         = sizeof(si);\n    si.dwFlags    = STARTF_USESTDHANDLES;\n    si.hStdInput  = in_r;\n    si.hStdOutput = out_w;\n    si.hStdError  = err_w;\n    PROCESS_INFORMATION pi = {0};\n    BOOL ok = CreateProcessA(launch.application, launch.command.data,\n                             NULL, NULL, TRUE, 0,\n                             NULL, NULL, &si, &pi);\n    DWORD spawn_err = ok ? 0 : GetLastError();\n    nurl__proc_launch_free(&launch);\n    /* Close inherited ends on the parent side once the child owns them. */\n    CloseHandle(in_r);\n    CloseHandle(out_w);\n    CloseHandle(err_w);\n\n    if (!ok) {\n        DWORD le = spawn_err;\n        CloseHandle(in_w);\n        CloseHandle(out_r);\n        CloseHandle(err_r);\n        if (le == ERROR_FILE_NOT_FOUND || le == ERROR_PATH_NOT_FOUND)\n            r->err_kind = NURL_PROC_ERR_NOTFOUND;\n        else\n            r->err_kind = NURL_PROC_ERR_EXEC_FAILED;\n        r->stdout_buf = strdup(\"\");\n        r->stderr_buf = strdup(\"\");\n        return (long long)(uintptr_t)r;\n    }\n\n    NurlProcReadCtx out_ctx = {0}, err_ctx = {0};\n    out_ctx.h = out_r;\n    err_ctx.h = err_r;\n    HANDLE out_th = (HANDLE)_beginthreadex(NULL, 0, nurl__proc_reader_thread, &out_ctx, 0, NULL);\n    HANDLE err_th = (HANDLE)_beginthreadex(NULL, 0, nurl__proc_reader_thread, &err_ctx, 0, NULL);\n\n    if (stdin_blob && *stdin_blob) {\n        size_t total = strlen(stdin_blob);\n        size_t off = 0;\n        while (off < total) {\n            DWORD written = 0;\n            BOOL wok = WriteFile(in_w, stdin_blob + off, (DWORD)(total - off), &written, NULL);\n            if (!wok || written == 0) break;\n            off += written;\n        }\n    }\n    CloseHandle(in_w);\n\n    WaitForSingleObject(pi.hProcess, INFINITE);\n    DWORD exit_code = 0;\n    GetExitCodeProcess(pi.hProcess, &exit_code);\n\n    if (out_th) { WaitForSingleObject(out_th, INFINITE); CloseHandle(out_th); }\n    if (err_th) { WaitForSingleObject(err_th, INFINITE); CloseHandle(err_th); }\n    CloseHandle(out_r);\n    CloseHandle(err_r);\n    CloseHandle(pi.hProcess);\n    CloseHandle(pi.hThread);\n\n    int io_err = out_ctx.io_err || err_ctx.io_err;\n    r->exit_code  = io_err ? -1 : (long long)exit_code;\n    r->err_kind   = io_err ? NURL_PROC_ERR_IO : NURL_PROC_ERR_OK;\n    r->stdout_buf = out_ctx.buf.data ? out_ctx.buf.data : strdup(\"\");\n    r->stdout_len = (long long)out_ctx.buf.len;\n    r->stderr_buf = err_ctx.buf.data ? err_ctx.buf.data : strdup(\"\");\n    r->stderr_len = (long long)err_ctx.buf.len;\n    return (long long)(uintptr_t)r;\n}\n\nlong long nurl_proc_run(const char *cmd, const char *argv_buf,\n                        long long argc, const char *stdin_blob) {\n    return nurl__proc_run_mode(cmd, argv_buf, argc, stdin_blob, 0);\n}\n\nlong long nurl_proc_run_shell(const char *command) {\n    return nurl__proc_run_mode(command, NULL, 0, \"\", 1);\n}\n\n#else  /* WASI — stub */\n\nlong long nurl_proc_run(const char *cmd, const char *argv_buf,\n                        long long argc, const char *stdin_blob) {\n    (void)cmd; (void)argv_buf; (void)argc; (void)stdin_blob;\n    NurlProcResult *r = (NurlProcResult*)calloc(1, sizeof(NurlProcResult));\n    if (!r) return 0;\n    r->err_kind   = NURL_PROC_ERR_OTHER;\n    r->stdout_buf = strdup(\"\");\n    r->stderr_buf = strdup(\"\");\n    return (long long)(uintptr_t)r;\n}\n\n#endif\n\n#if !defined(_WIN32) || defined(__wasi__)\n/* POSIX shells use std/process.nu; unsupported runtimes retain ProcessOther. */\nlong long nurl_proc_run_shell(const char *command) {\n    return nurl_proc_run(command, NULL, 0, \"\");\n}\n#endif\n\nlong long nurl_proc_exit_code(long long h) {\n    NurlProcResult *r = (NurlProcResult*)(uintptr_t)h;\n    return r ? r->exit_code : -1;\n}\n\nlong long nurl_proc_err_kind(long long h) {\n    NurlProcResult *r = (NurlProcResult*)(uintptr_t)h;\n    return r ? r->err_kind : NURL_PROC_ERR_OTHER;\n}\n\nconst char* nurl_proc_stdout(long long h) {\n    NurlProcResult *r = (NurlProcResult*)(uintptr_t)h;\n    return (r && r->stdout_buf) ? r->stdout_buf : \"\";\n}\n\nconst char* nurl_proc_stderr(long long h) {\n    NurlProcResult *r = (NurlProcResult*)(uintptr_t)h;\n    return (r && r->stderr_buf) ? r->stderr_buf : \"\";\n}\n\nlong long nurl_proc_stdout_len(long long h) {\n    NurlProcResult *r = (NurlProcResult*)(uintptr_t)h;\n    return r ? r->stdout_len : 0;\n}\n\nlong long nurl_proc_stderr_len(long long h) {\n    NurlProcResult *r = (NurlProcResult*)(uintptr_t)h;\n    return r ? r->stderr_len : 0;\n}\n\nvoid nurl_proc_free(long long h) {\n    NurlProcResult *r = (NurlProcResult*)(uintptr_t)h;\n    if (!r) return;\n    free(r->stdout_buf);\n    free(r->stderr_buf);\n    free(r);\n}\n\n/* ── §16b  Process spawn (duplex stdio, line-buffered) ───────── */\n/*\n * Persistent child with live stdin/stdout pipes — backs the MCP stdio\n * client (one write-line, one read-line per JSON-RPC round-trip, many\n * round-trips per child). Stderr is INHERITED so the parent sees diags.\n *\n * Read-line returns a BORROWED pointer into the handle's reused\n * line_buf (NUL-terminated, no trailing '\\n'); copy via string_from\n * before the next read. timeout_ms<=0 blocks; on timeout returns \"\"\n * with err_kind unchanged (caller probes _eof to distinguish from EOF);\n * on error returns \"\", err_kind=Io, last_io_err=errno.\n *\n * Write blocks until n bytes flushed; returns bytes written or -1.\n * SIGPIPE is locally ignored so a dead child doesn't kill the parent.\n *\n * NurlProcChild is laid out as 16 × i64 slots so the pure-NURL POSIX\n * backend (stdlib/std/process.nu) can index fields via nurl_peek/poke.\n * Win32 keeps HANDLE-typed fields at slot 6+, and its own state past\n * slot 15, because NURL never reads those slots directly: on Windows\n * every proc_* call dispatches into the C entry points below. */\ntypedef struct NurlProcChild {\n    long long  err_kind;       /* slot 0  */\n    long long  last_io_err;    /* slot 1  — errno snapshot */\n    long long  exit_code;      /* slot 2  — -1 until wait() */\n    long long  eof;            /* slot 3  — stdout drained 0/1 */\n    long long  waited;         /* slot 4  — exit_code valid */\n    long long  pid_or_0;       /* slot 5  */\n#if !defined(_WIN32) && !defined(__wasi__)\n    long long  pid;            /* slot 6  — pid_t widened */\n    long long  fd_in;          /* slot 7  — parent→child stdin */\n    long long  fd_out;         /* slot 8  — child→parent stdout */\n#elif defined(_WIN32) && !defined(__wasi__)\n    HANDLE     h_proc;         /* slot 6+ (Win32 layout — NURL never reads) */\n    HANDLE     h_in;           /* parent→child stdin (anonymous pipe) */\n    HANDLE     h_out;          /* child→parent stdout (overlapped pipe) */\n#endif\n    /* Read-overflow: bytes past the first '\\n' become the next line's head. */\n    long long  scratch;        /* slot 9  — char* widened */\n    long long  scratch_len;    /* slot 10 */\n    long long  scratch_cap;    /* slot 11 */\n    /* Resolved line — reused across calls. */\n    long long  line_buf;       /* slot 12 — char* widened */\n    long long  line_len;       /* slot 13 */\n    long long  line_cap;       /* slot 14 */\n    long long  _reserved;      /* slot 15 — round to 128 bytes */\n#if defined(_WIN32) && !defined(__wasi__)\n    /* Win32-only tail. It sits PAST slot 15 on purpose: the 16-slot\n     * prefix above is the contract with the pure-NURL POSIX backend,\n     * and nothing here is ever read from NURL. */\n    HANDLE     h_ev;           /* manual-reset event for `ov` */\n    OVERLAPPED ov;             /* the stdout read in flight, if any */\n    long long  read_pending;   /* 1 while `ov` owns `rdbuf` */\n    char       rdbuf[4096];    /* target of that read */\n#endif\n} NurlProcChild;\n\n#if !defined(_WIN32) && !defined(__wasi__)\n/* POSIX spawn surface lives in pure NURL; these stubs satisfy the linker. */\nlong long nurl_proc_spawn(const char *cmd, const char *argv_buf, long long argc) {\n    (void)cmd; (void)argv_buf; (void)argc;\n    return 0;\n}\nlong long nurl_proc_spawn_write(long long h, const char *buf, long long n) {\n    (void)h; (void)buf; (void)n; return -1;\n}\nvoid nurl_proc_spawn_close_stdin(long long h) { (void)h; }\nconst char* nurl_proc_spawn_read_line(long long h, long long t) { (void)h; (void)t; return \"\"; }\nlong long nurl_proc_spawn_wait(long long h) { (void)h; return -1; }\nlong long nurl_proc_spawn_kill(long long h, long long sig) { (void)h; (void)sig; return -1; }\n\n#elif defined(_WIN32) && !defined(__wasi__)\n/* ── Win32 duplex spawn backend ──────────────────────────────────\n *\n * The POSIX half of this API lives in pure NURL (fork + execvp +\n * poll(2) on a non-blocking stdout fd). Windows has neither fork nor\n * poll, and — the part that decides the design — an ANONYMOUS pipe\n * cannot be read with a timeout at all: ReadFile on one blocks until\n * bytes arrive, and anonymous pipes reject FILE_FLAG_OVERLAPPED. That\n * is why `proc_read_line`'s timeout contract cannot be met by the\n * reader-thread shape §16's `nurl_proc_run` uses: a thread that is\n * parked in ReadFile still has to be joined before its buffer can die.\n *\n * So the child's stdout is a NAMED pipe with a unique name, opened\n * overlapped on the parent side and inherited synchronously on the\n * child side — the standard \"async anonymous pipe\" construction. One\n * read is kept IN FLIGHT across calls: `read_line` issues it, waits on\n * the OVERLAPPED event for `timeout_ms`, and on WAIT_TIMEOUT returns\n * empty-handed while LEAVING the read pending. The next call waits on\n * the same read, so a timeout can never lose bytes and nothing is ever\n * cancelled on the hot path.\n *\n * stdin is a plain anonymous pipe (writes are always blocking), and\n * stderr is inherited from the parent, exactly like the POSIX backend\n * which leaves fd 2 alone.\n *\n * `proc_read_chunk` stays POSIX-only (it returns ProcessOther here, as\n * documented in stdlib/std/process.nu): it would need a new FFI entry\n * point, and every Windows caller — `nurl upgrade`, the MCP stdio\n * client — is line-oriented.\n */\n\n/* Unique-per-call pipe name. The counter is only a tiebreaker within\n * one process; FILE_FLAG_FIRST_PIPE_INSTANCE is what actually makes a\n * collision impossible (a second creator of the same name fails with\n * ERROR_ACCESS_DENIED instead of silently joining the pipe). */\nstatic volatile LONG nurl__proc_pipe_seq = 0;\n\nstatic int nurl__proc_overlapped_pipe(HANDLE *rd_out, HANDLE *wr_out) {\n    for (int attempt = 0; attempt < 64; attempt++) {\n        char name[96];\n        LONG seq = InterlockedIncrement(&nurl__proc_pipe_seq);\n        snprintf(name, sizeof(name), \"\\\\\\\\.\\\\pipe\\\\nurl-proc-%lu-%lu-%lu\",\n                (unsigned long)GetCurrentProcessId(),\n                (unsigned long)seq,\n                (unsigned long)GetTickCount());\n        /* Server (parent) end: read-only, overlapped, NOT inheritable. */\n        HANDLE rd = CreateNamedPipeA(\n            name,\n            PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED |\n                FILE_FLAG_FIRST_PIPE_INSTANCE,\n            PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,\n            1, 4096, 4096, 0, NULL);\n        if (rd == INVALID_HANDLE_VALUE) {\n            if (GetLastError() == ERROR_ACCESS_DENIED) continue;  /* name taken */\n            return 0;\n        }\n        /* Client (child) end: write-only, synchronous, inheritable.\n         * Opening it is what connects the pipe — with the client\n         * already waiting there is nothing for ConnectNamedPipe to do. */\n        SECURITY_ATTRIBUTES sa;\n        sa.nLength              = sizeof(sa);\n        sa.lpSecurityDescriptor = NULL;\n        sa.bInheritHandle       = TRUE;\n        HANDLE wr = CreateFileA(name, GENERIC_WRITE, 0, &sa,\n                                OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);\n        if (wr == INVALID_HANDLE_VALUE) { CloseHandle(rd); return 0; }\n        *rd_out = rd;\n        *wr_out = wr;\n        return 1;\n    }\n    return 0;\n}\n\n/* scratch / line buffers — the C twins of __pc_scratch_reserve,\n * __pc_line_reserve and __pc_drain_line in stdlib/std/process.nu. The\n * slots are typed `long long` so the POSIX side can nurl_poke them;\n * here they are just widened `char *`. */\nstatic int nurl__pc_scratch_reserve(NurlProcChild *c, long long want) {\n    if (c->scratch_cap >= want) return 1;\n    long long newcap = c->scratch_cap > 0 ? c->scratch_cap : 1024;\n    while (newcap < want) newcap *= 2;\n    char *p = (char*)realloc((void*)(uintptr_t)c->scratch, (size_t)newcap);\n    if (!p) return 0;\n    c->scratch     = (long long)(uintptr_t)p;\n    c->scratch_cap = newcap;\n    return 1;\n}\n\nstatic int nurl__pc_line_reserve(NurlProcChild *c, long long want) {\n    if (c->line_cap >= want + 1) return 1;\n    long long newcap = c->line_cap > 0 ? c->line_cap : 256;\n    while (newcap < want + 1) newcap *= 2;\n    char *p = (char*)realloc((void*)(uintptr_t)c->line_buf, (size_t)newcap);\n    if (!p) return 0;\n    c->line_buf = (long long)(uintptr_t)p;\n    c->line_cap = newcap;\n    return 1;\n}\n\n/* Move the first '\\n'-terminated line out of scratch and into line_buf.\n * The '\\n' is consumed but not copied; a '\\r' before it is stripped too,\n * so a child that writes CRLF (every Windows console program does) does\n * not hand the caller a stray carriage return. */\nstatic int nurl__pc_drain_line(NurlProcChild *c) {\n    char *sp = (char*)(uintptr_t)c->scratch;\n    if (!sp) return 0;\n    for (long long i = 0; i < c->scratch_len; i++) {\n        if (sp[i] != '\\n') continue;\n        long long take = i;\n        if (take > 0 && sp[take - 1] == '\\r') take--;\n        if (!nurl__pc_line_reserve(c, take)) return 0;\n        char *lp = (char*)(uintptr_t)c->line_buf;\n        if (take > 0) memcpy(lp, sp, (size_t)take);\n        lp[take] = 0;\n        c->line_len = take;\n        long long rem = c->scratch_len - (i + 1);\n        if (rem > 0) memmove(sp, sp + i + 1, (size_t)rem);  /* overlaps */\n        c->scratch_len = rem;\n        return 1;\n    }\n    return 0;\n}\n\n/* EOF with no trailing '\\n': the tail still counts as a line. */\nstatic int nurl__pc_flush_tail(NurlProcChild *c) {\n    if (c->scratch_len <= 0) return 0;\n    if (!nurl__pc_line_reserve(c, c->scratch_len)) return 0;\n    char *lp = (char*)(uintptr_t)c->line_buf;\n    char *sp = (char*)(uintptr_t)c->scratch;\n    long long take = c->scratch_len;\n    if (take > 0 && sp[take - 1] == '\\r') take--;\n    memcpy(lp, sp, (size_t)take);\n    lp[take] = 0;\n    c->line_len    = take;\n    c->scratch_len = 0;\n    return 1;\n}\n\nstatic void nurl__pc_out_eof(NurlProcChild *c) {\n    if (c->h_out) { CloseHandle(c->h_out); c->h_out = NULL; }\n    c->eof = 1;\n}\n\nlong long nurl_proc_spawn(const char *cmd, const char *argv_buf, long long argc) {\n    NurlProcChild *c = (NurlProcChild*)calloc(1, sizeof(NurlProcChild));\n    if (!c) return 0;\n    c->exit_code = -1;\n    if (!cmd || !*cmd) {\n        c->err_kind = NURL_PROC_ERR_NOTFOUND;\n        return (long long)(uintptr_t)c;\n    }\n    if (argc < 0) argc = 0;\n    const char *const *argv_user = (const char *const *)argv_buf;\n\n    NurlProcLaunch launch = {0};\n    char command_prompt[MAX_PATH + 16];\n    int build_ok = nurl__proc_prepare_launch(cmd, argv_user, argc, &launch,\n                                             command_prompt, sizeof(command_prompt));\n    if (!build_ok || !launch.command.data) {\n        nurl__proc_launch_free(&launch);\n        c->err_kind = NURL_PROC_ERR_OTHER;\n        return (long long)(uintptr_t)c;\n    }\n\n    SECURITY_ATTRIBUTES sa = {0};\n    sa.nLength        = sizeof(sa);\n    sa.bInheritHandle = TRUE;\n\n    HANDLE in_r = NULL, in_w = NULL, out_r = NULL, out_w = NULL;\n    if (!CreatePipe(&in_r, &in_w, &sa, 0)) {\n        nurl__proc_launch_free(&launch);\n        c->err_kind    = NURL_PROC_ERR_IO;\n        c->last_io_err = (long long)GetLastError();\n        return (long long)(uintptr_t)c;\n    }\n    SetHandleInformation(in_w, HANDLE_FLAG_INHERIT, 0);\n    if (!nurl__proc_overlapped_pipe(&out_r, &out_w)) {\n        DWORD le = GetLastError();\n        CloseHandle(in_r); CloseHandle(in_w);\n        nurl__proc_launch_free(&launch);\n        c->err_kind    = NURL_PROC_ERR_IO;\n        c->last_io_err = (long long)le;\n        return (long long)(uintptr_t)c;\n    }\n\n    /* The timeout machinery: one manual-reset event, reused by every\n     * read on this child. Allocated BEFORE CreateProcess so a failure\n     * here does not leave an orphan running. */\n    HANDLE ev = CreateEventA(NULL, TRUE, FALSE, NULL);\n    if (!ev) {\n        DWORD le = GetLastError();\n        CloseHandle(in_r); CloseHandle(in_w);\n        CloseHandle(out_r); CloseHandle(out_w);\n        nurl__proc_launch_free(&launch);\n        c->err_kind    = NURL_PROC_ERR_IO;\n        c->last_io_err = (long long)le;\n        return (long long)(uintptr_t)c;\n    }\n\n    /* stderr: hand the child an inheritable duplicate of ours, so its\n     * diagnostics reach the terminal (the POSIX backend inherits fd 2\n     * unchanged). A session with no stderr at all — a GUI subsystem\n     * parent — falls back to NUL, because STARTF_USESTDHANDLES with a\n     * NULL hStdError gives the child no stderr rather than the\n     * parent's. */\n    HANDLE err_dup = NULL;\n    HANDLE err_own = GetStdHandle(STD_ERROR_HANDLE);\n    if (err_own && err_own != INVALID_HANDLE_VALUE) {\n        if (!DuplicateHandle(GetCurrentProcess(), err_own,\n                             GetCurrentProcess(), &err_dup,\n                             0, TRUE, DUPLICATE_SAME_ACCESS)) {\n            err_dup = NULL;\n        }\n    }\n    if (!err_dup) {\n        err_dup = CreateFileA(\"NUL\", GENERIC_WRITE,\n                              FILE_SHARE_READ | FILE_SHARE_WRITE, &sa,\n                              OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);\n        if (err_dup == INVALID_HANDLE_VALUE) err_dup = NULL;\n    }\n\n    STARTUPINFOA si = {0};\n    si.cb         = sizeof(si);\n    si.dwFlags    = STARTF_USESTDHANDLES;\n    si.hStdInput  = in_r;\n    si.hStdOutput = out_w;\n    si.hStdError  = err_dup;\n    PROCESS_INFORMATION pi = {0};\n    BOOL ok = CreateProcessA(launch.application, launch.command.data,\n                             NULL, NULL, TRUE, 0,\n                             NULL, NULL, &si, &pi);\n    DWORD spawn_err = ok ? 0 : GetLastError();\n    nurl__proc_launch_free(&launch);\n    /* The child owns its ends now; the parent must drop them or the\n     * pipes never report EOF. */\n    CloseHandle(in_r);\n    CloseHandle(out_w);\n    if (err_dup) CloseHandle(err_dup);\n\n    if (!ok) {\n        CloseHandle(in_w);\n        CloseHandle(out_r);\n        CloseHandle(ev);\n        c->last_io_err = (long long)spawn_err;\n        c->err_kind = (spawn_err == ERROR_FILE_NOT_FOUND ||\n                       spawn_err == ERROR_PATH_NOT_FOUND)\n                    ? NURL_PROC_ERR_NOTFOUND\n                    : NURL_PROC_ERR_EXEC_FAILED;\n        return (long long)(uintptr_t)c;\n    }\n    CloseHandle(pi.hThread);\n\n    c->h_proc    = pi.hProcess;\n    c->h_in      = in_w;\n    c->h_out     = out_r;\n    c->h_ev      = ev;\n    c->pid_or_0  = (long long)pi.dwProcessId;\n    return (long long)(uintptr_t)c;\n}\n\nlong long nurl_proc_spawn_write(long long h, const char *buf, long long n) {\n    NurlProcChild *c = (NurlProcChild*)(uintptr_t)h;\n    if (!c) return -1;\n    if (!c->h_in) return 0;          /* stdin already closed — as POSIX */\n    if (n <= 0 || !buf) return 0;\n    long long total = 0;\n    while (total < n) {\n        DWORD wrote = 0;\n        if (!WriteFile(c->h_in, buf + total, (DWORD)(n - total), &wrote, NULL)) {\n            c->last_io_err = (long long)GetLastError();\n            return -1;\n        }\n        if (wrote == 0) break;\n        total += (long long)wrote;\n    }\n    return total;\n}\n\nvoid nurl_proc_spawn_close_stdin(long long h) {\n    NurlProcChild *c = (NurlProcChild*)(uintptr_t)h;\n    if (!c || !c->h_in) return;\n    CloseHandle(c->h_in);\n    c->h_in = NULL;\n}\n\nconst char* nurl_proc_spawn_read_line(long long h, long long timeout_ms) {\n    NurlProcChild *c = (NurlProcChild*)(uintptr_t)h;\n    if (!c) return \"\";\n\n    /* Reset the previous line first: the caller distinguishes \"no line\"\n     * from \"a line\" by read_line_len, not by the pointer. */\n    c->line_len = 0;\n    if (c->line_buf) ((char*)(uintptr_t)c->line_buf)[0] = 0;\n\n    if (nurl__pc_drain_line(c)) return (const char*)(uintptr_t)c->line_buf;\n    if (!c->h_out) { c->eof = 1; return \"\"; }\n\n    for (;;) {\n        DWORD nread   = 0;\n        int   hit_eof = 0;\n\n        if (!c->read_pending) {\n            memset(&c->ov, 0, sizeof(c->ov));\n            c->ov.hEvent = c->h_ev;\n            ResetEvent(c->h_ev);\n            if (ReadFile(c->h_out, c->rdbuf, (DWORD)sizeof(c->rdbuf),\n                         &nread, &c->ov)) {\n                /* Completed inline — nread is already set. */\n            } else {\n                DWORD le = GetLastError();\n                if (le == ERROR_IO_PENDING) {\n                    c->read_pending = 1;\n                } else if (le == ERROR_BROKEN_PIPE || le == ERROR_HANDLE_EOF) {\n                    hit_eof = 1;\n                } else {\n                    c->err_kind    = NURL_PROC_ERR_IO;\n                    c->last_io_err = (long long)le;\n                    return \"\";\n                }\n            }\n        }\n\n        if (!hit_eof && c->read_pending) {\n            DWORD w = WaitForSingleObject(\n                c->h_ev, timeout_ms > 0 ? (DWORD)timeout_ms : INFINITE);\n            if (w == WAIT_TIMEOUT) return \"\";   /* read stays in flight */\n            if (w != WAIT_OBJECT_0) {\n                c->err_kind    = NURL_PROC_ERR_IO;\n                c->last_io_err = (long long)GetLastError();\n                return \"\";\n            }\n            if (GetOverlappedResult(c->h_out, &c->ov, &nread, FALSE)) {\n                c->read_pending = 0;\n            } else {\n                DWORD le = GetLastError();\n                c->read_pending = 0;\n                if (le == ERROR_BROKEN_PIPE || le == ERROR_HANDLE_EOF ||\n                    le == ERROR_OPERATION_ABORTED) {\n                    hit_eof = 1;\n                } else {\n                    c->err_kind    = NURL_PROC_ERR_IO;\n                    c->last_io_err = (long long)le;\n                    return \"\";\n                }\n            }\n        }\n\n        if (!hit_eof) {\n            if (nread == 0) {\n                hit_eof = 1;               /* pipe closed, as read()==0 */\n            } else {\n                if (!nurl__pc_scratch_reserve(c, c->scratch_len + (long long)nread)) {\n                    c->err_kind = NURL_PROC_ERR_OTHER;\n                    return \"\";\n                }\n                memcpy((char*)(uintptr_t)c->scratch + c->scratch_len,\n                       c->rdbuf, (size_t)nread);\n                c->scratch_len += (long long)nread;\n                if (nurl__pc_drain_line(c))\n                    return (const char*)(uintptr_t)c->line_buf;\n            }\n        }\n\n        if (hit_eof) {\n            nurl__pc_out_eof(c);\n            if (nurl__pc_drain_line(c) || nurl__pc_flush_tail(c))\n                return (const char*)(uintptr_t)c->line_buf;\n            return \"\";\n        }\n        /* No '\\n' yet — issue the next read. */\n    }\n}\n\nlong long nurl_proc_spawn_wait(long long h) {\n    NurlProcChild *c = (NurlProcChild*)(uintptr_t)h;\n    if (!c) return -1;\n    if (c->waited) return c->exit_code;\n    if (!c->h_proc) return -1;\n    if (WaitForSingleObject(c->h_proc, INFINITE) != WAIT_OBJECT_0) {\n        c->last_io_err = (long long)GetLastError();\n        return -1;\n    }\n    DWORD ec = 0;\n    if (!GetExitCodeProcess(c->h_proc, &ec)) {\n        c->last_io_err = (long long)GetLastError();\n        return -1;\n    }\n    c->exit_code = (long long)ec;\n    c->waited    = 1;\n    return c->exit_code;\n}\n\n/* Windows has no signals. TerminateProcess is the only lever, and the\n * observable half of `kill` is the status a later `proc_wait` reports —\n * so the child is terminated with the code POSIX would report for that\n * signal (128 + sig), which keeps `proc_kill p 15` → `proc_wait` = 143\n * true on both platforms. */\nlong long nurl_proc_spawn_kill(long long h, long long sig) {\n    NurlProcChild *c = (NurlProcChild*)(uintptr_t)h;\n    if (!c || !c->h_proc) return -1;\n    UINT code = (UINT)(128 + (sig > 0 ? sig : 15));\n    if (TerminateProcess(c->h_proc, code)) return 0;\n    DWORD le = GetLastError();\n    /* Already gone: POSIX kill would say ESRCH, but the child having\n     * exited on its own is not a failure of the call for any caller\n     * that then waits. */\n    if (WaitForSingleObject(c->h_proc, 0) == WAIT_OBJECT_0) return 0;\n    c->last_io_err = (long long)le;\n    return -1;\n}\n\n/* Close pipes, settle any in-flight read, and reap — the Win32 twin of\n * __proc_free_posix's close/SIGTERM/500ms/SIGKILL sequence. Order\n * matters: stdin first (that alone ends a well-behaved filter), then\n * the grace window, then the kill. Only once the child is dead is the\n * pending overlapped read guaranteed to complete, and it MUST complete\n * before `rdbuf` — which lives inside this struct — is freed. */\nstatic void nurl__proc_child_shutdown(NurlProcChild *c) {\n    if (c->h_in) { CloseHandle(c->h_in); c->h_in = NULL; }\n    if (c->h_proc && !c->waited) {\n        if (WaitForSingleObject(c->h_proc, 500) != WAIT_OBJECT_0) {\n            TerminateProcess(c->h_proc, 1);\n            WaitForSingleObject(c->h_proc, 2000);\n        }\n        c->waited = 1;\n    }\n    if (c->read_pending && c->h_out) {\n        DWORD n = 0;\n        CancelIo(c->h_out);\n        GetOverlappedResult(c->h_out, &c->ov, &n, TRUE);\n        c->read_pending = 0;\n    }\n    if (c->h_out)  { CloseHandle(c->h_out);  c->h_out  = NULL; }\n    if (c->h_ev)   { CloseHandle(c->h_ev);   c->h_ev   = NULL; }\n    if (c->h_proc) { CloseHandle(c->h_proc); c->h_proc = NULL; }\n}\n\n#else  /* WASI */\nlong long nurl_proc_spawn(const char *cmd, const char *argv_buf, long long argc) {\n    (void)cmd; (void)argv_buf; (void)argc;\n    NurlProcChild *c = (NurlProcChild*)calloc(1, sizeof(NurlProcChild));\n    if (!c) return 0;\n    c->err_kind = NURL_PROC_ERR_OTHER;\n    c->exit_code = -1;\n    return (long long)(uintptr_t)c;\n}\nlong long nurl_proc_spawn_write(long long h, const char *buf, long long n) {\n    (void)h; (void)buf; (void)n; return -1;\n}\nvoid nurl_proc_spawn_close_stdin(long long h) { (void)h; }\nconst char* nurl_proc_spawn_read_line(long long h, long long t) { (void)h; (void)t; return \"\"; }\nlong long nurl_proc_spawn_wait(long long h) { (void)h; return -1; }\nlong long nurl_proc_spawn_kill(long long h, long long sig) { (void)h; (void)sig; return -1; }\n#endif\n\nlong long nurl_proc_spawn_err_kind(long long h) {\n    NurlProcChild *c = (NurlProcChild*)(uintptr_t)h;\n    return c ? c->err_kind : NURL_PROC_ERR_OTHER;\n}\n\nlong long nurl_proc_spawn_pid(long long h) {\n    NurlProcChild *c = (NurlProcChild*)(uintptr_t)h;\n    return c ? c->pid_or_0 : 0;\n}\n\nlong long nurl_proc_spawn_read_line_len(long long h) {\n    NurlProcChild *c = (NurlProcChild*)(uintptr_t)h;\n    return c ? (long long)c->line_len : 0;\n}\n\nlong long nurl_proc_spawn_eof(long long h) {\n    NurlProcChild *c = (NurlProcChild*)(uintptr_t)h;\n    return c ? (long long)c->eof : 1;\n}\n\nlong long nurl_proc_spawn_last_io_err(long long h) {\n    NurlProcChild *c = (NurlProcChild*)(uintptr_t)h;\n    return c ? c->last_io_err : 0;\n}\n\nvoid nurl_proc_spawn_free(long long h) {\n    NurlProcChild *c = (NurlProcChild*)(uintptr_t)h;\n    if (!c) return;\n#if defined(_WIN32) && !defined(__wasi__)\n    nurl__proc_child_shutdown(c);\n    free((void*)(uintptr_t)c->scratch);\n    free((void*)(uintptr_t)c->line_buf);\n#endif\n    /* POSIX `proc_free` does the close/reap/buffer-free in NURL. */\n    free(c);\n}\n\n/* ── §17  Crypto entropy bridge ─────────────────────────────── */\n/* Hash transforms (MD5/SHA-1/SHA-256/SHA-512/HMAC) live in pure NURL\n * (stdlib/std/hash_*.nu); rand_u64 / rand_hex_str in stdlib/std/random.nu.\n * What stays here is the OS-entropy bridge — three different syscall\n * APIs (getrandom / arc4random_buf / BCryptGenRandom) in three different\n * link-time libraries, with a /dev/urandom fallback. */\n\n#ifdef _WIN32\n#  include <bcrypt.h>\n/* MSVC-only. The pragma emits a /DEFAULTLIB:bcrypt.lib directive into this\n * object's .drectve section, which is how the MSVC toolchain finds the\n * import lib without anyone naming it on the link line. Under MinGW\n * (`zig cc`, x86_64-w64-mingw32) that directive still reaches lld-link,\n * which mangles the name the MinGW way and tries to open libbcrypt.a — a\n * file no MinGW toolchain here produces (zig synthesises bcrypt.lib), so\n * the link dies on a file it can never find. MinGW builds name the import\n * libs on the link line instead: nurl.bat and nurlapi/main.nu both do.\n * runtime_core.c guards its copy of this pragma the same way. */\n#  ifndef __MINGW32__\n#    pragma comment(lib, \"bcrypt.lib\")\n#  endif\n#endif\n\n#if defined(__linux__)\n#  include <sys/random.h>\n#endif\n#if defined(__wasi__)\n#  include <unistd.h>            /* getentropy — wasi-libc's random_get */\n#endif\n\n/* Fill buf with n cryptographically-strong bytes; return 1 on success.\n * 0 only on degraded fallback (LCG over time+clock) — shouldn't happen\n * on a modern host. */\nlong long nurl_rand_fill(unsigned char *buf, long long n) {\n    if (!buf || n <= 0) return 0;\n    size_t want = (size_t)n;\n#if defined(_WIN32)\n    if (BCryptGenRandom(NULL, buf, (ULONG)want, BCRYPT_USE_SYSTEM_PREFERRED_RNG) == 0)\n        return 1;\n#elif defined(__APPLE__)\n    arc4random_buf(buf, want);\n    return 1;\n#elif defined(__linux__)\n    size_t got = 0;\n    while (got < want) {\n        ssize_t r = getrandom(buf + got, want - got, 0);\n        if (r < 0) {\n            if (errno == EINTR) continue;\n            break;\n        }\n        got += (size_t)r;\n    }\n    if (got == want) return 1;\n    /* /dev/urandom for older kernels without getrandom. */\n    FILE *f = fopen(\"/dev/urandom\", \"rb\");\n    if (f) {\n        size_t r2 = fread(buf, 1, want, f);\n        fclose(f);\n        if (r2 == want) return 1;\n    }\n#elif defined(__wasi__)\n    /* WASI's CSPRNG is a first-class import: wasi-libc's getentropy() is\n     * wasi_snapshot_preview1's random_get, which every runtime backs with\n     * the host CSPRNG. Without this the wasi build fell through to the\n     * LCG below and returned 0 — and 0 means \"degraded\" to every caller,\n     * so std/x509_gen and the pure TLS stack panicked on a wasm target\n     * that in fact has perfectly good entropy. getentropy() takes at most\n     * 256 bytes per call (GETENTROPY_MAX). */\n    {\n        size_t got = 0;\n        while (got < want) {\n            size_t chunk = want - got;\n            if (chunk > 256) chunk = 256;\n            if (getentropy(buf + got, chunk) != 0) break;\n            got += chunk;\n        }\n        if (got == want) return 1;\n    }\n#else\n    FILE *f = fopen(\"/dev/urandom\", \"rb\");\n    if (f) {\n        size_t r = fread(buf, 1, want, f);\n        fclose(f);\n        if (r == want) return 1;\n    }\n#endif\n    /* Degraded LCG fallback — NOT cryptographically strong. wasi-sdk\n     * deprecates clock() so drop it from the mix there. */\n#ifdef __wasi__\n    uint64_t t = (uint64_t)time(NULL) ^ (uint64_t)(uintptr_t)buf;\n#else\n    uint64_t t = (uint64_t)time(NULL) ^ (uint64_t)(uintptr_t)buf ^ (uint64_t)clock();\n#endif\n    for (size_t i = 0; i < want; i++) {\n        t = t * 6364136223846793005ULL + 1442695040888963407ULL;\n        buf[i] = (uint8_t)(t >> 33);\n    }\n    return 0;\n}\n\n\n/* ── §18  TCP sockets + TLS (HTTP server foundation) ─────────── */\n/*\n * Blocking IPv4 TCP layer over libc / Winsock, optionally with OpenSSL\n * TLS (SNI + ALPN + mTLS). Handles are heap NurlTcp* cast to i64; the\n * handle is always non-zero so the caller probes nurl_tcp_err_kind.\n *\n * ABI:\n *   tcp_listen(host, port, backlog)         → LISTEN handle. host=\"\"/NULL\n *                                              → INADDR_ANY. backlog<=0\n *                                              → 16.\n *   tcp_connect(host, port)                 → CONN handle (IPv4 or IPv6\n *                                              via getaddrinfo).\n *   tcp_connect_ms(host, port, ms)          → same, one deadline over\n *                                              lookup + handshake (ms<=0\n *                                              none); fiber-aware — §18-r.\n *   tcp_accept(listener)                    → fresh CONN; eager peer cap.\n *   tcp_read (h, buf, n) → >0 bytes / 0 EOF / <0 err  (sets h->err_kind)\n *   tcp_write(h, buf, n) → total bytes or -1\n *   tcp_close(h)                            — call once, never twice.\n *   tcp_set_timeout(h, ms)                  — SO_RCVTIMEO + SO_SNDTIMEO.\n *   tcp_peer_addr(h)                        — borrowed \"ip:port\".\n *\n * NetErr tags (match stdlib/std/net.nu):\n *   0 ok 1 Bind 2 AddrInUse 3 Accept 4 Read 5 Write 6 Closed\n *   7 Timeout 8 Other\n *   9..12  TLS_CTX_INIT / TLS_CERT_LOAD / TLS_KEY_LOAD / TLS_HANDSHAKE\n */\n\n#define NURL_NET_ERR_OK             0\n#define NURL_NET_ERR_BIND           1\n#define NURL_NET_ERR_ADDRINUSE      2\n#define NURL_NET_ERR_ACCEPT         3\n#define NURL_NET_ERR_READ           4\n#define NURL_NET_ERR_WRITE          5\n#define NURL_NET_ERR_CLOSED         6\n#define NURL_NET_ERR_TIMEOUT        7\n#define NURL_NET_ERR_OTHER          8\n/* TLS error codes. TLS is pure NURL (std/tls.nu, std/tls_server.nu),\n * which reports CERT_LOAD / KEY_LOAD / HANDSHAKE itself; TLS_CTX_INIT\n * comes only from the inert libssl-era stubs below, which no .nu calls. */\n#define NURL_NET_ERR_TLS_CTX_INIT   9\n#define NURL_NET_ERR_TLS_CERT_LOAD  10\n#define NURL_NET_ERR_TLS_KEY_LOAD   11\n#define NURL_NET_ERR_TLS_HANDSHAKE  12\n\n\n#define NURL_TCP_KIND_LISTENER  0\n#define NURL_TCP_KIND_CONN      1\n\n/* ── NurlUdpAddr text codec (pure C, every target) ──────────────────\n *\n * The 24-byte NurlUdpAddr encoding is documented at §18b-ii below. The\n * two text helpers live outside the socket guards because the wasm\n * host bridge (which only speaks \"ip:port\" strings) and the native\n * path both need them, and neither libc's inet_ntop nor getnameinfo is\n * available on every target this file builds for. IPv6 text follows\n * RFC 5952 (lower-case hex, longest zero run compressed, v4-mapped\n * printed as ::ffff:a.b.c.d). */\n\n#define NURL_UDP_ADDR_LEN 24\n\nstatic int nurl__udpaddr_hex(int c) {\n    if (c >= '0' && c <= '9') return c - '0';\n    if (c >= 'a' && c <= 'f') return c - 'a' + 10;\n    if (c >= 'A' && c <= 'F') return c - 'A' + 10;\n    return -1;\n}\n\n/* \"a.b.c.d\" → 4 bytes. Returns chars consumed, 0 on failure. */\nstatic int nurl__udpaddr_parse_v4(const char *s, unsigned char *out) {\n    int i = 0, part = 0;\n    for (part = 0; part < 4; part++) {\n        int v = 0, digits = 0;\n        while (s[i] >= '0' && s[i] <= '9') {\n            v = v * 10 + (s[i] - '0');\n            if (v > 255) return 0;\n            i++; digits++;\n        }\n        if (!digits) return 0;\n        out[part] = (unsigned char)v;\n        if (part < 3) {\n            if (s[i] != '.') return 0;\n            i++;\n        }\n    }\n    return i;\n}\n\n/* IPv6 text (with :: compression and an optional trailing embedded\n * IPv4) → 16 bytes. `end` is the terminator ('\\0' or ']'). Returns 1\n * on success. */\nstatic int nurl__udpaddr_parse_v6(const char *s, const char *end, unsigned char *out) {\n    unsigned short groups[8];\n    int n = 0, gap = -1;\n    const char *p = s;\n    memset(out, 0, 16);\n    if (p[0] == ':' ) {\n        if (p[1] != ':') return 0;\n        gap = 0; p += 2;\n    }\n    while (p < end) {\n        if (n >= 8) return 0;\n        /* Embedded IPv4 tail: \"…:a.b.c.d\" */\n        const char *q = p;\n        int dots = 0;\n        while (q < end && ((*q >= '0' && *q <= '9') || *q == '.')) { if (*q == '.') dots++; q++; }\n        if (dots == 3 && q == end) {\n            unsigned char v4[4];\n            if (n > 6 || nurl__udpaddr_parse_v4(p, v4) == 0) return 0;\n            groups[n++] = (unsigned short)((v4[0] << 8) | v4[1]);\n            groups[n++] = (unsigned short)((v4[2] << 8) | v4[3]);\n            p = end;\n            break;\n        }\n        int v = 0, digits = 0;\n        while (p < end && nurl__udpaddr_hex(*p) >= 0) {\n            v = (v << 4) | nurl__udpaddr_hex(*p);\n            if (++digits > 4) return 0;\n            p++;\n        }\n        if (!digits) return 0;\n        groups[n++] = (unsigned short)v;\n        if (p == end) break;\n        if (*p != ':') return 0;\n        p++;\n        if (p < end && *p == ':') {\n            if (gap >= 0) return 0;\n            gap = n; p++;\n            if (p == end) break;\n        } else if (p == end) {\n            return 0;\n        }\n    }\n    if (gap < 0 && n != 8) return 0;\n    if (gap >= 0 && n >= 8) return 0;\n    int i, shift = (gap >= 0) ? 8 - n : 0;\n    for (i = 0; i < n; i++) {\n        int idx = (gap >= 0 && i >= gap) ? i + shift : i;\n        out[2 * idx]     = (unsigned char)(groups[i] >> 8);\n        out[2 * idx + 1] = (unsigned char)(groups[i] & 0xff);\n    }\n    return 1;\n}\n\n/* \"ip:port\" / \"[ip]:port\" / \"ip\" (port 0) → NurlUdpAddr. 0 ok, -1 bad. */\nstatic int nurl__udpaddr_parse(const char *text, unsigned char *a) {\n    memset(a, 0, NURL_UDP_ADDR_LEN);\n    if (!text || !*text) return -1;\n    const char *ip_start; const char *ip_end; const char *port_s = NULL;\n    if (text[0] == '[') {\n        ip_start = text + 1;\n        ip_end = strchr(ip_start, ']');\n        if (!ip_end) return -1;\n        if (ip_end[1] == ':') port_s = ip_end + 2;\n        else if (ip_end[1] != 0) return -1;\n    } else {\n        const char *first = strchr(text, ':');\n        const char *last  = strrchr(text, ':');\n        ip_start = text;\n        if (first && first == last) { ip_end = first; port_s = first + 1; }\n        else { ip_end = text + strlen(text); }   /* bare IPv6 or bare IPv4 */\n    }\n    unsigned port = 0;\n    if (port_s) {\n        if (!*port_s) return -1;\n        for (const char *p = port_s; *p; p++) {\n            if (*p < '0' || *p > '9') return -1;\n            port = port * 10 + (unsigned)(*p - '0');\n            if (port > 65535) return -1;\n        }\n    }\n    a[2] = (unsigned char)(port >> 8);\n    a[3] = (unsigned char)(port & 0xff);\n    if (memchr(ip_start, ':', (size_t)(ip_end - ip_start))) {\n        unsigned char v6[16];\n        if (!nurl__udpaddr_parse_v6(ip_start, ip_end, v6)) return -1;\n        static const unsigned char v4mapped[12] = {0,0,0,0,0,0,0,0,0,0,0xff,0xff};\n        if (memcmp(v6, v4mapped, 12) == 0) { a[0] = 4; memcpy(a + 4, v6 + 12, 4); }\n        else { a[0] = 6; memcpy(a + 4, v6, 16); }\n        return 0;\n    }\n    unsigned char v4[4];\n    int used = nurl__udpaddr_parse_v4(ip_start, v4);\n    if (used == 0 || ip_start + used != ip_end) return -1;\n    a[0] = 4; memcpy(a + 4, v4, 4);\n    return 0;\n}\n\n/* NurlUdpAddr → \"ip:port\" / \"[ip]:port\" into `out` (cap ≥ 64). Empty\n * string for an empty address. */\nstatic void nurl__udpaddr_format(const unsigned char *a, char *out, size_t cap) {\n    if (cap == 0) return;\n    out[0] = 0;\n    if (!a || (a[0] != 4 && a[0] != 6)) return;\n    unsigned port = ((unsigned)a[2] << 8) | (unsigned)a[3];\n    if (a[0] == 4) {\n        snprintf(out, cap, \"%u.%u.%u.%u:%u\", a[4], a[5], a[6], a[7], port);\n        return;\n    }\n    char ip[48]; size_t n = 0;\n    static const unsigned char v4mapped[12] = {0,0,0,0,0,0,0,0,0,0,0xff,0xff};\n    if (memcmp(a + 4, v4mapped, 12) == 0) {\n        snprintf(ip, sizeof(ip), \"::ffff:%u.%u.%u.%u\", a[16], a[17], a[18], a[19]);\n    } else {\n        unsigned short g[8]; int i;\n        for (i = 0; i < 8; i++) g[i] = (unsigned short)((a[4 + 2*i] << 8) | a[5 + 2*i]);\n        /* longest run of zero groups (length ≥ 2) gets the \"::\" */\n        int best = -1, bestlen = 0, cur = -1, curlen = 0;\n        for (i = 0; i < 8; i++) {\n            if (g[i] == 0) { if (cur < 0) { cur = i; curlen = 0; } curlen++;\n                             if (curlen > bestlen) { best = cur; bestlen = curlen; } }\n            else cur = -1;\n        }\n        if (bestlen < 2) best = -1;\n        for (i = 0; i < 8; ) {\n            if (i == best) {\n                /* \"::\" stands for the run; the group before it already\n                 * skipped its trailing ':' and the group after adds none. */\n                ip[n++] = ':'; ip[n++] = ':';\n                i += bestlen;\n                continue;\n            }\n            n += (size_t)snprintf(ip + n, sizeof(ip) - n, \"%x\", g[i]);\n            if (i < 7 && i + 1 != best) ip[n++] = ':';\n            i++;\n        }\n        ip[n] = 0;\n    }\n    snprintf(out, cap, \"[%s]:%u\", ip, port);\n}\n\n#if !defined(__wasi__)\n#  ifdef _WIN32\n#    ifndef __MINGW32__          /* MSVC-only; see the bcrypt pragma above */\n#      pragma comment(lib, \"ws2_32.lib\")\n#    endif\n#    include <ws2tcpip.h>          /* getaddrinfo — client-side connect */\ntypedef SOCKET nurl_sockfd_t;\n#    define NURL_INVALID_SOCK INVALID_SOCKET\n#    define nurl_close_sock(fd) closesocket(fd)\n#  else\n#    include <sys/socket.h>\n#    include <sys/uio.h>            /* struct iovec — nurl_tcp_write2 */\n#    include <netinet/in.h>\n#    include <netinet/tcp.h>        /* TCP_NODELAY on accepted conns */\n#    include <arpa/inet.h>\n#    include <netdb.h>             /* getaddrinfo — client-side connect */\n#    include <unistd.h>\n#    include <fcntl.h>\n#    include <sys/time.h>\n#    include <poll.h>              /* connect deadline off a fiber */\n#    include <sched.h>             /* sched_yield — a listener's stop */\n#    include <pthread.h>           /* §18-r resolver threads */\n#    if defined(__linux__)\n#      include <sys/eventfd.h>     /* §18-r resolver completion signal */\n#    endif\ntypedef int nurl_sockfd_t;\n#    define NURL_INVALID_SOCK (-1)\n#    if defined(__linux__)\n/* Closing a socket silently drops its epoll registration and orphans\n * its netpoller state (ready bits, parked waiters) — route every\n * socket close through the netpoller, which resets the per-fd slot and\n * closes the fd as one step under its lock, so waiters wake and no\n * registration can slip in before the fd number is reused. Defined in\n * §25. */\nextern int nurl__np_close(int fd);\n#      define nurl_close_sock(fd) nurl__np_close(fd)\n#    else\n#      define nurl_close_sock(fd) close(fd)\n#    endif\n#  endif\n\n/* A socket call failed for want of descriptors: collect the context's\n * unreachable Rc cycles (they may hold some) and say whether to retry\n * once (nurl_cc_reclaim_fds). Off the success path entirely. */\nint nurl_cc_reclaim_fds(void);\nint nurl_cc_collect_for_fds(void);\nstatic int nurl__fd_reclaim(void) {\n#  ifdef _WIN32\n    if (WSAGetLastError() == WSAEMFILE) return nurl_cc_collect_for_fds();\n#  endif\n    return nurl_cc_reclaim_fds();\n}\n\n\ntypedef struct NurlTcp {\n    nurl_sockfd_t fd;\n    long long     err_kind;\n    int           kind;\n    char         *peer;     /* owned \"ip:port\" or \"\" */\n    /* Listener-only accept wakeup. close(fd) from another thread does NOT\n     * interrupt a thread blocked in accept() on Linux, so a cross-thread\n     * nurl_tcp_shutdown needs an explicit wakeup: accept() polls\n     * {fd, wake_rfd}; shutdown sets `shutting_down` and writes one\n     * never-drained signal to wake_wfd (nurl__signal_raise) → every\n     * current and future poll returns. wake_* are NURL_INVALID_SOCK on\n     * conns / when the channel could not be opened (then accept falls\n     * back to plain blocking accept). Every platform: Winsock's\n     * closesocket would wake the accept, but frees the handle number for\n     * reuse while the accept may still be entering. */\n    nurl_sockfd_t wake_rfd;\n    nurl_sockfd_t wake_wfd;\n    volatile int  shutting_down;\n    /* nurl_tcp_conn_shutdown shut the read side (Windows reads it: a\n     * receive there after the shutdown fails, where POSIX reads EOF). */\n    volatile int  read_shut;\n    /* Windows: the signal a thread waiting for this connection to turn\n     * readable (nurl_tcp_wait_io) waits on beside it, raised by the\n     * read-side shutdown — which select and WSAPoll never report. Opened\n     * by the first such wait; NURL_INVALID_SOCK until then. */\n    nurl_sockfd_t rd_wake;\n    /* Listener-only: the accepts in progress (atomic). A stop closes the\n     * fd only once this is 0, so no accept ever reads a closed — or a\n     * reused — descriptor; one that starts after the stop sees\n     * `shutting_down` before it reads the fd (nurl__tcp_listener_stop). */\n    int           accepting;\n    /* Set by nurl_tcp_set_nonblock: the fiber/reactor accept path drives\n     * its own readiness wait, so accept() must do ONE non-blocking accept\n     * and hand EAGAIN back rather than entering the blocking poll loop. */\n    int           nonblock;\n    /* Content type of the record nurl_ktls_recv last read (kTLS RX). */\n    int           ktls_rtype;\n    /* Reference count (atomic). Created at 1; nurl_tcp_close releases that\n     * ref. server_run_async takes an extra ref so a concurrent close (e.g.\n     * server_stop from another thread) defers the struct free until the\n     * parked accept fiber has resumed and stopped touching the handle. */\n    int           refcount;\n    /* What `nurl_tcp_set_timeout` was last told, in milliseconds (0 =\n     * none). The socket option is the authority for a BLOCKING read;\n     * this copy is for the fiber path, which cannot use SO_RCVTIMEO —\n     * it parks on the reactor instead, and has to be told how long. */\n    long long     timeout_ms;\n    long long     write_deadline_ns; /* absolute CLOCK_MONOTONIC; 0 clears */\n    /* SO_SNDTIMEO currently holds a deadline remainder, not timeout_ms.\n     * Only then does clearing the deadline have to restore the idle\n     * timeout: an HTTP/2 connection brackets EVERY frame read with\n     * set/restore of its write deadline, and a fiber-served socket never\n     * pushes SO_SNDTIMEO at all, so dirtying unconditionally made every\n     * blocking read of a pool-served connection pay two setsockopt(). */\n    int           sndtimeo_deadline;\n    /* SO_RCVTIMEO/SO_SNDTIMEO not yet pushed to the kernel. Set by\n     * nurl_tcp_set_timeout, consumed by the first BLOCKING read/write:\n     * a fiber-served connection never blocks, so eagerly issuing the\n     * two setsockopt() calls there was two wasted syscalls on every\n     * accepted connection (the fiber path reads timeout_ms directly). */\n    int           timeo_dirty;\n} NurlTcp;\n\n/* Defined next to nurl_tcp_set_timeout below; used by the blocking\n * read/write paths to apply a deferred timeout. */\nstatic void nurl__tcp_apply_timeo(NurlTcp *h);\n\n/* Wake the async reactor (if running) so it re-polls. Called after a fd is\n * closed: a fiber parked on that fd would otherwise never wake (poll does\n * not re-evaluate an fd closed by another thread mid-call). After the wake\n * the reactor's next poll reports POLLNVAL for the closed fd and fails the\n * wait, so the parked fiber resumes and sees EBADF. Real impl lives with\n * the reactor; a no-op stub covers WASI. */\nvoid nurl__reactor_wake_if_started(void);\n\n#ifdef _WIN32\n/* Winsock's half of a receive-side shutdown, which WSAPoll does not\n * report (see nurl_tcp_conn_shutdown): the reactor wakes the fibers\n * waiting to read `fd`, and any that waits on it later, until the socket\n * is closed and forgotten. Defined with the reactor (§25). */\nvoid nurl__reactor_read_shut(long long fd);\nvoid nurl__reactor_forget_fd(long long fd);\n#endif\n\n\n/* errno/WSAGetLastError → NetErr; call sites override when needed. */\n#ifdef _WIN32\nstatic long long nurl__net_map_wsa(int we, long long deflt) {\n    switch (we) {\n    /* The twin of EAGAIN/EWOULDBLOCK below, and it must land on the same\n     * code: the nonblocking paths (nurl_tcp_write_nowait /\n     * nurl_tcp_read_nowait) tell \"the buffer is full, retry\" apart from a\n     * dead connection ONLY by NURL_NET_ERR_TIMEOUT. Without this case a\n     * would-block fell through to the caller's default — WRITE or READ —\n     * so on Windows every write that met backpressure was a terminal\n     * error, and an HTTP/2 client with a small send buffer failed its\n     * first big request instead of waiting for the peer to drain. */\n    case WSAEWOULDBLOCK:\n    case WSAETIMEDOUT:    return NURL_NET_ERR_TIMEOUT;\n    case WSAEADDRINUSE:   return NURL_NET_ERR_ADDRINUSE;\n    case WSAECONNRESET:\n    case WSAECONNABORTED:\n    case WSAESHUTDOWN:    return NURL_NET_ERR_CLOSED;\n    default:              return deflt;\n    }\n}\n#else\nstatic long long nurl__net_map_errno(int e, long long deflt) {\n    switch (e) {\n    case EAGAIN:\n#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN\n    case EWOULDBLOCK:\n#endif\n    case ETIMEDOUT:    return NURL_NET_ERR_TIMEOUT;\n    case EADDRINUSE:   return NURL_NET_ERR_ADDRINUSE;\n    case EPIPE:\n    case ECONNRESET:\n    case ENOTCONN:     return NURL_NET_ERR_CLOSED;\n    default:           return deflt;\n    }\n}\n#endif\n\n#ifdef _WIN32\n/* One-time WSAStartup; idempotent across calls. */\nstatic int nurl__net_wsa_init(void) {\n    static int initialised = 0;\n    if (initialised) return 1;\n    WSADATA w;\n    if (WSAStartup(MAKEWORD(2,2), &w) != 0) return 0;\n    initialised = 1;\n    return 1;\n}\n#endif\n\n/* poll(2) for the resolver wait here, the fiber spin probe (§24) and the\n * reactor (§25). Winsock's WSAPoll takes the same pollfd array, but its\n * fd is a SOCKET — a pointer-sized handle — which is why the fds those\n * pass along are long long or nurl_sockfd_t, never int. */\n#ifdef _WIN32\ntypedef WSAPOLLFD nurl_pollfd_t;\n#  define nurl__poll(fds, n, ms) WSAPoll((fds), (ULONG)(n), (ms))\n#else\ntypedef struct pollfd nurl_pollfd_t;\n#  define nurl__poll(fds, n, ms) poll((fds), (nfds_t)(n), (ms))\n#endif\n\nstatic int nurl__poll_ms(long long left) {\n    return left < 0 ? -1 : (int)(left > 0x7fffffff ? 0x7fffffff : left);\n}\n\n/* A one-shot cross-thread signal a poller can wait on: an eventfd on\n * Linux (rd == wr), a nonblocking pipe on the other POSIX hosts, and on\n * Windows — where WSAPoll waits on sockets only and pipe() is a stub — a\n * loopback UDP socket connected to itself (rd == wr). 0 on success. */\nstatic int nurl__signal_open(nurl_sockfd_t *rd, nurl_sockfd_t *wr) {\n#if defined(_WIN32)\n    if (!nurl__net_wsa_init()) return -1;\n    SOCKET s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);\n    if (s == INVALID_SOCKET) return -1;\n    struct sockaddr_in a;\n    int alen = (int)sizeof a;\n    memset(&a, 0, sizeof a);\n    a.sin_family = AF_INET;\n    a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n    u_long on = 1;\n    if (bind(s, (struct sockaddr *)&a, alen) != 0 ||\n        getsockname(s, (struct sockaddr *)&a, &alen) != 0 ||\n        connect(s, (struct sockaddr *)&a, alen) != 0 ||\n        ioctlsocket(s, FIONBIO, &on) != 0) {\n        closesocket(s);\n        return -1;\n    }\n    *rd = *wr = s;\n#elif defined(__linux__)\n    int fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);\n    if (fd < 0) return -1;\n    *rd = *wr = fd;\n#else\n    int p[2];\n    if (pipe(p) != 0) return -1;\n    for (int i = 0; i < 2; i++) {\n        fcntl(p[i], F_SETFD, FD_CLOEXEC);\n        fcntl(p[i], F_SETFL, fcntl(p[i], F_GETFL, 0) | O_NONBLOCK);\n    }\n    *rd = p[0];\n    *wr = p[1];\n#endif\n    return 0;\n}\n\n/* Raise the signal. Nonblocking: one pending signal is all a waiter\n * needs, so a full channel is not an error. */\nstatic void nurl__signal_raise(nurl_sockfd_t rd, nurl_sockfd_t wr) {\n#if defined(_WIN32)\n    (void)rd;\n    send(wr, \"w\", 1, 0);\n#else\n    unsigned long long one = 1;\n    ssize_t w = write(wr, &one, wr == rd ? sizeof one : 1);\n    (void)w;\n#endif\n}\n\n/* Empty the channel so the next wait blocks until the next raise. */\nstatic void nurl__signal_drain(nurl_sockfd_t rd) {\n    char buf[64];\n#if defined(_WIN32)\n    while (recv(rd, buf, sizeof buf, 0) > 0) {}\n#else\n    while (read(rd, buf, sizeof buf) > 0) {}\n#endif\n}\n\nstatic void nurl__signal_close(nurl_sockfd_t rd, nurl_sockfd_t wr) {\n    if (rd != NURL_INVALID_SOCK) nurl_close_sock(rd);\n    if (wr != NURL_INVALID_SOCK && wr != rd) nurl_close_sock(wr);\n}\n\nint nurl_cond_wait_ms(void *cv, void *m, long long ms);\n\n/* AF_INET sockaddr → owned \"ip:port\" string. */\nstatic char *nurl__net_format_peer(const struct sockaddr_in *sa) {\n    char ip[INET_ADDRSTRLEN] = {0};\n#ifdef _WIN32\n    InetNtopA(AF_INET, (PVOID)&sa->sin_addr, ip, sizeof(ip));\n#else\n    inet_ntop(AF_INET, &sa->sin_addr, ip, sizeof(ip));\n#endif\n    unsigned port = (unsigned)ntohs(sa->sin_port);\n    char *out = (char*)malloc(32);  /* INET_ADDRSTRLEN(16) + \":65535\\0\" fits */\n    if (!out) return strdup(\"\");\n    snprintf(out, 32, \"%s:%u\", ip, port);\n    return out;\n}\n\n/* Fresh handle in the \"failed\" state (err_kind=OTHER, fd=invalid);\n * call sites overwrite on success. NULL only on OOM. */\nstatic NurlTcp *nurl__tcp_new_handle(int kind) {\n    NurlTcp *h = (NurlTcp*)calloc(1, sizeof(NurlTcp));\n    if (!h) return NULL;\n    h->fd       = NURL_INVALID_SOCK;\n    h->err_kind = NURL_NET_ERR_OTHER;\n    h->kind     = kind;\n    h->peer     = NULL;\n    h->wake_rfd = NURL_INVALID_SOCK;\n    h->wake_wfd = NURL_INVALID_SOCK;\n    h->rd_wake  = NURL_INVALID_SOCK;\n    h->shutting_down = 0;\n    h->nonblock = 0;\n    h->refcount = 1;\n    return h;\n}\n\n/* Listener options (std/net.nu TcpListenOpts), a bit set:\n *   1  SO_REUSEPORT — several sockets bound to the same address, the\n *      kernel spreading incoming connections across them (Linux ≥ 3.9,\n *      the BSDs; on a platform without it the bit is ignored). Every\n *      socket bound to the port must ask for it, the first included. */\n#define NURL_LISTEN_REUSEPORT 1LL\n\n/* What every bound TCP socket asks for so a restarted server can bind its\n * port while the last run's connections sit in TIME_WAIT: SO_REUSEADDR\n * on POSIX. (Not a UDP socket: it has no TIME_WAIT, and there the option\n * lets sockets share a port — see nurl_udp_bind.) NOT on Windows, where the same name means something else —\n * \"let a second socket bind this address while the first still has it\",\n * so any process could take over a listening port — and where TIME_WAIT\n * never blocked a bind to begin with. There the default is already what\n * the POSIX option buys. */\nstatic void nurl__sock_restart_ok(nurl_sockfd_t fd) {\n#ifdef _WIN32\n    (void)fd;\n#else\n    int on = 1;\n    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, (int)sizeof(on));\n#endif\n}\n\nlong long nurl_tcp_listen_opts(const char *host, long long port, long long backlog, long long flags);\n\nlong long nurl_tcp_listen(const char *host, long long port, long long backlog) {\n    return nurl_tcp_listen_opts(host, port, backlog, 0);\n}\n\nlong long nurl_tcp_listen_opts(const char *host, long long port, long long backlog, long long flags) {\n#ifdef _WIN32\n    if (!nurl__net_wsa_init()) {\n        NurlTcp *h = nurl__tcp_new_handle(NURL_TCP_KIND_LISTENER);\n        if (!h) return 0;\n        h->err_kind = NURL_NET_ERR_OTHER;\n        return (long long)(uintptr_t)h;\n    }\n#endif\n    NurlTcp *h = nurl__tcp_new_handle(NURL_TCP_KIND_LISTENER);\n    if (!h) return 0;\n    /* port 0 = \"kernel picks an ephemeral port\", the POSIX contract, and\n     * the only collision-free way for parallel tests and short-lived\n     * servers to bind. Read the assigned port back with\n     * nurl_tcp_local_addr. nurl_udp_bind has always accepted it; this\n     * side rejected it as invalid, which made the whole pattern\n     * unavailable on TCP (and reported as NetBind, a bind failure that\n     * never happened). Only nurl_tcp_connect keeps `<= 0`, where a\n     * destination port of 0 really is meaningless. */\n    if (port < 0 || port > 65535) {\n        h->err_kind = NURL_NET_ERR_BIND;\n        return (long long)(uintptr_t)h;\n    }\n    if (backlog <= 0) backlog = 16;\n\n    nurl_sockfd_t fd = socket(AF_INET, SOCK_STREAM, 0);\n    if (fd == NURL_INVALID_SOCK && nurl__fd_reclaim()) fd = socket(AF_INET, SOCK_STREAM, 0);\n    if (fd == NURL_INVALID_SOCK) {\n        h->err_kind = NURL_NET_ERR_BIND;\n        return (long long)(uintptr_t)h;\n    }\n\n    nurl__sock_restart_ok(fd);\n    int on = 1;\n#if defined(_WIN32)\n    /* Windows' way for several sockets to share one port is SO_REUSEADDR\n     * itself (see nurl__sock_restart_ok) — asked for here, by the caller\n     * that means it, and only then. */\n    if ((flags & NURL_LISTEN_REUSEPORT) &&\n        setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, (int)sizeof(on)) != 0) {\n        h->err_kind = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_BIND);\n        nurl_close_sock(fd);\n        return (long long)(uintptr_t)h;\n    }\n#elif defined(SO_REUSEPORT)\n    if ((flags & NURL_LISTEN_REUSEPORT) &&\n        setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (const char*)&on, (int)sizeof(on)) != 0) {\n        h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_BIND);\n        nurl_close_sock(fd);\n        return (long long)(uintptr_t)h;\n    }\n#else\n    (void)flags; (void)on;\n#endif\n\n    struct sockaddr_in sa;\n    memset(&sa, 0, sizeof(sa));\n    sa.sin_family = AF_INET;\n    sa.sin_port   = htons((unsigned short)port);\n    if (!host || !*host) {\n        sa.sin_addr.s_addr = htonl(INADDR_ANY);\n    } else {\n#ifdef _WIN32\n        if (InetPtonA(AF_INET, host, &sa.sin_addr) != 1) {\n            nurl_close_sock(fd);\n            h->err_kind = NURL_NET_ERR_BIND;\n            return (long long)(uintptr_t)h;\n        }\n#else\n        if (inet_pton(AF_INET, host, &sa.sin_addr) != 1) {\n            nurl_close_sock(fd);\n            h->err_kind = NURL_NET_ERR_BIND;\n            return (long long)(uintptr_t)h;\n        }\n#endif\n    }\n\n    if (bind(fd, (struct sockaddr*)&sa, (int)sizeof(sa)) != 0) {\n#ifdef _WIN32\n        int we = WSAGetLastError();\n        h->err_kind = nurl__net_map_wsa(we, NURL_NET_ERR_BIND);\n#else\n        h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_BIND);\n#endif\n        nurl_close_sock(fd);\n        return (long long)(uintptr_t)h;\n    }\n\n    if (listen(fd, (int)backlog) != 0) {\n#ifdef _WIN32\n        int we = WSAGetLastError();\n        h->err_kind = nurl__net_map_wsa(we, NURL_NET_ERR_BIND);\n#else\n        h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_BIND);\n#endif\n        nurl_close_sock(fd);\n        return (long long)(uintptr_t)h;\n    }\n\n    h->fd       = fd;\n    h->err_kind = NURL_NET_ERR_OK;\n\n    /* Accept-wakeup channel (see struct comment). Best-effort: if it cannot\n     * be opened we leave wake_* invalid and accept() blocks the old way —\n     * only the cross-thread-shutdown path degrades, never the happy path. */\n    if (nurl__signal_open(&h->wake_rfd, &h->wake_wfd) == 0) {\n        /* Non-blocking listen fd: after poll reports POLLIN we accept;\n         * if a sibling worker grabbed the connection first, accept\n         * returns EAGAIN and we re-poll instead of blocking. */\n#ifdef _WIN32\n        u_long nb_on = 1;\n        ioctlsocket(fd, FIONBIO, &nb_on);\n#else\n        int fl = fcntl(fd, F_GETFL, 0);\n        if (fl != -1) fcntl(fd, F_SETFL, fl | O_NONBLOCK);\n#endif\n    }\n    return (long long)(uintptr_t)h;\n}\n\n/* ── §18-r  Outbound name resolution and connect, fiber-aware ───────\n *\n * A fiber that blocks in the kernel blocks its whole M:N worker and\n * every fiber queued behind it. Reads and writes have long parked on\n * the netpoller instead; the two outbound calls — getaddrinfo and\n * connect(2) — did not, so one slow DNS answer or one SYN into a black\n * hole stalled a worker for the resolver's or the kernel's full\n * timeout (minutes, for an unanswered SYN).\n *\n * CONNECT. On a fiber the socket is opened non-blocking; connect(2)\n * answers EINPROGRESS and the fiber parks for writability — a pending\n * connection turns writable exactly when it is established or fails —\n * then SO_ERROR says which. Off a fiber with no timeout nothing\n * changes: one blocking connect(2) per address, as before. A connect\n * timeout is one absolute deadline over the whole call, resolution\n * included: on a fiber it rides the netpoller's timer heap, off a\n * fiber it is a poll(2) (select() on Winsock). Every address the\n * resolver returns is tried in order until one connects (sequential\n * fallback); a deadline that passes ends the call, since it budgets\n * the whole connect, not each address. The connected socket comes back\n * BLOCKING whichever path made it — what the plain path returns —\n * because raw callers (std/tls.nu's __fill, ext/http_pure.nu) issue\n * plain nurl_tcp_read on it; the async wrappers ask for O_NONBLOCK\n * themselves, through the memo.\n *\n * RESOLUTION. A numeric literal never reaches a resolver: getaddrinfo\n * with AI_NUMERICHOST answers it in-process. A name, on a fiber or\n * under a deadline, goes to a small pool of resolver threads running\n * the real getaddrinfo — /etc/hosts, nsswitch and resolv.conf mean\n * exactly what they meant before — while the fiber parks on the job's\n * completion signal (nurl__signal_open: an eventfd on Linux, a pipe on\n * the other POSIX hosts, a loopback socket on Windows) through the\n * ordinary netpoller:\n * the runtime's existing cross-thread wake, with the timer heap's\n * deadline for free. A waiter whose deadline passes abandons the job,\n * and the resolver thread frees it when getaddrinfo returns\n * (getaddrinfo cannot be cancelled). The pool grows on demand to\n * NURL_DNS_MAX_THREADS and a thread idle for NURL_DNS_IDLE_S exits, so\n * a program that resolves nothing never owns one. Off a fiber with no\n * deadline getaddrinfo runs on the caller, as before. Every outbound\n * lookup in this file goes through nurl__resolve: tcp_connect,\n * udp_connect, udp_send_to, udp_addr_resolve, dns_resolve(_port); the\n * reverse one (dns_reverse's getnameinfo) through nurl__reverse, the\n * same pool with a job kind.\n *\n * FIBER MIGRATION. A parked fiber may resume on another worker thread,\n * and errno is a thread-local whose address the compiler treats as\n * constant within a function. So the functions that park\n * (nurl_tcp_connect_ms, nurl__resolve) never read errno themselves:\n * every errno read happens in a noinline helper that does not park\n * (nurl__connect_start / nurl__connect_finish) and comes back as a\n * plain value. (runtime_core.c now re-derives errno's address on every\n * access anyway — tools/check_errno_fiber_safe.sh — so this is the\n * belt to that brace.) */\n\n#define NURL_RESOLVE_TIMEOUT (-100000)  /* nurl__resolve: deadline passed */\n\nextern long long nurl_monotonic_ns(void);\nlong long nurl_fiber_current(void);\nlong long nurl_reactor_wait_read(long long fd, long long timeout_ms);\nlong long nurl_reactor_wait_write(long long fd, long long timeout_ms);\n\nstatic long long nurl__conn_now_ms(void) {\n    return nurl_monotonic_ns() / 1000000LL;\n}\n\n/* Milliseconds left until `deadline` (-1 = none → -1), never below 0. */\nstatic long long nurl__conn_left_ms(long long deadline) {\n    if (deadline < 0) return -1;\n    long long left = deadline - nurl__conn_now_ms();\n    return left > 0 ? left : 0;\n}\n\n#  define NURL_DNS_MAX_THREADS 8\n#  define NURL_DNS_IDLE_S      30\n\n/* One lookup for the pool: a forward getaddrinfo, or a reverse\n * getnameinfo (`reverse`, reading sa/salen and writing `name`). */\ntypedef struct NurlDnsJob {\n    struct NurlDnsJob *next;\n    int                reverse;\n    struct sockaddr_storage sa;\n    socklen_t          salen;\n    int                niflags;\n    char               name[NI_MAXHOST];\n    char              *host;\n    char               port[16];\n    int                noport;     /* service NULL, not \"\" */\n    struct addrinfo    hints;\n    struct addrinfo   *res;\n    int                rc;\n    int                done;       /* nurl__dns.lock */\n    int                abandoned;  /* nurl__dns.lock: the waiter gave up */\n    nurl_sockfd_t      rfd, wfd;   /* completion signal (nurl__signal_open) */\n} NurlDnsJob;\n\nstatic struct {\n    pthread_mutex_t lock;\n    pthread_cond_t  cv;\n    NurlDnsJob     *head, *tail;\n    int             queued, threads, idle;\n} nurl__dns = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER,\n                NULL, NULL, 0, 0, 0 };\n\n/* Test hook (compiler/tests/net_resolve_fiber.nu): every pool lookup\n * first sleeps this many ms. A real lookup answered from /etc/hosts is\n * often finished before its fiber looks — the woken resolver thread can\n * preempt the submitting worker on a busy machine — so without it a\n * test cannot show other fibers running while one waits. 0 = off. */\nstatic int nurl__dns_test_delay;\nvoid nurl_dns_test_delay_ms(long long ms) {\n    __atomic_store_n(&nurl__dns_test_delay, (int)(ms > 0 ? ms : 0), __ATOMIC_RELAXED);\n}\n\nstatic void nurl__dns_job_free(NurlDnsJob *j) {\n    if (j->res) freeaddrinfo(j->res);\n    /* Through the close hook: the waiter may have parked on rfd, and the\n     * fd number must not reach its next owner with netpoller state. */\n    nurl__signal_close(j->rfd, j->wfd);\n    free(j->host);\n    free(j);\n}\n\nstatic void *nurl__dns_thread(void *arg) {\n    (void)arg;\n    pthread_mutex_lock(&nurl__dns.lock);\n    for (;;) {\n        while (!nurl__dns.head) {\n            nurl__dns.idle++;\n            int signalled = nurl_cond_wait_ms(&nurl__dns.cv, &nurl__dns.lock,\n                                              NURL_DNS_IDLE_S * 1000LL);\n            nurl__dns.idle--;\n            if (!signalled && !nurl__dns.head) {\n                nurl__dns.threads--;\n                pthread_mutex_unlock(&nurl__dns.lock);\n                return NULL;\n            }\n        }\n        NurlDnsJob *j = nurl__dns.head;\n        nurl__dns.head = j->next;\n        if (!nurl__dns.head) nurl__dns.tail = NULL;\n        nurl__dns.queued--;\n        pthread_mutex_unlock(&nurl__dns.lock);\n\n        int delay = __atomic_load_n(&nurl__dns_test_delay, __ATOMIC_RELAXED);\n        if (delay > 0) {\n            struct timespec dt = { delay / 1000, (long)(delay % 1000) * 1000000L };\n            nanosleep(&dt, NULL);\n        }\n        struct addrinfo *res = NULL;\n        int rc = j->reverse\n            ? getnameinfo((struct sockaddr*)&j->sa, j->salen, j->name,\n                          sizeof j->name, NULL, 0, j->niflags)\n            : getaddrinfo(j->host, j->noport ? NULL : j->port, &j->hints, &res);\n\n        pthread_mutex_lock(&nurl__dns.lock);\n        j->rc   = rc;\n        j->res  = res;\n        j->done = 1;\n        if (j->abandoned) {\n            pthread_mutex_unlock(&nurl__dns.lock);\n            nurl__dns_job_free(j);\n            pthread_mutex_lock(&nurl__dns.lock);\n        } else {\n            /* Signal under the lock: once `done` is visible the waiter\n             * may free the job and close wfd, so the write lands first.\n             * Non-blocking, and never more than one signal deep. */\n            nurl__signal_raise(j->rfd, j->wfd);\n        }\n    }\n}\n\n#ifndef _WIN32\n/* A forked child inherits the queue but none of the threads: start it\n * from empty, or its first lookup would wait for ever. (Windows has no\n * fork.) */\nstatic void nurl__dns_atfork_child(void) {\n    pthread_mutex_init(&nurl__dns.lock, NULL);\n    pthread_cond_init(&nurl__dns.cv, NULL);\n    nurl__dns.head = nurl__dns.tail = NULL;\n    nurl__dns.queued = nurl__dns.threads = nurl__dns.idle = 0;\n}\n#endif\n\n/* Queue `j`, growing the pool when every thread is busy. 0 on success;\n * -1 when no thread exists to run it (creation failed, pool empty) —\n * the caller resolves inline instead. */\nstatic int nurl__dns_submit(NurlDnsJob *j) {\n    pthread_mutex_lock(&nurl__dns.lock);\n#ifndef _WIN32\n    static int atfork_done;\n    if (!atfork_done) {\n        atfork_done = 1;\n        pthread_atfork(NULL, NULL, nurl__dns_atfork_child);\n    }\n#endif\n    if (nurl__dns.queued + 1 > nurl__dns.idle &&\n        nurl__dns.threads < NURL_DNS_MAX_THREADS) {\n        /* Detached: a pool thread exits on its own after an idle spell\n         * and nobody joins it. */\n        pthread_t tid;\n        if (pthread_create(&tid, NULL, nurl__dns_thread, NULL) == 0) {\n            pthread_detach(tid);\n            nurl__dns.threads++;\n        }\n    }\n    if (nurl__dns.threads == 0) {\n        pthread_mutex_unlock(&nurl__dns.lock);\n        return -1;\n    }\n    j->next = NULL;\n    if (nurl__dns.tail) nurl__dns.tail->next = j;\n    else                nurl__dns.head = j;\n    nurl__dns.tail = j;\n    nurl__dns.queued++;\n    pthread_cond_signal(&nurl__dns.cv);\n    pthread_mutex_unlock(&nurl__dns.lock);\n    return 0;\n}\n\n/* Run `j` on the pool and wait for it — parked on the netpoller on a\n * fiber, in poll(2) off one. 0: done, the result is in `j` and the\n * caller frees it. -1: could not offload (no thread, no fd); the caller\n * frees `j` and does the lookup inline. NURL_RESOLVE_TIMEOUT: the\n * deadline passed and `j` now belongs to the resolver thread. */\nstatic int nurl__dns_offload(NurlDnsJob *j, int on_fiber, long long deadline) {\n    if (nurl__signal_open(&j->rfd, &j->wfd) != 0 || nurl__dns_submit(j) != 0) return -1;\n    /* A wake is only a hint (spurious, or the deadline) — `done` under\n     * the lock is the truth. */\n    for (;;) {\n        pthread_mutex_lock(&nurl__dns.lock);\n        int done = j->done;\n        if (!done && nurl__conn_left_ms(deadline) == 0) {\n            j->abandoned = 1;              /* the resolver thread frees it */\n            pthread_mutex_unlock(&nurl__dns.lock);\n            return NURL_RESOLVE_TIMEOUT;\n        }\n        pthread_mutex_unlock(&nurl__dns.lock);\n        if (done) return 0;\n        long long left = nurl__conn_left_ms(deadline);\n        if (!on_fiber || nurl_reactor_wait_read((long long)j->rfd, left) < 0) {\n            nurl_pollfd_t pf;\n            pf.fd = j->rfd;\n            pf.events = POLLIN;\n            pf.revents = 0;\n            nurl__poll(&pf, 1, nurl__poll_ms(left));\n        }\n    }\n}\n\n/* getaddrinfo that does not hold a fiber's worker. `deadline` is an\n * absolute nurl__conn_now_ms (-1 = none). Returns getaddrinfo's code,\n * or NURL_RESOLVE_TIMEOUT when the deadline passed first. */\n__attribute__((noinline))\nstatic int nurl__resolve(const char *host, const char *port,\n                         const struct addrinfo *hints,\n                         struct addrinfo **res, long long deadline) {\n    *res = NULL;\n    int on_fiber = nurl_fiber_current() != 0;\n    if (!on_fiber && deadline < 0) return getaddrinfo(host, port, hints, res);\n\n    /* Numeric fast path: answered in-process, no resolver, no thread. A\n     * name fails it (EAI_NONAME) and goes to the pool. */\n    struct addrinfo nh = *hints;\n    nh.ai_flags |= AI_NUMERICHOST;\n    if (getaddrinfo(host, port, &nh, res) == 0) return 0;\n    *res = NULL;\n\n    NurlDnsJob *j = (NurlDnsJob*)calloc(1, sizeof *j);\n    if (!j) return getaddrinfo(host, port, hints, res);\n    j->rfd = j->wfd = NURL_INVALID_SOCK;\n    j->host = strdup(host);\n    j->hints = *hints;\n    j->noport = !port;\n    snprintf(j->port, sizeof j->port, \"%s\", port ? port : \"\");\n    int st = j->host ? nurl__dns_offload(j, on_fiber, deadline) : -1;\n    if (st == NURL_RESOLVE_TIMEOUT) return st;\n    if (st != 0) {\n        nurl__dns_job_free(j);\n        return getaddrinfo(host, port, hints, res);\n    }\n    int rc = j->rc;\n    *res = j->res;\n    j->res = NULL;\n    nurl__dns_job_free(j);\n    return rc;\n}\n\n/* getnameinfo that does not hold a fiber's worker: the reverse twin\n * of nurl__resolve (no deadline — dns_reverse has none). Returns\n * getnameinfo's code; `out` gets the name. */\n__attribute__((noinline))\nstatic int nurl__reverse(const struct sockaddr *sa, socklen_t salen,\n                         char *out, size_t cap, int flags) {\n    int on_fiber = nurl_fiber_current() != 0;\n    if (on_fiber && salen <= (socklen_t)sizeof(struct sockaddr_storage)) {\n        NurlDnsJob *j = (NurlDnsJob*)calloc(1, sizeof *j);\n        if (j) {\n            j->rfd = j->wfd = NURL_INVALID_SOCK;\n            j->reverse = 1;\n            memcpy(&j->sa, sa, salen);\n            j->salen = salen;\n            j->niflags = flags;\n            if (nurl__dns_offload(j, 1, -1) == 0) {\n                int rc = j->rc;\n                if (rc == 0) snprintf(out, cap, \"%s\", j->name);\n                nurl__dns_job_free(j);\n                return rc;\n            }\n            nurl__dns_job_free(j);\n        }\n    }\n    return getnameinfo(sa, salen, out, (socklen_t)cap, NULL, 0, flags);\n}\n\n/* Open a socket for `ai` and start connecting. Returns 0 connected,\n * 1 in progress (only when `nb` asked for a non-blocking socket), or a\n * positive errno / WSA code; *out holds the socket on 0 and 1. Never\n * parks — see FIBER MIGRATION above. */\n__attribute__((noinline))\nstatic int nurl__connect_start(const struct addrinfo *ai, int nb,\n                               nurl_sockfd_t *out) {\n    int type = ai->ai_socktype;\n#if defined(SOCK_NONBLOCK) && !defined(_WIN32)\n    if (nb) type |= SOCK_NONBLOCK;\n#endif\n    nurl_sockfd_t fd = socket(ai->ai_family, type, ai->ai_protocol);\n    if (fd == NURL_INVALID_SOCK && nurl__fd_reclaim())\n        fd = socket(ai->ai_family, type, ai->ai_protocol);\n    if (fd == NURL_INVALID_SOCK) {\n#ifdef _WIN32\n        int we = WSAGetLastError();\n        return we ? we : WSAEMFILE;\n#else\n        return errno ? errno : EMFILE;\n#endif\n    }\n    if (nb) {\n#if defined(_WIN32)\n        u_long on = 1;\n        ioctlsocket(fd, FIONBIO, &on);\n#elif !defined(SOCK_NONBLOCK)\n        fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);\n#endif\n    }\n    if (connect(fd, ai->ai_addr, (int)ai->ai_addrlen) == 0) {\n        *out = fd;\n        return 0;\n    }\n#ifdef _WIN32\n    int e = WSAGetLastError();\n    if (nb && (e == WSAEWOULDBLOCK || e == WSAEINPROGRESS)) {\n        *out = fd;\n        return 1;\n    }\n    if (!e) e = WSAECONNREFUSED;\n#else\n    int e = errno;\n    /* EINTR on a non-blocking connect: the handshake carries on in the\n     * kernel — wait for it like EINPROGRESS. */\n    if (nb && (e == EINPROGRESS || e == EINTR)) {\n        *out = fd;\n        return 1;\n    }\n    if (!e) e = ECONNREFUSED;\n#endif\n    nurl_close_sock(fd);\n    return e;\n}\n\n/* The wait for a pending connect on `fd` ended: 0 established, -1 not\n * settled yet (a spurious wake — wait again), else the error code. */\n__attribute__((noinline))\nstatic int nurl__connect_finish(nurl_sockfd_t fd) {\n    int soerr = 0;\n#ifdef _WIN32\n    int len = (int)sizeof soerr;\n    if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char*)&soerr, &len) != 0) {\n        int we = WSAGetLastError();\n        return we ? we : WSAENOTCONN;\n    }\n    return soerr;\n#else\n    socklen_t len = (socklen_t)sizeof soerr;\n    if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &soerr, &len) != 0)\n        return errno ? errno : ENOTCONN;\n    if (soerr) return soerr;\n    struct sockaddr_storage ss;\n    socklen_t sl = (socklen_t)sizeof ss;\n    return getpeername(fd, (struct sockaddr*)&ss, &sl) == 0 ? 0 : -1;\n#endif\n}\n\n/* Wait off a fiber for a pending connect: 1 woke, 0 deadline. */\nstatic int nurl__connect_poll(nurl_sockfd_t fd, long long left) {\n#ifdef _WIN32\n    fd_set wfds, efds;\n    FD_ZERO(&wfds); FD_ZERO(&efds);\n    FD_SET(fd, &wfds); FD_SET(fd, &efds);\n    struct timeval tv, *tvp = NULL;\n    if (left >= 0) {\n        tv.tv_sec  = (long)(left / 1000);\n        tv.tv_usec = (long)((left % 1000) * 1000);\n        tvp = &tv;\n    }\n    return select(0, NULL, &wfds, &efds, tvp) > 0 ? 1 : 0;\n#else\n    struct pollfd pf = { .fd = fd, .events = POLLOUT, .revents = 0 };\n    /* -1 (EINTR) counts as a wake: SO_ERROR / getpeername settle it. */\n    return poll(&pf, 1, nurl__poll_ms(left)) != 0 ? 1 : 0;\n#endif\n}\n\n/* Client-side connect with an optional deadline: resolve `host` (DNS\n * name or IPv4/IPv6 literal), then try each address in order until one\n * connects. timeout_ms <= 0 = no deadline. The returned CONN handle\n * consumes read/write/close identically to an accept()ed conn; a\n * deadline that passes reports NURL_NET_ERR_TIMEOUT. */\nNURL_TLS_FN long long nurl_tcp_connect_ms(const char *host, long long port,\n                                          long long timeout_ms) {\n#ifdef _WIN32\n    if (!nurl__net_wsa_init()) {\n        NurlTcp *h = nurl__tcp_new_handle(NURL_TCP_KIND_CONN);\n        if (!h) return 0;\n        h->err_kind = NURL_NET_ERR_OTHER;\n        return (long long)(uintptr_t)h;\n    }\n#endif\n    NurlTcp *h = nurl__tcp_new_handle(NURL_TCP_KIND_CONN);\n    if (!h) return 0;\n    if (!host || !*host || port <= 0 || port > 65535) {\n        h->err_kind = NURL_NET_ERR_OTHER;\n        return (long long)(uintptr_t)h;\n    }\n    long long deadline = timeout_ms > 0 ? nurl__conn_now_ms() + timeout_ms : -1;\n    int on_fiber = nurl_fiber_current() != 0;\n\n    char portstr[16];\n    snprintf(portstr, sizeof(portstr), \"%ld\", (long)port);\n\n    struct addrinfo hints, *res = NULL, *ai;\n    memset(&hints, 0, sizeof(hints));\n    hints.ai_family   = AF_UNSPEC;   /* IPv4 or IPv6 */\n    hints.ai_socktype = SOCK_STREAM;\n    int grc = nurl__resolve(host, portstr, &hints, &res, deadline);\n    if (grc != 0 || !res) {\n        if (res) freeaddrinfo(res);\n        h->err_kind = grc == NURL_RESOLVE_TIMEOUT ? NURL_NET_ERR_TIMEOUT\n                                                  : NURL_NET_ERR_OTHER;\n        return (long long)(uintptr_t)h;\n    }\n\n    /* Non-blocking only where something must wait without blocking: a\n     * fiber, or a deadline. */\n    int nb = on_fiber || deadline >= 0;\n    nurl_sockfd_t fd = NURL_INVALID_SOCK;\n    int err = 0;\n    for (ai = res; ai; ai = ai->ai_next) {\n        nurl_sockfd_t cfd = NURL_INVALID_SOCK;\n        int st = nurl__connect_start(ai, nb, &cfd);\n        if (st == 1) {\n            /* In progress: park (fiber) or poll (thread) for writability\n             * until established, failed, or out of time. */\n            for (;;) {\n                long long left = nurl__conn_left_ms(deadline);\n                int woke = 0;\n                if (left != 0) {\n                    long long r = on_fiber\n                        ? nurl_reactor_wait_write((long long)cfd, left) : -1;\n                    woke = r < 0 ? nurl__connect_poll(cfd, left) : (int)r;\n                }\n                if (!woke) { st = -2; break; }        /* deadline */\n                st = nurl__connect_finish(cfd);\n                if (st != -1) break;                  /* settled */\n            }\n            if (st != 0) nurl_close_sock(cfd);\n        }\n        if (st == 0) { fd = cfd; break; }\n        err = st;\n        if (st == -2) break;                          /* budget spent */\n    }\n    freeaddrinfo(res);\n\n    if (fd == NURL_INVALID_SOCK) {\n#ifdef _WIN32\n        h->err_kind = err == -2 ? NURL_NET_ERR_TIMEOUT\n                                : nurl__net_map_wsa(err, NURL_NET_ERR_OTHER);\n#else\n        h->err_kind = err == -2 ? NURL_NET_ERR_TIMEOUT\n                                : nurl__net_map_errno(err, NURL_NET_ERR_OTHER);\n#endif\n        return (long long)(uintptr_t)h;\n    }\n    if (nb) {\n        /* Hand back the blocking socket the plain path returns (see the\n         * section comment); h->nonblock = 0 agrees with the kernel. */\n#ifdef _WIN32\n        u_long off = 0;\n        ioctlsocket(fd, FIONBIO, &off);\n#else\n        int fl = fcntl(fd, F_GETFL, 0);\n        if (fl >= 0) fcntl(fd, F_SETFL, fl & ~O_NONBLOCK);\n#endif\n    }\n    /* Disable Nagle, as the accept paths do. A client's exchanges are\n     * small writes that wait for an answer — a TLS ClientHello then\n     * Finished then the request, an HTTP request head then its body —\n     * and with Nagle on, each write after the first waits for the ACK of\n     * the one before, which the peer delays ~40 ms. tls-bench measured\n     * it: 42 ms per new http-client connection, against 0.8 ms for\n     * reqwest. */\n    {\n        int one = 1;\n        setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,\n                   (const char*)&one, (socklen_t)sizeof(one));\n    }\n    h->fd       = fd;\n    h->err_kind = NURL_NET_ERR_OK;\n    return (long long)(uintptr_t)h;\n}\n\nlong long nurl_tcp_connect(const char *host, long long port) {\n    return nurl_tcp_connect_ms(host, port, 0);\n}\n\n/* nurl__fd_reclaim from inside an accept. The collection it runs may\n * drop values, and a destructor may stop this very listener, which waits\n * for the accepts in progress: step out of the count for it, and retry\n * only if the listener is still running. */\nstatic int nurl__accept_reclaim(NurlTcp *l) {\n    __atomic_sub_fetch(&l->accepting, 1, __ATOMIC_SEQ_CST);\n    int r = nurl__fd_reclaim();\n    __atomic_add_fetch(&l->accepting, 1, __ATOMIC_SEQ_CST);\n    return r && !__atomic_load_n(&l->shutting_down, __ATOMIC_SEQ_CST);\n}\n\nstatic void nurl__tcp_accept_on(NurlTcp *l, NurlTcp *c);\n\nlong long nurl_tcp_accept(long long listener) {\n    NurlTcp *l = (NurlTcp*)(uintptr_t)listener;\n    NurlTcp *c = nurl__tcp_new_handle(NURL_TCP_KIND_CONN);\n    if (!c) return 0;\n    if (!l || l->kind != NURL_TCP_KIND_LISTENER) {\n        c->err_kind = NURL_NET_ERR_ACCEPT;\n        return (long long)(uintptr_t)c;\n    }\n    /* Counted in before the fd is read: a stop closes it only once no\n     * accept is in progress (nurl__tcp_listener_stop). */\n    __atomic_add_fetch(&l->accepting, 1, __ATOMIC_SEQ_CST);\n    if (__atomic_load_n(&l->shutting_down, __ATOMIC_SEQ_CST) || l->fd == NURL_INVALID_SOCK)\n        c->err_kind = NURL_NET_ERR_ACCEPT;\n    else\n        nurl__tcp_accept_on(l, c);\n    __atomic_sub_fetch(&l->accepting, 1, __ATOMIC_SEQ_CST);\n    return (long long)(uintptr_t)c;\n}\n\nstatic void nurl__tcp_accept_on(NurlTcp *l, NurlTcp *c) {\n    struct sockaddr_in peer;\n#ifdef _WIN32\n    int peerlen = (int)sizeof(peer);\n#else\n    socklen_t peerlen = (socklen_t)sizeof(peer);\n#endif\n    memset(&peer, 0, sizeof(peer));\n    nurl_sockfd_t fd = NURL_INVALID_SOCK;\n#ifdef _WIN32\n    /* The POSIX shape below, in Winsock's words. The fiber path does ONE\n     * accept on the non-blocking listener and leaves WSAEWOULDBLOCK to its\n     * caller (tcp_accept_async parks on the reactor); a Winsock accept\n     * hands the listener's non-blocking mode to the new socket, which the\n     * memo records. The blocking path polls {fd, wake_rfd} like POSIX:\n     * closesocket from another thread does wake a blocked accept, but it\n     * frees the handle number for reuse while that accept may still be\n     * entering — the wake channel stops it without touching the fd. */\n    if (l->nonblock) {\n        fd = accept(l->fd, (struct sockaddr*)&peer, &peerlen);\n        if (fd == NURL_INVALID_SOCK && nurl__accept_reclaim(l)) fd = accept(l->fd, (struct sockaddr*)&peer, &peerlen);\n        if (fd == NURL_INVALID_SOCK) {\n            c->err_kind = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_ACCEPT);\n            return;\n        }\n        c->fd       = fd;\n        c->err_kind = NURL_NET_ERR_OK;\n        c->peer     = nurl__net_format_peer(&peer);\n        {\n            int one = 1;\n            setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&one, (int)sizeof(one));\n        }\n        c->nonblock = 1;\n        return;\n    }\n    for (;;) {\n        if (__atomic_load_n(&l->shutting_down, __ATOMIC_SEQ_CST)) {\n            c->err_kind = NURL_NET_ERR_ACCEPT;\n            return;\n        }\n        if (l->wake_rfd != NURL_INVALID_SOCK) {\n            nurl_pollfd_t pfds[2];\n            pfds[0].fd = l->fd;       pfds[0].events = POLLIN; pfds[0].revents = 0;\n            pfds[1].fd = l->wake_rfd; pfds[1].events = POLLIN; pfds[1].revents = 0;\n            if (nurl__poll(pfds, 2, -1) < 0) {\n                c->err_kind = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_ACCEPT);\n                return;\n            }\n            if (__atomic_load_n(&l->shutting_down, __ATOMIC_SEQ_CST) ||\n                (pfds[1].revents & (POLLIN | POLLHUP | POLLERR))) {\n                c->err_kind = NURL_NET_ERR_ACCEPT;\n                return;\n            }\n            if (!(pfds[0].revents & (POLLIN | POLLHUP | POLLERR))) continue;\n        }\n        fd = accept(l->fd, (struct sockaddr*)&peer, &peerlen);\n        if (fd == NURL_INVALID_SOCK && nurl__accept_reclaim(l)) fd = accept(l->fd, (struct sockaddr*)&peer, &peerlen);\n        if (fd == NURL_INVALID_SOCK) {\n            int we = WSAGetLastError();\n            /* A sibling worker took the connection, or the peer reset it\n             * while it was queued: poll again. */\n            if (we == WSAEWOULDBLOCK || we == WSAECONNRESET || we == WSAEINTR) continue;\n            c->err_kind = nurl__net_map_wsa(we, NURL_NET_ERR_ACCEPT);\n            return;\n        }\n        break;\n    }\n#else\n    if (l->nonblock) {\n        /* Fiber/reactor accept path: ONE non-blocking accept; the caller\n         * (tcp_accept_async) parks on the reactor for EAGAIN. Must not\n         * enter the blocking poll loop below.\n         *\n         * The connection comes back NON-BLOCKING here, and the handle's\n         * memo says so: every consumer of a reactor-accepted connection\n         * is an async wrapper (tcp_read_chunk_async, tcp_write_all_async,\n         * the TLS record layer on a fiber) that asks for O_NONBLOCK before\n         * its first read or write anyway, and with the memo already set\n         * that ask is free. Accepting blocking and flipping later cost\n         * three fcntl(2) per connection — the F_GETFL of the normalise\n         * block below plus the GET+SET of the first async wrapper — a\n         * third of everything the server asked the kernel for under\n         * connection churn (strace -c: 6007 fcntl / 2000 connections).\n         * accept4 with SOCK_NONBLOCK folds it into the accept itself;\n         * where accept4 does not exist (macOS) the flag is set once,\n         * still ahead of the memo. */\n#  if defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC)\n        fd = accept4(l->fd, (struct sockaddr*)&peer, &peerlen,\n                     SOCK_NONBLOCK | SOCK_CLOEXEC);\n        if (fd == NURL_INVALID_SOCK && nurl__accept_reclaim(l))\n            fd = accept4(l->fd, (struct sockaddr*)&peer, &peerlen, SOCK_NONBLOCK | SOCK_CLOEXEC);\n#  else\n        fd = accept(l->fd, (struct sockaddr*)&peer, &peerlen);\n        if (fd == NURL_INVALID_SOCK && nurl__accept_reclaim(l)) fd = accept(l->fd, (struct sockaddr*)&peer, &peerlen);\n        if (fd != NURL_INVALID_SOCK) {\n            int fl = fcntl(fd, F_GETFL, 0);\n            if (fl >= 0 && !(fl & O_NONBLOCK)) fcntl(fd, F_SETFL, fl | O_NONBLOCK);\n        }\n#  endif\n        if (fd == NURL_INVALID_SOCK) {\n            c->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_ACCEPT);\n            return;\n        }\n        c->fd       = fd;\n        c->err_kind = NURL_NET_ERR_OK;\n        c->peer     = nurl__net_format_peer(&peer);\n        {\n            int one = 1;\n            setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY,\n                       (const char*)&one, (socklen_t)sizeof(one));\n        }\n        c->nonblock = 1;\n        return;\n    } else\n    /* Blocking accept (single-threaded + server_run_pool). If this listener\n     * has no wakeup pipe (pipe() failed at listen) the loop's poll is\n     * skipped and we do a plain blocking accept. Otherwise poll\n     * {fd, wake_rfd} so a cross-thread nurl_tcp_shutdown reliably unblocks\n     * every worker. */\n    for (;;) {\n        if (__atomic_load_n(&l->shutting_down, __ATOMIC_SEQ_CST)) {\n            c->err_kind = NURL_NET_ERR_ACCEPT;\n            return;\n        }\n        if (l->wake_rfd != NURL_INVALID_SOCK) {\n            struct pollfd pfds[2];\n            pfds[0].fd = l->fd;       pfds[0].events = POLLIN; pfds[0].revents = 0;\n            pfds[1].fd = l->wake_rfd; pfds[1].events = POLLIN; pfds[1].revents = 0;\n            int pr = poll(pfds, 2, -1);\n            if (pr < 0) {\n                if (errno == EINTR) continue;\n                c->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_ACCEPT);\n                return;\n            }\n            /* Shutdown wins: the wake byte is never drained, so this stays\n             * readable for every worker, not just the first to wake. */\n            if (__atomic_load_n(&l->shutting_down, __ATOMIC_SEQ_CST) ||\n                (pfds[1].revents & (POLLIN | POLLHUP | POLLERR))) {\n                c->err_kind = NURL_NET_ERR_ACCEPT;\n                return;\n            }\n            if (!(pfds[0].revents & (POLLIN | POLLHUP | POLLERR))) continue;\n        }\n        fd = accept(l->fd, (struct sockaddr*)&peer, &peerlen);\n        if (fd == NURL_INVALID_SOCK && nurl__accept_reclaim(l)) fd = accept(l->fd, (struct sockaddr*)&peer, &peerlen);\n        if (fd == NURL_INVALID_SOCK) {\n            if (errno == EINTR || errno == ECONNABORTED ||\n                errno == EAGAIN || errno == EWOULDBLOCK) {\n                /* Spurious wake or sibling stole the connection — re-poll.\n                 * (A blocking listener without a wakeup pipe never reports\n                 * EAGAIN, so this cannot spin there.) */\n                continue;\n            }\n            c->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_ACCEPT);\n            return;\n        }\n        break;\n    }\n#endif\n    c->fd       = fd;\n    c->err_kind = NURL_NET_ERR_OK;\n    c->peer     = nurl__net_format_peer(&peer);\n    /* Disable Nagle for accepted connections. HTTP/2 servers send small\n     * framing-level acknowledgements (SETTINGS-ACK, PING-ACK,\n     * WINDOW_UPDATE) whose latency matters for interop suites (h2spec)\n     * and for normal request/response patterns where peer is waiting\n     * for these specifically. Without TCP_NODELAY, Nagle can pin a 9-\n     * byte ACK frame for up to 40 ms behind the previous write. */\n    {\n        int one = 1;\n        setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY,\n                   (const char*)&one, (socklen_t)sizeof(one));\n    }\n#ifndef _WIN32\n    /* Force the accepted socket BLOCKING, whatever the listener was.\n     *\n     * The listener is put in O_NONBLOCK above (the accept-wakeup pipe needs\n     * it), and POSIX leaves it *unspecified* whether accept() hands that\n     * flag to the new socket. Linux does not; the BSDs do. So on FreeBSD\n     * every accepted connection came back non-blocking, and any caller\n     * doing a plain blocking read got EAGAIN instead of data.\n     *\n     * net.nu's tcp_read_chunk survived that (it loops on EAGAIN), but\n     * tls.nu's __fill deliberately issues one raw nurl_tcp_read — see its\n     * comment — so the pure TLS server failed to read the ClientHello and\n     * every handshake died with TlsRead. Normalising here means every\n     * caller sees the Linux behaviour, which is what the whole net stack\n     * was written against. A caller that wants non-blocking still asks for\n     * it explicitly through nurl_tcp_set_nonblock. */\n    {\n        int fl = fcntl(c->fd, F_GETFL, 0);\n        if (fl >= 0 && (fl & O_NONBLOCK)) fcntl(c->fd, F_SETFL, fl & ~O_NONBLOCK);\n    }\n    c->nonblock = 0;\n#else\n    /* Winsock always hands the listener's mode on, and the listener is\n     * non-blocking whenever it has a wake channel (see nurl_tcp_listen):\n     * the same normalisation, for the same callers. */\n    {\n        u_long off = 0;\n        ioctlsocket(c->fd, FIONBIO, &off);\n    }\n    c->nonblock = 0;\n#endif\n}\n\n/* ════════════════════════════════════════════════════════════════════\n * TLS transport: INERT STUBS (§8 P4 — libssl/libcrypto removed)\n *\n * ALL TLS — client and server, handshake + record layer + X.509 + the\n * crypto — is now PURE NURL: stdlib/std/tls.nu (client), tls_server.nu\n * (server), and the primitives in stdlib/std/{x25519,p256,chacha,aesgcm,\n * sha256,hkdf,ecdsa_p256,rsa,x509,...}.nu. net.nu drives them directly\n * over plain sockets (nurl_tcp_connect/read/write below) — see\n * tcp_connect_tls / tcp_starttls / tcp_listen_tls in net.nu.\n *\n * These nurl_tcp_*_tls* C functions are no longer reached by any .nu\n * code; the compiler's runtime-FFI prelude still emits `declare`s for a\n * few of them (listen_tls, listen_tls_alpn, alpn_selected,\n * peer_cert_subject), so they survive here as inert stubs that set\n * err_kind = TLS_CTX_INIT / return empty. The OpenSSL implementations\n * (dlopen vtable + SSL_* call sites) were deleted wholesale. The\n * doc-comments below still describe the old OpenSSL semantics for\n * historical context only — the bodies do nothing.\n * ════════════════════════════════════════════════════════════════════ */\n\nlong long nurl_tcp_listen_tls(const char *host, long long port,\n                              long long backlog,\n                              const char *cert_path, const char *key_path) {\n    (void)host; (void)port; (void)backlog;\n    (void)cert_path; (void)key_path;\n    NurlTcp *h = nurl__tcp_new_handle(NURL_TCP_KIND_LISTENER);\n    if (!h) return 0;\n    h->err_kind = NURL_NET_ERR_TLS_CTX_INIT;\n    return (long long)(uintptr_t)h;\n}\n\n/* Client-side TLS connect — plain connect + client handshake. Resulting\n * CONN handle carries ssl/ssl_ctx so read/write/close dispatch via\n * libssl. verify != 0 → peer-cert + hostname verification against the\n * system trust store; verify == 0 still encrypts but skips verification\n * (the MQTT `--insecure` choice). SNI is always sent. */\nlong long nurl_tcp_connect_tls(const char *host, long long port,\n                               long long verify) {\n    (void)host; (void)port; (void)verify;\n    NurlTcp *h = nurl__tcp_new_handle(NURL_TCP_KIND_CONN);\n    if (!h) return 0;\n    h->err_kind = NURL_NET_ERR_TLS_CTX_INIT;\n    return (long long)(uintptr_t)h;\n}\n\n/* In-place TLS upgrade of an already-connected plaintext CONN handle —\n * the STARTTLS pattern (SMTP RFC 3207, IMAP, POP3, XMPP, FTP). The\n * caller has dialed plaintext with nurl_tcp_connect, exchanged the\n * protocol's pre-TLS greeting (e.g. EHLO + STARTTLS, read the 220), and\n * now drives the client handshake over the SAME fd. On success the\n * handle becomes a TLS conn (read/write dispatch via libssl from here\n * on); on failure the fd is closed and err_kind is set, exactly like a\n * failed nurl_tcp_connect_tls. Returns the same handle (as i64) so the\n * caller re-reads err_kind. `host` drives SNI + (when verify) hostname\n * verification. A no-op error if the handle is already TLS. */\nlong long nurl_tcp_starttls(long long conn, const char *host,\n                            long long verify) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)conn;\n    if (!h) return conn;\n    (void)host; (void)verify;\n    h->err_kind = NURL_NET_ERR_TLS_CTX_INIT;\n    return conn;\n}\n\n/* Client-side TLS connect WITH ALPN (RFC 7301) — required to speak\n * HTTP/2 (\"h2\") to real-world servers, which only switch to h2 when the\n * client offers it via ALPN (RFC 9113 §3.3). Identical to\n * nurl_tcp_connect_tls plus an SSL_set_alpn_protos before the\n * handshake. `alpn_protocols` is a space-separated preference list in\n * the same \"h2 http/1.1\" form the listener API accepts; it's packed to\n * ALPN wire-format (length-prefixed) via nurl__alpn_pack. After the\n * handshake the negotiated protocol is read with nurl_tcp_alpn_selected.\n * Empty/NULL list ⇒ no ALPN sent (behaves like nurl_tcp_connect_tls). */\nlong long nurl_tcp_connect_tls_alpn(const char *host, long long port,\n                                    long long verify,\n                                    const char *alpn_protocols) {\n    (void)host; (void)port; (void)verify; (void)alpn_protocols;\n    NurlTcp *h = nurl__tcp_new_handle(NURL_TCP_KIND_CONN);\n    if (!h) return 0;\n    h->err_kind = NURL_NET_ERR_TLS_CTX_INIT;\n    return (long long)(uintptr_t)h;\n}\n\n/* TLS listener + ALPN. alpn_protocols is a space-separated server-\n * preference list (\"h2 http/1.1\"). Clients that offer no listed\n * protocol still handshake (SSL_TLSEXT_ERR_NOACK) — server treats them\n * as the default (HTTP/1.1). */\nlong long nurl_tcp_listen_tls_alpn(const char *host, long long port,\n                                   long long backlog,\n                                   const char *cert_path, const char *key_path,\n                                   const char *alpn_protocols) {\n    (void)host; (void)port; (void)backlog;\n    (void)cert_path; (void)key_path; (void)alpn_protocols;\n    NurlTcp *h = nurl__tcp_new_handle(NURL_TCP_KIND_LISTENER);\n    if (!h) return 0;\n    h->err_kind = NURL_NET_ERR_TLS_CTX_INIT;\n    return (long long)(uintptr_t)h;\n}\n\n/* Build SSL_CTX with cert+key. NULL + sets h->err_kind on failure. */\n\n/* Register an SNI hostname → cert/key on a TLS listener. The default\n * ctx is used on no-SNI or no-match (RFC 6066 §3 fallback). Idempotent\n * re-adds replace the matching entry; existing conns stay valid because\n * SSL_CTX is refcounted. Returns 0 / NurlNetErr. */\nlong long nurl_tcp_tls_add_sni(long long handle, const char *hostname,\n                               const char *cert_path, const char *key_path) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return NURL_NET_ERR_OTHER;\n    (void)hostname; (void)cert_path; (void)key_path;\n    h->err_kind = NURL_NET_ERR_TLS_CTX_INIT;\n    return NURL_NET_ERR_TLS_CTX_INIT;\n}\n\n/* Live cert reload. hostname NULL/\"\" → swap the default ctx; otherwise\n * swap the matching SNI entry (fail if no match). Old SSL_CTX is freed\n * via SSL_CTX_free; refcounting keeps in-flight conns valid. */\nlong long nurl_tcp_tls_reload(long long handle, const char *hostname,\n                              const char *cert_path, const char *key_path) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return NURL_NET_ERR_OTHER;\n    (void)hostname; (void)cert_path; (void)key_path;\n    h->err_kind = NURL_NET_ERR_TLS_CTX_INIT;\n    return NURL_NET_ERR_TLS_CTX_INIT;\n}\n\n/* Require client-cert auth (mTLS). ca_bundle_path is a PEM file with\n * the trust roots. strict != 0 → mandatory (SSL_VERIFY_FAIL_IF_NO_PEER_CERT);\n * otherwise handshake completes unauth and the app decides via\n * nurl_tcp_peer_cert_subject. */\nlong long nurl_tcp_tls_require_client_cert(long long handle,\n                                            const char *ca_bundle_path,\n                                            long long strict) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return NURL_NET_ERR_OTHER;\n    (void)ca_bundle_path; (void)strict;\n    h->err_kind = NURL_NET_ERR_TLS_CTX_INIT;\n    return NURL_NET_ERR_TLS_CTX_INIT;\n}\n\n/* Peer-cert subject DN (OpenSSL one-line). Owned NUL-terminated string;\n * \"\" when no peer cert or non-TLS. Caller frees via nurl_free. */\nconst char *nurl_tcp_peer_cert_subject(long long handle) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return strdup(\"\");\n    return strdup(\"\");\n}\n\n/* Negotiated ALPN protocol (\"h2\" / \"http/1.1\" / ...). Owned string,\n * \"\" when ALPN wasn't negotiated. Caller frees via nurl_free. */\nconst char *nurl_tcp_alpn_selected(long long handle) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return strdup(\"\");\n    return strdup(\"\");\n}\n\n#ifdef _WIN32\n/* A receive that failed because nurl_tcp_conn_shutdown shut the read side\n * — WSAESHUTDOWN for one that started after it, WSAEINTR for one its\n * CancelIoEx ended — is the EOF a POSIX receive-side shutdown reads. */\nstatic int nurl__win_read_shut_eof(NurlTcp *h, int we) {\n    if (!h->read_shut || (we != WSAESHUTDOWN && we != WSAEINTR &&\n                          we != WSA_OPERATION_ABORTED)) return 0;\n    h->err_kind = NURL_NET_ERR_OK;\n    return 1;\n}\n#endif\n\nlong long nurl_tcp_read(long long handle, const char *buf, long long n) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return -1;\n    if (h->fd == NURL_INVALID_SOCK) {\n        h->err_kind = NURL_NET_ERR_CLOSED;\n        return -1;\n    }\n    if (n <= 0) return 0;\n    if (!buf) {\n        h->err_kind = NURL_NET_ERR_READ;\n        return -1;\n    }\n    if (h->timeo_dirty && !h->nonblock) nurl__tcp_apply_timeo(h);\n#ifdef _WIN32\n    int rd = recv(h->fd, (char*)buf, (n > 0x40000000 ? 0x40000000 : (int)n), 0);\n#else\n    ssize_t rd;\n    do {\n        rd = recv(h->fd, (void*)buf, (size_t)n, 0);\n    } while (rd < 0 && errno == EINTR);\n#endif\n    if (rd > 0) {\n        h->err_kind = NURL_NET_ERR_OK;\n        return (long long)rd;\n    }\n    if (rd == 0) return 0;   /* clean EOF — not an error */\n#ifdef _WIN32\n    int we = WSAGetLastError();\n    if (nurl__win_read_shut_eof(h, we)) return 0;\n    h->err_kind = nurl__net_map_wsa(we, NURL_NET_ERR_READ);\n#else\n    h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_READ);\n#endif\n    return -1;\n}\n\n/* A write budget belongs to the operation, not to each syscall or TLS\n * record. Partial progress must never extend this absolute monotonic bound. */\nextern long long nurl_monotonic_ns(void);\n\nvoid nurl_tcp_set_write_deadline(long long handle, long long ns) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return;\n    h->write_deadline_ns = ns > 0 ? ns : 0;\n    /* Restore the configured idle timeout once a deadline remainder has\n     * actually replaced it in the kernel (see sndtimeo_deadline). */\n    if (h->sndtimeo_deadline) h->timeo_dirty = 1;\n}\n\nlong long nurl_tcp_write_deadline(long long handle) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    return h ? h->write_deadline_ns : 0;\n}\n\n/* Reactor vocabulary: -1 unlimited, 0 expired, positive rounded-up ms.\n * The configured idle timeout may shorten a wait, but never extend a bound. */\nlong long nurl_tcp_write_wait_ms(long long handle) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return 0;\n    long long ms = h->timeout_ms > 0 ? h->timeout_ms : -1;\n    if (h->write_deadline_ns > 0) {\n        long long now = nurl_monotonic_ns();\n        if (now <= 0 || now >= h->write_deadline_ns) {\n            h->err_kind = NURL_NET_ERR_TIMEOUT;\n            return 0;\n        }\n        long long left = (h->write_deadline_ns - now - 1) / 1000000 + 1;\n        if (ms < 0 || left < ms) ms = left;\n    }\n    return ms;\n}\n\n/* Check and configure before EVERY blocking syscall, including EINTR\n * retries. SO_SNDTIMEO never changes the independent read timeout. */\nstatic int nurl__tcp_prepare_write(NurlTcp *h) {\n    if (h->timeo_dirty && !h->nonblock) nurl__tcp_apply_timeo(h);\n    long long ms = nurl_tcp_write_wait_ms((long long)(uintptr_t)h);\n    if (ms == 0) return 0;\n    if (h->write_deadline_ns == 0 || h->nonblock) return 1;\n#ifdef _WIN32\n    DWORD timeout = (DWORD)(ms > 2147483647 ? 2147483647 : ms);\n    if (setsockopt(h->fd, SOL_SOCKET, SO_SNDTIMEO,\n                   (const char*)&timeout, (int)sizeof(timeout)) != 0) {\n        h->err_kind = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_WRITE);\n        return 0;\n    }\n#else\n    struct timeval timeout;\n    timeout.tv_sec = (time_t)(ms / 1000);\n    timeout.tv_usec = (suseconds_t)((ms % 1000) * 1000);\n    if (setsockopt(h->fd, SOL_SOCKET, SO_SNDTIMEO,\n                   &timeout, (socklen_t)sizeof(timeout)) != 0) {\n        h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_WRITE);\n        return 0;\n    }\n#endif\n    h->sndtimeo_deadline = 1;\n    return 1;\n}\n\n/* No repeated SO_SNDTIMEO windows. Return every byte already sent; a\n * caller continuing a short write rechecks the same absolute deadline. */\nstatic long long nurl__tcp_short_send(NurlTcp *h, long long wn, long long total) {\n    if (wn == 0) h->err_kind = NURL_NET_ERR_WRITE;\n#ifdef _WIN32\n    else h->err_kind = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_WRITE);\n#else\n    else h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_WRITE);\n#endif\n    return total > 0 ? total : -1;\n}\n\n/* One nonblocking attempt, preserving the connection's normal blocking\n * mode and read timeout. A caller retains its encoded bytes after EAGAIN. */\nlong long nurl_tcp_write_nowait(long long handle, const char *buf, long long n) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return -1;\n    if (h->fd == NURL_INVALID_SOCK) { h->err_kind = NURL_NET_ERR_CLOSED; return -1; }\n    if (n <= 0) return 0;\n    if (!buf) { h->err_kind = NURL_NET_ERR_WRITE; return -1; }\n    for (;;) {\n        if (nurl_tcp_write_wait_ms(handle) == 0) return -1;\n#ifdef _WIN32\n        u_long on = 1, restore = h->nonblock ? 1 : 0;\n        if (!restore && ioctlsocket(h->fd, FIONBIO, &on) != 0) {\n            h->err_kind = NURL_NET_ERR_WRITE; return -1;\n        }\n        int wn = send(h->fd, buf, (int)(n > 0x40000000 ? 0x40000000 : n), 0);\n        int saved = WSAGetLastError();\n        if (!restore && ioctlsocket(h->fd, FIONBIO, &restore) != 0) {\n            h->err_kind = NURL_NET_ERR_WRITE; return wn > 0 ? wn : -1;\n        }\n        WSASetLastError(saved);\n        if (wn < 0 && saved == WSAEINTR) continue;\n#else\n        int flags = 0;\n# ifdef MSG_NOSIGNAL\n        flags |= MSG_NOSIGNAL;\n# endif\n# ifdef MSG_DONTWAIT\n        flags |= MSG_DONTWAIT;\n# else\n        int original = fcntl(h->fd, F_GETFL, 0);\n        if (original < 0 || fcntl(h->fd, F_SETFL, original | O_NONBLOCK) != 0) {\n            h->err_kind = NURL_NET_ERR_WRITE; return -1;\n        }\n# endif\n        ssize_t wn = send(h->fd, buf, (size_t)n, flags);\n        int saved = errno;\n# ifndef MSG_DONTWAIT\n        if (fcntl(h->fd, F_SETFL, original) != 0) {\n            h->err_kind = NURL_NET_ERR_WRITE; return wn > 0 ? wn : -1;\n        }\n# endif\n        errno = saved;\n        if (wn < 0 && saved == EINTR) continue;\n#endif\n        if (wn <= 0) return nurl__tcp_short_send(h, (long long)wn, 0);\n        h->err_kind = NURL_NET_ERR_OK;\n        return (long long)wn;\n    }\n}\n\n/* One receive attempt; EAGAIN leaves partial TLS ciphertext in the\n * source-layer buffer instead of parking with an unsent output queue. */\nlong long nurl_tcp_read_nowait(long long handle, const char *buf, long long n) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return -1;\n    if (h->fd == NURL_INVALID_SOCK) { h->err_kind = NURL_NET_ERR_CLOSED; return -1; }\n    if (n <= 0) return 0;\n    if (!buf) { h->err_kind = NURL_NET_ERR_READ; return -1; }\n    for (;;) {\n#ifdef _WIN32\n        u_long on = 1, restore = h->nonblock ? 1 : 0;\n        if (!restore && ioctlsocket(h->fd, FIONBIO, &on) != 0) { h->err_kind = NURL_NET_ERR_READ; return -1; }\n        int got = recv(h->fd, (char*)buf, (int)(n > 0x40000000 ? 0x40000000 : n), 0);\n        int saved = WSAGetLastError();\n        if (!restore && ioctlsocket(h->fd, FIONBIO, &restore) != 0) { h->err_kind = NURL_NET_ERR_READ; return got > 0 ? got : -1; }\n        WSASetLastError(saved);\n        if (got < 0 && saved == WSAEINTR) continue;\n#else\n        int flags = 0;\n# ifdef MSG_DONTWAIT\n        flags = MSG_DONTWAIT;\n# else\n        int original = fcntl(h->fd, F_GETFL, 0);\n        if (original < 0 || fcntl(h->fd, F_SETFL, original | O_NONBLOCK) != 0) { h->err_kind = NURL_NET_ERR_READ; return -1; }\n# endif\n        ssize_t got = recv(h->fd, (void*)buf, (size_t)n, flags);\n        int saved = errno;\n# ifndef MSG_DONTWAIT\n        if (fcntl(h->fd, F_SETFL, original) != 0) { h->err_kind = NURL_NET_ERR_READ; return got > 0 ? got : -1; }\n# endif\n        errno = saved;\n        if (got < 0 && saved == EINTR) continue;\n#endif\n        if (got >= 0) { h->err_kind = NURL_NET_ERR_OK; return (long long)got; }\n#ifdef _WIN32\n        if (nurl__win_read_shut_eof(h, saved)) return 0;\n        h->err_kind = nurl__net_map_wsa(saved, NURL_NET_ERR_READ);\n#else\n        h->err_kind = nurl__net_map_errno(saved, NURL_NET_ERR_READ);\n#endif\n        return -1;\n    }\n}\n\n#ifdef _WIN32\n/* Guards each connection's rd_wake and read_shut together, so a wait that\n * starts as the read side is shut either sees the flag or has its signal\n * raised (nurl_tcp_wait_io / nurl_tcp_conn_shutdown). Taken once per wait\n * and once per shutdown. */\nstatic pthread_mutex_t nurl__rd_wake_lock = PTHREAD_MUTEX_INITIALIZER;\n#endif\n\n/* Wait for either read (bit 1) or write (bit 2). EINTR does not restart\n * the timeout; a pending absolute write budget additionally bounds POLLOUT. */\nlong long nurl_tcp_wait_io(long long handle, long long events, long long ms) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h || h->fd == NURL_INVALID_SOCK || !(events & 3)) return -1;\n#ifdef _WIN32\n    /* A read-side shutdown is readable (the read returns EOF), as on\n     * POSIX — but Winsock does not say so, so it is asked here and, for a\n     * wait already under way, signalled through rd_wake. */\n    if (events & 1) {\n        pthread_mutex_lock(&nurl__rd_wake_lock);\n        int shut = h->read_shut;\n        if (!shut && h->rd_wake == NURL_INVALID_SOCK) {\n            nurl_sockfd_t wr;\n            if (nurl__signal_open(&h->rd_wake, &wr) != 0) h->rd_wake = NURL_INVALID_SOCK;\n        }\n        pthread_mutex_unlock(&nurl__rd_wake_lock);\n        if (shut) return 1;\n    }\n#endif\n    long long start = nurl_monotonic_ns();\n    for (;;) {\n        long long wait_ms = ms;\n        if (ms > 0) {\n            long long elapsed = (nurl_monotonic_ns() - start) / 1000000;\n            if (elapsed >= ms) return 0;\n            wait_ms = ms - elapsed;\n        }\n        if (events & 2) {\n            long long remaining = nurl_tcp_write_wait_ms(handle);\n            if (remaining == 0) return 0;\n            if (remaining >= 0 && (wait_ms < 0 || remaining < wait_ms)) wait_ms = remaining;\n        }\n        int bounded = wait_ms < 0 ? -1 : (int)(wait_ms > 2147483647 ? 2147483647 : wait_ms);\n#ifdef _WIN32\n        nurl_pollfd_t fds[2];\n        int nfds = 1;\n        fds[0].fd = h->fd;\n        fds[0].events = (short)(((events & 1) ? POLLIN : 0) | ((events & 2) ? POLLOUT : 0));\n        fds[0].revents = 0;\n        if ((events & 1) && h->rd_wake != NURL_INVALID_SOCK) {\n            fds[1].fd = h->rd_wake;\n            fds[1].events = POLLIN;\n            fds[1].revents = 0;\n            nfds = 2;\n        }\n        int ready = nurl__poll(fds, nfds, bounded);\n        if (ready < 0 && WSAGetLastError() == WSAEINTR) continue;\n        if (ready > 0 && nfds == 2 && fds[1].revents) return 1;   /* read side shut */\n#else\n        struct pollfd fd;\n        fd.fd = h->fd;\n        fd.events = (short)(((events & 1) ? POLLIN : 0) | ((events & 2) ? POLLOUT : 0));\n        fd.revents = 0;\n        int ready = poll(&fd, 1, bounded);\n        if (ready < 0 && errno == EINTR) continue;\n#endif\n        return ready > 0 ? 1 : ready;\n    }\n}\n\nlong long nurl_tcp_write(long long handle, const char *buf, long long n) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return -1;\n    if (h->fd == NURL_INVALID_SOCK) {\n        h->err_kind = NURL_NET_ERR_CLOSED;\n        return -1;\n    }\n    if (n <= 0) return 0;\n    if (!buf) {\n        h->err_kind = NURL_NET_ERR_WRITE;\n        return -1;\n    }\n    long long total = 0;\n    while (total < n) {\n        if (!nurl__tcp_prepare_write(h)) return total > 0 ? total : -1;\n        long long want = n - total;\n#ifdef _WIN32\n        int chunk = (int)(want > 0x40000000 ? 0x40000000 : want);\n        int wn = send(h->fd, buf + total, chunk, 0);\n#else\n        ssize_t wn = send(h->fd, buf + total, (size_t)want,\n#  ifdef MSG_NOSIGNAL\n                      MSG_NOSIGNAL);\n#  else\n                      0);\n#  endif\n        if (wn < 0 && errno == EINTR) continue;\n#endif\n        if (wn <= 0) return nurl__tcp_short_send(h, (long long)wn, total);\n        total += (long long)wn;\n    }\n    h->err_kind = NURL_NET_ERR_OK;\n    return total;\n}\n\n/* Two-segment write: `head` then `body` leave through ONE sendmsg(2) /\n * WSASend call, so a response head and its body do not have to be\n * concatenated into a single buffer first. That concat was the HTTP\n * server's second full copy of every response body (the first being\n * response_set_body_*) — at 1 MB per response it was ~half the CPU\n * gap to hyper, which writes its body slice by reference.\n *\n * Contract is nurl_tcp_write's, over the logical concatenation\n * head‖body: returns the number of bytes written across BOTH segments\n * (head first), which may be short; -1 with err_kind set when nothing\n * was written. Either segment may be empty — with one empty segment\n * this is exactly nurl_tcp_write (same send() call), so a caller that\n * always goes through this entry point pays nothing for the generality. */\nlong long nurl_tcp_write2(long long handle, const char *b1, long long n1,\n                          const char *b2, long long n2) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return -1;\n    if (n1 < 0) n1 = 0;\n    if (n2 < 0) n2 = 0;\n    if (n1 == 0) return nurl_tcp_write(handle, b2, n2);\n    if (n2 == 0) return nurl_tcp_write(handle, b1, n1);\n    if (h->fd == NURL_INVALID_SOCK) {\n        h->err_kind = NURL_NET_ERR_CLOSED;\n        return -1;\n    }\n    if (!b1 || !b2) {\n        h->err_kind = NURL_NET_ERR_WRITE;\n        return -1;\n    }\n    long long n = n1 + n2;\n    long long total = 0;\n    while (total < n) {\n        if (!nurl__tcp_prepare_write(h)) return total > 0 ? total : -1;\n        /* Re-point the vector at whatever is still unsent. */\n#ifdef _WIN32\n        WSABUF bufs[2];\n        DWORD cnt = 0, sent = 0;\n        if (total < n1) {\n            long long r1 = n1 - total;\n            bufs[cnt].buf = (char*)b1 + total;\n            bufs[cnt].len = (ULONG)(r1 > 0x40000000 ? 0x40000000 : r1);\n            cnt++;\n            bufs[cnt].buf = (char*)b2;\n            bufs[cnt].len = (ULONG)(n2 > 0x40000000 ? 0x40000000 : n2);\n            cnt++;\n        } else {\n            long long r2 = n - total;\n            bufs[cnt].buf = (char*)b2 + (total - n1);\n            bufs[cnt].len = (ULONG)(r2 > 0x40000000 ? 0x40000000 : r2);\n            cnt++;\n        }\n        long long wn = WSASend(h->fd, bufs, cnt, &sent, 0, NULL, NULL) == 0\n                     ? (long long)sent : -1;\n#else\n        struct iovec iov[2];\n        int cnt = 0;\n        if (total < n1) {\n            iov[cnt].iov_base = (void*)(b1 + total);\n            iov[cnt].iov_len  = (size_t)(n1 - total);\n            cnt++;\n            iov[cnt].iov_base = (void*)b2;\n            iov[cnt].iov_len  = (size_t)n2;\n            cnt++;\n        } else {\n            iov[cnt].iov_base = (void*)(b2 + (total - n1));\n            iov[cnt].iov_len  = (size_t)(n - total);\n            cnt++;\n        }\n        struct msghdr msg;\n        memset(&msg, 0, sizeof msg);\n        msg.msg_iov    = iov;\n        msg.msg_iovlen = cnt;\n        ssize_t wn = sendmsg(h->fd, &msg,\n#  ifdef MSG_NOSIGNAL\n                         MSG_NOSIGNAL);\n#  else\n                         0);\n#  endif\n        if (wn < 0 && errno == EINTR) continue;\n#endif\n        if (wn <= 0) return nurl__tcp_short_send(h, (long long)wn, total);\n        total += (long long)wn;\n    }\n    h->err_kind = NURL_NET_ERR_OK;\n    return total;\n}\n\n/* ── kTLS: TLS 1.3 record protection handed to the kernel ──────────\n * std/tls.nu runs the handshake itself, then (opt-in) gives each\n * direction's traffic key to the socket: setsockopt(TCP_ULP, \"tls\") once,\n * then SOL_TLS / TLS_TX or TLS_RX with the TLS 1.3 crypto_info. From then\n * on application data is plain write()/recv() on the socket and the\n * kernel seals and opens the records (sendfile included); a record of\n * another content type goes out with a TLS_SET_RECORD_TYPE cmsg and\n * comes in with TLS_GET_RECORD_TYPE (docs.kernel.org/networking/tls.html).\n *\n * KeyUpdate needs the kernel to take a new key in a direction that is\n * already installed: Linux 6.14 and later (RX pauses with EKEYEXPIRED\n * after the KeyUpdate record until the new key is set). Older kernels\n * are refused here, so the connection keeps its userspace record layer\n * instead of failing on its first KeyUpdate. */\n#if defined(__linux__)\n#include <linux/tls.h>\n#include <sys/utsname.h>\n#ifndef TCP_ULP\n#define TCP_ULP 31\n#endif\n#ifndef SOL_TLS\n#define SOL_TLS 282\n#endif\n\n/* 1 when this kernel can rekey an installed direction (>= 6.14). */\nlong long nurl_ktls_supported(void) {\n    static int cached = -1;\n    if (cached >= 0) return cached;\n    struct utsname u;\n    int ok = 0;\n    if (uname(&u) == 0) {\n        int maj = 0, min = 0;\n        if (sscanf(u.release, \"%d.%d\", &maj, &min) == 2)\n            ok = maj > 6 || (maj == 6 && min >= 14);\n    }\n    cached = ok;\n    return ok;\n}\n\n/* Install one direction: dir 1 = TX (records this end writes), 2 = RX.\n * cipher 1 = AES-128-GCM (16-byte key), 0 = ChaCha20-Poly1305 (32);\n * iv = the 12-byte static iv; seq = the next record's sequence number.\n * The first call attaches the \"tls\" ULP. 0 on success, -1 when the kernel\n * lacks the ULP (module not loaded and not loadable unprivileged) or the\n * cipher — the caller keeps its userspace record layer then. */\nlong long nurl_ktls_set(long long handle, long long dir, long long cipher,\n                        const char *key, const char *iv, long long seq) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h || h->fd == NURL_INVALID_SOCK || !key || !iv) return -1;\n    if (dir != 1 && dir != 2) return -1;\n    if (!nurl_ktls_supported()) return -1;\n    if (setsockopt(h->fd, SOL_TCP, TCP_ULP, \"tls\", 3) != 0 && errno != EEXIST)\n        return -1;\n    unsigned char rec_seq[8];\n    for (int i = 0; i < 8; i++) rec_seq[i] = (unsigned char)((unsigned long long)seq >> (56 - 8 * i));\n    int opt = dir == 1 ? TLS_TX : TLS_RX;\n    int rc;\n    if (cipher == 1) {\n        struct tls12_crypto_info_aes_gcm_128 ci;\n        memset(&ci, 0, sizeof ci);\n        ci.info.version = TLS_1_3_VERSION;\n        ci.info.cipher_type = TLS_CIPHER_AES_GCM_128;\n        memcpy(ci.salt, iv, TLS_CIPHER_AES_GCM_128_SALT_SIZE);\n        memcpy(ci.iv, iv + TLS_CIPHER_AES_GCM_128_SALT_SIZE, TLS_CIPHER_AES_GCM_128_IV_SIZE);\n        memcpy(ci.key, key, TLS_CIPHER_AES_GCM_128_KEY_SIZE);\n        memcpy(ci.rec_seq, rec_seq, 8);\n        rc = setsockopt(h->fd, SOL_TLS, opt, &ci, sizeof ci);\n        explicit_bzero(&ci, sizeof ci);\n    } else if (cipher == 0) {\n        struct tls12_crypto_info_chacha20_poly1305 ci;\n        memset(&ci, 0, sizeof ci);\n        ci.info.version = TLS_1_3_VERSION;\n        ci.info.cipher_type = TLS_CIPHER_CHACHA20_POLY1305;\n        memcpy(ci.iv, iv, TLS_CIPHER_CHACHA20_POLY1305_IV_SIZE);\n        memcpy(ci.key, key, TLS_CIPHER_CHACHA20_POLY1305_KEY_SIZE);\n        memcpy(ci.rec_seq, rec_seq, 8);\n        rc = setsockopt(h->fd, SOL_TLS, opt, &ci, sizeof ci);\n        explicit_bzero(&ci, sizeof ci);\n    } else {\n        return -1;\n    }\n    if (rc != 0) return -1;\n    /* A kTLS socket flushes pending records outside the call that queued\n     * them — in close(), say, after the peer has gone — with that call's\n     * flags, which for sendfile carry no MSG_NOSIGNAL: the SIGPIPE it\n     * raises then lands where no call can mask it, and its default action\n     * ends the process. A process that hands TLS to the kernel therefore\n     * ignores SIGPIPE (write errors come back as EPIPE, as on every\n     * socket write here) — unless the program installed its own handler. */\n    static volatile int sigpipe_set = 0;\n    if (!sigpipe_set) {\n        struct sigaction sa;\n        if (sigaction(SIGPIPE, NULL, &sa) == 0 && sa.sa_handler == SIG_DFL) {\n            sa.sa_handler = SIG_IGN;\n            (void)sigaction(SIGPIPE, &sa, NULL);\n        }\n        sigpipe_set = 1;\n    }\n    if (dir == 2) {\n        /* TLS 1.3 records from a peer that does not pad decrypt straight\n         * into the reader's buffer; a padded one is re-read, not lost. */\n        int one = 1;\n        (void)setsockopt(h->fd, SOL_TLS, TLS_RX_EXPECT_NO_PAD, &one, sizeof one);\n    }\n    return 0;\n}\n\n/* One record of content type `rtype` (an alert, a post-handshake\n * message) through kTLS TX: sendmsg with a TLS_SET_RECORD_TYPE cmsg. The\n * bytes sent, or -1 with err_kind set (EAGAIN → would-block). */\nlong long nurl_ktls_send(long long handle, long long rtype, const char *buf, long long n) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return -1;\n    if (h->fd == NURL_INVALID_SOCK) { h->err_kind = NURL_NET_ERR_CLOSED; return -1; }\n    if (n < 0 || (n > 0 && !buf)) { h->err_kind = NURL_NET_ERR_WRITE; return -1; }\n    char cbuf[CMSG_SPACE(sizeof(unsigned char))];\n    memset(cbuf, 0, sizeof cbuf);\n    struct iovec iov;\n    iov.iov_base = (void*)buf;\n    iov.iov_len  = (size_t)n;\n    struct msghdr msg;\n    memset(&msg, 0, sizeof msg);\n    msg.msg_iov = &iov;\n    msg.msg_iovlen = 1;\n    msg.msg_control = cbuf;\n    msg.msg_controllen = sizeof cbuf;\n    struct cmsghdr *cm = CMSG_FIRSTHDR(&msg);\n    cm->cmsg_level = SOL_TLS;\n    cm->cmsg_type  = TLS_SET_RECORD_TYPE;\n    cm->cmsg_len   = CMSG_LEN(sizeof(unsigned char));\n    *CMSG_DATA(cm) = (unsigned char)rtype;\n    ssize_t wn;\n    do {\n        wn = sendmsg(h->fd, &msg,\n#  ifdef MSG_NOSIGNAL\n                     MSG_NOSIGNAL);\n#  else\n                     0);\n#  endif\n    } while (wn < 0 && errno == EINTR);\n    if (wn < 0) { h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_WRITE); return -1; }\n    h->err_kind = NURL_NET_ERR_OK;\n    return (long long)wn;\n}\n\n/* Read through kTLS RX: up to `n` plaintext bytes of ONE content type\n * (the kernel never merges records of different types into one read);\n * the type is nurl_ktls_rtype's afterwards. `nowait` makes this one\n * call non-blocking whatever the socket's mode. 0 at EOF, -1 with\n * err_kind set (EAGAIN → would-block). */\nlong long nurl_ktls_recv(long long handle, const char *buf, long long n, long long nowait) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return -1;\n    if (h->fd == NURL_INVALID_SOCK) { h->err_kind = NURL_NET_ERR_CLOSED; return -1; }\n    if (n <= 0) return 0;\n    if (!buf) { h->err_kind = NURL_NET_ERR_READ; return -1; }\n    if (h->timeo_dirty && !h->nonblock) nurl__tcp_apply_timeo(h);\n    char cbuf[CMSG_SPACE(sizeof(unsigned char))];\n    struct iovec iov;\n    iov.iov_base = (void*)buf;\n    iov.iov_len  = (size_t)n;\n    struct msghdr msg;\n    memset(&msg, 0, sizeof msg);\n    msg.msg_iov = &iov;\n    msg.msg_iovlen = 1;\n    msg.msg_control = cbuf;\n    msg.msg_controllen = sizeof cbuf;\n    ssize_t rd;\n    do {\n        rd = recvmsg(h->fd, &msg, nowait ? MSG_DONTWAIT : 0);\n    } while (rd < 0 && errno == EINTR);\n    if (rd < 0) { h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_READ); return -1; }\n    h->ktls_rtype = 23;\n    for (struct cmsghdr *cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm))\n        if (cm->cmsg_level == SOL_TLS && cm->cmsg_type == TLS_GET_RECORD_TYPE)\n            h->ktls_rtype = *CMSG_DATA(cm);\n    h->err_kind = NURL_NET_ERR_OK;\n    return (long long)rd;\n}\n#else\nlong long nurl_ktls_supported(void) { return 0; }\nlong long nurl_ktls_set(long long handle, long long dir, long long cipher,\n                        const char *key, const char *iv, long long seq) {\n    (void)handle; (void)dir; (void)cipher; (void)key; (void)iv; (void)seq; return -1;\n}\nlong long nurl_ktls_send(long long handle, long long rtype, const char *buf, long long n) {\n    (void)handle; (void)rtype; (void)buf; (void)n; return -1;\n}\nlong long nurl_ktls_recv(long long handle, const char *buf, long long n, long long nowait) {\n    (void)handle; (void)buf; (void)n; (void)nowait; return -1;\n}\n#endif\n\n/* Up to `n` bytes of file descriptor `fd` from offset `off` to the\n * connection, copied by the kernel from the page cache (sendfile(2)) —\n * on a kTLS socket the kernel seals the records as it sends. One call,\n * as far as the socket takes: the bytes sent (0 at the end of the\n * file), -1 with err_kind set (EAGAIN → would-block), or -2 where this\n * platform or this file cannot be sent this way — the caller reads and\n * writes instead. */\n#if defined(__linux__)\n#include <sys/sendfile.h>\nlong long nurl_tcp_sendfile(long long handle, long long fd, long long off, long long n) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return -1;\n    if (h->fd == NURL_INVALID_SOCK) { h->err_kind = NURL_NET_ERR_CLOSED; return -1; }\n    if (n <= 0) return 0;\n    if (!nurl__tcp_prepare_write(h)) return -1;\n    off_t o = (off_t)off;\n    ssize_t wn;\n    /* sendfile has no MSG_NOSIGNAL: a peer that has gone raises SIGPIPE,\n     * whose default action ends the process — every other write here\n     * passes MSG_NOSIGNAL. Block it for the call, and consume one the\n     * call raised (only if none was pending before, so another cause's\n     * signal is left alone). */\n    sigset_t pipe_set, old_mask, pending;\n    sigemptyset(&pipe_set);\n    sigaddset(&pipe_set, SIGPIPE);\n    pthread_sigmask(SIG_BLOCK, &pipe_set, &old_mask);\n    sigemptyset(&pending);\n    sigpending(&pending);\n    int had_pipe = sigismember(&pending, SIGPIPE);\n    do {\n        wn = sendfile(h->fd, (int)fd, &o, (size_t)(n > 0x7ffff000 ? 0x7ffff000 : n));\n    } while (wn < 0 && errno == EINTR);\n    int saved = errno;\n    /* Whatever the return: on a kTLS socket the signal can be raised by\n     * a call that still reports bytes sent. */\n    if (!had_pipe) {\n        sigemptyset(&pending);\n        sigpending(&pending);\n        if (sigismember(&pending, SIGPIPE)) {\n            struct timespec zero = { 0, 0 };\n            while (sigtimedwait(&pipe_set, NULL, &zero) < 0 && errno == EINTR) {}\n        }\n    }\n    pthread_sigmask(SIG_SETMASK, &old_mask, NULL);\n    errno = saved;\n    if (wn < 0) {\n        if (errno == EINVAL || errno == ENOSYS || errno == EOPNOTSUPP) return -2;\n        h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_WRITE);\n        return -1;\n    }\n    h->err_kind = NURL_NET_ERR_OK;\n    return (long long)wn;\n}\n#else\nlong long nurl_tcp_sendfile(long long handle, long long fd, long long off, long long n) {\n    (void)handle; (void)fd; (void)off; (void)n; return -2;\n}\n#endif\n\n/* The content type of the record nurl_ktls_recv last read. */\nlong long nurl_ktls_rtype(long long handle) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    return h ? h->ktls_rtype : 0;\n}\n\n/* Async-runtime hooks — expose fd for reactor registration; toggle\n * O_NONBLOCK. Blocking accept/read/write keep working (NURL async\n * wrappers loop on the kernel's EAGAIN). */\nlong long nurl_tcp_get_fd(long long handle) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return -1;\n    return (long long)h->fd;\n}\n\nvoid nurl_tcp_set_nonblock(long long handle, long long on) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h || h->fd == NURL_INVALID_SOCK) return;\n    /* Memoized on h->nonblock — every mutation of the fd's O_NONBLOCK\n     * goes through here (or through the accept path, which normalises\n     * the flag AND the cache together), so a matching cache means the\n     * kernel flag already agrees. The async net wrappers call this at\n     * the top of EVERY read/write; without the memo that was 2 fcntl\n     * syscalls per operation = 4 per HTTP request, 46% of the async\n     * server's syscall time. */\n    if (h->nonblock == (on ? 1 : 0)) return;\n#if defined(_WIN32)\n    u_long mode = on ? 1 : 0;\n    ioctlsocket(h->fd, FIONBIO, &mode);\n#else\n    int fl = fcntl(h->fd, F_GETFL, 0);\n    if (fl < 0) return;\n    if (on) fl |=  O_NONBLOCK;\n    else    fl &= ~O_NONBLOCK;\n    fcntl(h->fd, F_SETFL, fl);\n#endif\n    h->nonblock = on ? 1 : 0;\n}\n\n/* Full teardown — the destructor reached when the last reference drops.\n * Conn closes run this immediately (refcount 1 → 0); listeners that an\n * async accept fiber still references defer it until that fiber's ref is\n * released (see nurl_tcp_close / nurl_tcp_ref). */\nstatic void nurl__tcp_destroy(NurlTcp *h) {\n    if (h->fd != NURL_INVALID_SOCK) {\n#ifdef _WIN32\n        if (h->read_shut) nurl__reactor_forget_fd((long long)h->fd);\n        if (h->rd_wake != NURL_INVALID_SOCK) {\n            nurl__signal_close(h->rd_wake, h->rd_wake);\n            h->rd_wake = NURL_INVALID_SOCK;\n        }\n#endif\n        nurl_close_sock(h->fd);\n        h->fd = NURL_INVALID_SOCK;\n    }\n    nurl__signal_close(h->wake_rfd, h->wake_wfd);\n    h->wake_rfd = h->wake_wfd = NURL_INVALID_SOCK;\n    free(h->peer);\n    free(h);\n}\n\n/* Take an extra reference so the handle survives past a concurrent\n * nurl_tcp_close. server_run_async uses this: a parked accept fiber holds\n * the listener while another thread may call server_stop. */\nvoid nurl_tcp_ref(long long handle) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return;\n    __atomic_fetch_add(&h->refcount, 1, __ATOMIC_SEQ_CST);\n}\n\n/* Drop a reference taken with nurl_tcp_ref; the last drop destroys. */\nvoid nurl_tcp_unref(long long handle) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return;\n    if (__atomic_sub_fetch(&h->refcount, 1, __ATOMIC_SEQ_CST) == 0)\n        nurl__tcp_destroy(h);\n}\n\n/* Stop a listener: every accept in progress returns, every later one\n * fails, and the fd is closed — but never under an accept that may still\n * read it, which would then accept on a closed or a reused descriptor.\n * The struct stays: whatever still names it (the NURL handle's copies, a\n * retaining accept fiber) reads a stopped listener, not freed memory.\n * Idempotent, from any thread.\n *\n * The wake signal (never drained) makes every current and future accept\n * poll return, so the accepts in progress leave at once; one that starts\n * now sees `shutting_down` before it reads the fd (both sides are\n * sequentially consistent: either this sees its count, or it sees the\n * flag). Then nothing reads the fd, and it is closed. Without a wake\n * channel (it could not be opened at listen) it is closed at once, as\n * before. */\nstatic void nurl__tcp_listener_stop(NurlTcp *h) {\n    __atomic_store_n(&h->shutting_down, 1, __ATOMIC_SEQ_CST);\n    if (h->wake_wfd != NURL_INVALID_SOCK) {\n        nurl__signal_raise(h->wake_rfd, h->wake_wfd);\n        while (__atomic_load_n(&h->accepting, __ATOMIC_SEQ_CST) > 0) {\n#ifdef _WIN32\n            SwitchToThread();\n#else\n            sched_yield();\n#endif\n        }\n    }\n    nurl_sockfd_t fd = __atomic_exchange_n(&h->fd, NURL_INVALID_SOCK, __ATOMIC_SEQ_CST);\n    if (fd != NURL_INVALID_SOCK) nurl_close_sock(fd);\n    /* …and a fiber parked on it in the reactor wakes (its accept fails). */\n    nurl__reactor_wake_if_started();\n}\n\nvoid nurl_tcp_close(long long handle) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return;\n    /* A LISTENER stops now, even if the struct free is deferred (an async\n     * accept fiber may still hold a ref): it resumes, sees the listener\n     * stopped, and exits — at which point its ref drop runs the\n     * destructor. Conns take the plain release path (refcount 1 → 0 →\n     * destroy), preserving the exact SSL_shutdown-before-fd-close\n     * ordering inside nurl__tcp_destroy. */\n    if (h->kind == NURL_TCP_KIND_LISTENER) nurl__tcp_listener_stop(h);\n    nurl_tcp_unref(handle);\n}\n\n/* Stop a listener (nurl__tcp_listener_stop) but KEEP the struct and this\n * handle's reference to it: server_run_pool's shutdown thread stops the\n * listener its workers are blocked accepting on, and they then read\n * h->err_kind on exit; tcp_close_listener stops one every copy of the\n * NURL handle still names. The last owner's nurl_tcp_close frees it. */\nvoid nurl_tcp_shutdown(long long handle) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h) return;\n    if (h->kind == NURL_TCP_KIND_LISTENER) { nurl__tcp_listener_stop(h); return; }\n    /* A connection (packages/nwasm's guest `tcp_shutdown`): its fd closes\n     * now, the struct with its owner's nurl_tcp_close. */\n    nurl_sockfd_t fd = __atomic_exchange_n(&h->fd, NURL_INVALID_SOCK, __ATOMIC_SEQ_CST);\n    if (fd != NURL_INVALID_SOCK) nurl_close_sock(fd);\n    nurl__reactor_wake_if_started();\n}\n\n/* Shut a CONNECTION down without closing its fd — its read side, or\n * (`both`) both ways: the HTTP server's graceful drain (ext/http_server.nu)\n * ends an idle keep-alive connection, or one past the drain deadline,\n * from another thread. A thread blocked in recv on it returns EOF (and\n * one in send an error, when `both`), a fiber parked on it wakes (the\n * reactor sees the hang-up), and the fd itself is still released by its\n * owner's nurl_tcp_close — closing it here would race a concurrent recv\n * and fd reuse, as for the listener above. Winsock does not reliably wake\n * a recv for a receive-only shutdown, so Windows always shuts both. */\nvoid nurl_tcp_conn_shutdown(long long handle, long long both) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h || h->fd == NURL_INVALID_SOCK) return;\n#ifdef _WIN32\n    /* Measured on Winsock: a receive-side shutdown wakes neither a thread\n     * blocked in recv nor one in select/WSAPoll, and a recv issued after\n     * it fails with WSAESHUTDOWN. So, after shutting the side, each kind of\n     * waiter is woken its own way, every one of them sticky against a wait\n     * that has not started yet:\n     *   - in recv: CancelIoEx ends it (the write side keeps working, which\n     *     is what lets an HTTP/2 connection send its GOAWAY on the way\n     *     out); a recv that starts later fails at once;\n     *   - in nurl_tcp_wait_io: rd_wake is raised, and a wait that starts\n     *     later sees read_shut — the two under one lock;\n     *   - a parked fiber: the reactor (nurl__reactor_read_shut).\n     * The reads turn those failures into the EOF POSIX gives\n     * (nurl__win_read_shut_eof). CancelIoEx cancels everything pending on\n     * the handle, a blocked send too: the drain shuts a connection's read\n     * side only while it is idle, with nothing being sent. */\n    shutdown(h->fd, both ? SD_BOTH : SD_RECEIVE);\n    pthread_mutex_lock(&nurl__rd_wake_lock);\n    h->read_shut = 1;\n    if (h->rd_wake != NURL_INVALID_SOCK) nurl__signal_raise(h->rd_wake, h->rd_wake);\n    pthread_mutex_unlock(&nurl__rd_wake_lock);\n    CancelIoEx((HANDLE)h->fd, NULL);\n    nurl__reactor_read_shut((long long)h->fd);\n#else\n    shutdown(h->fd, both ? SHUT_RDWR : SHUT_RD);\n#endif\n    nurl__reactor_wake_if_started();\n}\n\nlong long nurl_tcp_err_kind(long long handle) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    return h ? h->err_kind : NURL_NET_ERR_OTHER;\n}\n\nconst char *nurl_tcp_peer_addr(long long handle) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h || !h->peer) return \"\";\n    return h->peer;\n}\n\n/* Defined with the UDP block below (§18b) — one formatter for both\n * families and both protocols, rather than a TCP-shaped copy. */\nstatic char *nurl__net_format_sockaddr(const struct sockaddr *sa,\n                                       socklen_t salen);\n\n/* Heap \"ip:port\" of the locally-bound endpoint of a listener or conn.\n * Caller frees via nurl_free; empty string on error. This is what makes\n * `nurl_tcp_listen(host, 0, …)` usable: the ephemeral port the kernel\n * chose is otherwise unknowable to the process that asked for it.\n * Mirrors nurl_udp_local_addr exactly, including the ownership rule. */\nchar *nurl_tcp_local_addr(long long handle) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h || h->fd == NURL_INVALID_SOCK) return strdup(\"\");\n    struct sockaddr_storage sa;\n    memset(&sa, 0, sizeof(sa));\n#ifdef _WIN32\n    int salen = (int)sizeof(sa);\n#else\n    socklen_t salen = (socklen_t)sizeof(sa);\n#endif\n    if (getsockname(h->fd, (struct sockaddr*)&sa, &salen) != 0) {\n        return strdup(\"\");\n    }\n    char *out = nurl__net_format_sockaddr((struct sockaddr*)&sa,\n                                          (socklen_t)salen);\n    return out ? out : strdup(\"\");\n}\n\n/* The deadline a read on this socket should honour, in ms; 0 = wait for\n * ever. `std/net.nu`'s fiber path asks, because a parked fiber does not\n * go through SO_RCVTIMEO and would otherwise wait for ever on a socket\n * whose owner asked for 150 ms. */\nlong long nurl_tcp_timeout_ms(long long handle) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    return h ? h->timeout_ms : 0;\n}\n\n/* Push the stored timeout to the kernel socket options. Only a BLOCKING\n * read/write needs SO_RCVTIMEO/SO_SNDTIMEO (a non-blocking socket\n * returns EAGAIN regardless and the fiber path parks on the reactor\n * with timeout_ms), so nurl_tcp_set_timeout defers this until the first\n * blocking operation actually happens — see timeo_dirty. */\nstatic void nurl__tcp_apply_timeo(NurlTcp *h) {\n    long long ms = h->timeout_ms;\n    h->timeo_dirty = 0;\n    h->sndtimeo_deadline = 0;\n#ifdef _WIN32\n    /* Win32 SO_RCVTIMEO is a DWORD of ms (not a timeval). */\n    DWORD tv = (ms > 0) ? (DWORD)ms : 0;\n    setsockopt(h->fd, SOL_SOCKET, SO_RCVTIMEO,\n               (const char*)&tv, (int)sizeof(tv));\n    setsockopt(h->fd, SOL_SOCKET, SO_SNDTIMEO,\n               (const char*)&tv, (int)sizeof(tv));\n#else\n    struct timeval tv;\n    if (ms > 0) {\n        tv.tv_sec  = (time_t)(ms / 1000);\n        tv.tv_usec = (suseconds_t)((ms % 1000) * 1000);\n    } else {\n        tv.tv_sec  = 0;\n        tv.tv_usec = 0;\n    }\n    setsockopt(h->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, (socklen_t)sizeof(tv));\n    setsockopt(h->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, (socklen_t)sizeof(tv));\n#endif\n}\n\nvoid nurl_tcp_set_timeout(long long handle, long long ms) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h || h->fd == NURL_INVALID_SOCK) return;\n    h->timeout_ms = ms > 0 ? ms : 0;\n    h->timeo_dirty = 1;\n}\n\n/* Return the NetErr ABI code directly (zero on success), so even a\n * provider without native socket options can report an explicit refusal.\n * The kernel may clamp or enlarge the requested buffer. Keep SOCKET,\n * int and socklen_t widths on this side of the portable NURL ABI. */\nlong long nurl_tcp_set_send_buffer(long long handle, long long bytes) {\n    NurlTcp *h = (NurlTcp*)(uintptr_t)handle;\n    if (!h || h->fd == NURL_INVALID_SOCK) return NURL_NET_ERR_CLOSED;\n    if (bytes <= 0 || bytes > INT_MAX) return NURL_NET_ERR_OTHER;\n    int size = (int)bytes;\n    if (setsockopt(h->fd, SOL_SOCKET, SO_SNDBUF,\n                   (const char*)&size, (socklen_t)sizeof(size)) != 0) {\n#ifdef _WIN32\n        return nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_OTHER);\n#else\n        return nurl__net_map_errno(errno, NURL_NET_ERR_OTHER);\n#endif\n    }\n    return NURL_NET_ERR_OK;\n}\n\n/* ── §18b  UDP sockets (dual-stack IPv4/IPv6 + multicast) ──────── */\n/*\n * Datagram counterpart to §18. Each NurlUdp owns one socket plus a heap\n * \"ip:port\" peer-address string that gets refreshed on the most recent\n * recv_from / connect. NURL_NET_ERR_* codes are shared with §18 so the\n * NURL `NetErr` enum classifies UDP failures the same way.\n *\n * Dual-stack: udp_bind(\"\", port) creates an AF_INET6 socket with\n * IPV6_V6ONLY=0 so a single fd serves IPv4 + IPv6 traffic. Literal\n * binds honour the literal's family (so udp_bind(\"127.0.0.1\", port)\n * is IPv4-only on purpose).\n *\n * Multicast: udp_join_group / _leave_group dispatch on the group's\n * address family. The `iface` argument is an IPv4 literal of the\n * desired interface IP (v4 path) or a numeric ifindex string (v6\n * path); empty string ⇒ the default interface (INADDR_ANY / 0). We\n * deliberately do NOT call if_nametoindex to keep Win32 builds free\n * of the -liphlpapi link dep.\n */\n\ntypedef struct NurlUdp {\n    nurl_sockfd_t fd;\n    long long     err_kind;\n    int           family;     /* AF_INET / AF_INET6 / AF_UNSPEC */\n    int           nonblock;   /* cache of the fd's O_NONBLOCK — see\n                                 nurl_udp_set_nonblock's memo note */\n    char         *peer;       /* owned \"ip:port\" of last peer; NULL if none */\n} NurlUdp;\n\nstatic NurlUdp *nurl__udp_new_handle(void) {\n    NurlUdp *h = (NurlUdp*)calloc(1, sizeof(NurlUdp));\n    if (!h) return NULL;\n    h->fd       = NURL_INVALID_SOCK;\n    h->err_kind = NURL_NET_ERR_OTHER;\n    h->family   = AF_UNSPEC;\n    h->peer     = NULL;\n    return h;\n}\n\n/* sockaddr → owned \"ip:port\" (IPv4) or \"[ip]:port\" (IPv6). NULL on OOM.\n * Uses getnameinfo with NI_NUMERICHOST so no DNS lookup happens. */\nstatic char *nurl__net_format_sockaddr(const struct sockaddr *sa,\n                                       socklen_t salen) {\n    char host[INET6_ADDRSTRLEN + 4] = {0};\n    char port[16] = {0};\n    if (!sa || salen == 0) return strdup(\"\");\n    if (getnameinfo(sa, salen, host, sizeof(host), port, sizeof(port),\n                    NI_NUMERICHOST | NI_NUMERICSERV) != 0) {\n        return strdup(\"\");\n    }\n    size_t need = strlen(host) + strlen(port) + 8;\n    char *out = (char*)malloc(need);\n    if (!out) return NULL;\n    if (sa->sa_family == AF_INET6) {\n        snprintf(out, need, \"[%s]:%s\", host, port);\n    } else {\n        snprintf(out, need, \"%s:%s\", host, port);\n    }\n    return out;\n}\n\nlong long nurl_udp_bind(const char *host, long long port) {\n#ifdef _WIN32\n    if (!nurl__net_wsa_init()) {\n        NurlUdp *h = nurl__udp_new_handle();\n        if (!h) return 0;\n        h->err_kind = NURL_NET_ERR_OTHER;\n        return (long long)(uintptr_t)h;\n    }\n#endif\n    NurlUdp *h = nurl__udp_new_handle();\n    if (!h) return 0;\n    if (port < 0 || port > 65535) {\n        h->err_kind = NURL_NET_ERR_BIND;\n        return (long long)(uintptr_t)h;\n    }\n    char portstr[16];\n    snprintf(portstr, sizeof(portstr), \"%ld\", (long)port);\n\n    struct addrinfo hints, *res = NULL, *ai;\n    memset(&hints, 0, sizeof(hints));\n    hints.ai_family   = AF_UNSPEC;\n    hints.ai_socktype = SOCK_DGRAM;\n    hints.ai_protocol = IPPROTO_UDP;\n    const char *node;\n    if (host && *host) {\n        node = host;\n        /* Numeric literal? Avoid a DNS round-trip for the common case. */\n        hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;\n        if (getaddrinfo(node, portstr, &hints, &res) != 0 || !res) {\n            /* Not numeric — retry as a hostname (e.g. \"localhost\"). */\n            res = NULL;\n            hints.ai_flags = 0;\n            if (getaddrinfo(node, portstr, &hints, &res) != 0 || !res) {\n                h->err_kind = NURL_NET_ERR_BIND;\n                return (long long)(uintptr_t)h;\n            }\n        }\n    } else {\n        node = NULL;\n        /* Prefer IPv6 dual-stack for the wildcard bind. */\n        hints.ai_family = AF_INET6;\n        hints.ai_flags  = AI_PASSIVE | AI_NUMERICSERV;\n        if (getaddrinfo(node, portstr, &hints, &res) != 0 || !res) {\n            /* Stack lacks IPv6 → fall back to IPv4 wildcard. */\n            res = NULL;\n            hints.ai_family = AF_INET;\n            if (getaddrinfo(node, portstr, &hints, &res) != 0 || !res) {\n                h->err_kind = NURL_NET_ERR_BIND;\n                return (long long)(uintptr_t)h;\n            }\n        }\n    }\n\n    nurl_sockfd_t fd = NURL_INVALID_SOCK;\n    int chosen_family = AF_UNSPEC;\n    long long last_err = NURL_NET_ERR_BIND;\n    for (ai = res; ai; ai = ai->ai_next) {\n        fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);\n        if (fd == NURL_INVALID_SOCK && nurl__fd_reclaim()) fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);\n        if (fd == NURL_INVALID_SOCK) continue;\n        /* No SO_REUSEADDR (nurl__sock_restart_ok): it is for TCP's\n         * TIME_WAIT, which UDP does not have, and on a UDP socket it lets\n         * sockets share a port — an ephemeral `udp_bind host 0` was handed\n         * a port another socket held, and datagrams sent to \"its\" port\n         * went to the other (one in five of 300 binds), and any program\n         * could bind a server's port beside it. */\n        /* Dual-stack wildcard: ask the kernel to also serve IPv4. */\n        if (ai->ai_family == AF_INET6 && (!host || !*host)) {\n            int off = 0;\n            setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY,\n                       (const char*)&off, (int)sizeof(off));\n        }\n        if (bind(fd, ai->ai_addr, (int)ai->ai_addrlen) == 0) {\n            chosen_family = ai->ai_family;\n            break;\n        }\n#ifdef _WIN32\n        last_err = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_BIND);\n#else\n        last_err = nurl__net_map_errno(errno, NURL_NET_ERR_BIND);\n#endif\n        nurl_close_sock(fd);\n        fd = NURL_INVALID_SOCK;\n    }\n    freeaddrinfo(res);\n\n    /* A resolver can hand out the IPv6 wildcard on a host whose kernel\n     * has no IPv6 at all (a container with it disabled: socket() fails\n     * with EAFNOSUPPORT). The wildcard then means the IPv4 one. */\n    if (fd == NURL_INVALID_SOCK && (!host || !*host) &&\n        hints.ai_family == AF_INET6) {\n        struct addrinfo h4;\n        memset(&h4, 0, sizeof(h4));\n        h4.ai_family   = AF_INET;\n        h4.ai_socktype = SOCK_DGRAM;\n        h4.ai_protocol = IPPROTO_UDP;\n        h4.ai_flags    = AI_PASSIVE | AI_NUMERICSERV;\n        res = NULL;\n        if (getaddrinfo(NULL, portstr, &h4, &res) == 0 && res) {\n            for (ai = res; ai; ai = ai->ai_next) {\n                fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);\n                if (fd == NURL_INVALID_SOCK && nurl__fd_reclaim()) fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);\n                if (fd == NURL_INVALID_SOCK) continue;\n                /* (no SO_REUSEADDR: see above) */\n                if (bind(fd, ai->ai_addr, (int)ai->ai_addrlen) == 0) {\n                    chosen_family = ai->ai_family;\n                    break;\n                }\n#ifdef _WIN32\n                last_err = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_BIND);\n#else\n                last_err = nurl__net_map_errno(errno, NURL_NET_ERR_BIND);\n#endif\n                nurl_close_sock(fd);\n                fd = NURL_INVALID_SOCK;\n            }\n            freeaddrinfo(res);\n        }\n    }\n\n    if (fd == NURL_INVALID_SOCK) {\n        h->err_kind = last_err;\n        return (long long)(uintptr_t)h;\n    }\n    h->fd       = fd;\n    h->family   = chosen_family;\n    h->err_kind = NURL_NET_ERR_OK;\n    return (long long)(uintptr_t)h;\n}\n\nNURL_TLS_FN long long nurl_udp_connect(long long handle,\n                           const char *host, long long port) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h) return -1;\n    if (h->fd == NURL_INVALID_SOCK) {\n        h->err_kind = NURL_NET_ERR_CLOSED;\n        return -1;\n    }\n    if (!host || !*host || port < 0 || port > 65535) {\n        h->err_kind = NURL_NET_ERR_OTHER;\n        return -1;\n    }\n    char portstr[16];\n    snprintf(portstr, sizeof(portstr), \"%ld\", (long)port);\n    struct addrinfo hints, *res = NULL;\n    memset(&hints, 0, sizeof(hints));\n    hints.ai_family   = AF_UNSPEC;\n    hints.ai_socktype = SOCK_DGRAM;\n    if (nurl__resolve(host, portstr, &hints, &res, -1) != 0 || !res) {\n        if (res) freeaddrinfo(res);\n        h->err_kind = NURL_NET_ERR_OTHER;\n        return -1;\n    }\n    int rc = connect(h->fd, res->ai_addr, (int)res->ai_addrlen);\n    if (rc == 0) {\n        free(h->peer);\n        h->peer = nurl__net_format_sockaddr(res->ai_addr,\n                                            (socklen_t)res->ai_addrlen);\n    }\n    freeaddrinfo(res);\n    if (rc != 0) {\n#ifdef _WIN32\n        h->err_kind = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_OTHER);\n#else\n        h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_OTHER);\n#endif\n        return -1;\n    }\n    h->err_kind = NURL_NET_ERR_OK;\n    return 0;\n}\n\nNURL_TLS_FN long long nurl_udp_send_to(long long handle, const char *buf, long long n,\n                           const char *host, long long port) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h) return -1;\n    if (h->fd == NURL_INVALID_SOCK) {\n        h->err_kind = NURL_NET_ERR_CLOSED;\n        return -1;\n    }\n    if (n < 0 || (n > 0 && !buf) || !host || !*host ||\n        port < 0 || port > 65535) {\n        h->err_kind = NURL_NET_ERR_WRITE;\n        return -1;\n    }\n    char portstr[16];\n    snprintf(portstr, sizeof(portstr), \"%ld\", (long)port);\n    struct addrinfo hints, *res = NULL;\n    memset(&hints, 0, sizeof(hints));\n    hints.ai_family   = AF_UNSPEC;\n    hints.ai_socktype = SOCK_DGRAM;\n    if (nurl__resolve(host, portstr, &hints, &res, -1) != 0 || !res) {\n        if (res) freeaddrinfo(res);\n        h->err_kind = NURL_NET_ERR_WRITE;\n        return -1;\n    }\n#ifdef _WIN32\n    int sent = sendto(h->fd, buf, (int)n, 0,\n                      res->ai_addr, (int)res->ai_addrlen);\n#else\n    ssize_t sent;\n    do {\n        sent = sendto(h->fd, buf, (size_t)n,\n#  ifdef MSG_NOSIGNAL\n                      MSG_NOSIGNAL,\n#  else\n                      0,\n#  endif\n                      res->ai_addr, (socklen_t)res->ai_addrlen);\n    } while (sent < 0 && errno == EINTR);\n#endif\n    freeaddrinfo(res);\n    if (sent < 0) {\n#ifdef _WIN32\n        h->err_kind = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_WRITE);\n#else\n        h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_WRITE);\n#endif\n        return -1;\n    }\n    h->err_kind = NURL_NET_ERR_OK;\n    return (long long)sent;\n}\n\nlong long nurl_udp_recv_from(long long handle, char *buf, long long n) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h) return -1;\n    if (h->fd == NURL_INVALID_SOCK) {\n        h->err_kind = NURL_NET_ERR_CLOSED;\n        return -1;\n    }\n    if (n <= 0 || !buf) {\n        h->err_kind = NURL_NET_ERR_READ;\n        return -1;\n    }\n    struct sockaddr_storage src;\n    memset(&src, 0, sizeof(src));\n#ifdef _WIN32\n    int srclen = (int)sizeof(src);\n    int rd = recvfrom(h->fd, buf, (int)n, 0,\n                      (struct sockaddr*)&src, &srclen);\n#else\n    socklen_t srclen = (socklen_t)sizeof(src);\n    ssize_t rd;\n    do {\n        rd = recvfrom(h->fd, buf, (size_t)n, 0,\n                      (struct sockaddr*)&src, &srclen);\n    } while (rd < 0 && errno == EINTR);\n#endif\n    if (rd < 0) {\n#ifdef _WIN32\n        h->err_kind = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_READ);\n#else\n        h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_READ);\n#endif\n        return -1;\n    }\n    free(h->peer);\n    h->peer = nurl__net_format_sockaddr((struct sockaddr*)&src,\n                                        (socklen_t)srclen);\n    h->err_kind = NURL_NET_ERR_OK;\n    return (long long)rd;\n}\n\nlong long nurl_udp_send(long long handle, const char *buf, long long n) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h) return -1;\n    if (h->fd == NURL_INVALID_SOCK) {\n        h->err_kind = NURL_NET_ERR_CLOSED;\n        return -1;\n    }\n    if (n < 0 || (n > 0 && !buf)) {\n        h->err_kind = NURL_NET_ERR_WRITE;\n        return -1;\n    }\n#ifdef _WIN32\n    int sent = send(h->fd, buf, (int)n, 0);\n#else\n    ssize_t sent;\n    do {\n        sent = send(h->fd, buf, (size_t)n,\n#  ifdef MSG_NOSIGNAL\n                    MSG_NOSIGNAL\n#  else\n                    0\n#  endif\n                    );\n    } while (sent < 0 && errno == EINTR);\n#endif\n    if (sent < 0) {\n#ifdef _WIN32\n        h->err_kind = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_WRITE);\n#else\n        h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_WRITE);\n#endif\n        return -1;\n    }\n    h->err_kind = NURL_NET_ERR_OK;\n    return (long long)sent;\n}\n\nlong long nurl_udp_recv(long long handle, char *buf, long long n) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h) return -1;\n    if (h->fd == NURL_INVALID_SOCK) {\n        h->err_kind = NURL_NET_ERR_CLOSED;\n        return -1;\n    }\n    if (n <= 0 || !buf) {\n        h->err_kind = NURL_NET_ERR_READ;\n        return -1;\n    }\n#ifdef _WIN32\n    int rd = recv(h->fd, buf, (int)n, 0);\n#else\n    ssize_t rd;\n    do { rd = recv(h->fd, buf, (size_t)n, 0); }\n    while (rd < 0 && errno == EINTR);\n#endif\n    if (rd < 0) {\n#ifdef _WIN32\n        h->err_kind = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_READ);\n#else\n        h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_READ);\n#endif\n        return -1;\n    }\n    h->err_kind = NURL_NET_ERR_OK;\n    return (long long)rd;\n}\n\nconst char *nurl_udp_peer_addr(long long handle) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h || !h->peer) return \"\";\n    return h->peer;\n}\n\n/* Heap \"ip:port\" of the locally-bound endpoint. Caller frees via\n * nurl_free. Empty string on error. */\nchar *nurl_udp_local_addr(long long handle) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h || h->fd == NURL_INVALID_SOCK) return strdup(\"\");\n    struct sockaddr_storage sa;\n    memset(&sa, 0, sizeof(sa));\n#ifdef _WIN32\n    int salen = (int)sizeof(sa);\n#else\n    socklen_t salen = (socklen_t)sizeof(sa);\n#endif\n    if (getsockname(h->fd, (struct sockaddr*)&sa, &salen) != 0) {\n        return strdup(\"\");\n    }\n    char *out = nurl__net_format_sockaddr((struct sockaddr*)&sa,\n                                          (socklen_t)salen);\n    return out ? out : strdup(\"\");\n}\n\nlong long nurl_udp_err_kind(long long handle) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    return h ? h->err_kind : NURL_NET_ERR_OTHER;\n}\n\nlong long nurl_udp_get_fd(long long handle) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h) return -1;\n    return (long long)h->fd;\n}\n\nlong long nurl_udp_family(long long handle) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h) return -1;\n    return (long long)h->family;\n}\n\nvoid nurl_udp_set_nonblock(long long handle, long long on) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h || h->fd == NURL_INVALID_SOCK) return;\n    /* Memoized like nurl_tcp_set_nonblock: the async wrappers call\n     * this per operation; the fd's flag only ever changes through this\n     * function, so a matching cache skips both fcntl syscalls. UDP\n     * sockets are created blocking (nonblock=0 via calloc), matching\n     * the fresh fd. */\n    if (h->nonblock == (on ? 1 : 0)) return;\n#ifdef _WIN32\n    u_long mode = on ? 1 : 0;\n    ioctlsocket(h->fd, FIONBIO, &mode);\n#else\n    int fl = fcntl(h->fd, F_GETFL, 0);\n    if (fl < 0) return;\n    if (on) fl |=  O_NONBLOCK;\n    else    fl &= ~O_NONBLOCK;\n    fcntl(h->fd, F_SETFL, fl);\n#endif\n    h->nonblock = on ? 1 : 0;\n}\n\nvoid nurl_udp_set_timeout(long long handle, long long ms) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h || h->fd == NURL_INVALID_SOCK) return;\n#ifdef _WIN32\n    DWORD tv = (ms > 0) ? (DWORD)ms : 0;\n    setsockopt(h->fd, SOL_SOCKET, SO_RCVTIMEO,\n               (const char*)&tv, (int)sizeof(tv));\n    setsockopt(h->fd, SOL_SOCKET, SO_SNDTIMEO,\n               (const char*)&tv, (int)sizeof(tv));\n#else\n    struct timeval tv;\n    if (ms > 0) {\n        tv.tv_sec  = (time_t)(ms / 1000);\n        tv.tv_usec = (suseconds_t)((ms % 1000) * 1000);\n    } else {\n        tv.tv_sec  = 0;\n        tv.tv_usec = 0;\n    }\n    setsockopt(h->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, (socklen_t)sizeof(tv));\n    setsockopt(h->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, (socklen_t)sizeof(tv));\n#endif\n}\n\nvoid nurl_udp_close(long long handle) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h) return;\n    if (h->fd != NURL_INVALID_SOCK) {\n        nurl_close_sock(h->fd);\n        h->fd = NURL_INVALID_SOCK;\n    }\n    free(h->peer);\n    h->peer = NULL;\n    free(h);\n}\n\n/* Stop a UDP socket now (`udp_close`): close its fd and KEEP the struct.\n * Every copy of the handle still names it — reading it after the close\n * read freed memory — and its last owner releases it (nurl_udp_close, the\n * handle's drop). Idempotent; a later call on it reports NetClosed. */\nvoid nurl_udp_shutdown(long long handle) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h) return;\n    if (h->fd != NURL_INVALID_SOCK) {\n        nurl_close_sock(h->fd);\n        h->fd = NURL_INVALID_SOCK;\n    }\n    h->err_kind = NURL_NET_ERR_CLOSED;\n}\n\nlong long nurl_udp_set_broadcast(long long handle, long long on) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h || h->fd == NURL_INVALID_SOCK) {\n        if (h) h->err_kind = NURL_NET_ERR_CLOSED;\n        return -1;\n    }\n    int v = on ? 1 : 0;\n    if (setsockopt(h->fd, SOL_SOCKET, SO_BROADCAST,\n                   (const char*)&v, (int)sizeof(v)) != 0) {\n#ifdef _WIN32\n        h->err_kind = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_OTHER);\n#else\n        h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_OTHER);\n#endif\n        return -1;\n    }\n    h->err_kind = NURL_NET_ERR_OK;\n    return 0;\n}\n\n/* Resolve a numeric IP literal into its sockaddr_storage form. Used by\n * the multicast helpers so they can branch on family without forcing\n * the caller to pass it separately. Returns 0 on success and writes\n * *out_family + *out_sa + *out_salen; -1 on error. */\nstatic int nurl__parse_numeric_addr(const char *s, int *out_family,\n                                    struct sockaddr_storage *out_sa,\n                                    socklen_t *out_salen) {\n    if (!s || !*s) return -1;\n    struct addrinfo hints, *res = NULL;\n    memset(&hints, 0, sizeof(hints));\n    hints.ai_family = AF_UNSPEC;\n    hints.ai_flags  = AI_NUMERICHOST;\n    if (getaddrinfo(s, NULL, &hints, &res) != 0 || !res) return -1;\n    if (res->ai_addrlen > sizeof(*out_sa)) {\n        freeaddrinfo(res);\n        return -1;\n    }\n    memcpy(out_sa, res->ai_addr, res->ai_addrlen);\n    *out_salen  = (socklen_t)res->ai_addrlen;\n    *out_family = res->ai_family;\n    freeaddrinfo(res);\n    return 0;\n}\n\n/* Portable non-negative decimal parser. Deliberately avoids strtoul/strtol/\n * atoi: on glibc >= 2.38 those resolve (in C23 translation mode) to the\n * versioned symbols __isoc23_strtoul / __isoc23_strtol, which are baked into\n * the shipped, LTO-distributed runtime bitcode and then fail to link against\n * an OLDER-glibc target (e.g. Raspberry Pi OS aarch64: \"undefined symbol:\n * __isoc23_strtoul\"). Parsing digits by hand keeps the runtime libc-only and\n * portable across every glibc/musl version and target. Leading blanks are\n * skipped; *end (if non-NULL) points past the last digit consumed, so callers\n * can detect trailing garbage exactly as strtoul's `end` did. */\nstatic unsigned long nurl__parse_ul(const char *s, const char **end) {\n    while (*s == ' ' || *s == '\\t') s++;\n    unsigned long v = 0;\n    while (*s >= '0' && *s <= '9') { v = v * 10UL + (unsigned long)(*s - '0'); s++; }\n    if (end) *end = s;\n    return v;\n}\n\n/* iface argument convention (intentionally minimal — no if_nametoindex\n * to keep Win32 builds free of -liphlpapi):\n *   \"\"                           default interface (INADDR_ANY / 0)\n *   IPv4 literal \"192.168.1.5\"   that interface's IP (v4 group only)\n *   integer string \"3\"           interface index (v6 group only) */\nstatic long long nurl__udp_mcast_op(long long handle, const char *group,\n                                    const char *iface, int op_v4, int op_v6) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h || h->fd == NURL_INVALID_SOCK || !group || !*group) {\n        if (h) h->err_kind = h ? NURL_NET_ERR_OTHER : 0;\n        return -1;\n    }\n    int gfam = AF_UNSPEC;\n    struct sockaddr_storage gsa;\n    socklen_t gsalen = 0;\n    if (nurl__parse_numeric_addr(group, &gfam, &gsa, &gsalen) != 0) {\n        h->err_kind = NURL_NET_ERR_OTHER;\n        return -1;\n    }\n    int rc;\n    if (gfam == AF_INET) {\n        struct ip_mreq mr;\n        memset(&mr, 0, sizeof(mr));\n        mr.imr_multiaddr = ((struct sockaddr_in*)&gsa)->sin_addr;\n        if (iface && *iface) {\n            int ifam = AF_UNSPEC;\n            struct sockaddr_storage isa;\n            socklen_t isalen = 0;\n            if (nurl__parse_numeric_addr(iface, &ifam, &isa, &isalen) != 0\n                || ifam != AF_INET) {\n                h->err_kind = NURL_NET_ERR_OTHER;\n                return -1;\n            }\n            mr.imr_interface = ((struct sockaddr_in*)&isa)->sin_addr;\n        } else {\n            mr.imr_interface.s_addr = htonl(INADDR_ANY);\n        }\n        rc = setsockopt(h->fd, IPPROTO_IP, op_v4,\n                        (const char*)&mr, (int)sizeof(mr));\n    } else if (gfam == AF_INET6) {\n        struct ipv6_mreq mr;\n        memset(&mr, 0, sizeof(mr));\n        mr.ipv6mr_multiaddr = ((struct sockaddr_in6*)&gsa)->sin6_addr;\n        unsigned ifidx = 0;\n        if (iface && *iface) {\n            const char *end = NULL;\n            unsigned long v = nurl__parse_ul(iface, &end);\n            if (!end || end == iface || *end != 0) {\n                h->err_kind = NURL_NET_ERR_OTHER;\n                return -1;\n            }\n            ifidx = (unsigned)v;\n        }\n        mr.ipv6mr_interface = ifidx;\n        rc = setsockopt(h->fd, IPPROTO_IPV6, op_v6,\n                        (const char*)&mr, (int)sizeof(mr));\n    } else {\n        h->err_kind = NURL_NET_ERR_OTHER;\n        return -1;\n    }\n    if (rc != 0) {\n#ifdef _WIN32\n        h->err_kind = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_OTHER);\n#else\n        h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_OTHER);\n#endif\n        return -1;\n    }\n    h->err_kind = NURL_NET_ERR_OK;\n    return 0;\n}\n\nlong long nurl_udp_join_group(long long handle, const char *group,\n                              const char *iface) {\n    return nurl__udp_mcast_op(handle, group, iface,\n                              IP_ADD_MEMBERSHIP, IPV6_JOIN_GROUP);\n}\n\nlong long nurl_udp_leave_group(long long handle, const char *group,\n                               const char *iface) {\n    return nurl__udp_mcast_op(handle, group, iface,\n                              IP_DROP_MEMBERSHIP, IPV6_LEAVE_GROUP);\n}\n\nlong long nurl_udp_set_multicast_ttl(long long handle, long long ttl) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h || h->fd == NURL_INVALID_SOCK) {\n        if (h) h->err_kind = NURL_NET_ERR_CLOSED;\n        return -1;\n    }\n    if (ttl < 0 || ttl > 255) {\n        h->err_kind = NURL_NET_ERR_OTHER;\n        return -1;\n    }\n    int rc;\n    if (h->family == AF_INET6) {\n        int v = (int)ttl;\n        rc = setsockopt(h->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,\n                        (const char*)&v, (int)sizeof(v));\n    } else {\n        /* IPv4 TTL setsockopt takes an unsigned char on POSIX, int on Win32. */\n#ifdef _WIN32\n        DWORD v = (DWORD)ttl;\n        rc = setsockopt(h->fd, IPPROTO_IP, IP_MULTICAST_TTL,\n                        (const char*)&v, (int)sizeof(v));\n#else\n        unsigned char v = (unsigned char)ttl;\n        rc = setsockopt(h->fd, IPPROTO_IP, IP_MULTICAST_TTL,\n                        (const char*)&v, (int)sizeof(v));\n#endif\n    }\n    if (rc != 0) {\n#ifdef _WIN32\n        h->err_kind = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_OTHER);\n#else\n        h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_OTHER);\n#endif\n        return -1;\n    }\n    h->err_kind = NURL_NET_ERR_OK;\n    return 0;\n}\n\nlong long nurl_udp_set_multicast_loop(long long handle, long long on) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h || h->fd == NURL_INVALID_SOCK) {\n        if (h) h->err_kind = NURL_NET_ERR_CLOSED;\n        return -1;\n    }\n    int rc;\n    if (h->family == AF_INET6) {\n        int v = on ? 1 : 0;\n        rc = setsockopt(h->fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,\n                        (const char*)&v, (int)sizeof(v));\n    } else {\n#ifdef _WIN32\n        DWORD v = on ? 1 : 0;\n        rc = setsockopt(h->fd, IPPROTO_IP, IP_MULTICAST_LOOP,\n                        (const char*)&v, (int)sizeof(v));\n#else\n        unsigned char v = on ? 1 : 0;\n        rc = setsockopt(h->fd, IPPROTO_IP, IP_MULTICAST_LOOP,\n                        (const char*)&v, (int)sizeof(v));\n#endif\n    }\n    if (rc != 0) {\n#ifdef _WIN32\n        h->err_kind = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_OTHER);\n#else\n        h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_OTHER);\n#endif\n        return -1;\n    }\n    h->err_kind = NURL_NET_ERR_OK;\n    return 0;\n}\n\n/* ── §18c  DNS resolution (getaddrinfo + getnameinfo) ──────────── */\n/*\n * Two-shot resolvers handing back newline-separated heap strings the\n * NURL caller splits with `string_split s \"\\n\"`. Empty return means\n * \"no addresses\" (host doesn't resolve, NXDOMAIN, …); the result is\n * always a heap-owned String, never NULL — NURL frees via nurl_free.\n *\n *   nurl_dns_resolve(host)         → \"1.2.3.4\\n2001:db8::1\\n…\" (no port)\n *   nurl_dns_resolve_port(h, port) → \"1.2.3.4:80\\n[2001:db8::1]:80\\n…\"\n *   nurl_dns_reverse(ip)           → \"host.example.com\" or \"\"\n *\n * Deduplicated and order-preserving: getaddrinfo's \"what the kernel\n * picks first\" order survives, but duplicate strings are skipped so a\n * dual-stack host doesn't list 127.0.0.1 four times.\n */\n\n/* Append `line` + '\\n' to *buf (heap, may grow). Returns 0 on success. */\nstatic int nurl__dns_append(char **buf, size_t *len, size_t *cap,\n                            const char *line) {\n    size_t add = strlen(line) + 1;\n    if (*len + add + 1 > *cap) {\n        size_t ncap = (*cap == 0) ? 256 : (*cap * 2);\n        while (ncap < *len + add + 1) ncap *= 2;\n        char *nb = (char*)realloc(*buf, ncap);\n        if (!nb) return -1;\n        *buf = nb;\n        *cap = ncap;\n    }\n    memcpy(*buf + *len, line, add - 1);\n    (*buf)[*len + add - 1] = '\\n';\n    *len += add;\n    (*buf)[*len] = '\\0';\n    return 0;\n}\n\n/* Linear \"have we already emitted this exact line?\" — fine for the\n * handful of A+AAAA records a host typically resolves to. */\nstatic int nurl__dns_seen(const char *buf, size_t len, const char *line) {\n    if (!buf || len == 0) return 0;\n    size_t llen = strlen(line);\n    if (llen == 0) return 0;\n    const char *p = buf;\n    const char *end = buf + len;\n    while (p < end) {\n        const char *nl = (const char*)memchr(p, '\\n', (size_t)(end - p));\n        size_t row = nl ? (size_t)(nl - p) : (size_t)(end - p);\n        if (row == llen && memcmp(p, line, llen) == 0) return 1;\n        if (!nl) break;\n        p = nl + 1;\n    }\n    return 0;\n}\n\nNURL_TLS_FN char *nurl_dns_resolve(const char *host) {\n#ifdef _WIN32\n    if (!nurl__net_wsa_init()) return strdup(\"\");\n#endif\n    if (!host || !*host) return strdup(\"\");\n    struct addrinfo hints, *res = NULL, *ai;\n    memset(&hints, 0, sizeof(hints));\n    hints.ai_family   = AF_UNSPEC;\n    hints.ai_socktype = SOCK_STREAM;   /* dedupe across DGRAM+STREAM duplicates */\n    if (nurl__resolve(host, NULL, &hints, &res, -1) != 0 || !res) {\n        if (res) freeaddrinfo(res);\n        return strdup(\"\");\n    }\n\n    char  *buf = NULL;\n    size_t len = 0, cap = 0;\n    char ipstr[INET6_ADDRSTRLEN + 4];\n    for (ai = res; ai; ai = ai->ai_next) {\n        ipstr[0] = '\\0';\n        if (getnameinfo(ai->ai_addr, (socklen_t)ai->ai_addrlen,\n                        ipstr, sizeof(ipstr), NULL, 0,\n                        NI_NUMERICHOST) != 0) continue;\n        if (!ipstr[0]) continue;\n        if (nurl__dns_seen(buf, len, ipstr)) continue;\n        if (nurl__dns_append(&buf, &len, &cap, ipstr) != 0) {\n            free(buf);\n            freeaddrinfo(res);\n            return strdup(\"\");\n        }\n    }\n    freeaddrinfo(res);\n    return buf ? buf : strdup(\"\");\n}\n\nNURL_TLS_FN char *nurl_dns_resolve_port(const char *host, long long port) {\n#ifdef _WIN32\n    if (!nurl__net_wsa_init()) return strdup(\"\");\n#endif\n    if (!host || !*host || port < 0 || port > 65535) return strdup(\"\");\n    char portstr[16];\n    snprintf(portstr, sizeof(portstr), \"%ld\", (long)port);\n    struct addrinfo hints, *res = NULL, *ai;\n    memset(&hints, 0, sizeof(hints));\n    hints.ai_family   = AF_UNSPEC;\n    hints.ai_socktype = SOCK_STREAM;\n    if (nurl__resolve(host, portstr, &hints, &res, -1) != 0 || !res) {\n        if (res) freeaddrinfo(res);\n        return strdup(\"\");\n    }\n\n    char *buf = NULL;\n    size_t len = 0, cap = 0;\n    for (ai = res; ai; ai = ai->ai_next) {\n        char *one = nurl__net_format_sockaddr(ai->ai_addr,\n                                              (socklen_t)ai->ai_addrlen);\n        if (!one || !*one) { free(one); continue; }\n        if (!nurl__dns_seen(buf, len, one)) {\n            if (nurl__dns_append(&buf, &len, &cap, one) != 0) {\n                free(one);\n                free(buf);\n                freeaddrinfo(res);\n                return strdup(\"\");\n            }\n        }\n        free(one);\n    }\n    freeaddrinfo(res);\n    return buf ? buf : strdup(\"\");\n}\n\n/* ── §18b-ii  Address-carrying UDP: recv_into / send_addr ──────────\n *\n * The §18b entry points above were shaped for request/response tools\n * (STUN, SWIM, DNS): one heap buffer + one \"ip:port\" string per\n * datagram in, one getaddrinfo per datagram out. A datagram-per-packet\n * transport (QUIC) cannot afford any of those three per packet, so\n * this half adds the same operations without them:\n *\n *   nurl_udp_recv_into   — recvfrom into the CALLER's buffer, peer\n *                          address written as a 24-byte NurlUdpAddr\n *                          (no malloc, no getnameinfo, h->peer untouched)\n *   nurl_udp_send_addr   — sendto a NurlUdpAddr (no getaddrinfo)\n *   nurl_udp_addr_resolve — host:port → NurlUdpAddr, once per peer\n *   nurl_udp_addr_format  — NurlUdpAddr → heap \"ip:port\", for logs\n *   nurl_udp_setsockopt_int — the integer setsockopt the reuseport /\n *                          GSO / ECN work needs; level+opt come from\n *                          posix_const so no Linux numbers are baked in\n *\n * NurlUdpAddr is NURL's own encoding, not the kernel's sockaddr: the\n * same 24 bytes mean the same thing on Linux, BSD, Windows, the wasm\n * host bridge and the in-process unikernel stack, so NURL code can\n * compare, hash and store peers as plain bytes.\n *\n *   [0]      family: 4 or 6 (0 = empty)\n *   [1]      reserved, 0\n *   [2..4)   port, big-endian\n *   [4..20)  address: IPv4 in [4..8) (rest 0), IPv6 all 16\n *   [20..24) IPv6 scope id, big-endian (0 for IPv4)\n *\n * An IPv4-mapped IPv6 source (::ffff:a.b.c.d — what a dual-stack\n * socket reports for an IPv4 peer) is normalised to family 4 on the\n * way in and mapped back on the way out when the socket is AF_INET6,\n * so the address a program sees never depends on which socket family\n * happened to receive it. */\n\n\nstatic int nurl__udp_addr_from_sa(const struct sockaddr *sa, socklen_t len,\n                                  unsigned char *out) {\n    memset(out, 0, NURL_UDP_ADDR_LEN);\n    if (!sa) return -1;\n    if (sa->sa_family == AF_INET && len >= (socklen_t)sizeof(struct sockaddr_in)) {\n        const struct sockaddr_in *in4 = (const struct sockaddr_in*)sa;\n        out[0] = 4;\n        out[2] = (unsigned char)(ntohs(in4->sin_port) >> 8);\n        out[3] = (unsigned char)(ntohs(in4->sin_port) & 0xff);\n        memcpy(out + 4, &in4->sin_addr, 4);\n        return 0;\n    }\n    if (sa->sa_family == AF_INET6 && len >= (socklen_t)sizeof(struct sockaddr_in6)) {\n        const struct sockaddr_in6 *in6 = (const struct sockaddr_in6*)sa;\n        const unsigned char *b = (const unsigned char*)&in6->sin6_addr;\n        unsigned port = ntohs(in6->sin6_port);\n        out[2] = (unsigned char)(port >> 8);\n        out[3] = (unsigned char)(port & 0xff);\n        static const unsigned char v4mapped[12] =\n            {0,0,0,0,0,0,0,0,0,0,0xff,0xff};\n        if (memcmp(b, v4mapped, 12) == 0) {\n            out[0] = 4;\n            memcpy(out + 4, b + 12, 4);\n            return 0;\n        }\n        out[0] = 6;\n        memcpy(out + 4, b, 16);\n        unsigned long sc = (unsigned long)in6->sin6_scope_id;\n        out[20] = (unsigned char)(sc >> 24);\n        out[21] = (unsigned char)(sc >> 16);\n        out[22] = (unsigned char)(sc >> 8);\n        out[23] = (unsigned char)(sc);\n        return 0;\n    }\n    return -1;\n}\n\n/* Build the sockaddr a socket of family `sock_family` can send to.\n * Returns the sockaddr length, 0 if the address is empty/unknown or an\n * IPv6 address is handed to an IPv4-only socket. */\nstatic socklen_t nurl__udp_addr_to_sa(const unsigned char *a, int sock_family,\n                                      struct sockaddr_storage *ss) {\n    memset(ss, 0, sizeof(*ss));\n    unsigned port = ((unsigned)a[2] << 8) | (unsigned)a[3];\n    if (a[0] == 4) {\n        if (sock_family == AF_INET6) {\n            struct sockaddr_in6 *in6 = (struct sockaddr_in6*)ss;\n            in6->sin6_family = AF_INET6;\n            in6->sin6_port   = htons((unsigned short)port);\n            unsigned char *b = (unsigned char*)&in6->sin6_addr;\n            b[10] = 0xff; b[11] = 0xff;\n            memcpy(b + 12, a + 4, 4);\n            return (socklen_t)sizeof(struct sockaddr_in6);\n        }\n        struct sockaddr_in *in4 = (struct sockaddr_in*)ss;\n        in4->sin_family = AF_INET;\n        in4->sin_port   = htons((unsigned short)port);\n        memcpy(&in4->sin_addr, a + 4, 4);\n        return (socklen_t)sizeof(struct sockaddr_in);\n    }\n    if (a[0] == 6) {\n        if (sock_family == AF_INET) return 0;\n        struct sockaddr_in6 *in6 = (struct sockaddr_in6*)ss;\n        in6->sin6_family = AF_INET6;\n        in6->sin6_port   = htons((unsigned short)port);\n        memcpy(&in6->sin6_addr, a + 4, 16);\n        in6->sin6_scope_id = ((unsigned long)a[20] << 24) | ((unsigned long)a[21] << 16)\n                           | ((unsigned long)a[22] << 8)  |  (unsigned long)a[23];\n        return (socklen_t)sizeof(struct sockaddr_in6);\n    }\n    return 0;\n}\n\nlong long nurl_udp_recv_into(long long handle, char *buf, long long cap,\n                             char *addr_out) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h) return -1;\n    if (h->fd == NURL_INVALID_SOCK) {\n        h->err_kind = NURL_NET_ERR_CLOSED;\n        return -1;\n    }\n    if (cap <= 0 || !buf) {\n        h->err_kind = NURL_NET_ERR_READ;\n        return -1;\n    }\n    struct sockaddr_storage src;\n    memset(&src, 0, sizeof(src));\n#ifdef _WIN32\n    int srclen = (int)sizeof(src);\n    int rd = recvfrom(h->fd, buf, (int)cap, 0,\n                      (struct sockaddr*)&src, &srclen);\n#else\n    socklen_t srclen = (socklen_t)sizeof(src);\n    ssize_t rd;\n    do {\n        rd = recvfrom(h->fd, buf, (size_t)cap, 0,\n                      (struct sockaddr*)&src, &srclen);\n    } while (rd < 0 && errno == EINTR);\n#endif\n    if (rd < 0) {\n#ifdef _WIN32\n        h->err_kind = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_READ);\n#else\n        h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_READ);\n#endif\n        return -1;\n    }\n    if (addr_out) {\n        nurl__udp_addr_from_sa((struct sockaddr*)&src, (socklen_t)srclen,\n                               (unsigned char*)addr_out);\n    }\n    h->err_kind = NURL_NET_ERR_OK;\n    return (long long)rd;\n}\n\nlong long nurl_udp_send_addr(long long handle, const char *buf, long long n,\n                             const char *addr) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h) return -1;\n    if (h->fd == NURL_INVALID_SOCK) {\n        h->err_kind = NURL_NET_ERR_CLOSED;\n        return -1;\n    }\n    if (n < 0 || (n > 0 && !buf) || !addr) {\n        h->err_kind = NURL_NET_ERR_WRITE;\n        return -1;\n    }\n    struct sockaddr_storage dst;\n    socklen_t dlen = nurl__udp_addr_to_sa((const unsigned char*)addr,\n                                          h->family, &dst);\n    if (dlen == 0) {\n        h->err_kind = NURL_NET_ERR_WRITE;\n        return -1;\n    }\n#ifdef _WIN32\n    int sent = sendto(h->fd, buf, (int)n, 0, (struct sockaddr*)&dst, (int)dlen);\n#else\n    ssize_t sent;\n    do {\n        sent = sendto(h->fd, buf, (size_t)n,\n#  ifdef MSG_NOSIGNAL\n                      MSG_NOSIGNAL,\n#  else\n                      0,\n#  endif\n                      (struct sockaddr*)&dst, dlen);\n    } while (sent < 0 && errno == EINTR);\n#endif\n    if (sent < 0) {\n#ifdef _WIN32\n        h->err_kind = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_WRITE);\n#else\n        h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_WRITE);\n#endif\n        return -1;\n    }\n    h->err_kind = NURL_NET_ERR_OK;\n    return (long long)sent;\n}\n\n/* host:port → NurlUdpAddr. Numeric literals never touch a resolver;\n * names do (one getaddrinfo per call — the point is to call this once\n * per peer, not per packet). 0 on success, -1 otherwise. */\nNURL_TLS_FN long long nurl_udp_addr_resolve(const char *host, long long port, char *addr_out) {\n    if (!addr_out) return -1;\n    memset(addr_out, 0, NURL_UDP_ADDR_LEN);\n    if (!host || !*host || port < 0 || port > 65535) return -1;\n#ifdef _WIN32\n    if (!nurl__net_wsa_init()) return -1;\n#endif\n    char portstr[16];\n    snprintf(portstr, sizeof(portstr), \"%ld\", (long)port);\n    struct addrinfo hints, *res = NULL;\n    memset(&hints, 0, sizeof(hints));\n    hints.ai_family   = AF_UNSPEC;\n    hints.ai_socktype = SOCK_DGRAM;\n    if (nurl__resolve(host, portstr, &hints, &res, -1) != 0 || !res) {\n        if (res) freeaddrinfo(res);\n        return -1;\n    }\n    int rc = nurl__udp_addr_from_sa(res->ai_addr, (socklen_t)res->ai_addrlen,\n                                    (unsigned char*)addr_out);\n    freeaddrinfo(res);\n    return rc;\n}\n\n/* NurlUdpAddr → heap \"ip:port\" / \"[ip]:port\" (caller nurl_frees). Pure\n * text codec, no resolver. Empty string for an empty address. */\nchar *nurl_udp_addr_format(const char *addr) {\n    char out[80];\n    nurl__udpaddr_format((const unsigned char*)addr, out, sizeof(out));\n    return strdup(out);\n}\n\n/* setsockopt with an int value. `level` / `opt` are whatever\n * posix_const answered for SOL_SOCKET / IPPROTO_* / SO_* / IP_* /\n * UDP_* — a -1 from posix_const (option unknown on this platform) is\n * refused here rather than passed to the kernel as a real number. */\nlong long nurl_udp_setsockopt_int(long long handle, long long level,\n                                  long long opt, long long val) {\n    NurlUdp *h = (NurlUdp*)(uintptr_t)handle;\n    if (!h || h->fd == NURL_INVALID_SOCK) {\n        if (h) h->err_kind = NURL_NET_ERR_CLOSED;\n        return -1;\n    }\n    if (level < 0 || opt < 0) {\n        h->err_kind = NURL_NET_ERR_OTHER;\n        return -1;\n    }\n    int v = (int)val;\n    int rc = setsockopt(h->fd, (int)level, (int)opt,\n                        (const char*)&v, (socklen_t)sizeof(v));\n    if (rc != 0) {\n#ifdef _WIN32\n        h->err_kind = nurl__net_map_wsa(WSAGetLastError(), NURL_NET_ERR_OTHER);\n#else\n        h->err_kind = nurl__net_map_errno(errno, NURL_NET_ERR_OTHER);\n#endif\n        return -1;\n    }\n    h->err_kind = NURL_NET_ERR_OK;\n    return 0;\n}\n\nNURL_TLS_FN char *nurl_dns_reverse(const char *ip) {\n#ifdef _WIN32\n    if (!nurl__net_wsa_init()) return strdup(\"\");\n#endif\n    if (!ip || !*ip) return strdup(\"\");\n    int fam = AF_UNSPEC;\n    struct sockaddr_storage sa;\n    socklen_t salen = 0;\n    if (nurl__parse_numeric_addr(ip, &fam, &sa, &salen) != 0) return strdup(\"\");\n    /* Port doesn't matter for PTR — getnameinfo wants a non-zero sockaddr\n     * to know the family + bytes. */\n    char host[NI_MAXHOST] = {0};\n    if (nurl__reverse((struct sockaddr*)&sa, salen, host, sizeof(host),\n                      NI_NAMEREQD) != 0) return strdup(\"\");\n    return strdup(host);\n}\n\n#elif defined(NURL_WASM_NET)\n/*\n * WASI has no socket layer of its own (preview1 can only accept on a\n * socket the host preopened), so the sockets come from the host as wasm\n * IMPORTS in the `nurl_net` module. Each function below is a thin\n * wrapper over one import, keeping the ABI the NURL side already\n * declares in stdlib/core/builtins.nu; the handle is the host's own\n * handle, opaque to the guest.\n *\n * Two properties make this safe to have in every build:\n *   • wasm-ld's --gc-sections drops a wrapper no one calls, and an\n *     import referenced by nothing is not emitted — so a module that\n *     never touches std/net.nu carries no `nurl_net` import at all and\n *     still runs on a plain WASI runtime.\n *   • A module that DOES open sockets now says so in its import\n *     section. That is the honest statement of what it needs; a host\n *     without sockets refuses to instantiate it instead of pretending\n *     every connect failed.\n *\n * TLS needs nothing here: stdlib's TLS 1.3 is pure NURL and runs inside\n * the guest over these plaintext sockets.\n *\n * Strings that the host produces (peer/local address, DNS answers) are\n * written into a caller-supplied buffer and the import returns the\n * length — the host cannot allocate in the guest's linear memory.\n */\n\n#define NURL_NET_IMPORT(nm) \\\n    __attribute__((import_module(\"nurl_net\"), import_name(nm)))\n\nNURL_NET_IMPORT(\"tcp_listen\")   extern long long nurl__ni_tcp_listen(const char*, long long, long long);\nNURL_NET_IMPORT(\"tcp_connect\")  extern long long nurl__ni_tcp_connect(const char*, long long);\nNURL_NET_IMPORT(\"tcp_accept\")   extern long long nurl__ni_tcp_accept(long long);\nNURL_NET_IMPORT(\"tcp_read\")     extern long long nurl__ni_tcp_read(long long, const char*, long long);\nNURL_NET_IMPORT(\"tcp_write\")    extern long long nurl__ni_tcp_write(long long, const char*, long long);\nNURL_NET_IMPORT(\"tcp_close\")    extern void      nurl__ni_tcp_close(long long);\nNURL_NET_IMPORT(\"tcp_shutdown\") extern void      nurl__ni_tcp_shutdown(long long);\nNURL_NET_IMPORT(\"tcp_err_kind\") extern long long nurl__ni_tcp_err_kind(long long);\nNURL_NET_IMPORT(\"tcp_set_timeout\") extern void   nurl__ni_tcp_set_timeout(long long, long long);\nNURL_NET_IMPORT(\"tcp_set_send_buffer\") extern long long nurl__ni_tcp_set_send_buffer(long long, long long);\nNURL_NET_IMPORT(\"tcp_timeout_ms\")  extern long long nurl__ni_tcp_timeout_ms(long long);\nNURL_NET_IMPORT(\"tcp_set_write_deadline\") extern void nurl__ni_tcp_set_write_deadline(long long, long long);\nNURL_NET_IMPORT(\"tcp_write_deadline\") extern long long nurl__ni_tcp_write_deadline(long long);\nNURL_NET_IMPORT(\"tcp_write_wait_ms\") extern long long nurl__ni_tcp_write_wait_ms(long long);\nNURL_NET_IMPORT(\"tcp_write_nowait\") extern long long nurl__ni_tcp_write_nowait(long long, const char*, long long);\nNURL_NET_IMPORT(\"tcp_wait_io\") extern long long nurl__ni_tcp_wait_io(long long, long long, long long);\nNURL_NET_IMPORT(\"tcp_read_nowait\") extern long long nurl__ni_tcp_read_nowait(long long, const char*, long long);\nNURL_NET_IMPORT(\"tcp_peer_addr\")   extern long long nurl__ni_tcp_peer_addr(long long, char*, long long);\nNURL_NET_IMPORT(\"tcp_local_addr\")  extern long long nurl__ni_tcp_local_addr(long long, char*, long long);\nNURL_NET_IMPORT(\"tcp_get_fd\")      extern long long nurl__ni_tcp_get_fd(long long);\nNURL_NET_IMPORT(\"tcp_set_nonblock\") extern void  nurl__ni_tcp_set_nonblock(long long, long long);\nNURL_NET_IMPORT(\"tcp_ref\")      extern void      nurl__ni_tcp_ref(long long);\nNURL_NET_IMPORT(\"tcp_unref\")    extern void      nurl__ni_tcp_unref(long long);\n\nNURL_NET_IMPORT(\"udp_bind\")     extern long long nurl__ni_udp_bind(const char*, long long);\nNURL_NET_IMPORT(\"udp_connect\")  extern long long nurl__ni_udp_connect(long long, const char*, long long);\nNURL_NET_IMPORT(\"udp_send\")     extern long long nurl__ni_udp_send(long long, const char*, long long);\nNURL_NET_IMPORT(\"udp_recv\")     extern long long nurl__ni_udp_recv(long long, char*, long long);\nNURL_NET_IMPORT(\"udp_send_to\")  extern long long nurl__ni_udp_send_to(long long, const char*, long long, const char*, long long);\nNURL_NET_IMPORT(\"udp_recv_from\") extern long long nurl__ni_udp_recv_from(long long, char*, long long);\nNURL_NET_IMPORT(\"udp_close\")    extern void      nurl__ni_udp_close(long long);\nNURL_NET_IMPORT(\"udp_err_kind\") extern long long nurl__ni_udp_err_kind(long long);\nNURL_NET_IMPORT(\"udp_peer_addr\")  extern long long nurl__ni_udp_peer_addr(long long, char*, long long);\nNURL_NET_IMPORT(\"udp_local_addr\") extern long long nurl__ni_udp_local_addr(long long, char*, long long);\nNURL_NET_IMPORT(\"udp_set_timeout\")  extern void   nurl__ni_udp_set_timeout(long long, long long);\nNURL_NET_IMPORT(\"udp_set_nonblock\") extern void   nurl__ni_udp_set_nonblock(long long, long long);\nNURL_NET_IMPORT(\"udp_family\")   extern long long nurl__ni_udp_family(long long);\nNURL_NET_IMPORT(\"udp_get_fd\")   extern long long nurl__ni_udp_get_fd(long long);\nNURL_NET_IMPORT(\"udp_set_broadcast\") extern long long nurl__ni_udp_set_broadcast(long long, long long);\nNURL_NET_IMPORT(\"udp_join_group\")    extern long long nurl__ni_udp_join_group(long long, const char*, const char*);\nNURL_NET_IMPORT(\"udp_leave_group\")   extern long long nurl__ni_udp_leave_group(long long, const char*, const char*);\nNURL_NET_IMPORT(\"udp_set_multicast_ttl\")  extern long long nurl__ni_udp_set_multicast_ttl(long long, long long);\nNURL_NET_IMPORT(\"udp_set_multicast_loop\") extern long long nurl__ni_udp_set_multicast_loop(long long, long long);\n\nNURL_NET_IMPORT(\"dns_resolve\")      extern long long nurl__ni_dns_resolve(const char*, char*, long long);\nNURL_NET_IMPORT(\"dns_resolve_port\") extern long long nurl__ni_dns_resolve_port(const char*, long long, char*, long long);\nNURL_NET_IMPORT(\"dns_reverse\")      extern long long nurl__ni_dns_reverse(const char*, char*, long long);\n\n/* An address is at most \"[<45-char v6>]:65535\"; DNS answers are a\n * comma-joined list, so give those room and truncate rather than grow. */\n#define NURL_NET_ADDR_CAP 128\n#define NURL_NET_DNS_CAP  2048\n\nstatic char *nurl__net_take(const char *buf, long long n) {\n    if (n < 0) n = 0;\n    char *out = (char*)malloc((size_t)n + 1);\n    if (!out) return NULL;\n    if (n > 0) memcpy(out, buf, (size_t)n);\n    out[n] = 0;\n    return out;\n}\n\nlong long nurl_tcp_listen(const char *host, long long port, long long backlog) {\n    return nurl__ni_tcp_listen(host ? host : \"\", port, backlog);\n}\n/* The host import takes no socket options: SO_REUSEPORT is not available. */\nlong long nurl_tcp_listen_opts(const char *host, long long port, long long backlog, long long flags) {\n    (void)flags;\n    return nurl_tcp_listen(host, port, backlog);\n}\n/* The TLS listener is the pure-NURL stack over a plaintext socket\n * (std/net.nu builds the handshake itself); the PEM arguments are the\n * NURL side's business, not the host's. */\nlong long nurl_tcp_listen_tls(const char *host, long long port, long long backlog,\n                              const char *cert, const char *key) {\n    (void)cert; (void)key;\n    return nurl__ni_tcp_listen(host ? host : \"\", port, backlog);\n}\nlong long nurl_tcp_connect(const char *host, long long port) {\n    return nurl__ni_tcp_connect(host ? host : \"\", port);\n}\n/* The bridge has no deadline argument: a wasm guest is one thread, the\n * host's connect is the host's business. The timeout is accepted and\n * ignored rather than refused, so portable code links. */\nlong long nurl_tcp_connect_ms(const char *host, long long port, long long ms) {\n    (void)ms;\n    return nurl__ni_tcp_connect(host ? host : \"\", port);\n}\nlong long nurl_tcp_accept(long long listener) { return nurl__ni_tcp_accept(listener); }\nlong long nurl_tcp_read(long long h, const char *buf, long long n) {\n    return nurl__ni_tcp_read(h, buf, n);\n}\nlong long nurl_tcp_write(long long h, const char *buf, long long n) {\n    return nurl__ni_tcp_write(h, buf, n);\n}\n/* The bridge has no vectored write, and adding an import would refuse\n * to instantiate every module on a host that predates it. Compose it\n * from two writes under nurl_tcp_write2's contract: the count covers\n * both segments, and a short first segment is a short total. */\nlong long nurl_tcp_write2(long long h, const char *b1, long long n1,\n                          const char *b2, long long n2) {\n    if (n1 < 0) n1 = 0;\n    if (n2 < 0) n2 = 0;\n    if (n1 == 0) return nurl__ni_tcp_write(h, b2, n2);\n    long long w1 = nurl__ni_tcp_write(h, b1, n1);\n    if (w1 < n1 || n2 == 0) return w1;\n    long long w2 = nurl__ni_tcp_write(h, b2, n2);\n    return w2 < 0 ? w1 : w1 + w2;\n}\nvoid nurl_tcp_close(long long h)    { nurl__ni_tcp_close(h); }\nvoid nurl_tcp_shutdown(long long h) { nurl__ni_tcp_shutdown(h); }\n/* No host import shuts a connection: a drain falls back to its deadline. */\nvoid nurl_tcp_conn_shutdown(long long h, long long both) { (void)h; (void)both; }\nlong long nurl_tcp_err_kind(long long h) { return nurl__ni_tcp_err_kind(h); }\n/* Borrowed, as on every other platform: valid until the next call. */\nconst char *nurl_tcp_peer_addr(long long h) {\n    static char buf[NURL_NET_ADDR_CAP];\n    long long n = nurl__ni_tcp_peer_addr(h, buf, (long long)sizeof(buf) - 1);\n    if (n < 0) n = 0;\n    buf[n] = 0;\n    return buf;\n}\nchar *nurl_tcp_local_addr(long long h) {\n    char buf[NURL_NET_ADDR_CAP];\n    long long n = nurl__ni_tcp_local_addr(h, buf, (long long)sizeof(buf));\n    return nurl__net_take(buf, n);\n}\nvoid nurl_tcp_set_timeout(long long h, long long ms) { nurl__ni_tcp_set_timeout(h, ms); }\nlong long nurl_tcp_set_send_buffer(long long h, long long bytes) { return nurl__ni_tcp_set_send_buffer(h, bytes); }\nlong long nurl_tcp_timeout_ms(long long h)           { return nurl__ni_tcp_timeout_ms(h); }\nvoid nurl_tcp_set_write_deadline(long long h, long long ns) { nurl__ni_tcp_set_write_deadline(h, ns); }\nlong long nurl_tcp_write_deadline(long long h) { return nurl__ni_tcp_write_deadline(h); }\nlong long nurl_tcp_write_wait_ms(long long h) { return nurl__ni_tcp_write_wait_ms(h); }\nlong long nurl_tcp_write_nowait(long long h, const char *buf, long long n) { return nurl__ni_tcp_write_nowait(h, buf, n); }\nlong long nurl_tcp_wait_io(long long h, long long events, long long ms) { return nurl__ni_tcp_wait_io(h, events, ms); }\nlong long nurl_tcp_read_nowait(long long h, const char *buf, long long n) { return nurl__ni_tcp_read_nowait(h, buf, n); }\nlong long nurl_tcp_get_fd(long long h)                { return nurl__ni_tcp_get_fd(h); }\n/* No kTLS here: std/tls.nu keeps its userspace record layer. */\nlong long nurl_ktls_supported(void) { return 0; }\nlong long nurl_ktls_set(long long h, long long dir, long long cipher,\n                        const char *key, const char *iv, long long seq) {\n    (void)h; (void)dir; (void)cipher; (void)key; (void)iv; (void)seq; return -1;\n}\nlong long nurl_ktls_send(long long h, long long rtype, const char *buf, long long n) {\n    (void)h; (void)rtype; (void)buf; (void)n; return -1;\n}\nlong long nurl_ktls_recv(long long h, const char *buf, long long n, long long nowait) {\n    (void)h; (void)buf; (void)n; (void)nowait; return -1;\n}\nlong long nurl_ktls_rtype(long long h) { (void)h; return 0; }\nlong long nurl_tcp_sendfile(long long h, long long fd, long long off, long long n) {\n    (void)h; (void)fd; (void)off; (void)n; return -2;\n}\nvoid nurl_tcp_set_nonblock(long long h, long long on) { nurl__ni_tcp_set_nonblock(h, on); }\nvoid nurl_tcp_ref(long long h)                        { nurl__ni_tcp_ref(h); }\nvoid nurl_tcp_unref(long long h)                      { nurl__ni_tcp_unref(h); }\n\nlong long nurl_udp_bind(const char *h, long long p) {\n    return nurl__ni_udp_bind(h ? h : \"\", p);\n}\nlong long nurl_udp_connect(long long h, const char *host, long long p) {\n    return nurl__ni_udp_connect(h, host ? host : \"\", p);\n}\nlong long nurl_udp_send_to(long long h, const char *b, long long n,\n                           const char *host, long long p) {\n    return nurl__ni_udp_send_to(h, b, n, host ? host : \"\", p);\n}\nlong long nurl_udp_recv_from(long long h, char *b, long long n) {\n    return nurl__ni_udp_recv_from(h, b, n);\n}\nlong long nurl_udp_send(long long h, const char *b, long long n) {\n    return nurl__ni_udp_send(h, b, n);\n}\nlong long nurl_udp_recv(long long h, char *b, long long n) {\n    return nurl__ni_udp_recv(h, b, n);\n}\nconst char *nurl_udp_peer_addr(long long h) {\n    static char buf[NURL_NET_ADDR_CAP];\n    long long n = nurl__ni_udp_peer_addr(h, buf, (long long)sizeof(buf) - 1);\n    if (n < 0) n = 0;\n    buf[n] = 0;\n    return buf;\n}\nchar *nurl_udp_local_addr(long long h) {\n    char buf[NURL_NET_ADDR_CAP];\n    long long n = nurl__ni_udp_local_addr(h, buf, (long long)sizeof(buf));\n    return nurl__net_take(buf, n);\n}\nlong long nurl_udp_err_kind(long long h)               { return nurl__ni_udp_err_kind(h); }\nlong long nurl_udp_get_fd(long long h)                 { return nurl__ni_udp_get_fd(h); }\nlong long nurl_udp_family(long long h)                 { return nurl__ni_udp_family(h); }\nvoid nurl_udp_set_nonblock(long long h, long long on)  { nurl__ni_udp_set_nonblock(h, on); }\nvoid nurl_udp_set_timeout(long long h, long long ms)   { nurl__ni_udp_set_timeout(h, ms); }\nvoid nurl_udp_close(long long h)                       { nurl__ni_udp_close(h); }\n/* The host has no half-close: the socket stays open until its last owner\n * closes it (nurl_udp_close). */\nvoid nurl_udp_shutdown(long long h)                    { (void)h; }\nlong long nurl_udp_set_broadcast(long long h, long long on) { return nurl__ni_udp_set_broadcast(h, on); }\nlong long nurl_udp_join_group(long long h, const char *g, const char *i) {\n    return nurl__ni_udp_join_group(h, g ? g : \"\", i ? i : \"\");\n}\nlong long nurl_udp_leave_group(long long h, const char *g, const char *i) {\n    return nurl__ni_udp_leave_group(h, g ? g : \"\", i ? i : \"\");\n}\nlong long nurl_udp_set_multicast_ttl(long long h, long long t)   { return nurl__ni_udp_set_multicast_ttl(h, t); }\nlong long nurl_udp_set_multicast_loop(long long h, long long on) { return nurl__ni_udp_set_multicast_loop(h, on); }\n\nchar *nurl_dns_resolve(const char *h) {\n    char buf[NURL_NET_DNS_CAP];\n    long long n = nurl__ni_dns_resolve(h ? h : \"\", buf, (long long)sizeof(buf));\n    return nurl__net_take(buf, n);\n}\nchar *nurl_dns_resolve_port(const char *h, long long p) {\n    char buf[NURL_NET_DNS_CAP];\n    long long n = nurl__ni_dns_resolve_port(h ? h : \"\", p, buf, (long long)sizeof(buf));\n    return nurl__net_take(buf, n);\n}\n/* §18b-ii over the host bridge: the host only speaks \"ip:port\"\n * strings, so the address-carrying calls are the string calls plus\n * the NurlUdpAddr text codec. What they still save is the NURL-side\n * allocation per datagram; the per-packet cost of the bridge itself\n * is the host's. */\nlong long nurl_udp_recv_into(long long h, char *buf, long long cap, char *addr_out) {\n    long long n = nurl__ni_udp_recv_from(h, buf, cap);\n    if (n >= 0 && addr_out) {\n        char peer[NURL_NET_ADDR_CAP];\n        long long pn = nurl__ni_udp_peer_addr(h, peer, (long long)sizeof(peer) - 1);\n        if (pn < 0) pn = 0;\n        peer[pn] = 0;\n        nurl__udpaddr_parse(peer, (unsigned char*)addr_out);\n    }\n    return n;\n}\nlong long nurl_udp_send_addr(long long h, const char *b, long long n, const char *addr) {\n    const unsigned char *a = (const unsigned char*)addr;\n    if (!a || (a[0] != 4 && a[0] != 6)) return -1;\n    char host[64];\n    unsigned port = ((unsigned)a[2] << 8) | (unsigned)a[3];\n    if (a[0] == 4) snprintf(host, sizeof(host), \"%u.%u.%u.%u\", a[4], a[5], a[6], a[7]);\n    else {\n        char full[80];\n        nurl__udpaddr_format(a, full, sizeof(full));   /* \"[ip]:port\" */\n        char *rb = strchr(full, ']');\n        if (!rb) return -1;\n        *rb = 0;\n        snprintf(host, sizeof(host), \"%s\", full + 1);\n    }\n    return nurl__ni_udp_send_to(h, b, n, host, (long long)port);\n}\nlong long nurl_udp_addr_resolve(const char *host, long long port, char *addr_out) {\n    if (!addr_out) return -1;\n    memset(addr_out, 0, NURL_UDP_ADDR_LEN);\n    if (!host || !*host || port < 0 || port > 65535) return -1;\n    unsigned char *a = (unsigned char*)addr_out;\n    if (nurl__udpaddr_parse(host, a) != 0) {\n        char buf[NURL_NET_DNS_CAP];\n        long long n = nurl__ni_dns_resolve(host, buf, (long long)sizeof(buf) - 1);\n        if (n <= 0) return -1;\n        buf[n] = 0;\n        char *comma = strchr(buf, ',');\n        if (comma) *comma = 0;\n        if (nurl__udpaddr_parse(buf, a) != 0) return -1;\n    }\n    a[2] = (unsigned char)(port >> 8);\n    a[3] = (unsigned char)(port & 0xff);\n    return 0;\n}\nchar *nurl_udp_addr_format(const char *addr) {\n    char out[80];\n    nurl__udpaddr_format((const unsigned char*)addr, out, sizeof(out));\n    return strdup(out);\n}\nlong long nurl_udp_setsockopt_int(long long h, long long level, long long opt, long long val) {\n    (void)h; (void)level; (void)opt; (void)val;\n    return -1;   /* the bridge exposes no setsockopt; say so */\n}\n\nchar *nurl_dns_reverse(const char *ip) {\n    char buf[NURL_NET_ADDR_CAP];\n    long long n = nurl__ni_dns_reverse(ip ? ip : \"\", buf, (long long)sizeof(buf));\n    return nurl__net_take(buf, n);\n}\n\n\n#else  /* __wasi__ without the socket bridge — every call returns NetOther.\n        * wasmbuilder links this variant unless the program actually uses\n        * std/net.nu: a module with no socket call must not carry a\n        * `nurl_net` import, or a runtime that does not provide one refuses\n        * to instantiate it — which is exactly what happened to every\n        * benchmark once --no-gc-sections stopped dropping the wrappers. */\n\nlong long nurl_tcp_listen(const char *host, long long port, long long backlog) {\n    (void)host; (void)port; (void)backlog;\n    return 0;\n}\nlong long nurl_tcp_listen_opts(const char *host, long long port, long long backlog, long long flags) {\n    (void)host; (void)port; (void)backlog; (void)flags;\n    return 0;\n}\nlong long nurl_tcp_listen_tls(const char *host, long long port, long long backlog,\n                              const char *cert, const char *key) {\n    (void)host; (void)port; (void)backlog; (void)cert; (void)key;\n    return 0;\n}\nlong long nurl_tcp_accept(long long listener) { (void)listener; return 0; }\nlong long nurl_tcp_read(long long h, const char *buf, long long n) {\n    (void)h; (void)buf; (void)n; return -1;\n}\nlong long nurl_tcp_write(long long h, const char *buf, long long n) {\n    (void)h; (void)buf; (void)n; return -1;\n}\nlong long nurl_tcp_write2(long long h, const char *b1, long long n1,\n                          const char *b2, long long n2) {\n    (void)h; (void)b1; (void)n1; (void)b2; (void)n2; return -1;\n}\nvoid nurl_tcp_close(long long h) { (void)h; }\nvoid nurl_tcp_shutdown(long long h) { (void)h; }\nvoid nurl_tcp_conn_shutdown(long long h, long long both) { (void)h; (void)both; }\nlong long nurl_tcp_err_kind(long long h) { (void)h; return NURL_NET_ERR_OTHER; }\nconst char *nurl_tcp_peer_addr(long long h) { (void)h; return \"\"; }\nchar       *nurl_tcp_local_addr(long long h) { (void)h; return strdup(\"\"); }\nvoid nurl_tcp_set_timeout(long long h, long long ms) { (void)h; (void)ms; }\nlong long nurl_tcp_set_send_buffer(long long h, long long bytes) { (void)h; (void)bytes; return 8; }\n/* Async-runtime hooks. The non-WASI variants live above the #else gate;\n * mirror them as no-ops here so wasm-ld doesn't fail with undefined\n * symbols for any example that imports the async/HTTP-server stack\n * (which references these unconditionally — they just never fire under\n * WASI because the underlying tcp_listen/_accept returned 0). */\nlong long nurl_tcp_get_fd(long long h)                 { (void)h; return -1; }\n/* No kTLS here: std/tls.nu keeps its userspace record layer. */\nlong long nurl_ktls_supported(void) { return 0; }\nlong long nurl_ktls_set(long long h, long long dir, long long cipher,\n                        const char *key, const char *iv, long long seq) {\n    (void)h; (void)dir; (void)cipher; (void)key; (void)iv; (void)seq; return -1;\n}\nlong long nurl_ktls_send(long long h, long long rtype, const char *buf, long long n) {\n    (void)h; (void)rtype; (void)buf; (void)n; return -1;\n}\nlong long nurl_ktls_recv(long long h, const char *buf, long long n, long long nowait) {\n    (void)h; (void)buf; (void)n; (void)nowait; return -1;\n}\nlong long nurl_ktls_rtype(long long h) { (void)h; return 0; }\nlong long nurl_tcp_sendfile(long long h, long long fd, long long off, long long n) {\n    (void)h; (void)fd; (void)off; (void)n; return -2;\n}\nvoid nurl_tcp_set_nonblock(long long h, long long on)  { (void)h; (void)on; }\nvoid nurl_tcp_ref(long long h)                         { (void)h; }\nvoid nurl_tcp_unref(long long h)                       { (void)h; }\n\n/* §18b / §18c WASI stubs — wasi-libc has no socket layer. */\nlong long nurl_udp_bind(const char *h, long long p) {\n    (void)h; (void)p; return 0;\n}\nlong long nurl_udp_connect(long long h, const char *host, long long p) {\n    (void)h; (void)host; (void)p; return -1;\n}\nlong long nurl_udp_send_to(long long h, const char *b, long long n,\n                           const char *host, long long p) {\n    (void)h; (void)b; (void)n; (void)host; (void)p; return -1;\n}\nlong long nurl_udp_recv_from(long long h, char *b, long long n) {\n    (void)h; (void)b; (void)n; return -1;\n}\nlong long nurl_udp_send(long long h, const char *b, long long n) {\n    (void)h; (void)b; (void)n; return -1;\n}\nlong long nurl_udp_recv(long long h, char *b, long long n) {\n    (void)h; (void)b; (void)n; return -1;\n}\nconst char *nurl_udp_peer_addr(long long h)            { (void)h; return \"\"; }\nchar       *nurl_udp_local_addr(long long h)           { (void)h; return strdup(\"\"); }\nlong long nurl_udp_err_kind(long long h)               { (void)h; return NURL_NET_ERR_OTHER; }\nlong long nurl_udp_get_fd(long long h)                 { (void)h; return -1; }\nlong long nurl_udp_family(long long h)                 { (void)h; return -1; }\nvoid nurl_udp_set_nonblock(long long h, long long on)  { (void)h; (void)on; }\nvoid nurl_udp_set_timeout(long long h, long long ms)   { (void)h; (void)ms; }\nvoid nurl_udp_close(long long h)                       { (void)h; }\nvoid nurl_udp_shutdown(long long h)                    { (void)h; }\nlong long nurl_udp_set_broadcast(long long h, long long on)            { (void)h; (void)on; return -1; }\nlong long nurl_udp_join_group(long long h, const char *g, const char *i){ (void)h; (void)g; (void)i; return -1; }\nlong long nurl_udp_leave_group(long long h, const char *g, const char *i){ (void)h; (void)g; (void)i; return -1; }\nlong long nurl_udp_set_multicast_ttl(long long h, long long t)         { (void)h; (void)t; return -1; }\nlong long nurl_udp_set_multicast_loop(long long h, long long on)       { (void)h; (void)on; return -1; }\nchar *nurl_dns_resolve(const char *h)                  { (void)h; return strdup(\"\"); }\nchar *nurl_dns_resolve_port(const char *h, long long p){ (void)h; (void)p; return strdup(\"\"); }\nchar *nurl_dns_reverse(const char *ip)                 { (void)ip; return strdup(\"\"); }\nlong long nurl_udp_recv_into(long long h, char *b, long long cap, char *addr_out) {\n    (void)h; (void)b; (void)cap; (void)addr_out; return -1;\n}\nlong long nurl_udp_send_addr(long long h, const char *b, long long n, const char *addr) {\n    (void)h; (void)b; (void)n; (void)addr; return -1;\n}\nlong long nurl_udp_addr_resolve(const char *host, long long port, char *addr_out) {\n    (void)host; (void)port; if (addr_out) memset(addr_out, 0, NURL_UDP_ADDR_LEN); return -1;\n}\nchar *nurl_udp_addr_format(const char *addr) {\n    char out[80];\n    nurl__udpaddr_format((const unsigned char*)addr, out, sizeof(out));\n    return strdup(out);\n}\nlong long nurl_udp_setsockopt_int(long long h, long long level, long long opt, long long val) {\n    (void)h; (void)level; (void)opt; (void)val; return -1;\n}\n\n\n/* These two were missing from the original stub set, which is why they\n * leaked out as `env` imports and made even a socket-free program need a\n * host that could answer them. */\nlong long nurl_tcp_connect(const char *host, long long port) {\n    (void)host; (void)port; return 0;\n}\nlong long nurl_tcp_connect_ms(const char *host, long long port, long long ms) {\n    (void)host; (void)port; (void)ms; return 0;\n}\nlong long nurl_tcp_timeout_ms(long long h) { (void)h; return 0; }\nvoid nurl_tcp_set_write_deadline(long long h, long long ns) { (void)h; (void)ns; }\nlong long nurl_tcp_write_deadline(long long h) { (void)h; return 0; }\nlong long nurl_tcp_write_wait_ms(long long h) { (void)h; return 0; }\nlong long nurl_tcp_write_nowait(long long h, const char *buf, long long n) { (void)h; (void)buf; (void)n; return -1; }\nlong long nurl_tcp_wait_io(long long h, long long events, long long ms) { (void)h; (void)events; (void)ms; return -1; }\nlong long nurl_tcp_read_nowait(long long h, const char *buf, long long n) { (void)h; (void)buf; (void)n; return -1; }\n#endif /* __wasi__ guard for §18 */\n\n\n/* ── §19  Thread by-value trampolines + WASI pthread stubs ────── */\n/*\n * Thread/mutex/cond surface is pure NURL (stdlib/std/thread.nu) over\n * libpthread (winpthreads on mingw). What remains here:\n *\n *   • nurl_pthread_join_ptr / _detach_ptr — pthread_t passes by value;\n *     on winpthreads it's a 16-byte struct that NURL's &-FFI can't\n *     express, so these tiny trampolines deref a pointer instead.\n *   • WASI pthread shims — wasi-libc has no threads; spawn fails\n *     (NURL surfaces ThreadCreate), mutex/cond ops succeed silently.\n *\n * NURL closure → pthread_create: the closure compiles to\n * `void(*)(void *env)`; pthread expects `void *(*)(void *)`. The\n * return-value mismatch is harmless under SysV / aarch64 / riscv64\n * because join() always discards the return through a throwaway slot.\n */\n\n#if !defined(__wasi__)\n\nint nurl_pthread_join_ptr(pthread_t *t) {\n    if (!t) return -1;\n    void *rv = NULL;\n    return pthread_join(*t, &rv);\n}\n\nvoid nurl_pthread_detach_ptr(pthread_t *t) {\n    if (t) pthread_detach(*t);\n}\n\n/* cond_wait_ms (stdlib/std/thread.nu): pthread_cond_timedwait takes an\n * absolute CLOCK_REALTIME deadline in a struct timespec, which NURL's\n * FFI would have to lay out per platform. 1 = signalled (or a spurious\n * wake — the caller re-checks its predicate either way), 0 = timed out.\n * The condvars are made with default attributes, whose clock is\n * CLOCK_REALTIME on every POSIX libc; a wall-clock step moves the\n * deadline with it, which a caller that loops on its own monotonic\n * deadline absorbs. */\nint nurl_cond_wait_ms(void *cv, void *m, long long ms) {\n    if (ms < 0) return pthread_cond_wait((pthread_cond_t *)cv, (pthread_mutex_t *)m) == 0;\n#  ifdef NURL_WIN32_PTHREAD_SHIM\n    /* The shim's condvar is a Win32 CONDITION_VARIABLE over an SRW lock,\n     * whose wait already takes a relative timeout in ms (INFINITE is\n     * reserved, so clamp just below it). */\n    DWORD w = ms >= (long long)INFINITE ? INFINITE - 1 : (DWORD)ms;\n    if (SleepConditionVariableSRW((pthread_cond_t *)cv, (pthread_mutex_t *)m, w, 0)) return 1;\n    return GetLastError() != ERROR_TIMEOUT;\n#  else\n    struct timespec ts;\n    clock_gettime(CLOCK_REALTIME, &ts);\n    ts.tv_sec += (time_t)(ms / 1000);\n    ts.tv_nsec += (long)((ms % 1000) * 1000000L);\n    if (ts.tv_nsec >= 1000000000L) { ts.tv_sec += 1; ts.tv_nsec -= 1000000000L; }\n    return pthread_cond_timedwait((pthread_cond_t *)cv, (pthread_mutex_t *)m, &ts) != ETIMEDOUT;\n#  endif\n}\n\n#elif defined(__wasm_atomics__)  /* WASI + the threads proposal */\n/*\n * wasi-threads, guest side. wasi-libc ships no pthread implementation\n * for this target, so the whole surface stdlib/std/thread.nu declares is\n * built here on the two things the proposal does give us: the\n * `wasi.thread-spawn` import, and atomics on shared linear memory.\n *\n * Spawning: the guest allocates the new thread's stack and start block,\n * hands the block to the host, and the host gives the thread a fresh\n * instance of this module — its own stacks and globals — over the same\n * memory, with `__stack_pointer` set from the block. That last part is\n * the host's job because C cannot assign a wasm global; everything else\n * is here.\n *\n * Mutex and condvar are the textbook futex pair over\n * memory.atomic.wait32 / notify. Join is the same futex on a done flag.\n */\n\n#include <stdatomic.h>\n#include <stdint.h>\n\n__attribute__((import_module(\"wasi\"), import_name(\"thread-spawn\")))\nextern int32_t __nurl_wasi_thread_spawn(void *start_arg);\n\n/* 1 MiB per thread, matching the default the main stack gets. Keep the\n * field order: the host reads stack_top at offset 8. */\n#define NURL_WASM_TSTACK (8 * 1024 * 1024)\n\ntypedef struct NurlWasmThread {\n    int32_t          fn;          /* 0: void *(*)(void *) */\n    int32_t          arg;         /* 4 */\n    int32_t          stack_top;   /* 8: the host sets __stack_pointer here */\n    int32_t          tid;         /* 12 */\n    _Atomic int32_t  done;        /* 16 */\n    int32_t          ret;         /* 20 */\n    int32_t          stack_base;  /* 24: freed by join */\n} NurlWasmThread;\n\nstatic void nurl__wait32(_Atomic int32_t *addr, int32_t expected) {\n    __builtin_wasm_memory_atomic_wait32((int32_t*)addr, expected, -1);\n}\n\nstatic void nurl__wake(_Atomic int32_t *addr, int32_t count) {\n    __builtin_wasm_memory_atomic_notify((int32_t*)addr, count);\n}\n\n/*\n * The host calls this on the new thread with the block it was given.\n *\n * The entry point is called as `void (*)(void *)`, which is the shape a\n * NURL closure compiles to — and on this target that is the only thing\n * that ever reaches pthread_create (stdlib/std/thread.nu decomposes the\n * closure into fn + env). Elsewhere the mismatch with POSIX's\n * `void *(*)(void *)` is harmless because the caller discards the\n * return; wasm type-checks call_indirect exactly, so here it is not a\n * matter of taste: calling it with the POSIX shape traps with a\n * signature mismatch before the thread body runs.\n */\n/* wasm-ld synthesises this when the module has thread-local data; each\n * thread gets its own block or every `__thread` variable — errno among\n * them — would be one shared word. */\nextern void __wasm_init_tls(void *);\nextern void nurl_eprint(const char *);\n\n__attribute__((export_name(\"wasi_thread_start\")))\nvoid wasi_thread_start(int32_t tid, void *start_arg) {\n    NurlWasmThread *ts = (NurlWasmThread*)start_arg;\n    ts->tid = tid;\n    {\n        size_t tls_size = __builtin_wasm_tls_size();\n        if (tls_size) {\n            size_t tls_align = __builtin_wasm_tls_align();\n            if (tls_align < 16) tls_align = 16;\n            unsigned char *raw = (unsigned char*)malloc(tls_size + tls_align);\n            if (raw) {\n                uintptr_t base = ((uintptr_t)raw + tls_align - 1) & ~(uintptr_t)(tls_align - 1);\n                __wasm_init_tls((void*)base);\n            }\n        }\n    }\n    void (*entry)(void*) = (void (*)(void*))(uintptr_t)ts->fn;\n    entry((void*)(uintptr_t)ts->arg);\n    nurl_cc_context_end();\n    {\n        const unsigned char *guard = (const unsigned char*)(uintptr_t)ts->stack_base;\n        int broken = 0;\n        for (int i = 0; i < 4096; i++) if (guard[i] != 0xA5) { broken = 1; break; }\n        if (broken) nurl_eprint(\"nurl: wasm thread stack overflow (canary broken)\\n\");\n    }\n    ts->ret = 0;\n    atomic_store(&ts->done, 1);\n    nurl__wake(&ts->done, -1);\n}\n\nint pthread_create(void *t, void *a, void *start, void *arg) {\n    (void)a;\n    if (!t || !start) return 22;  /* EINVAL */\n    NurlWasmThread *ts = (NurlWasmThread*)calloc(1, sizeof *ts);\n    if (!ts) return 11;  /* EAGAIN */\n    /* wasm has no guard page, so a stack that runs off its end writes\n     * into whatever heap block sits below it and the damage shows up\n     * somewhere else entirely. A canary under the stack turns that into\n     * a diagnosis instead of a mystery. */\n    unsigned char *stack = (unsigned char*)malloc(NURL_WASM_TSTACK + 4096);\n    if (!stack) { free(ts); return 11; }\n    memset(stack, 0xA5, 4096);\n    ts->fn         = (int32_t)(uintptr_t)start;\n    ts->arg        = (int32_t)(uintptr_t)arg;\n    ts->stack_base = (int32_t)(uintptr_t)stack;\n    /* The stack grows down from the top, 16-byte aligned; the low 4 KiB\n     * is the canary, so the usable stack starts above it. */\n    ts->stack_top  = (int32_t)(((uintptr_t)stack + 4096 + NURL_WASM_TSTACK) & ~(uintptr_t)15);\n    atomic_store(&ts->done, 0);\n    if (__nurl_wasi_thread_spawn(ts) < 0) { free(stack); free(ts); return 11; }\n    *(void**)t = ts;   /* pthread_t is this block's address */\n    return 0;\n}\n\nint nurl_pthread_join_ptr(void *tp) {\n    if (!tp) return -1;\n    NurlWasmThread *ts = *(NurlWasmThread**)tp;\n    if (!ts) return -1;\n    while (atomic_load(&ts->done) == 0) nurl__wait32(&ts->done, 0);\n    free((void*)(uintptr_t)ts->stack_base);\n    free(ts);\n    *(void**)tp = NULL;\n    return 0;\n}\n\n/* Detach cannot free the block — the thread is still running out of it —\n * so it only forgets it. The stack is reclaimed when the process exits;\n * a thread that is never joined is a thread whose stack is live anyway. */\nvoid nurl_pthread_detach_ptr(void *tp) { if (tp) *(void**)tp = NULL; }\n\n/* 0 = free, 1 = held, 2 = held and someone is waiting. */\nint pthread_mutex_init(void *m, void *a) {\n    (void)a;\n    if (m) atomic_store((_Atomic int32_t*)m, 0);\n    return 0;\n}\n\nint pthread_mutex_lock(void *mp) {\n    _Atomic int32_t *m = (_Atomic int32_t*)mp;\n    if (!m) return 0;\n    int32_t expected = 0;\n    if (atomic_compare_exchange_strong(m, &expected, 1)) return 0;\n    do {\n        expected = 1;\n        if (atomic_load(m) == 2 || atomic_compare_exchange_strong(m, &expected, 2))\n            nurl__wait32(m, 2);\n        expected = 0;\n    } while (!atomic_compare_exchange_strong(m, &expected, 2));\n    return 0;\n}\n\nint pthread_mutex_unlock(void *mp) {\n    _Atomic int32_t *m = (_Atomic int32_t*)mp;\n    if (!m) return 0;\n    if (atomic_fetch_sub(m, 1) != 1) {\n        atomic_store(m, 0);\n        nurl__wake(m, 1);\n    }\n    return 0;\n}\n\nint pthread_mutex_destroy(void *m) { (void)m; return 0; }\n\n/* A condvar is a sequence counter: a waiter reads it, drops the mutex,\n * and sleeps until the counter moves. */\nint pthread_cond_init(void *c, void *a) {\n    (void)a;\n    if (c) atomic_store((_Atomic int32_t*)c, 0);\n    return 0;\n}\n\nint pthread_cond_wait(void *cp, void *mp) {\n    _Atomic int32_t *c = (_Atomic int32_t*)cp;\n    if (!c) return 0;\n    int32_t seq = atomic_load(c);\n    pthread_mutex_unlock(mp);\n    nurl__wait32(c, seq);\n    pthread_mutex_lock(mp);\n    return 0;\n}\n\nint pthread_cond_signal(void *cp) {\n    _Atomic int32_t *c = (_Atomic int32_t*)cp;\n    if (!c) return 0;\n    atomic_fetch_add(c, 1);\n    nurl__wake(c, 1);\n    return 0;\n}\n\n/* The bounded wait: the same futex sleep with a timeout in ns. */\nint nurl_cond_wait_ms(void *cp, void *mp, long long ms) {\n    _Atomic int32_t *c = (_Atomic int32_t*)cp;\n    if (!c) return 0;\n    int32_t seq = atomic_load(c);\n    pthread_mutex_unlock(mp);\n    int r = __builtin_wasm_memory_atomic_wait32((int32_t*)c, seq,\n                                                ms < 0 ? -1 : ms * 1000000LL);\n    pthread_mutex_lock(mp);\n    return r != 2;   /* 2 = timed out */\n}\n\nint pthread_cond_broadcast(void *cp) {\n    _Atomic int32_t *c = (_Atomic int32_t*)cp;\n    if (!c) return 0;\n    atomic_fetch_add(c, 1);\n    nurl__wake(c, -1);\n    return 0;\n}\n\nint pthread_cond_destroy(void *c) { (void)c; return 0; }\n\n/* ── the async runtime, as one thread per fiber ──────────────────\n *\n * stdlib's M:N runtime needs a stackful context switch, which wasm has\n * no way to express (the stack-switching proposal is not here yet). But\n * the API it exports does not actually require M:N — it requires that a\n * spawned body runs concurrently and that `runtime_run` returns when\n * they are all done. With wasi-threads that is a 1:1 backend: a fiber IS\n * a thread. Cheaper multiplexing can come later; correct concurrency is\n * what the relay, the HTTP server and every `spawn` in the stdlib are\n * actually asking for.\n *\n * The reactor stays stubbed on this target: sockets here are blocking,\n * so `nurl_reactor_wait_*` answering \"not on a fiber\" makes the async\n * paths take their blocking fallback, which is exactly right when each\n * fiber owns a thread.\n */\n\ntypedef struct NurlWasmFiber {\n    void            (*fn)(void*);\n    void             *env;\n    int32_t           own_env;   /* free env after the body returns */\n    int32_t           joinable;\n    _Atomic int32_t   done;\n    _Atomic int32_t   parked;    /* futex word for park/unpark */\n    void             *tid;       /* pthread_t block, when joinable */\n    struct NurlWfGroup *group;   /* the runtime_run that waits for it */\n} NurlWasmFiber;\n\n/* Fibers belong to the thread that started them, exactly as the M:N\n * runtime's scheduler is per worker. One PROCESS-wide counter made\n * `runtime_run` wait for every fiber anywhere — so a worker thread that\n * ran the runtime blocked on the relay's accept loop, a fiber that by\n * design never ends. Every thread parked, 1660 bytes sat unread, and the\n * node wedged. A group is created by the first spawn on a root thread\n * and inherited by the fibers it starts. */\ntypedef struct NurlWfGroup {\n    _Atomic int32_t live;\n    _Atomic int32_t gen;\n} NurlWfGroup;\n\nstatic __thread NurlWfGroup *nurl__wf_group = NULL;\n\nstatic NurlWfGroup *nurl__wf_group_get(void) {\n    if (!nurl__wf_group) {\n        nurl__wf_group = (NurlWfGroup*)calloc(1, sizeof *nurl__wf_group);\n    }\n    return nurl__wf_group;\n}\n\nstatic void nurl__wf_body(void *arg) {\n    NurlWasmFiber *f = (NurlWasmFiber*)arg;\n    nurl__wf_group = f->group;   /* inherit, so nested spawns count here */\n    f->fn(f->env);\n    /* nurl_free, NOT free: an owned closure environment came from\n     * nurl_alloc, which also records the pointer in the panic-unwind\n     * journal and recycles the block through the per-thread size-class\n     * caches. Releasing it with libc's free leaves a dangling journal\n     * entry and hands a block back to the wrong allocator — which is\n     * how a relay connection fiber ended up freeing a wild pointer,\n     * intermittently, depending on whether the block had come from a\n     * cache. The M:N backend frees it exactly this way. */\n    if (f->own_env && f->env) { nurl_closure_drop(f->env); f->env = NULL; }\n    nurl_cc_context_end();   /* the fiber's refcount cycles, if any */\n    atomic_store(&f->done, 1);\n    nurl__wake(&f->done, -1);\n    {\n        NurlWfGroup *g = f->group;\n        if (g) {\n            atomic_fetch_sub(&g->live, 1);\n            atomic_fetch_add(&g->gen, 1);\n            nurl__wake(&g->gen, -1);\n        }\n    }\n    if (!f->joinable) {\n        if (f->tid) { free(f->tid); }\n        free(f);\n    }\n}\n\nstatic long long nurl__wf_spawn(void *fn, void *env, int own_env, int joinable) {\n    if (!fn) return 0;\n    NurlWasmFiber *f = (NurlWasmFiber*)calloc(1, sizeof *f);\n    if (!f) return 0;\n    f->fn = (void (*)(void*))fn;\n    f->env = env;\n    f->own_env = own_env;\n    f->joinable = joinable;\n    void **tid = (void**)calloc(1, sizeof(void*));\n    if (!tid) { free(f); return 0; }\n    f->tid = tid;\n    NurlWfGroup *g = nurl__wf_group_get();\n    f->group = g;\n    if (g) atomic_fetch_add(&g->live, 1);\n    if (pthread_create(tid, NULL, (void*)nurl__wf_body, f) != 0) {\n        if (g) atomic_fetch_sub(&g->live, 1);\n        free(tid); free(f);\n        return 0;\n    }\n    return (long long)(uintptr_t)f;\n}\n\n/* Every spawn keeps its OWN copy of the closure env (docs/MEMORY.md §7.4):\n * the spawner's env stays the spawner's, whatever happens to it after the\n * call, and the fiber drops its copy when the body returns. The copy owns\n * what the env only viewed (nurl_closure_own_copy): a `??` arm's payload\n * or a borrowed parameter the spawner's frame releases before the fiber\n * reads it. */\nlong long nurl_fiber_spawn(void *fn, void *env)          { return nurl__wf_spawn(fn, nurl_closure_own_copy(env), 1, 0); }\nlong long nurl_fiber_spawn_owned(void *fn, void *env)    { return nurl__wf_spawn(fn, nurl_closure_own_copy(env), 1, 0); }\nlong long nurl_fiber_spawn_joinable(void *fn, void *env) { return nurl__wf_spawn(fn, nurl_closure_own_copy(env), 1, 1); }\n\nvoid nurl_fiber_join(long long fiber) {\n    NurlWasmFiber *f = (NurlWasmFiber*)(uintptr_t)fiber;\n    if (!f) return;\n    while (atomic_load(&f->done) == 0) nurl__wait32(&f->done, 0);\n    if (f->tid) { nurl_pthread_join_ptr(f->tid); free(f->tid); }\n    free(f);\n}\n\n/* Nothing to yield to: the OS is the scheduler here. */\nvoid nurl_fiber_yield(void) {}\n\nlong long nurl_fiber_current(void)   { return 0; }\nlong long nurl_fiber_worker_id(void) { return -1; }\n\n/* park/unpark are the futex pair again, on the fiber's own word. */\nvoid nurl_fiber_park_with_mutex(long long mutex_h) {\n    (void)mutex_h;\n}\n\nvoid nurl_fiber_unpark(long long fiber) {\n    NurlWasmFiber *f = (NurlWasmFiber*)(uintptr_t)fiber;\n    if (!f) return;\n    atomic_store(&f->parked, 0);\n    nurl__wake(&f->parked, -1);\n}\n\nvoid nurl_runtime_init(long long worker_count) { (void)worker_count; }\nlong long nurl_runtime_worker_count(void) { return 1; }\n\n/* Block until every spawned fiber has finished — the same contract the\n * M:N runtime has, and the reason a relay's `runtime_run` never returns:\n * its accept loop is a fiber that runs forever. */\nvoid nurl_runtime_run(void) {\n    NurlWfGroup *g = nurl__wf_group;   /* no spawn here → nothing to wait for */\n    if (!g) return;\n    for (;;) {\n        if (atomic_load(&g->live) <= 0) return;\n        int32_t gen = atomic_load(&g->gen);\n        nurl__wait32(&g->gen, gen);\n    }\n}\n\nvoid nurl_runtime_shutdown(void) {}\n\n#else  /* WASI without atomics — no threading. */\n\nint  pthread_create(void *t, void *a, void *s, void *arg) {\n    (void)t; (void)a; (void)s; (void)arg; return -1;\n}\nint  nurl_pthread_join_ptr  (void *t) { (void)t; return -1; }\nvoid nurl_pthread_detach_ptr(void *t) { (void)t; }\nint pthread_mutex_init(void *m, void *a)  { (void)m; (void)a; return 0; }\nint pthread_mutex_lock(void *m)            { (void)m; return 0; }\nint pthread_mutex_unlock(void *m)          { (void)m; return 0; }\nint pthread_mutex_destroy(void *m)         { (void)m; return 0; }\nint pthread_cond_init(void *c, void *a)    { (void)c; (void)a; return 0; }\nint pthread_cond_wait(void *c, void *m)    { (void)c; (void)m; return 0; }\n/* One thread: nothing can signal, so a bounded wait is over at once. */\nint nurl_cond_wait_ms(void *c, void *m, long long ms) { (void)c; (void)m; (void)ms; return 0; }\nint pthread_cond_signal(void *c)           { (void)c; return 0; }\nint pthread_cond_broadcast(void *c)        { (void)c; return 0; }\nint pthread_cond_destroy(void *c)          { (void)c; return 0; }\n\n#endif\n\n/*\n * thread_spawn_owned (stdlib/std/thread.nu): the thread frees the\n * closure's environment once the body returns. `thread_spawn` BORROWS\n * the env, and a fire-and-forget spawner — one that detaches and moves\n * on — has no correct place to free it: too early and the running body\n * reads freed captures, and there is no \"later\" it will be told about.\n * The fiber runtime's spawn_owned answers the same question for fibers.\n *\n * The boot block is libc-malloc'd and freed here, before the body runs,\n * so a body that never returns leaks nothing but itself. The env came\n * from nurl_alloc and goes back through nurl_free — libc's free would\n * hand a size-class block to the wrong allocator (see nurl__wf_body).\n *\n * The trampoline has the shape a NURL closure has, `void (*)(void *)`,\n * because that is the only shape every pthread_create here is prepared\n * to call: wasm type-checks call_indirect exactly, and the SysV targets\n * discard the return through a throwaway slot on join.\n */\ntypedef struct { void (*fn)(void *); void *env; } NurlThrOwned;\n\nstatic void nurl__thr_owned_tramp(void *p) {\n    NurlThrOwned b = *(NurlThrOwned *)p;\n    free(p);\n    b.fn(b.env);\n    if (b.env) nurl_closure_drop(b.env);\n    nurl_cc_context_end();   /* the thread's refcount cycles, if any */\n    nurl__journal_thread_exit();\n    nurl__panic_thread_exit();\n}\n\n/* The thread runs on its OWN copy of the closure env and drops it when the\n * body returns (docs/MEMORY.md §7.4); the spawner's env stays the\n * spawner's. */\nint nurl_pthread_create_owned(void *t, void *fn, void *env) {\n    NurlThrOwned *b = (NurlThrOwned *)malloc(sizeof *b);\n    if (!b) return 11;  /* EAGAIN */\n    b->fn  = (void (*)(void *))fn;\n    b->env = nurl_closure_own_copy(env);\n    int rc = pthread_create(t, NULL, (void *(*)(void *))nurl__thr_owned_tramp, b);\n    if (rc != 0) { nurl_closure_drop(b->env); free(b); }\n    return rc;\n}\n\n/* ── §19b  Reader-writer lock (stdlib/std/rwlock.nu) ───────────────\n *\n * Many readers or one writer. A shared release and an exclusive release\n * are separate calls because a Win32 SRWLOCK must be told which one it\n * is letting go (pthread_rwlock_unlock works it out itself). The lock is\n * a block of nurl_rwlock_size() zeroed bytes the NURL side allocates.\n *\n *   POSIX, mingw (winpthreads)   pthread_rwlock_t\n *   MSVC-target clang             SRWLOCK (the pthread shim's own type)\n *   WASI                          the mutex shim: readers exclude each\n *                                 other too — correct, with no read\n *                                 concurrency, as wasi-libc has no rwlock\n *\n * _tryrdlock / _trywrlock take the lock when they can without waiting:\n * 1 taken, 0 not.\n */\n/* Each lock remembers its writer and each holder (a thread or a fiber:\n * NurlRwHolder in runtime_core.c) the locks it holds for reading, so\n *\n *   - a second read guard of a lock its holder already reads is counted,\n *     not taken again. Taken again, it waited behind a writer that was\n *     waiting for the first one: an SRWLOCK and FreeBSD's and macOS's\n *     rwlocks let a waiting writer go first (glibc happens to let the\n *     reader in), so the same program ran on Linux and hung elsewhere;\n *   - asking for a lock its holder already has — for writing, or for\n *     reading and now for writing — is a panic instead of waiting for\n *     itself (pthreads says EDEADLK for the first only, and only some of\n *     them: FreeBSD waits). A caller that went on as if it held the lock\n *     would release it twice; the guards the unwind drops let go of what\n *     the holder does hold. */\n__attribute__((noinline, cold)) static void nurl__rwlock_failed(const char *how, const char *why) {\n    char msg[160];\n    snprintf(msg, sizeof msg, \"nurl: cannot take an RwLocked for %s: %s\", how, why);\n    nurl_panic(msg);\n}\n#define NURL__RW_HAS_W \"this thread already holds it for writing (it would wait for itself)\"\n#define NURL__RW_HAS_R \"this thread already holds it for reading (it would wait for itself)\"\n#if defined(__wasi__)\n/* The mutex shim: readers exclude each other too — correct, with no read\n * concurrency, as wasi-libc has no rwlock. */\ntypedef pthread_mutex_t NurlRwOs;\n/* The mutex above is a word: 0 free, 1 held, 2 held with waiters (no\n * threads at all without atomics: always free). */\nstatic int nurl__rw_wasi_try(NurlRwOs *o) {\n#if defined(__wasm_atomics__)\n    int expected = 0;\n    return __atomic_compare_exchange_n((int *)o, &expected, 1, 0, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED);\n#else\n    (void)o;\n    return 1;\n#endif\n}\nstatic void nurl__rw_os_init(NurlRwOs *o)     { pthread_mutex_init(o, NULL); }\nstatic int  nurl__rw_os_rd(NurlRwOs *o)       { return pthread_mutex_lock(o); }\nstatic int  nurl__rw_os_wr(NurlRwOs *o)       { return pthread_mutex_lock(o); }\nstatic int  nurl__rw_os_tryrd(NurlRwOs *o)    { return nurl__rw_wasi_try(o); }\nstatic int  nurl__rw_os_trywr(NurlRwOs *o)    { return nurl__rw_wasi_try(o); }\nstatic void nurl__rw_os_rdun(NurlRwOs *o)     { pthread_mutex_unlock(o); }\nstatic void nurl__rw_os_wrun(NurlRwOs *o)     { pthread_mutex_unlock(o); }\nstatic void nurl__rw_os_destroy(NurlRwOs *o)  { pthread_mutex_destroy(o); }\n#elif defined(_WIN32) && defined(NURL_WIN32_PTHREAD_SHIM)\ntypedef SRWLOCK NurlRwOs;\nstatic void nurl__rw_os_init(NurlRwOs *o)     { InitializeSRWLock(o); }\nstatic int  nurl__rw_os_rd(NurlRwOs *o)       { AcquireSRWLockShared(o); return 0; }\nstatic int  nurl__rw_os_wr(NurlRwOs *o)       { AcquireSRWLockExclusive(o); return 0; }\nstatic int  nurl__rw_os_tryrd(NurlRwOs *o)    { return TryAcquireSRWLockShared(o) ? 1 : 0; }\nstatic int  nurl__rw_os_trywr(NurlRwOs *o)    { return TryAcquireSRWLockExclusive(o) ? 1 : 0; }\nstatic void nurl__rw_os_rdun(NurlRwOs *o)     { ReleaseSRWLockShared(o); }\nstatic void nurl__rw_os_wrun(NurlRwOs *o)     { ReleaseSRWLockExclusive(o); }\nstatic void nurl__rw_os_destroy(NurlRwOs *o)  { (void)o; }\n#else\ntypedef pthread_rwlock_t NurlRwOs;\nstatic void nurl__rw_os_init(NurlRwOs *o)     { pthread_rwlock_init(o, NULL); }\nstatic int  nurl__rw_os_rd(NurlRwOs *o)       { return pthread_rwlock_rdlock(o); }\nstatic int  nurl__rw_os_wr(NurlRwOs *o)       { return pthread_rwlock_wrlock(o); }\nstatic int  nurl__rw_os_tryrd(NurlRwOs *o)    { return pthread_rwlock_tryrdlock(o) == 0; }\nstatic int  nurl__rw_os_trywr(NurlRwOs *o)    { return pthread_rwlock_trywrlock(o) == 0; }\nstatic void nurl__rw_os_rdun(NurlRwOs *o)     { pthread_rwlock_unlock(o); }\nstatic void nurl__rw_os_wrun(NurlRwOs *o)     { pthread_rwlock_unlock(o); }\nstatic void nurl__rw_os_destroy(NurlRwOs *o)  { pthread_rwlock_destroy(o); }\n#endif\ntypedef struct { NurlRwOs os; NurlRwHolder *writer; } NurlRw;\n\n__attribute__((noinline, cold)) static void nurl__rwlock_os_failed(const char *how, int rc) {\n    nurl__rwlock_failed(how, rc == EDEADLK ? NURL__RW_HAS_W : strerror(rc));\n}\n/* The running holder's record (allocated for a fiber on its first lock). */\n__attribute__((noinline)) static NurlRwHolder *nurl__rw_me_first(void) {\n    NurlRwHolder *h = nurl__rw_in_fiber() ? (NurlRwHolder *)calloc(1, sizeof *h) : &nurl__rw_thread_rec;\n    nurl__rw_holder = h;\n    return h;\n}\nstatic inline NurlRwHolder *nurl__rw_me(void) {\n    NurlRwHolder *h = nurl__rw_holder;\n    return __builtin_expect(h != NULL, 1) ? h : nurl__rw_me_first();\n}\nstatic NurlRwHeldEnt *nurl__rw_ent(NurlRwHolder *h, int k) {\n    return k < NURL_RW_INLINE ? &h->in[k] : &h->more[k - NURL_RW_INLINE];\n}\nstatic NurlRwHeldEnt *nurl__rw_find(NurlRwHolder *h, void *l) {\n    for (int k = 0; k < h->n; k++) {\n        NurlRwHeldEnt *e = nurl__rw_ent(h, k);\n        if (e->l == l) return e;\n    }\n    return NULL;\n}\n__attribute__((noinline)) static void nurl__rw_push(NurlRwHolder *h, void *l) {\n    if (h->n >= NURL_RW_INLINE + h->cap) {\n        int cap = h->cap ? h->cap * 2 : NURL_RW_INLINE;\n        h->more = (NurlRwHeldEnt *)realloc(h->more, (size_t)cap * sizeof *h->more);\n        h->cap = cap;\n    }\n    NurlRwHeldEnt *e = nurl__rw_ent(h, h->n++);\n    e->l = l;\n    e->c = 1;\n}\nstatic void nurl__rw_drop(NurlRwHolder *h, NurlRwHeldEnt *e) {\n    *e = *nurl__rw_ent(h, --h->n);\n    if (h->n <= NURL_RW_INLINE && h->more) { free(h->more); h->more = NULL; h->cap = 0; }\n}\nstatic NurlRwHolder *nurl__rw_writer(NurlRw *r) { return __atomic_load_n(&r->writer, __ATOMIC_RELAXED); }\n\nNURL_TLS_FN long long nurl_rwlock_size(void) { return (long long)sizeof(NurlRw); }\nNURL_TLS_FN void nurl_rwlock_init(void *l)   { NurlRw *r = l; nurl__rw_os_init(&r->os); r->writer = NULL; }\nNURL_TLS_FN void nurl_rwlock_destroy(void *l) { nurl__rw_os_destroy(&((NurlRw *)l)->os); }\n/* The paths a page latch takes on every read: a holder with no read lock\n * yet (nothing to look up), a record with room inline, and a release of\n * the lock taken last (guards go in reverse order). */\nstatic inline void nurl__rw_add(NurlRwHolder *me, void *l) {\n    int n = me->n;\n    if (__builtin_expect(n < NURL_RW_INLINE, 1)) {\n        me->in[n].l = l;\n        me->in[n].c = 1;\n        me->n = n + 1;\n    } else {\n        nurl__rw_push(me, l);\n    }\n}\nNURL_TLS_FN void nurl_rwlock_rdlock(void *l) {\n    NurlRw *r = l;\n    NurlRwHolder *me = nurl__rw_me();\n    if (__builtin_expect(me->n != 0, 0)) {\n        NurlRwHeldEnt *e = nurl__rw_find(me, l);\n        if (e) { e->c++; return; }\n    }\n    if (__builtin_expect(nurl__rw_writer(r) == me, 0)) nurl__rwlock_failed(\"reading\", NURL__RW_HAS_W);\n    int rc = nurl__rw_os_rd(&r->os);\n    if (__builtin_expect(rc != 0, 0)) nurl__rwlock_os_failed(\"reading\", rc);\n    nurl__rw_add(me, l);\n}\nNURL_TLS_FN void nurl_rwlock_rdunlock(void *l) {\n    NurlRwHolder *me = nurl__rw_me();\n    int k = me->n - 1;\n    if (__builtin_expect(k >= 0 && k < NURL_RW_INLINE && me->in[k].l == l, 1)) {\n        if (--me->in[k].c > 0) return;\n        me->n = k;\n    } else {\n        NurlRwHeldEnt *e = nurl__rw_find(me, l);\n        if (e) {\n            if (--e->c > 0) return;\n            nurl__rw_drop(me, e);\n        }\n    }\n    nurl__rw_os_rdun(&((NurlRw *)l)->os);\n}\nNURL_TLS_FN void nurl_rwlock_wrlock(void *l) {\n    NurlRw *r = l;\n    NurlRwHolder *me = nurl__rw_me();\n    if (nurl__rw_writer(r) == me) nurl__rwlock_failed(\"writing\", NURL__RW_HAS_W);\n    if (__builtin_expect(me->n != 0, 0) && nurl__rw_find(me, l)) nurl__rwlock_failed(\"writing\", NURL__RW_HAS_R);\n    int rc = nurl__rw_os_wr(&r->os);\n    if (__builtin_expect(rc != 0, 0)) nurl__rwlock_os_failed(\"writing\", rc);\n    __atomic_store_n(&r->writer, me, __ATOMIC_RELAXED);\n}\nNURL_TLS_FN void nurl_rwlock_wrunlock(void *l) {\n    NurlRw *r = l;\n    __atomic_store_n(&r->writer, (NurlRwHolder *)NULL, __ATOMIC_RELAXED);\n    nurl__rw_os_wrun(&r->os);\n}\n/* Take it when that needs no waiting: 1 taken, 0 not. */\nNURL_TLS_FN long long nurl_rwlock_tryrdlock(void *l) {\n    NurlRw *r = l;\n    NurlRwHolder *me = nurl__rw_me();\n    NurlRwHeldEnt *e = nurl__rw_find(me, l);\n    if (e) { e->c++; return 1; }\n    if (nurl__rw_writer(r) == me || !nurl__rw_os_tryrd(&r->os)) return 0;\n    nurl__rw_add(me, l);\n    return 1;\n}\nNURL_TLS_FN long long nurl_rwlock_trywrlock(void *l) {\n    NurlRw *r = l;\n    NurlRwHolder *me = nurl__rw_me();\n    if (nurl__rw_writer(r) == me || nurl__rw_find(me, l) || !nurl__rw_os_trywr(&r->os)) return 0;\n    __atomic_store_n(&r->writer, me, __ATOMIC_RELAXED);\n    return 1;\n}\n\n/* ── §21  Signal handling — generic + legacy shutdown bridge ──── */\n/*\n * Two layers in one place:\n *\n *   (a) Generic per-signal NURL handler registration. NURL closure\n *       ({fn, env}) per signum; the OS handler is async-signal-safe\n *       (just a `volatile sig_atomic_t` flag + a self-pipe wake byte),\n *       and the closure runs synchronously on the main thread via\n *       `nurl_signal_dispatch()`. Self-pipe FD is exposed so callers\n *       can integrate signal wake-ups into their own select/poll\n *       loops without polling a flag.\n *\n *   (b) Legacy listener-shutdown bridge — `nurl_signal_install_shutdown`\n *       continues to register a single TCP listener whose fd gets\n *       `shutdown(RDWR)` from the OS handler on SIGINT/SIGTERM\n *       (POSIX) or CTRL_C/CTRL_BREAK/CTRL_CLOSE (Win32). The\n *       shutdown happens in the async-signal-safe handler itself\n *       because `shutdown(2)` is on SUSv4 §2.4.3's safe-functions\n *       list — this is the one place a NURL program can act on a\n *       signal without first calling dispatch().\n *\n * Async-signal-safety contract for caller-supplied NURL handlers:\n * the handler runs on the main thread between dispatch ticks, so any\n * NURL code is allowed (alloc, mutex, log, …). The handler is NOT\n * invoked from the OS signal context.\n */\n\n#if !defined(__wasi__)\n\n#  include <signal.h>\n#  ifdef _WIN32\n#    include <windows.h>\n#  endif\n\n/* Cap matches Linux's NSIG (real-time signals 32-64 included).\n * macOS NSIG is 32; Win32 CRT defines ≤ 23. Pick the union so the\n * same table fits all targets; out-of-range signums are bounce-\n * checked on register. */\n#  define NURL_SIG_MAX 64\n\ntypedef struct { void *fn; void *env; } NurlSignalSlot;\nstatic volatile NurlSignalSlot g_signal_slots[NURL_SIG_MAX];\nstatic volatile sig_atomic_t   g_signal_pending[NURL_SIG_MAX];\n\n/* Legacy listener slot — set by nurl_signal_install_shutdown, read by\n * the OS handler in async-signal context. shutdown(2) on the fd is\n * safe to call from a handler; the struct itself is not touched. */\nstatic volatile NurlTcp *g_signal_listener = NULL;\n\n/* Self-pipe: read end exposed to NURL via nurl_signal_pipe_fd so the\n * caller can wake a select/poll loop from a signal. Lazy-init on\n * first register; both ends are non-blocking + CLOEXEC. write(2)\n * is async-signal-safe (SUSv4 §2.4.3). */\n#  ifndef _WIN32\nstatic int g_signal_pipe[2] = { -1, -1 };\n#  endif\n\n/* Stop the registered listener's accept loop, from anywhere — including\n * async-signal context, which is why every step here is on SUSv4\n * §2.4.3's safe list (a plain store, shutdown(2), write(2)).\n *\n * `shutdown(2)` on a LISTENING socket is a Linux extension. Linux wakes\n * a blocked accept/poll; BSD returns ENOTCONN and leaves the thread\n * parked, so on FreeBSD and macOS SIGINT could not stop a NURL server's\n * accept loop at all — the signal arrived, the flag was set, and the\n * accepting thread stayed asleep forever. It surfaced as a hung\n * signal_basic on FreeBSD the first time that test was allowed to run.\n *\n * The listener already carries the self-pipe that cross-thread\n * nurl_tcp_shutdown uses (see the wake_rfd/wake_wfd comment on NurlTcp):\n * accept polls {fd, wake_rfd}, and one never-drained byte wakes every\n * current and future poll. Use it here too, and keep the shutdown(2)\n * call as the Linux fast path and as the only lever when pipe() failed\n * at listen time. `shutting_down` is what the loop actually tests, so it\n * must be set before the wake, not after. */\nstatic void nurl__signal_stop_listener(void) {\n    NurlTcp *h = (NurlTcp *)g_signal_listener;\n    if (!h || h->fd == NURL_INVALID_SOCK) return;\n    h->shutting_down = 1;\n#  ifdef _WIN32\n    shutdown(h->fd, SD_BOTH);\n#  else\n    shutdown(h->fd, SHUT_RDWR);\n#  endif\n    if (h->wake_wfd != NURL_INVALID_SOCK)\n        nurl__signal_raise(h->wake_rfd, h->wake_wfd);\n}\n\n/* OS-level handler. Async-signal-safe: only sig_atomic_t writes,\n * a non-blocking single-byte write(2), and the listener stop above\n * if SIGINT/SIGTERM fired. */\nstatic void nurl__signal_os_handler(int sig) {\n    if (sig > 0 && sig < NURL_SIG_MAX) {\n        g_signal_pending[sig] = 1;\n    }\n#  ifndef _WIN32\n    if (g_signal_pipe[1] >= 0) {\n        unsigned char b = (unsigned char)(sig & 0xFF);\n        /* Pipe is non-blocking; EAGAIN means the reader hasn't\n         * drained yet but the pending flag still records the\n         * signal, so a missed pipe byte is harmless. */\n        ssize_t r = write(g_signal_pipe[1], &b, 1);\n        (void)r;\n    }\n#  endif\n    if (sig == SIGINT || sig == SIGTERM) {\n        nurl__signal_stop_listener();\n    }\n}\n\n#  ifdef _WIN32\n/* Win32 console-control bridge — translates ctrl-type into a SIGINT\n * delivery so the same dispatch path covers Ctrl+C/Ctrl+Break/Close. */\nstatic BOOL WINAPI nurl__signal_console_bridge(DWORD ctrl_type) {\n    if (ctrl_type == CTRL_C_EVENT     ||\n        ctrl_type == CTRL_BREAK_EVENT ||\n        ctrl_type == CTRL_CLOSE_EVENT ||\n        ctrl_type == CTRL_LOGOFF_EVENT||\n        ctrl_type == CTRL_SHUTDOWN_EVENT) {\n        nurl__signal_os_handler(SIGINT);\n        return TRUE;\n    }\n    return FALSE;\n}\nstatic int g_signal_console_installed = 0;\n#  endif\n\n#  ifndef _WIN32\n/* Create the self-pipe once. Errors silently degrade — register\n * still arms sigaction, only the pipe-fd integration is lost. */\nstatic void nurl__signal_pipe_lazy_init(void) {\n    if (g_signal_pipe[0] >= 0) return;\n    if (pipe(g_signal_pipe) != 0) {\n        g_signal_pipe[0] = g_signal_pipe[1] = -1;\n        return;\n    }\n    /* Non-blocking on both ends, CLOEXEC so child processes don't\n     * inherit a fd that's only meaningful to us. */\n    int fl;\n    fl = fcntl(g_signal_pipe[0], F_GETFL); if (fl >= 0) fcntl(g_signal_pipe[0], F_SETFL, fl | O_NONBLOCK);\n    fl = fcntl(g_signal_pipe[1], F_GETFL); if (fl >= 0) fcntl(g_signal_pipe[1], F_SETFL, fl | O_NONBLOCK);\n    fl = fcntl(g_signal_pipe[0], F_GETFD); if (fl >= 0) fcntl(g_signal_pipe[0], F_SETFD, fl | FD_CLOEXEC);\n    fl = fcntl(g_signal_pipe[1], F_GETFD); if (fl >= 0) fcntl(g_signal_pipe[1], F_SETFD, fl | FD_CLOEXEC);\n}\n#  endif\n\n/* Arm an OS-level handler for `signum`. Idempotent. */\nstatic int nurl__signal_arm(int signum) {\n#  ifdef _WIN32\n    /* Win32 CRT signal() handles SIGINT/SIGTERM/SIGABRT/SIGFPE/SIGILL/\n     * SIGSEGV; other numbers (e.g. SIGUSR1) just return SIG_ERR. The\n     * console-ctrl bridge below covers Ctrl+C/Break/Close → SIGINT. */\n    void (*prev)(int) = signal(signum, nurl__signal_os_handler);\n    if (prev == SIG_ERR) return -1;\n    if (signum == SIGINT && !g_signal_console_installed) {\n        SetConsoleCtrlHandler(nurl__signal_console_bridge, TRUE);\n        g_signal_console_installed = 1;\n    }\n    return 0;\n#  else\n    struct sigaction sa;\n    memset(&sa, 0, sizeof(sa));\n    sa.sa_handler = nurl__signal_os_handler;\n    sigemptyset(&sa.sa_mask);\n    /* No SA_RESTART: NURL programs that are blocked in accept(),\n     * read(), select(), etc. must observe EINTR so they can pick up\n     * the pending flag rather than transparently resuming. */\n    sa.sa_flags = 0;\n    return sigaction(signum, &sa, NULL);\n#  endif\n}\n\n/* Restore the default disposition for `signum`. */\nstatic void nurl__signal_disarm(int signum) {\n#  ifdef _WIN32\n    signal(signum, SIG_DFL);\n#  else\n    struct sigaction sa;\n    memset(&sa, 0, sizeof(sa));\n    sa.sa_handler = SIG_DFL;\n    sigemptyset(&sa.sa_mask);\n    sigaction(signum, &sa, NULL);\n#  endif\n}\n\n/* ── Public C API (called from NURL) ─────────────────────────── */\n\n/* Register a NURL closure to run on `signum`. fn is the closure's\n * compiled body — `void (*)(void *env, long long sig)`; env is its\n * captured-environment block. Pass fn=NULL to leave the OS handler\n * armed but route through `_pending`/`_poll` only (used by the\n * legacy shutdown registration, which doesn't need a NURL handler). */\nlong long nurl_signal_register(long long signum, void *fn, void *env) {\n    if (signum <= 0 || signum >= NURL_SIG_MAX) return -1;\n#  ifndef _WIN32\n    nurl__signal_pipe_lazy_init();\n#  endif\n    /* The slot keeps its own copy of the handler's env (docs/MEMORY.md\n     * §7.4) and drops the handler it replaces. */\n    void *old = g_signal_slots[signum].env;\n    g_signal_slots[signum].fn  = fn;\n    g_signal_slots[signum].env = nurl_closure_own_copy(env);\n    nurl_closure_drop(old);\n    return (long long)nurl__signal_arm((int)signum);\n}\n\n/* Restore the default disposition and clear the NURL slot. */\nvoid nurl_signal_unregister(long long signum) {\n    if (signum <= 0 || signum >= NURL_SIG_MAX) return;\n    void *old = g_signal_slots[signum].env;\n    g_signal_slots[signum].fn  = NULL;\n    g_signal_slots[signum].env = NULL;\n    g_signal_pending[signum]   = 0;\n    nurl__signal_disarm((int)signum);\n    nurl_closure_drop(old);\n}\n\n/* Non-destructive pending check. Returns 1 iff the signal fired\n * since the last `dispatch` or matching `_poll`. */\nlong long nurl_signal_pending(long long signum) {\n    if (signum <= 0 || signum >= NURL_SIG_MAX) return 0;\n    return g_signal_pending[signum] ? 1 : 0;\n}\n\n/* Destructive single-signum probe. Returns the lowest pending\n * signum and clears its flag, or -1 if none are pending. */\nlong long nurl_signal_poll(void) {\n    for (int s = 1; s < NURL_SIG_MAX; s++) {\n        if (g_signal_pending[s]) {\n            g_signal_pending[s] = 0;\n            return s;\n        }\n    }\n    return -1;\n}\n\n/* Run every pending NURL handler on the calling thread. Closure\n * call shape: `void (*)(void *env, long long sig)`. Pending flags\n * are cleared BEFORE the closure runs so a re-entrant raise of the\n * same signal inside a handler is observed on the next dispatch. */\nvoid nurl_signal_dispatch(void) {\n    for (int s = 1; s < NURL_SIG_MAX; s++) {\n        if (!g_signal_pending[s]) continue;\n        g_signal_pending[s] = 0;\n        NurlSignalSlot slot = g_signal_slots[s];\n        if (slot.fn) {\n            ((void (*)(void *, long long))slot.fn)(slot.env, (long long)s);\n        }\n    }\n#  ifndef _WIN32\n    /* Drain any queued wake bytes — the flags have been observed. */\n    if (g_signal_pipe[0] >= 0) {\n        char buf[64];\n        while (read(g_signal_pipe[0], buf, sizeof buf) > 0) {}\n    }\n#  endif\n}\n\n/* Read end of the self-pipe; level-triggered, single-byte writes\n * per signal delivery. Caller may add this fd to select/poll/epoll\n * to wake the loop on signal. Returns -1 if unavailable\n * (Win32, allocation failure, or no register call yet). */\nlong long nurl_signal_pipe_fd(void) {\n#  ifdef _WIN32\n    return -1;\n#  else\n    return (long long)g_signal_pipe[0];\n#  endif\n}\n\n/* Synchronous self-signal — used by tests and by callers that want\n * the same dispatch path the OS handler would trigger. raise(3) on\n * Win32 is supported for SIGINT/SIGTERM/SIGABRT/SIGFPE/SIGILL/SIGSEGV. */\nlong long nurl_signal_raise(long long signum) {\n    if (signum <= 0 || signum >= NURL_SIG_MAX) return -1;\n    return (long long)raise((int)signum);\n}\n\n/* ── Legacy shutdown bridge ─────────────────────────────────── */\n\n/* Register a listener for soft shutdown on SIGINT/SIGTERM (POSIX) or\n * Ctrl+C/Break/Close (Win32). Pass 0 to clear. Internally arms the\n * generic handler for SIGINT + SIGTERM if not already armed; the\n * OS handler performs `shutdown(fd, RDWR)` directly so the accept()\n * loop wakes without waiting for a dispatch tick. */\nvoid nurl_signal_install_shutdown(long long listener) {\n    g_signal_listener = (NurlTcp *)(uintptr_t)listener;\n    if (listener) {\n        nurl__signal_arm(SIGINT);\n        nurl__signal_arm(SIGTERM);\n    }\n}\n\n/* Test helper — synthesise the listener shutdown without raising\n * the signal (Win32 can't deliver SIGINT programmatically; POSIX\n * raise() is fine but the registration order in tests can be racy). */\nvoid nurl_signal_trigger_shutdown(void) {\n    nurl__signal_stop_listener();\n}\n\n#else  /* WASI — no signals at all. Every entry is a no-op stub. */\n\nlong long nurl_signal_register(long long signum, void *fn, void *env) {\n    (void)signum; (void)fn; (void)env; return -1;\n}\nvoid      nurl_signal_unregister(long long signum)        { (void)signum; }\nlong long nurl_signal_pending(long long signum)           { (void)signum; return 0; }\nlong long nurl_signal_poll(void)                          { return -1; }\nvoid      nurl_signal_dispatch(void)                      {}\nlong long nurl_signal_pipe_fd(void)                       { return -1; }\nlong long nurl_signal_raise(long long signum)             { (void)signum; return -1; }\nvoid      nurl_signal_install_shutdown(long long listener){ (void)listener; }\nvoid      nurl_signal_trigger_shutdown(void)              {}\n\n#endif\n/* ── §22  DEFLATE / gzip / zlib ────────────────────────────────────\n *\n * Entirely pure NURL now (stdlib/std/deflate.nu — RFC 1951 inflate +\n * fixed-Huffman/LZ77 deflate, RFC 1950/1952 framing + crc32/adler32 in\n * stdlib/ext/compress.nu). No libz; nothing remains here. */\n\n\n/* ── §24  Async runtime — stackful fibers, M:N work-stealing ──── */\n/*\n * N worker pthreads, each with a mutex-protected FIFO local runqueue.\n * A shared global queue absorbs spawns from non-fiber contexts and\n * overflow; idle workers steal half a peer's queue, then drain global,\n * then park on idle_cv. spawn_joinable + join give a synchronous\n * completion signal; plain spawn fires-and-forgets and frees on DONE.\n *\n * mmap'd 64 KB stacks with a 4 KB guard page → SIGSEGV at a\n * deterministic page on overflow. Channel park integration in Phase 4,\n * I/O reactor in Phase 5 (see §25). Full design in docs/ASYNC.md.\n *\n * Fiber handles are long long (uintptr_t cast); 0 = error/no fiber.\n * pthreads + a stackful switch (own-ctx or ucontext; Win32 x64 on its own\n * switch and VirtualAlloc stacks); WASI is stubbed below.\n * Closure shape matches nurl_thread_spawn in §19. */\nlong long nurl_fiber_spawn(void *fn, void *env);\nlong long nurl_fiber_spawn_owned(void *fn, void *env);\nlong long nurl_fiber_spawn_joinable(void *fn, void *env);\nvoid      nurl_fiber_join(long long fiber);\nvoid      nurl_fiber_yield(void);\nlong long nurl_fiber_current(void);\nlong long nurl_fiber_worker_id(void);\nvoid      nurl_fiber_park_with_mutex(long long mutex_h);\nvoid      nurl_fiber_unpark(long long fiber);\nvoid      nurl_runtime_init(long long worker_count);\nvoid      nurl_runtime_run(void);\nvoid      nurl_runtime_shutdown(void);\n\n/* Fiber platform gate — NURL_HAVE_FIBERS. The runtime needs pthreads,\n * mmap and a stackful context switch; the first two are POSIX, the\n * third is the interesting one, and there are two backends:\n *\n *   NURL_FIBER_OWN_CTX — runtime_ctx.c's switch. Depends on the\n *     instruction set alone, so it works where ucontext does not\n *     (musl ships none) and skips swapcontext's sigprocmask syscall.\n *     Preferred wherever the primitive exists.\n *\n *   ucontext — everywhere else. musl omits it and has no detection\n *     macro, so we ALLOWLIST the libcs that ship a working one rather\n *     than blocklist musl: glibc, macOS, and the BSDs that keep\n *     makecontext/swapcontext (FreeBSD / NetBSD / DragonFly — NOT\n *     OpenBSD, which removed it). macOS gates it behind _XOPEN_SOURCE.\n *\n * Win32 has no ucontext but has the own-ctx switch on x64 (runtime_ctx.c,\n * NURL_CTX_WIN64), threads through winpthreads or runtime_core.c's shim,\n * and VirtualAlloc where POSIX has mmap — so it takes the own-ctx branch.\n * wasi has none of it and falls through to the stub block. §25 (reactor)\n * tests NURL_HAVE_FIBERS, so the two gates cannot drift apart the way two\n * spelled-out conditions did. */\n#if !defined(__wasi__)\n#  if defined(NURL_CTX_AVAILABLE)\n#    define NURL_HAVE_FIBERS   1\n#    define NURL_FIBER_OWN_CTX 1\n#  elif !defined(_WIN32) && (defined(__GLIBC__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__))\n#    define NURL_HAVE_FIBERS   1\n#  endif\n#endif\n\n#ifdef NURL_HAVE_FIBERS\n#ifndef NURL_FIBER_OWN_CTX\n#if defined(__APPLE__) && !defined(_XOPEN_SOURCE)\n#  define _XOPEN_SOURCE 600\n#endif\n#include <ucontext.h>\n#endif\n#ifndef _WIN32\n#include <sys/mman.h>\n#include <unistd.h>\n#include <pthread.h>\n#endif\n\n/* macOS deprecates ucontext_t but keeps it working — silence one warn. */\n#if defined(__APPLE__)\n#  pragma clang diagnostic push\n#  pragma clang diagnostic ignored \"-Wdeprecated-declarations\"\n#endif\n\n#define NURL_FIBER_STACK_BYTES   (64 * 1024)\n#define NURL_FIBER_GUARD_BYTES   4096\n#define NURL_MAX_WORKERS         64\n\n/* One name for whichever backend the gate above selected. */\n#ifdef NURL_FIBER_OWN_CTX\ntypedef nurl_ctx_t NurlFiberCtx;\n#else\ntypedef ucontext_t NurlFiberCtx;\n#endif\n\n/* Forward decl — full struct lives in §25 (I/O reactor). */\nstruct NurlReactorWait;\n\ntypedef enum {\n    NF_NEW = 0,\n    NF_RUNNABLE,\n    NF_RUNNING,\n    NF_PARKED,\n    NF_DONE\n} NurlFiberState;\n\n/* Per-fiber recovery state (runtime_core.c: NurlRecoverCtx, which\n * static-asserts it fits). */\n#define NURL_RCTX_WORDS 14\nvoid nurl__rctx_swap(void *ctx);\nvoid nurl__rctx_release(void *ctx);\n\ntypedef struct NurlFiber {\n    NurlFiberCtx     ctx;\n    void            *stack_base;     /* mmap origin (guard page + usable) */\n    size_t           stack_total_sz;\n    void            *stack_lo;       /* usable region = base + guard      */\n    size_t           stack_usable;\n    /* ASan's per-stack fake stack, parked here across a swap-out. Only\n     * the own-ctx backend touches it — ASan annotates swapcontext by\n     * interception, so the ucontext backend needs nothing. */\n    void            *fake_stack;\n    void           (*fn)(void*);\n    void            *env;\n    NurlFiberState   state;\n    struct NurlFiber*next;           /* intrusive runqueue link */\n    /* When this fiber last became runnable: a stamp from\n     * nurl__sched.rseq_clock, taken as it joins a run queue. Workers\n     * serve the oldest stamp first (see \"Service order\"). */\n    unsigned long long rseq;\n    int              joinable;       /* survives DONE for join() */\n    /* Fire-and-forget spawn whose closure env the FIBER owns: freed\n     * (nurl_free — envs come from nurl_malloc) right after the body\n     * returns. The plain spawn BORROWS its env, which is right for a\n     * long-lived closure the spawner frees later — but a per-item\n     * inline closure (one fiber per accepted connection, say) leaked\n     * its env per spawn with no correct place to free it. */\n    int              own_env;\n    int              done_taken;     /* set after join freed it */\n    /* Set (under join_m) by the worker once it is through with a finished\n     * fiber — after it has swapped the fiber's recovery state back out. A\n     * joiner waits for THIS, not for NF_DONE: the fiber publishes NF_DONE\n     * before it switches back, and a joiner that saw it early freed the\n     * fiber while the worker still swapped its state (ASan: a\n     * use-after-free in nurl__rctx_swap, send_closure_ok). */\n    int              released;\n    pthread_mutex_t  join_m;\n    pthread_cond_t   join_c;\n    /* Park-with-unlock: worker releases this mutex AFTER swap-out so\n     * concurrent unparkers see a stable PARKED state. NULL = no unlock\n     * (reactor-driven park). */\n    pthread_mutex_t *pending_unlock;\n    /* Reactor wake-up reason: 1 fd-ready, 0 timeout. */\n    int              last_park_result;\n    /* How long this fiber's I/O waits take, ns (an exponentially\n     * weighted average, 0 until the first wait): what nurl__spin_ready\n     * sizes its probe window by. */\n    long long        wait_ewma_ns;\n    /* Pending reactor wait — activated by worker after swap-out so the\n     * reactor never matches a not-yet-suspended fiber. */\n    struct NurlReactorWait *pending_reactor_wait;\n    /* The wait entry of a nurl_fiber_park_with_mutex_ms park, for\n     * nurl_fiber_wake to find and claim — NULL once it is claimed: by the\n     * wake, or by its deadline firing. Read and cleared only under the\n     * reactor's lock; set by the parking fiber before its mutex is\n     * released, so a waker (which must hold that mutex to find the\n     * fiber) never sees a stale value. */\n    struct NurlReactorWait *timed_park;\n    /* This fiber's recover-frame chain and panic journal, exchanged with\n     * the worker thread's around every switch (nurl__rctx_swap in\n     * runtime_core.c, which asserts the size). Zero = no extent open. */\n    void            *rctx[NURL_RCTX_WORDS];\n} NurlFiber;\n\ntypedef struct NurlWorker {\n    pthread_t        thread;\n    int              id;\n    int              started;\n    NurlFiberCtx     loop_ctx;\n    void            *fake_stack;     /* ASan, own-ctx backend only */\n    /* `current` is read by the running fiber and written by the worker\n     * loop on either side of swapcontext. `volatile` defeats the LTO\n     * hoist that would otherwise reuse a stale load across the swap. */\n    NurlFiber *volatile current;\n    NurlFiber       *rq_head;\n    NurlFiber       *rq_tail;\n    pthread_mutex_t  rq_lock;\n    /* Exact queue length, maintained under rq_lock but READ without it\n     * by the steal path's pre-probe. Stealing only ever takes from a\n     * queue holding >= 2 fibers, so a relaxed length read lets a thief\n     * skip empty/singleton victims without touching their lock — under\n     * an HTTP-serving load the steal loop ran ~26 attempts per request\n     * with a 0.008% hit rate, i.e. ~24 futile mutex acquisitions per\n     * request that this probe eliminates. A stale read is benign either\n     * way: a missed just-pushed fiber is found via the global queue or\n     * the next probe round; a spurious probe just falls through to the\n     * locked steal, which re-checks under the lock. */\n    int              rq_len;         /* accessed via __atomic_* only */\n    unsigned         steal_rng;      /* xorshift victim picker */\n    unsigned         tick;           /* dispatch counter (netpoller cadence) */\n} NurlWorker;\n\ntypedef struct NurlScheduler {\n    NurlWorker       workers[NURL_MAX_WORKERS];\n    int              worker_count;\n    NurlFiber       *global_head;\n    NurlFiber       *global_tail;\n    int              global_len;     /* maintained under global_lock */\n    /* The head's rseq, 0 when the global queue is empty: written under\n     * global_lock, read without it (__atomic_*) by a worker deciding\n     * whether its own queue's head or the global head is older. */\n    unsigned long long global_head_rseq;\n    unsigned long long rseq_clock;   /* __atomic_*: last stamp handed out */\n    pthread_mutex_t  global_lock;\n    long long        pending;        /* live fibers (spawned, not freed) */\n    pthread_mutex_t  pending_lock;\n    pthread_cond_t   pending_zero;   /* signalled when pending → 0 */\n    pthread_mutex_t  idle_lock;\n    pthread_cond_t   idle_cv;        /* signalled when work appears */\n    int              idle_waiters;   /* workers parked on idle_cv */\n    int              initialized;\n    volatile int     shutdown;\n} NurlScheduler;\n\nstatic NurlScheduler nurl__sched;\nstatic __thread NurlWorker *nurl__tls_worker = NULL;\n/* An RwLocked holder is the running fiber when there is one (§19b). */\nstatic int nurl__rw_in_fiber(void) { NurlWorker *w = nurl__tls_worker; return w && w->current; }\n\n/* ── Context switching ───────────────────────────────────────────────\n *\n * Every switch goes through one of these two, so the ASan fiber\n * annotations exist in exactly one place per direction. Without them a\n * sanitized build reports every frame a fiber builds as an\n * out-of-bounds write — ASan knows only the thread stack, and nothing\n * intercepts our own switch the way it intercepts swapcontext.\n *\n * The worker loop's stack bounds are the awkward half: they belong to a\n * pthread, and the portable way to learn them is to ask ASan from the\n * far side of a switch. The fiber does exactly that on arrival and\n * stashes them per thread — which is also why a fiber resumed on a\n * DIFFERENT worker still names the right stack: it re-reads the bounds\n * every time it is resumed, from the thread it was resumed on.\n *\n * All of it compiles out entirely when the build is not sanitized. The\n * annotation helpers are already no-ops there, but their out-params are\n * writes to thread-local state that a plain build would still perform —\n * and this is a path that now costs ~24 ns end to end. */\n#if defined(NURL_FIBER_OWN_CTX) && defined(NURL_CTX_ASAN)\n#  define NURL_FIBER_ASAN 1\nstatic __thread const void   *nurl__tls_loop_lo   = NULL;\nstatic __thread unsigned long nurl__tls_loop_size = 0;\n#endif\n\n/* Fiber → worker loop. Returns when the fiber is resumed, which may be\n * on another worker thread entirely. */\nstatic void nurl__swap_to_loop(NurlFiber *f, NurlWorker *w) {\n#ifdef NURL_FIBER_OWN_CTX\n#  ifdef NURL_FIBER_ASAN\n    nurl_ctx_asan_start_switch(&f->fake_stack,\n                               nurl__tls_loop_lo, nurl__tls_loop_size);\n#  endif\n    nurl_ctx_switch(&f->ctx, &w->loop_ctx);\n#  ifdef NURL_FIBER_ASAN\n    nurl_ctx_asan_finish_switch(f->fake_stack,\n                                &nurl__tls_loop_lo, &nurl__tls_loop_size);\n#  endif\n#else\n    swapcontext(&f->ctx, &w->loop_ctx);\n#endif\n}\n\n/* Worker loop → fiber. Returns when the fiber yields, parks or ends. */\nstatic void nurl__swap_to_fiber(NurlWorker *w, NurlFiber *f) {\n#ifdef NURL_FIBER_OWN_CTX\n#  ifdef NURL_FIBER_ASAN\n    nurl_ctx_asan_start_switch(&w->fake_stack, f->stack_lo, f->stack_usable);\n#  endif\n    nurl_ctx_switch(&w->loop_ctx, &f->ctx);\n#  ifdef NURL_FIBER_ASAN\n    nurl_ctx_asan_finish_switch(w->fake_stack, NULL, NULL);\n#  endif\n#else\n    swapcontext(&w->loop_ctx, &f->ctx);\n#endif\n}\n\n/* ── Pending counter ──────────────────────────────────────────────── */\n\nstatic void nurl__pending_inc(void) {\n    pthread_mutex_lock(&nurl__sched.pending_lock);\n    nurl__sched.pending++;\n    pthread_mutex_unlock(&nurl__sched.pending_lock);\n}\n\nstatic void nurl__pending_dec(void) {\n    pthread_mutex_lock(&nurl__sched.pending_lock);\n    long long now = --nurl__sched.pending;\n    if (now <= 0) pthread_cond_broadcast(&nurl__sched.pending_zero);\n    pthread_mutex_unlock(&nurl__sched.pending_lock);\n}\n\n/* ── Service order ───────────────────────────────────────────────────\n *\n * A worker runs its fibers in the order they became runnable. Every\n * push onto a run queue stamps the fiber with the next value of one\n * clock (rseq), so each queue holds its fibers oldest first, and a\n * worker compares the head of its own queue with the global head and\n * takes the older (nurl__rq_pop_oldest). A yield re-stamps: the fiber\n * goes behind everything that became runnable meanwhile.\n *\n * Before this, the local queue simply came first. A fiber unparked or\n * spilled onto the global queue waited until the local queue emptied —\n * or for the 128th-dispatch starvation guard — while everything pushed\n * locally after it (a spawn, a yield, the netpoller's local share) ran\n * ahead. On a saturated worker that is service out of arrival order,\n * and it is the shape tls-bench measured: handshake p50 below rustls's,\n * p99 well above it (agora #113). Now a worker never runs a fiber\n * while an older one waits in its own queue or the global queue, on\n * any path into them, and a queue that never empties cannot starve the\n * other one. (Across workers the order is looser: a peer runs what it\n * holds or steals. Ready I/O enters the order when it is polled — see\n * the cadence poll in nurl__worker_next.)\n *\n * Stamps of different workers interleave in clock order; a steal moves\n * the victim's oldest half with their stamps, so the thief's queue stays\n * ordered too. One relaxed fetch-add per push.\n *\n * Admission. A fiber parked in an accept (nurl_reactor_wait_accept) runs\n * the moment its listener is polled ready, ahead of everything queued:\n * on Linux the netpoller hands it back to run now, and the poll(2)\n * reactor puts it at the head of the global queue with the oldest stamp\n * there is (nurl__fiber_unpark_admit). What it does is cheap — accept and\n * spawn — and it is how the connections waiting in the kernel's queue\n * enter the service order: each one's fiber is stamped when it is\n * spawned. Queued at the tail like any fiber, the accept waited a full\n * pass of the run queue first, and everything that became runnable\n * meanwhile went ahead of connections that had arrived before it: on a\n * saturated server a connection waited one pass in the kernel's queue\n * and another in the run queue, by however much work happened to be\n * queued at each, and tls-bench's handshake latency spread over twice\n * the rustls range (agora #113). The poll(2) reactor is a thread of its\n * own, though, so there the wake itself still waits for that thread's\n * time slice, tens of milliseconds on a loaded FreeBSD or Windows host\n * (agora #137). */\nstatic unsigned long long nurl__rseq_next(void) {\n    return __atomic_add_fetch(&nurl__sched.rseq_clock, 1, __ATOMIC_RELAXED);\n}\n\n/* The stamp of an admitted fiber (see Admission): older than any the\n * clock hands out once it has started, and not 0, which says \"the global\n * queue is empty\". */\n#define NURL_RSEQ_ADMIT 1ULL\n\n/* Publish the global head's stamp (global_lock held). */\nstatic void nurl__global_head_publish(void) {\n    NurlFiber *h = nurl__sched.global_head;\n    __atomic_store_n(&nurl__sched.global_head_rseq, h ? h->rseq : 0ULL,\n                     __ATOMIC_RELAXED);\n}\n\n/* ── Per-worker FIFO runqueue ────────────────────────────────────── */\n\nstatic void nurl__rq_push_local(NurlWorker *w, NurlFiber *f) {\n    f->next = NULL;\n    f->rseq = nurl__rseq_next();\n    pthread_mutex_lock(&w->rq_lock);\n    if (w->rq_tail) w->rq_tail->next = f;\n    else            w->rq_head = f;\n    w->rq_tail = f;\n    __atomic_fetch_add(&w->rq_len, 1, __ATOMIC_RELAXED);\n    pthread_mutex_unlock(&w->rq_lock);\n}\n\nstatic NurlFiber *nurl__rq_pop_local(NurlWorker *w) {\n    pthread_mutex_lock(&w->rq_lock);\n    NurlFiber *f = w->rq_head;\n    if (f) {\n        w->rq_head = f->next;\n        if (!w->rq_head) w->rq_tail = NULL;\n        f->next = NULL;\n        __atomic_fetch_sub(&w->rq_len, 1, __ATOMIC_RELAXED);\n    }\n    pthread_mutex_unlock(&w->rq_lock);\n    return f;\n}\n\n/* Half-steal — take the front half of a victim's queue: one fiber to\n * run now, rest pushed onto our own local queue (relative order kept).\n * Empirically balances load faster than single-fiber stealing. */\nstatic NurlFiber *nurl__rq_steal_from(NurlWorker *victim, NurlWorker *thief) {\n    if (victim == thief) return NULL;\n    /* Lock-free pre-probe: stealing only takes from a queue of >= 2, so\n     * skip victims below that without touching their lock (see the\n     * rq_len field note). The locked path below re-counts under the\n     * lock, so a stale probe can only cost a wasted lock, never a\n     * miscounted steal. */\n    if (__atomic_load_n(&victim->rq_len, __ATOMIC_RELAXED) < 2) return NULL;\n    NurlFiber *taken_head = NULL, *taken_tail = NULL;\n    int count = 0;\n    int take = 0;\n    pthread_mutex_lock(&victim->rq_lock);\n    NurlFiber *p = victim->rq_head;  /* runqueues are short — linear count is fine */\n    while (p) { count++; p = p->next; }\n    if (count >= 2) {\n        take = count / 2;\n        taken_head = victim->rq_head;\n        p = taken_head;\n        for (int i = 1; i < take; i++) p = p->next;\n        taken_tail = p;\n        victim->rq_head = p->next;\n        if (!victim->rq_head) victim->rq_tail = NULL;\n        taken_tail->next = NULL;\n        __atomic_fetch_sub(&victim->rq_len, take, __ATOMIC_RELAXED);\n    }\n    pthread_mutex_unlock(&victim->rq_lock);\n    if (!taken_head) return NULL;\n    NurlFiber *next = taken_head;\n    NurlFiber *rest_head = next->next;\n    next->next = NULL;\n    if (rest_head) {\n        pthread_mutex_lock(&thief->rq_lock);\n        NurlFiber *rest_tail = rest_head;\n        int rest = 0;\n        while (rest_tail->next) { rest++; rest_tail = rest_tail->next; }\n        rest++;\n        if (thief->rq_tail) thief->rq_tail->next = rest_head;\n        else                thief->rq_head = rest_head;\n        thief->rq_tail = rest_tail;\n        __atomic_fetch_add(&thief->rq_len, rest, __ATOMIC_RELAXED);\n        pthread_mutex_unlock(&thief->rq_lock);\n    }\n    return next;\n}\n\n/* ── Global runqueue (spawn-from-non-fiber, overflow) ──────────── */\n\n/* Stamped under global_lock, so the global queue is in stamp order even\n * when several threads push at once. */\nstatic void nurl__rq_push_global(NurlFiber *f) {\n    f->next = NULL;\n    pthread_mutex_lock(&nurl__sched.global_lock);\n    f->rseq = nurl__rseq_next();\n    if (nurl__sched.global_tail) nurl__sched.global_tail->next = f;\n    else                         nurl__sched.global_head = f;\n    nurl__sched.global_tail = f;\n    nurl__sched.global_len++;\n    nurl__global_head_publish();\n    pthread_mutex_unlock(&nurl__sched.global_lock);\n}\n\n/* Append a pre-linked chain of n fibers in one lock acquisition — the\n * netpoller's surplus lands here as a single unit, stamped in order. */\nstatic void nurl__rq_push_global_chain(NurlFiber *head, NurlFiber *tail, int n) {\n    if (!head) return;\n    tail->next = NULL;\n    pthread_mutex_lock(&nurl__sched.global_lock);\n    for (NurlFiber *p = head; p; p = p->next) p->rseq = nurl__rseq_next();\n    if (nurl__sched.global_tail) nurl__sched.global_tail->next = head;\n    else                         nurl__sched.global_head = head;\n    nurl__sched.global_tail = tail;\n    nurl__sched.global_len += n;\n    nurl__global_head_publish();\n    pthread_mutex_unlock(&nurl__sched.global_lock);\n}\n\n#if !defined(__linux__)\n/* An admitted fiber at the head of the global queue, stamped as the\n * oldest: the next worker to look runs it before anything it holds.\n * (The poll(2) reactor's path; Linux's netpoller runs it at once.) */\nstatic void nurl__rq_push_global_front(NurlFiber *f) {\n    f->rseq = NURL_RSEQ_ADMIT;\n    pthread_mutex_lock(&nurl__sched.global_lock);\n    f->next = nurl__sched.global_head;\n    nurl__sched.global_head = f;\n    if (!nurl__sched.global_tail) nurl__sched.global_tail = f;\n    nurl__sched.global_len++;\n    nurl__global_head_publish();\n    pthread_mutex_unlock(&nurl__sched.global_lock);\n}\n#endif\n\nstatic NurlFiber *nurl__rq_pop_global(void) {\n    pthread_mutex_lock(&nurl__sched.global_lock);\n    NurlFiber *f = nurl__sched.global_head;\n    if (f) {\n        nurl__sched.global_head = f->next;\n        if (!nurl__sched.global_head) nurl__sched.global_tail = NULL;\n        f->next = NULL;\n        nurl__sched.global_len--;\n        nurl__global_head_publish();\n    }\n    pthread_mutex_unlock(&nurl__sched.global_lock);\n    return f;\n}\n\n/* Take a fair share of the global queue: the first fiber to run now,\n * up to (len / worker_count + 1, capped) more onto the local queue.\n * One global-lock acquisition either way (Go's globrunqget shape).\n * Only with w's local queue EMPTY: the share is appended, and anything\n * already queued locally may be newer than it (see \"Service order\"). */\nstatic NurlFiber *nurl__rq_take_global_batch(NurlWorker *w) {\n    pthread_mutex_lock(&nurl__sched.global_lock);\n    int len = nurl__sched.global_len;\n    if (len <= 0) {\n        pthread_mutex_unlock(&nurl__sched.global_lock);\n        return NULL;\n    }\n    int n = len / nurl__sched.worker_count + 1;\n    if (n > 16) n = 16;\n    /* The share formula exceeds the queue when there is ONE worker\n     * (len/1 + 1 = len + 1): the walk below then steps past the last\n     * fiber and dereferences NULL. That is every single-core deploy —\n     * NURL_WORKERS=1 or a one-CPU container with the per-core default —\n     * the first time a fiber lands on the global queue. */\n    if (n > len) n = len;\n    NurlFiber *head = nurl__sched.global_head;\n    NurlFiber *tail = head;\n    for (int i = 1; i < n; i++) tail = tail->next;\n    nurl__sched.global_head = tail->next;\n    if (!nurl__sched.global_head) nurl__sched.global_tail = NULL;\n    nurl__sched.global_len -= n;\n    nurl__global_head_publish();\n    pthread_mutex_unlock(&nurl__sched.global_lock);\n    tail->next = NULL;\n    NurlFiber *first = head;\n    NurlFiber *rest = first->next;\n    first->next = NULL;\n    if (rest) {\n        pthread_mutex_lock(&w->rq_lock);\n        int cnt = 0;\n        NurlFiber *rt = rest;\n        while (rt->next) { cnt++; rt = rt->next; }\n        cnt++;\n        if (w->rq_tail) w->rq_tail->next = rest;\n        else            w->rq_head = rest;\n        w->rq_tail = rt;\n        __atomic_fetch_add(&w->rq_len, cnt, __ATOMIC_RELAXED);\n        pthread_mutex_unlock(&w->rq_lock);\n    }\n    return first;\n}\n\n/* w's next fiber in service order: the head of its own queue or the\n * global head, whichever became runnable first. With its own queue\n * empty it takes a fair share of the global queue; with both non-empty\n * and the global head older, just that one fiber, so its own queue\n * stays ordered. NULL when both are empty. Only w pushes to its own\n * queue (peers only steal from it), so \"empty\" holds until w pushes. */\nstatic NurlFiber *nurl__rq_pop_oldest(NurlWorker *w) {\n    unsigned long long g =\n        __atomic_load_n(&nurl__sched.global_head_rseq, __ATOMIC_RELAXED);\n    pthread_mutex_lock(&w->rq_lock);\n    NurlFiber *f = w->rq_head;\n    if (f && (g == 0 || f->rseq < g)) {\n        w->rq_head = f->next;\n        if (!w->rq_head) w->rq_tail = NULL;\n        f->next = NULL;\n        __atomic_fetch_sub(&w->rq_len, 1, __ATOMIC_RELAXED);\n        pthread_mutex_unlock(&w->rq_lock);\n        return f;\n    }\n    pthread_mutex_unlock(&w->rq_lock);\n    if (g == 0) return NULL;\n    if (!f) return nurl__rq_take_global_batch(w);\n    NurlFiber *gf = nurl__rq_pop_global();\n    return gf ? gf : nurl__rq_pop_local(w);   /* a peer took it first */\n}\n\n/* Wake one parked worker (if any). Cheap when idle_waiters is 0.\n * On Linux an idle worker may be parked inside epoll_wait rather than\n * on idle_cv — when nobody is on the cv, poke the poller's eventfd so\n * new runnable work (a spawn, a channel unpark) is picked up without\n * waiting for the next I/O event. Declared here, defined in §25. */\n#if defined(__linux__)\nstatic void nurl__np_poke_if_sleeping(void);\n#endif\nstatic void nurl__wake_one(void) {\n    pthread_mutex_lock(&nurl__sched.idle_lock);\n    int waiters = nurl__sched.idle_waiters;\n    if (waiters > 0) pthread_cond_signal(&nurl__sched.idle_cv);\n    pthread_mutex_unlock(&nurl__sched.idle_lock);\n#if defined(__linux__)\n    if (waiters == 0) nurl__np_poke_if_sleeping();\n#endif\n}\n\n/* Wake up to n parked workers with one lock acquisition. */\nstatic void nurl__wake_n(int n) {\n    pthread_mutex_lock(&nurl__sched.idle_lock);\n    int waiters = nurl__sched.idle_waiters;\n    if (n > waiters) n = waiters;\n    for (int i = 0; i < n; i++) pthread_cond_signal(&nurl__sched.idle_cv);\n    pthread_mutex_unlock(&nurl__sched.idle_lock);\n#if defined(__linux__)\n    if (waiters == 0) nurl__np_poke_if_sleeping();\n#endif\n}\n\nstatic void nurl__wake_all(void) {\n    pthread_mutex_lock(&nurl__sched.idle_lock);\n    pthread_cond_broadcast(&nurl__sched.idle_cv);\n    pthread_mutex_unlock(&nurl__sched.idle_lock);\n}\n\n/* ── Fiber lifecycle ─────────────────────────────────────────────── */\n\n#ifdef NURL_FIBER_OWN_CTX\n/* Fiber body, own-ctx backend: the entry takes its argument as a plain\n * pointer, so there is no splitting to undo. */\nstatic void nurl__fiber_entry(void *arg) {\n    NurlFiber *f = (NurlFiber*)arg;\n#ifdef NURL_FIBER_ASAN\n    /* First arrival on this stack. NULL: a fresh stack has no fake\n     * stack to restore. The out-params are the point of the call — this\n     * is where the worker loop's bounds are learned. */\n    nurl_ctx_asan_finish_switch(NULL, &nurl__tls_loop_lo, &nurl__tls_loop_size);\n#endif\n    if (f && f->fn) f->fn(f->env);\n    if (f && f->own_env && f->env) { nurl_closure_drop(f->env); f->env = NULL; }\n    nurl_cc_context_end();   /* the fiber's refcount cycles, if any */\n    if (f) f->state = NF_DONE;\n    NurlWorker *w = nurl__tls_worker;\n    if (w) {\n#ifdef NURL_FIBER_ASAN\n        /* Leaving for good — NULL destroys the fake stack instead of\n         * leaking it, which matters because the worker is about to\n         * munmap the stack this frame sits on. */\n        nurl_ctx_asan_start_switch(NULL, nurl__tls_loop_lo, nurl__tls_loop_size);\n#endif\n        /* Never returns: the loop sees NF_DONE and never resumes us. */\n        nurl_ctx_switch(&f->ctx, &w->loop_ctx);\n    }\n    abort();  /* unreachable: a fiber runs only on a worker */\n}\n#else\n/* makecontext on 64-bit POSIX passes args as int (32 bits) — reassemble\n * the fiber pointer from two halves on entry. */\nstatic void nurl__fiber_entry(unsigned hi, unsigned lo) {\n    uintptr_t p = ((uintptr_t)hi << 32) | (uintptr_t)lo;\n    NurlFiber *f = (NurlFiber*)p;\n    if (f && f->fn) f->fn(f->env);\n    if (f && f->own_env && f->env) { nurl_closure_drop(f->env); f->env = NULL; }\n    nurl_cc_context_end();   /* the fiber's refcount cycles, if any */\n    if (f) f->state = NF_DONE;\n    NurlWorker *w = nurl__tls_worker;\n    if (w) setcontext(&w->loop_ctx);\n    pthread_exit(NULL);  /* unreachable */\n}\n#endif\n\n/* ── fiber-stack cache ──────────────────────────────────────────────\n * Recycled fiber stacks. A fiber-per-connection server churns one\n * stack per accepted conn; paying mmap + mprotect on every spawn and\n * munmap on every exit is 3 syscalls plus — dominating on the free\n * side — cross-CPU TLB-shootdown IPIs for the unmap. All stacks share\n * one geometry (guard + usable, fixed at compile time), so a plain\n * bounded LIFO of base pointers suffices; the freelist link is stored\n * in the (writable) usable area itself, so the cache costs no heap.\n * The guard page of a cached stack stays PROT_NONE — recycled stacks\n * keep their overflow protection. Beyond the cap, stacks fall through\n * to munmap; the cap bounds virtual (not resident) memory at\n * cap × ~68 KB. */\n#define NURL_FIBER_STACK_CACHE_CAP 256\nstatic pthread_mutex_t nurl__stack_cache_lock = PTHREAD_MUTEX_INITIALIZER;\nstatic void  *nurl__stack_cache_head = NULL;\nstatic int    nurl__stack_cache_len  = 0;\n\n/* A fiber stack's memory: `total` bytes whose lowest `guard` bytes fault\n * on any access, so an overflow dies at a deterministic page instead of\n * writing into whatever lies below. NULL when either step fails. */\nstatic long nurl__page_size(void) {\n#ifdef _WIN32\n    SYSTEM_INFO si;\n    GetSystemInfo(&si);\n    return (long)si.dwPageSize;\n#else\n    return sysconf(_SC_PAGESIZE);\n#endif\n}\n\nstatic void nurl__stack_unmap(void *base, size_t total) {\n#ifdef _WIN32\n    (void)total;\n    VirtualFree(base, 0, MEM_RELEASE);\n#else\n    munmap(base, total);\n#endif\n}\n\nstatic void *nurl__stack_map(size_t total, size_t guard) {\n#ifdef _WIN32\n    /* Committed whole, like the POSIX mapping: the TIB's StackLimit is\n     * set to the stack's low end while the fiber runs (runtime_ctx.c), so\n     * nothing relies on Windows' grow-on-touch thread-stack protocol. */\n    void *base = VirtualAlloc(NULL, total, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);\n    if (!base) return NULL;\n    DWORD old;\n    if (!VirtualProtect(base, guard, PAGE_NOACCESS, &old)) {\n        VirtualFree(base, 0, MEM_RELEASE);\n        return NULL;\n    }\n    return base;\n#else\n#if defined(MAP_ANONYMOUS)\n    int mflags = MAP_PRIVATE | MAP_ANONYMOUS;\n#else\n    int mflags = MAP_PRIVATE | MAP_ANON;\n#endif\n    void *base = mmap(NULL, total, PROT_READ | PROT_WRITE, mflags, -1, 0);\n    if (base == MAP_FAILED) return NULL;\n    if (mprotect(base, guard, PROT_NONE) != 0) {\n        munmap(base, total);\n        return NULL;\n    }\n    return base;\n#endif\n}\n\nstatic NurlFiber *nurl__fiber_alloc(void *fn, void *env, int joinable) {\n    NurlFiber *f = (NurlFiber*)calloc(1, sizeof(NurlFiber));\n    if (!f) return NULL;\n\n    long pg = nurl__page_size();\n    if (pg <= 0) pg = 4096;\n    size_t guard = ((size_t)NURL_FIBER_GUARD_BYTES + (size_t)pg - 1)\n                 / (size_t)pg * (size_t)pg;\n    size_t usable = ((size_t)NURL_FIBER_STACK_BYTES + (size_t)pg - 1)\n                  / (size_t)pg * (size_t)pg;\n    size_t total = guard + usable;\n\n    void *base = NULL;\n    pthread_mutex_lock(&nurl__stack_cache_lock);\n    if (nurl__stack_cache_head) {\n        base = nurl__stack_cache_head;\n        nurl__stack_cache_head = *(void**)((char*)base + guard);\n        nurl__stack_cache_len--;\n    }\n    pthread_mutex_unlock(&nurl__stack_cache_lock);\n\n    if (!base) {\n        base = nurl__stack_map(total, guard);\n        if (!base) { free(f); return NULL; }\n    }\n    f->stack_base = base;\n    f->stack_total_sz = total;\n    f->stack_lo = (char*)base + guard;\n    f->stack_usable = usable;\n    f->fake_stack = NULL;\n    f->fn = (void(*)(void*))fn;\n    f->env = env;\n    f->state = NF_RUNNABLE;\n    f->joinable = joinable;\n    if (joinable) {\n        pthread_mutex_init(&f->join_m, NULL);\n        pthread_cond_init(&f->join_c, NULL);\n    }\n\n#ifdef NURL_FIBER_OWN_CTX\n    nurl_ctx_init(&f->ctx, f->stack_lo, (unsigned long)usable,\n                  nurl__fiber_entry, f);\n#else\n    if (getcontext(&f->ctx) != 0) {\n        nurl__stack_unmap(base, total);\n        free(f);\n        return NULL;\n    }\n    f->ctx.uc_stack.ss_sp   = f->stack_lo;\n    f->ctx.uc_stack.ss_size = usable;\n    f->ctx.uc_link          = NULL;     /* worker sets loop_ctx at run-time */\n    uintptr_t p = (uintptr_t)f;\n    unsigned hi = (unsigned)((p >> 32) & 0xFFFFFFFFu);\n    unsigned lo = (unsigned)(p & 0xFFFFFFFFu);\n    makecontext(&f->ctx, (void(*)(void))nurl__fiber_entry, 2,\n                (int)hi, (int)lo);\n#endif\n    return f;\n}\n\nstatic void nurl__fiber_free(NurlFiber *f) {\n    if (!f) return;\n    nurl__rctx_release(f->rctx);\n    if (f->joinable) {\n        pthread_mutex_destroy(&f->join_m);\n        pthread_cond_destroy(&f->join_c);\n    }\n    if (f->stack_base) {\n        /* Recycle onto the stack cache (see its header note); the link\n         * lives at stack_lo, the first writable byte past the guard. */\n        int cached = 0;\n        pthread_mutex_lock(&nurl__stack_cache_lock);\n        if (nurl__stack_cache_len < NURL_FIBER_STACK_CACHE_CAP) {\n            *(void**)f->stack_lo = nurl__stack_cache_head;\n            nurl__stack_cache_head = f->stack_base;\n            nurl__stack_cache_len++;\n            cached = 1;\n        }\n        pthread_mutex_unlock(&nurl__stack_cache_lock);\n        if (!cached) nurl__stack_unmap(f->stack_base, f->stack_total_sz);\n    }\n    free(f);\n}\n\n/* Enqueue a fresh fiber — local queue if on a worker, else global.\n * Bumps pending and wakes one idle worker. */\nstatic void nurl__enqueue_new(NurlFiber *f) {\n    nurl__pending_inc();\n    NurlWorker *w = nurl__tls_worker;\n    if (w) nurl__rq_push_local(w, f);\n    else   nurl__rq_push_global(f);\n    nurl__wake_one();\n}\n\n/* A fiber that could not be created cannot be handed back. `spawn` in\n * stdlib/std/async.nu returns a Fiber, not a Result, so a zero here\n * becomes a handle to nothing: the body never runs and the program\n * reports whatever the missing worker's absence looks like — measured\n * on a small machine, a program that asked for 200 fibers printed the\n * count of the 11 it got and exited 0. The runtime already aborts when\n * malloc fails; a fiber stack is an allocation like any other. */\nstatic void nurl__fiber_spawn_failed(void) {\n    fprintf(stderr, \"nurl: cannot create a fiber — out of memory for its \"\n                    \"stack (or the stack could not be protected)\\n\");\n    fflush(stderr);\n    fflush(stdout);\n    abort();\n}\n\n/* noinline on the whole spawn family: nurl__enqueue_new reads the\n * __thread nurl__tls_worker, and letting LTO inline a spawn into NURL\n * code mislowers that TLS access (same class as nurl_fiber_current\n * below — measured as a fiber enqueued onto a queue no worker ever\n * drains: the async HTTP test hung deterministically the moment a NEW\n * spawn entry point was added and inlined, while the existing ones\n * dodged it only by the optimizer's inlining choices). */\n__attribute__((noinline))\nlong long nurl_fiber_spawn(void *fn, void *env) {\n    if (!fn) return 0;\n    if (!nurl__sched.initialized) nurl_runtime_init(0);\n    NurlFiber *f = nurl__fiber_alloc(fn, nurl_closure_own_copy(env), 0);\n    if (!f) nurl__fiber_spawn_failed();\n    f->own_env = 1;\n    nurl__enqueue_new(f);\n    return (long long)(uintptr_t)f;\n}\n\n/* Every spawn runs on its OWN copy of the closure env (docs/MEMORY.md\n * §7.4), dropped after the fiber body returns (see NurlFiber.own_env):\n * the spawner's env stays the spawner's, so a closure spawned a hundred\n * times — or spawned and then dropped at the end of the spawner's scope —\n * is never read after its owner released it. `_owned` is the historical\n * name for the same thing. */\n__attribute__((noinline))\nlong long nurl_fiber_spawn_owned(void *fn, void *env) {\n    return nurl_fiber_spawn(fn, env);\n}\n\n__attribute__((noinline))\nlong long nurl_fiber_spawn_joinable(void *fn, void *env) {\n    if (!fn) return 0;\n    if (!nurl__sched.initialized) nurl_runtime_init(0);\n    NurlFiber *f = nurl__fiber_alloc(fn, nurl_closure_own_copy(env), 1);\n    if (!f) nurl__fiber_spawn_failed();\n    f->own_env = 1;\n    nurl__enqueue_new(f);\n    return (long long)(uintptr_t)f;\n}\n\n/* noinline keeps the __thread TLS load on this side of the LTO\n * boundary — LTO across the NURL/C edge mislowered the TLS access. */\n__attribute__((noinline))\nvoid nurl_fiber_yield(void) {\n    NurlWorker *w = nurl__tls_worker;\n    if (!w) return;\n    NurlFiber *cur = w->current;\n    if (!cur) return;\n    cur->state = NF_RUNNABLE;\n    /* Do NOT publish to the run queue here. If we pushed before the\n     * swap-out, another worker could steal this fiber, run it to\n     * completion, and free it — all before the worker loop finishes\n     * reading f->state after the swap returns (a heap-use-after-free).\n     * The worker loop re-queues it (see the NF_RUNNABLE branch) only\n     * once it is done inspecting the fiber, keeping it private until its\n     * context is fully saved. */\n    nurl__swap_to_loop(cur, w);\n}\n\n__attribute__((noinline))\nlong long nurl_fiber_current(void) {\n    NurlWorker *w = nurl__tls_worker;\n    if (!w) return 0;\n    NurlFiber *cur = __atomic_load_n(&w->current, __ATOMIC_SEQ_CST);\n    return (long long)(uintptr_t)cur;\n}\n\n__attribute__((noinline))\nlong long nurl_fiber_worker_id(void) {\n    NurlWorker *w = nurl__tls_worker;\n    return w ? (long long)w->id : -1;\n}\n\n/* Park-with-mutex from inside a fiber; caller holds `mutex_h`.\n *\n * Lost-wakeup-free handoff:\n *   1. Caller registers the fiber on the waiter list under the mutex,\n *      then calls this — pending_unlock = mutex_h, state = PARKED,\n *      swap-out into the worker loop.\n *   2. Worker observes PARKED and releases pending_unlock AFTER swap-\n *      out completes. Only now can a sender grab the mutex and see the\n *      stable PARKED state.\n *   3. Sender pops the waiter and nurl_fiber_unparks it; state goes to\n *      RUNNABLE, global queue, wake-one. Resumed fiber re-locks the\n *      mutex and re-checks its predicate. */\n__attribute__((noinline))\nvoid nurl_fiber_park_with_mutex(long long mutex_h) {\n    NurlWorker *w = nurl__tls_worker;\n    if (!w) return;\n    NurlFiber *cur = w->current;\n    if (!cur) return;\n    pthread_mutex_t *m = (pthread_mutex_t *)(uintptr_t)mutex_h;\n    cur->pending_unlock = m;\n    cur->state = NF_PARKED;\n    nurl__swap_to_loop(cur, w);\n}\n\nvoid nurl_fiber_unpark(long long fiber_h) {\n    NurlFiber *f = (NurlFiber*)(uintptr_t)fiber_h;\n    if (!f) return;\n    f->state = NF_RUNNABLE;\n    nurl__rq_push_global(f);\n    nurl__wake_one();\n}\n\n#if !defined(__linux__)\n/* nurl_fiber_unpark for a fiber parked in an accept: it runs next\n * (Admission, under \"Service order\"). */\nstatic void nurl__fiber_unpark_admit(NurlFiber *f) {\n    if (!f) return;\n    f->state = NF_RUNNABLE;\n    nurl__rq_push_global_front(f);\n    nurl__wake_one();\n}\n#endif\n\n/* Block until the target fiber's body returns, then free it. Calling\n * join twice on the same handle is undefined. */\nvoid nurl_fiber_join(long long fiber) {\n    NurlFiber *f = (NurlFiber*)(uintptr_t)fiber;\n    if (!f || !f->joinable) return;\n    pthread_mutex_lock(&f->join_m);\n    while (!f->released) {\n        pthread_cond_wait(&f->join_c, &f->join_m);\n    }\n    f->done_taken = 1;\n    pthread_mutex_unlock(&f->join_m);\n    nurl__fiber_free(f);\n}\n\n/* ── Worker loop ─────────────────────────────────────────────────── */\n\n#if defined(__linux__)\n#include <sched.h>   /* sched_getaffinity, CPU_COUNT (under _GNU_SOURCE) */\n/* ceil(quota / period) from a cgroup cpu limit, or 0 when unlimited or\n * unreadable. v2: one file \"cpu.max\" = \"<quota|max> <period>\"; v1: two\n * files cpu.cfs_quota_us (-1 = none) and cpu.cfs_period_us. */\nstatic long long nurl__cgroup_cpu_limit(void) {\n    char buf[64];\n    FILE *f = fopen(\"/sys/fs/cgroup/cpu.max\", \"r\");\n    if (f) {\n        long long quota = 0, period = 0;\n        int ok = fscanf(f, \"%lld %lld\", &quota, &period) == 2;\n        fclose(f);\n        if (ok && quota > 0 && period > 0) return (quota + period - 1) / period;\n        /* \"max <period>\" fails the %lld on \"max\": unlimited */\n        return 0;\n    }\n    f = fopen(\"/sys/fs/cgroup/cpu/cpu.cfs_quota_us\", \"r\");\n    if (!f) f = fopen(\"/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us\", \"r\");\n    if (f) {\n        long long quota = -1, period = 0;\n        if (!fgets(buf, sizeof buf, f)) buf[0] = 0;\n        fclose(f);\n        quota = atoll(buf);\n        if (quota <= 0) return 0;\n        FILE *p = fopen(\"/sys/fs/cgroup/cpu/cpu.cfs_period_us\", \"r\");\n        if (!p) p = fopen(\"/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us\", \"r\");\n        if (!p) return 0;\n        if (!fgets(buf, sizeof buf, p)) buf[0] = 0;\n        fclose(p);\n        period = atoll(buf);\n        if (period <= 0) return 0;\n        return (quota + period - 1) / period;\n    }\n    return 0;\n}\n#endif\n\n/* HOSTED ONLY (this file is the half a freestanding target does not\n * have; the unikernel's cooperative runtime never sizes a pool).\n * How many threads can run at once FOR THIS PROCESS — the number the\n * fiber scheduler sizes its worker pool by. Not the machine's CPU count:\n * a process pinned with taskset/cpuset to 6 of 12 hardware threads, or\n * capped by a container's cgroup CPU quota, gets exactly that many\n * runnable threads, and every worker beyond it is a thread the kernel\n * has to time-slice against a sibling. Measured on the reference host\n * (12 hardware threads, server pinned to cores 0-5): the online count\n * gave 12 workers on 6 cores — 0.75 context switches and 0.2 CPU\n * migrations PER REQUEST at 20 connections, 124k req/s at p50 0.135 ms;\n * six workers gave 153k at p50 0.115 ms with 50x fewer migrations. This\n * is what Rust's std::thread::available_parallelism() returns, and thus\n * what the tokio peer sizes itself by. Falls back to nurl_cpu_count. */\nlong long nurl_available_parallelism(void) {\n    long long n = nurl_cpu_count();\n#if defined(__linux__)\n    {\n        cpu_set_t set;\n        CPU_ZERO(&set);\n        if (sched_getaffinity(0, sizeof set, &set) == 0) {\n            long long c = (long long)CPU_COUNT(&set);\n            if (c > 0 && c < n) n = c;\n        }\n        long long q = nurl__cgroup_cpu_limit();\n        if (q > 0 && q < n) n = q;\n    }\n#elif defined(_WIN32)\n    {\n        DWORD_PTR pmask = 0, smask = 0;\n        if (GetProcessAffinityMask(GetCurrentProcess(), &pmask, &smask) && pmask) {\n            long long c = 0;\n            while (pmask) { c += (long long)(pmask & 1); pmask >>= 1; }\n            if (c > 0 && c < n) n = c;\n        }\n    }\n#endif\n    return n > 0 ? n : 1;\n}\n\n/* ── Spin-before-park ─────────────────────────────────────────────\n * Before a fiber parks on an fd, a worker with nothing else to run may\n * probe the fd for a while instead: a park and its wake cost a blocking\n * epoll_wait, a scheduler wake-up and a context switch, and on a\n * keep-alive connection the peer's next request often lands within\n * tens of microseconds. At c=1 that is the difference between ~12 k and\n * ~28 k req/s at half the latency (and, where the kernel's park path is\n * expensive, lower CPU per request too). But a probe window that does\n * NOT end in readiness is CPU burned for nothing — a client with think\n * time between requests, an idle keep-alive connection, a listener:\n * spinning a fixed 256 poll(2) calls (80 to 250 µs, by the machine's\n * syscall cost) on each of those multiplied the CPU of a lightly loaded\n * server several times over.\n *\n * So each fiber keeps an estimate of how long its waits take — every\n * wait counts, a spin that caught the fd and a park alike, measured\n * from the start of the wait to its end — and spins only when that\n * estimate is below the cap, for twice the estimate (plus a margin), or\n * for the whole cap while it has no estimate yet. A sample is clamped\n * at twice the cap: a wait that long says \"do not spin\" as clearly as a\n * longer one, and one stray stall must not keep a steady peer unspun\n * for long. The cap is NURL_SPIN_US (µs; 0 = never spin), else 100 µs,\n * else 0 under a cgroup CPU quota or with one usable CPU, where a burned\n * core is somebody else's time. As before only a runtime with at most\n * four live fibers spins at all: a loaded server's workers always have\n * something to run, and spinning there was measured to cost 15-20 % of\n * c=10..50 throughput.\n *\n * The reads of `pending` and the queues are racy by design: a stale\n * answer only mis-sizes a spin, never loses a wakeup — the park that\n * follows re-registers with the poller from scratch. */\nstatic long long nurl__spin_cap_ns = -1;   /* -1 = not decided yet */\n\nstatic long long nurl__spin_cap(void) {\n    long long cap = __atomic_load_n(&nurl__spin_cap_ns, __ATOMIC_RELAXED);\n    if (cap >= 0) return cap;\n    const char *e = getenv(\"NURL_SPIN_US\");\n    if (e && *e) {\n        cap = atoll(e) * 1000LL;\n    } else {\n        cap = 100000LL;\n        if (nurl_available_parallelism() < 2) cap = 0;\n#if defined(__linux__)\n        if (nurl__cgroup_cpu_limit() > 0) cap = 0;\n#endif\n    }\n    if (cap < 0) cap = 0;\n    __atomic_store_n(&nurl__spin_cap_ns, cap, __ATOMIC_RELAXED);\n    return cap;\n}\n\n/* Fold one observed wait into the fiber's estimate (weight 1/4). */\nstatic void nurl__spin_observe(NurlFiber *f, long long waited, long long cap) {\n    if (waited < 0) waited = 0;\n    if (waited > 2 * cap) waited = 2 * cap;\n    f->wait_ewma_ns += (waited - f->wait_ewma_ns) / 4;\n}\n\n/* 1 when `fd` became ready for `events` within the fiber's spin window\n * (the caller skips the park), 0 to park. `t0` is the wait's start. */\nstatic int nurl__spin_ready(NurlWorker *w, NurlFiber *f, long long fd, short events,\n                            long long t0, long long cap) {\n    if (fd < 0 || cap <= 0) return 0;\n    if (__atomic_load_n(&nurl__sched.pending, __ATOMIC_RELAXED) > 4) return 0;\n    long long est = f->wait_ewma_ns;\n    if (est >= cap) return 0;\n    long long budget = est == 0 ? cap : 2 * est + 10000LL;\n    if (budget > cap) budget = cap;\n    for (;;) {\n        /* Other work for this worker: park now and run it. */\n        if (__atomic_load_n(&w->rq_len, __ATOMIC_RELAXED) > 0) return 0;\n        if (__atomic_load_n(&nurl__sched.global_head, __ATOMIC_RELAXED)) return 0;\n        nurl_pollfd_t probe;\n        probe.fd = (nurl_sockfd_t)fd;\n        probe.events = events;\n        probe.revents = 0;\n        int pr = nurl__poll(&probe, 1, 0);\n        long long t = nurl_monotonic_ns() - t0;\n        if (pr > 0) { nurl__spin_observe(f, t, cap); return 1; }\n#ifdef _WIN32\n        if (pr < 0) return 0;          /* WSAPoll has no EINTR */\n#else\n        if (pr < 0 && errno != EINTR) return 0;\n#endif\n        if (t >= budget) return 0;\n    }\n}\n\n/* xorshift32 — branch-free victim picker. */\nstatic unsigned nurl__rng_next(unsigned *st) {\n    unsigned x = *st;\n    if (x == 0) x = 0x9E3779B9u;\n    x ^= x << 13;\n    x ^= x >> 17;\n    x ^= x << 5;\n    *st = x;\n    return x;\n}\n\n/* Netpoller hooks (Linux): an idle worker BECOMES the I/O poller —\n * it takes the poll lease and blocks in epoll_wait itself, and a ready\n * fiber is resumed directly on that worker with no queue hop, no\n * cross-thread wake chain and no core migration. Definitions live in\n * §25; declared here because the worker loop precedes it. */\n#if defined(__linux__)\nstatic NurlFiber *nurl__np_poll(NurlWorker *w, int *held);\nstatic NurlFiber *nurl__np_poll_nowait(NurlWorker *w);\nstatic void       nurl__np_poke_if_sleeping(void);\n#endif\n\n/* Next runnable for `w`: the oldest of its own queue and the global\n * queue → steal up to 2×worker_count peers → global → block in the\n * netpoller (Linux; one worker at a time) → park on idle_cv. NULL on\n * shutdown. */\nstatic NurlFiber *nurl__worker_next(NurlWorker *w) {\n    for (;;) {\n        if (nurl__sched.shutdown) return NULL;\n        unsigned tick = ++w->tick;\n#if defined(__linux__)\n        /* Cadence poll: drain the epoll instance every 16th dispatch\n         * whatever is queued. I/O that is ready but not yet polled has\n         * no place in the service order at all, and everything that\n         * becomes runnable meanwhile goes ahead of it; polling only once\n         * the local queue emptied (or every 128th dispatch) let a busy\n         * worker leave a ready connection in the epoll instance for\n         * hundreds of dispatches. What the poll wakes joins the TAIL\n         * (nurl__np_hand_out). A saturated pool also no longer lives in\n         * feast-famine cycles, nobody polling until the queues exhaust\n         * and the whole pool then stalling on one big poll. */\n        if (__builtin_expect((tick & 15u) == 0, 0)) {\n            NurlFiber *nf = nurl__np_poll_nowait(w);\n            if (nf) return nf;\n        }\n#else\n        (void)tick;\n#endif\n        NurlFiber *f = nurl__rq_pop_oldest(w);\n        if (f) return f;\n#if defined(__linux__)\n        {\n            NurlFiber *nf = nurl__np_poll_nowait(w);\n            if (nf) return nf;\n        }\n#endif\n        int wc = nurl__sched.worker_count;\n        for (int tries = 0; tries < wc * 2; tries++) {\n            int victim_id = (int)(nurl__rng_next(&w->steal_rng) % (unsigned)wc);\n            NurlWorker *victim = &nurl__sched.workers[victim_id];\n            if (victim == w) continue;\n            NurlFiber *st = nurl__rq_steal_from(victim, w);\n            if (st) {\n                /* Wake cascade: a steal takes HALF the victim's queue,\n                 * so a haul that leaves fibers in our own queue means\n                 * there is more parallelism to hand out — recruit the\n                 * next idle peer, which steals half of ours and wakes\n                 * the next in turn. One wake per doubling instead of\n                 * one wake per fiber: the netpoller's batch (up to 128\n                 * ready fibers pushed to one worker's queue) fans out\n                 * across the pool in log2 steps without a global-queue\n                 * convoy. */\n                if (__atomic_load_n(&w->rq_len, __ATOMIC_RELAXED) > 0)\n                    nurl__wake_one();\n                return st;\n            }\n        }\n        f = nurl__rq_take_global_batch(w);\n        if (f) return f;\n#if defined(__linux__)\n        {\n            /* Try to become the poller. Held-but-empty (a wake poke, a\n             * timer pass) loops back to re-check the queues; lease\n             * taken by a peer falls through to the idle park, from\n             * which the poller's own wake_one calls recruit us. */\n            int held = 0;\n            NurlFiber *nf = nurl__np_poll(w, &held);\n            if (nf) return nf;\n            if (held) continue;\n        }\n#endif\n        pthread_mutex_lock(&nurl__sched.idle_lock);\n        if (nurl__sched.shutdown) {\n            pthread_mutex_unlock(&nurl__sched.idle_lock);\n            return NULL;\n        }\n        nurl__sched.idle_waiters++;\n        /* Re-check the queues now that we are counted as a waiter. A\n         * spawn that pushed after our last look and then ran wake_one\n         * BEFORE we took idle_lock saw idle_waiters == 0 and signalled\n         * nobody — parking now would sleep through that fiber for good\n         * (runtime_init 1 + spawn + runtime_run hung with both threads\n         * on a futex, 1 run in ~12 under load). The pusher releases\n         * global_lock before it takes idle_lock, so its push is visible\n         * here; a push after this point finds us counted and signals. */\n        if (__atomic_load_n(&nurl__sched.global_len, __ATOMIC_ACQUIRE) > 0 ||\n            __atomic_load_n(&w->rq_len, __ATOMIC_ACQUIRE) > 0) {\n            nurl__sched.idle_waiters--;\n            pthread_mutex_unlock(&nurl__sched.idle_lock);\n            continue;\n        }\n        pthread_cond_wait(&nurl__sched.idle_cv, &nurl__sched.idle_lock);\n        nurl__sched.idle_waiters--;\n        pthread_mutex_unlock(&nurl__sched.idle_lock);\n    }\n}\n\nstatic void *nurl__worker_loop(void *arg) {\n    NurlWorker *w = (NurlWorker*)arg;\n    nurl__tls_worker = w;\n    w->steal_rng = (unsigned)(0xC2B2AE35u ^ (unsigned)(w->id * 2654435761u));\n    for (;;) {\n        NurlFiber *f = nurl__worker_next(w);\n        if (!f) break;     /* shutdown */\n        f->state = NF_RUNNING;\n        __atomic_store_n(&w->current, f, __ATOMIC_SEQ_CST);\n        /* The fiber's recovery state in, the worker's out — and back\n         * after it suspends or ends (see NurlFiber.rctx). */\n        nurl__rctx_swap(f->rctx);\n        nurl__swap_to_fiber(w, f);\n        nurl__rctx_swap(f->rctx);\n        __atomic_store_n(&w->current, (NurlFiber*)NULL, __ATOMIC_SEQ_CST);\n        if (f->state == NF_DONE) {\n            if (f->joinable) {\n                pthread_mutex_lock(&f->join_m);\n                f->released = 1;\n                pthread_cond_broadcast(&f->join_c);\n                pthread_mutex_unlock(&f->join_m);\n                /* Joiner does the free. */\n            } else {\n                nurl__fiber_free(f);\n            }\n            nurl__pending_dec();\n        } else if (f->state == NF_PARKED) {\n            /* Release pending_unlock AFTER swap-out so the waker only\n             * observes the fiber once its context is fully saved. Same\n             * race-closer for reactor parks (activate then poke). Capture\n             * BOTH handoff fields before releasing the mutex: once pm is\n             * unlocked a sender may unpark (and eventually free) the\n             * fiber, so reading f->pending_reactor_wait afterward would\n             * race. */\n            extern void nurl__reactor_activate(struct NurlReactorWait *w);\n            pthread_mutex_t *pm = f->pending_unlock;\n            struct NurlReactorWait *prw = f->pending_reactor_wait;\n            f->pending_unlock = NULL;\n            f->pending_reactor_wait = NULL;\n            if (pm) pthread_mutex_unlock(pm);\n            if (prw) nurl__reactor_activate(prw);\n        } else {\n            /* NF_RUNNABLE — the fiber yielded. Publish it to our local\n             * run queue now, after all reads of f above; before this\n             * point the fiber is private to this worker, which closes the\n             * steal-then-free race against the f->state read. */\n            nurl__rq_push_local(w, f);\n        }\n    }\n    nurl__tls_worker = NULL;\n    return NULL;\n}\n\n/* ── Runtime lifecycle ────────────────────────────────────────────── */\n\n/* Worker pthreads the M:N runtime runs (0 before runtime_init). */\nlong long nurl_runtime_worker_count(void) {\n    return nurl__sched.initialized ? (long long)nurl__sched.worker_count : 0;\n}\n\nvoid nurl_runtime_init(long long worker_count) {\n    if (nurl__sched.initialized) return;\n\n    pthread_mutex_init(&nurl__sched.global_lock, NULL);\n    pthread_mutex_init(&nurl__sched.pending_lock, NULL);\n    pthread_cond_init(&nurl__sched.pending_zero, NULL);\n    pthread_mutex_init(&nurl__sched.idle_lock, NULL);\n    pthread_cond_init(&nurl__sched.idle_cv, NULL);\n    nurl__sched.idle_waiters = 0;\n    nurl__sched.pending = 0;\n    nurl__sched.shutdown = 0;\n    nurl__sched.global_head = nurl__sched.global_tail = NULL;\n    nurl__sched.global_len = 0;\n\n    int wc = (int)worker_count;\n    if (wc <= 0) {\n        const char *env = getenv(\"NURL_WORKERS\");\n        if (env && *env) wc = (int)nurl__parse_ul(env, NULL);\n        if (wc <= 0) {\n            /* One worker per thread this PROCESS may run — the affinity\n             * mask and the cgroup quota, not the machine's CPU count\n             * (see nurl_available_parallelism in runtime_core.c for the\n             * measurement that made the difference visible). */\n            long long n = nurl_available_parallelism();\n            wc = (n > 0) ? (int)n : 2;\n        }\n    }\n    if (wc < 1) wc = 1;\n    if (wc > NURL_MAX_WORKERS) wc = NURL_MAX_WORKERS;\n    nurl__sched.worker_count = wc;\n\n    for (int i = 0; i < wc; i++) {\n        NurlWorker *w = &nurl__sched.workers[i];\n        w->id = i;\n        w->started = 0;\n        w->current = NULL;\n        w->fake_stack = NULL;\n        w->rq_head = w->rq_tail = NULL;\n        __atomic_store_n(&w->rq_len, 0, __ATOMIC_RELAXED);\n        pthread_mutex_init(&w->rq_lock, NULL);\n        w->steal_rng = (unsigned)(0xC2B2AE35u ^ (unsigned)(i * 2654435761u));\n    }\n    nurl__sched.initialized = 1;\n\n    for (int i = 0; i < wc; i++) {\n        NurlWorker *w = &nurl__sched.workers[i];\n        if (pthread_create(&w->thread, NULL, nurl__worker_loop, w) == 0) {\n            w->started = 1;\n        }\n    }\n}\n\n/* Block until pending fibers reach zero. Workers stay alive between\n * calls; only runtime_shutdown exits them. */\nvoid nurl_runtime_run(void) {\n    if (!nurl__sched.initialized) nurl_runtime_init(0);\n    pthread_mutex_lock(&nurl__sched.pending_lock);\n    while (nurl__sched.pending > 0 && !nurl__sched.shutdown) {\n        pthread_cond_wait(&nurl__sched.pending_zero, &nurl__sched.pending_lock);\n    }\n    pthread_mutex_unlock(&nurl__sched.pending_lock);\n}\n\nvoid nurl_runtime_shutdown(void) {\n    if (!nurl__sched.initialized) return;\n    nurl__sched.shutdown = 1;\n    nurl__wake_all();\n    pthread_mutex_lock(&nurl__sched.pending_lock);\n    pthread_cond_broadcast(&nurl__sched.pending_zero);\n    pthread_mutex_unlock(&nurl__sched.pending_lock);\n    /* Stop the reactor first — it may hold refs to fibers the workers\n     * are about to free. */\n    extern void nurl__reactor_shutdown(void);\n    nurl__reactor_shutdown();\n    /* Workers must be joined before scheduler state is torn down — ALL of\n     * them before any run-queue lock goes: a worker not yet joined may\n     * still be stealing from one that has been. Destroying worker i's\n     * rq_lock right after joining i let a later worker lock the destroyed\n     * mutex; glibc then refuses the unlock (EINVAL on __kind -1), the lock\n     * stays held by a thread that has exited, and every other worker —\n     * and this join — waits on it for good (async_basic, ~1 run in 100\n     * under load). */\n    for (int i = 0; i < nurl__sched.worker_count; i++) {\n        NurlWorker *w = &nurl__sched.workers[i];\n        if (w->started) pthread_join(w->thread, NULL);\n        w->started = 0;\n    }\n    for (int i = 0; i < nurl__sched.worker_count; i++)\n        pthread_mutex_destroy(&nurl__sched.workers[i].rq_lock);\n    /* Post-join half of the netpoller teardown (Linux frees its state\n     * here — a worker could still be inside it before the join). */\n    extern void nurl__reactor_cleanup(void);\n    nurl__reactor_cleanup();\n    pthread_mutex_destroy(&nurl__sched.global_lock);\n    pthread_mutex_destroy(&nurl__sched.pending_lock);\n    pthread_cond_destroy(&nurl__sched.pending_zero);\n    pthread_mutex_destroy(&nurl__sched.idle_lock);\n    pthread_cond_destroy(&nurl__sched.idle_cv);\n    nurl__sched.initialized = 0;\n    nurl__sched.worker_count = 0;\n}\n\n#if defined(__APPLE__)\n#  pragma clang diagnostic pop\n#endif\n\n#elif !(defined(__wasi__) && defined(__wasm_atomics__))\n/* no fiber backend (wasm without threads, an ISA with no switch) — stubs */\n\nlong long nurl_fiber_spawn(void *fn, void *env) {\n    (void)fn; (void)env; return 0;\n}\nlong long nurl_fiber_spawn_owned(void *fn, void *env) {\n    (void)fn; (void)env; return 0;\n}\nlong long nurl_fiber_spawn_joinable(void *fn, void *env) {\n    (void)fn; (void)env; return 0;\n}\nvoid      nurl_fiber_join(long long fiber) { (void)fiber; }\nvoid      nurl_fiber_yield(void) {}\nlong long nurl_fiber_current(void) { return 0; }\nlong long nurl_fiber_worker_id(void) { return -1; }\nvoid      nurl_fiber_park_with_mutex(long long mutex_h) { (void)mutex_h; }\nvoid      nurl_fiber_unpark(long long fiber) { (void)fiber; }\nvoid      nurl_runtime_init(long long worker_count) { (void)worker_count; }\nlong long nurl_runtime_worker_count(void) { return 1; }\nvoid      nurl_runtime_run(void) {}\nvoid      nurl_runtime_shutdown(void) {}\n\n#endif /* fiber platform guard */\n#ifndef NURL_HAVE_FIBERS\n/* No fiber migrates between threads here: a lock's holder is the thread. */\nstatic int nurl__rw_in_fiber(void) { return 0; }\n#endif\n\n/* ── §25  I/O reactor + timer wheel ─────────────────────────────── */\n/*\n * One reactor thread runs poll(2) over a dynamic (fd, events) list.\n * Async wrappers on EAGAIN/EWOULDBLOCK call nurl_reactor_wait_*, which\n * registers (fd, events, deadline) → current fiber, pokes the wake\n * pipe, and parks. On readiness (or deadline) the reactor unparks the\n * fiber and drops the entry. sleep_ms registers without an fd — just a\n * deadline; poll's timeout is min(next_deadline, INFINITE).\n *\n * poll(2), or WSAPoll on Windows. kqueue/IOCP wait for a real 10 K+ consumer.\n * WASI falls through to the stub block.\n *\n * ABI: nurl_reactor_wait_read(fd, timeout_ms) → 1 ready / 0 timeout /\n * -1 not on a fiber. Same shape for _write. nurl_fiber_sleep_ms always\n * returns 0. */\n\nlong long nurl_reactor_wait_read(long long fd, long long timeout_ms);\nlong long nurl_reactor_wait_write(long long fd, long long timeout_ms);\n/* Does this runtime multiplex everything onto the caller's thread?\n * Here: no. There is an operating system underneath and the fibers ride\n * real threads, so a thread that blocks blocks only itself. The\n * freestanding twin (unikernel/runtime_bare.c) answers 1, and\n * `std/time.nu`'s `sleep_ms` is the caller that needs to know: a plain\n * sleep in the main context is harmless here and stops the whole\n * machine there. */\nlong long nurl_runtime_is_cooperative(void) { return 0; }\n\nlong long nurl_fiber_sleep_ms(long long ms);\n\n/* The reactor is built on the fiber primitives, so it lives or dies with\n * them — one macro, not a second copy of the platform condition. */\n#ifdef NURL_HAVE_FIBERS\n#ifndef _WIN32\n#include <poll.h>\n#include <fcntl.h>\n#endif\n#include <errno.h>\n\n#if defined(__linux__)\n/* ── Linux backend: the epoll netpoller ──────────────────────────────\n *\n * There is no reactor THREAD on Linux any more. The old design ran a\n * dedicated poll(2) thread: every I/O park cost a pipe poke to the\n * reactor, a full O(waiters) rebuild of the pollfd array, a second\n * walk to match revents, a global-queue push and a cond-var wake of\n * some worker — measured at ~4.5 futex calls, ~1.5 wake-pipe\n * reads/writes and 0.44 CPU MIGRATIONS per HTTP request, because the\n * woken fiber resumed on whichever worker won the global queue.\n *\n * Now an IDLE WORKER becomes the poller (the Go netpoller / tokio\n * driver shape): it takes the poll lease and blocks in epoll_wait\n * itself. A ready fiber is resumed DIRECTLY on that worker — no queue,\n * no second wake, same core, warm cache. Of the surplus, the oldest\n * few go to this worker's own queue and the rest to the global queue,\n * which recruits peers (nurl__np_hand_out); the lease itself stays\n * sticky — no replacement-poller wake when it is released, a peer that\n * goes idle takes it. Registrations are per-fd and PERSISTENT — one\n * EPOLL_CTL_ADD with EPOLLIN|EPOLLOUT|EPOLLET, never re-armed (see\n * nurl__np_slot) — so the per-event cost does not scale with the\n * number of parked connections; the wake pipe is an eventfd; deadlines\n * live in a min-heap.\n *\n * Waiters on one fd chain (a read fiber and a write fiber may park on\n * the same socket); an edge wakes the waiters whose direction it\n * carries, and a direction nobody waits on is banked in the slot's\n * ready bits for the next wait's fast path. A closed or\n * unpollable fd cannot deliver an epoll event (close silently drops\n * the registration), so every close/shutdown path that could strand a\n * parked waiter calls nurl__reactor_wake_if_started(), which sets a\n * rescan flag: the poller then probes every registered fd with a\n * zero-timeout poll(2) and wakes waiters the old full-rescan loop\n * would have woken (POLLNVAL/HUP included).\n *\n * ABI (unchanged): nurl_reactor_wait_read(fd, timeout_ms) → 1 ready /\n * 0 timeout / -1 not on a fiber; same for _write; sleep_ms parks on a\n * pure deadline. Entries are registered INACTIVE by the parking fiber\n * and armed by the worker loop AFTER swap-out completes\n * (nurl__reactor_activate), which closes the unpark-before-park\n * double-execution race exactly as before — with epoll the \"arm\" IS\n * the activation, so an event physically cannot fire early. */\n#include <sys/epoll.h>\n#include <sys/eventfd.h>\n\ntypedef struct NurlReactorWait {\n    int                     fd;          /* -1 = pure timer */\n    short                   events;      /* POLLIN | POLLOUT */\n    long long               deadline_ms; /* absolute; -1 = none */\n    NurlFiber              *fiber;\n    int                     result;      /* 1 ready, 0 timeout */\n    struct NurlReactorWait *fd_next;     /* same-fd waiter chain */\n    int                     heap_idx;    /* -1 = not in timer heap */\n    struct NurlReactorWait *free_next;\n    struct NurlReactorWait *wake_next;   /* transient wake batch */\n    /* A nurl_fiber_park_with_mutex_ms park (fd = -1): the fiber's\n     * timed_park points here until a wake or the deadline claims it.\n     * park_state: 0 registered, not yet activated; 1 activated (in the\n     * heap when it has a deadline); 2 woken before its activation —\n     * the activation then wakes it at once. Lock held for both. */\n    int                     mutex_park;\n    int                     park_state;\n    /* An accept's wait: the fiber runs as soon as it is polled ready\n     * (Admission, under \"Service order\"). */\n    int                     admit;\n} NurlReactorWait;\n\n/* Per-fd netpoller slot. `ready` accumulates edge-triggered readiness\n * (POLLIN/POLLOUT spelling) that arrived while no waiter was parked —\n * the fast path consumes it and skips the park entirely. Registration\n * is PERSISTENT: one EPOLL_CTL_ADD with EPOLLIN|EPOLLOUT|EPOLLET on\n * the first wait ever, zero epoll_ctl per park afterwards (the oneshot\n * re-arm was measured at ~1 syscall per HTTP request). The\n * edge-triggered contract is safe here because every stdlib wait runs\n * only after the fd answered EAGAIN — the buffer is empty/full at park\n * time, so the next readiness transition is a fresh edge. */\ntypedef struct NurlNpFd {\n    unsigned         ready;       /* POLLIN|POLLOUT edges not yet consumed */\n    int              registered;  /* fd is in the epoll set */\n    NurlReactorWait *chain;       /* parked waiters */\n} NurlNpFd;\n\nstatic struct {\n    int               ep;            /* epoll instance */\n    int               evfd;          /* wake eventfd (in the epoll set) */\n    pthread_mutex_t   lock;          /* slots, heap, freelist, rescan */\n    NurlNpFd         *byfd;          /* fd → slot */\n    int               byfd_cap;\n    NurlReactorWait **heap;          /* min-heap on deadline_ms */\n    int               heap_len, heap_cap;\n    NurlReactorWait  *freelist;\n    int               rescan;        /* probe all fds on next pass */\n    int               started;\n    volatile int      shutdown;\n    int               lease;         /* __atomic_*: one poller at a time */\n    int               sleeping;      /* __atomic_*: poller inside epoll_wait */\n    long long         sleep_deadline;/* __atomic_*: its timeout target; -1 inf */\n} nurl__np = { .ep = -1, .evfd = -1 };\n\nstatic long long nurl__now_ms_internal(void) {\n    struct timespec ts;\n    clock_gettime(CLOCK_MONOTONIC, &ts);\n    return (long long)ts.tv_sec * 1000LL + (long long)(ts.tv_nsec / 1000000L);\n}\n\nstatic void nurl__np_poke(void) {\n    if (nurl__np.evfd >= 0) {\n        unsigned long long one = 1;\n        ssize_t r = write(nurl__np.evfd, &one, sizeof one);\n        (void)r;\n    }\n}\n\nstatic void nurl__np_poke_if_sleeping(void) {\n    if (nurl__np.started &&\n        __atomic_load_n(&nurl__np.sleeping, __ATOMIC_ACQUIRE))\n        nurl__np_poke();\n}\n\nstatic void nurl__np_init_once_impl(void) {\n    pthread_mutex_init(&nurl__np.lock, NULL);\n    nurl__np.ep   = epoll_create1(EPOLL_CLOEXEC);\n    nurl__np.evfd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);\n    if (nurl__np.ep >= 0 && nurl__np.evfd >= 0) {\n        struct epoll_event ev;\n        ev.events = EPOLLIN;\n        ev.data.fd = nurl__np.evfd;\n        if (epoll_ctl(nurl__np.ep, EPOLL_CTL_ADD, nurl__np.evfd, &ev) == 0)\n            nurl__np.started = 1;\n    }\n}\n\nstatic void nurl__np_start_if_needed(void) {\n    static pthread_once_t once = PTHREAD_ONCE_INIT;\n    pthread_once(&once, nurl__np_init_once_impl);\n}\n\n/* ── timer heap (lock held) ── */\nstatic void nurl__np_heap_place(NurlReactorWait *w, int i) {\n    nurl__np.heap[i] = w;\n    w->heap_idx = i;\n}\n\nstatic void nurl__np_heap_up(int i) {\n    while (i > 0) {\n        int p = (i - 1) / 2;\n        if (nurl__np.heap[p]->deadline_ms <= nurl__np.heap[i]->deadline_ms) break;\n        NurlReactorWait *t = nurl__np.heap[p];\n        nurl__np_heap_place(nurl__np.heap[i], p);\n        nurl__np_heap_place(t, i);\n        i = p;\n    }\n}\n\nstatic void nurl__np_heap_down(int i) {\n    for (;;) {\n        int l = 2 * i + 1, r = l + 1, m = i;\n        if (l < nurl__np.heap_len &&\n            nurl__np.heap[l]->deadline_ms < nurl__np.heap[m]->deadline_ms) m = l;\n        if (r < nurl__np.heap_len &&\n            nurl__np.heap[r]->deadline_ms < nurl__np.heap[m]->deadline_ms) m = r;\n        if (m == i) break;\n        NurlReactorWait *t = nurl__np.heap[m];\n        nurl__np_heap_place(nurl__np.heap[i], m);\n        nurl__np_heap_place(t, i);\n        i = m;\n    }\n}\n\nstatic void nurl__np_heap_push(NurlReactorWait *w) {\n    if (nurl__np.heap_len == nurl__np.heap_cap) {\n        int nc = nurl__np.heap_cap ? nurl__np.heap_cap * 2 : 64;\n        NurlReactorWait **nh =\n            (NurlReactorWait**)realloc(nurl__np.heap, (size_t)nc * sizeof *nh);\n        if (!nh) return;    /* deadline lost; wait becomes fd-only */\n        nurl__np.heap = nh;\n        nurl__np.heap_cap = nc;\n    }\n    nurl__np_heap_place(w, nurl__np.heap_len++);\n    nurl__np_heap_up(w->heap_idx);\n}\n\nstatic void nurl__np_heap_remove(NurlReactorWait *w) {\n    int i = w->heap_idx;\n    if (i < 0) return;\n    w->heap_idx = -1;\n    int last = --nurl__np.heap_len;\n    if (i == last) return;\n    nurl__np_heap_place(nurl__np.heap[last], i);\n    nurl__np_heap_down(i);\n    nurl__np_heap_up(i);\n}\n\n/* ── fd slots (lock held) ── */\nstatic NurlNpFd *nurl__np_slot(int fd, int grow) {\n    if (fd < 0) return NULL;\n    if (fd >= nurl__np.byfd_cap) {\n        if (!grow) return NULL;\n        /* Only an open descriptor earns a slot (agora #133): the table is\n         * indexed by fd, and a number that names nothing — a handle passed\n         * where its fd belonged — grew it to that number, gigabytes for one\n         * wait. The caller answers an unpollable fd \"ready\" either way. */\n        if (fcntl(fd, F_GETFD) < 0) return NULL;\n        int nc = nurl__np.byfd_cap * 2;\n        if (nc < fd + 64) nc = fd + 64;\n        NurlNpFd *nb =\n            (NurlNpFd*)realloc(nurl__np.byfd, (size_t)nc * sizeof *nb);\n        if (!nb) return NULL;\n        memset(nb + nurl__np.byfd_cap, 0,\n               (size_t)(nc - nurl__np.byfd_cap) * sizeof *nb);\n        nurl__np.byfd = nb;\n        nurl__np.byfd_cap = nc;\n    }\n    return &nurl__np.byfd[fd];\n}\n\nstatic void nurl__np_chain_remove(NurlReactorWait *w) {\n    NurlNpFd *s = nurl__np_slot(w->fd, 0);\n    if (!s) return;\n    NurlReactorWait **pp = &s->chain;\n    while (*pp && *pp != w) pp = &(*pp)->fd_next;\n    if (*pp) *pp = w->fd_next;\n    w->fd_next = NULL;\n}\n\n/* Persistent edge-triggered registration: once per fd lifetime.\n * 0 on success; -1 when the fd cannot be polled (closed, or a plain\n * file) — the caller wakes the waiter instead, the answer poll(2)\n * gave for the same fds (POLLNVAL / always-ready). */\nstatic int nurl__np_register_fd(NurlNpFd *s, int fd) {\n    if (s->registered) return 0;\n    struct epoll_event ev;\n    ev.events = EPOLLIN | EPOLLOUT | EPOLLET;\n    ev.data.fd = fd;\n    if (epoll_ctl(nurl__np.ep, EPOLL_CTL_ADD, fd, &ev) != 0 &&\n        errno != EEXIST)\n        return -1;\n    s->registered = 1;\n    return 0;\n}\n\n/* Move expired timers onto the wake batch (lock held). */\nstatic NurlReactorWait *nurl__np_expire_locked(long long now,\n                                               NurlReactorWait *batch) {\n    while (nurl__np.heap_len > 0 &&\n           nurl__np.heap[0]->deadline_ms <= now) {\n        NurlReactorWait *w = nurl__np.heap[0];\n        nurl__np_heap_remove(w);\n        if (w->fd >= 0) nurl__np_chain_remove(w);\n        /* A timed mutex park is the deadline's now: a later wake finds\n         * nothing to claim and leaves the fiber alone. */\n        if (w->mutex_park && w->fiber) w->fiber->timed_park = NULL;\n        w->result = 0;\n        w->wake_next = batch;\n        batch = w;\n    }\n    return batch;\n}\n\n/* Zero-timeout probe of every registered fd — the close/shutdown\n * rescue path (lock held). poll(2) still answers for an fd epoll has\n * silently dropped: POLLNVAL, and ERR/HUP wake everything. */\nstatic NurlReactorWait *nurl__np_probe_all_locked(NurlReactorWait *batch) {\n    for (int fd = 0; fd < nurl__np.byfd_cap; fd++) {\n        NurlReactorWait *head = nurl__np.byfd[fd].chain;\n        if (!head) continue;\n        struct pollfd p;\n        p.fd = fd;\n        p.events = 0;\n        for (NurlReactorWait *w = head; w; w = w->fd_next) p.events |= w->events;\n        p.revents = 0;\n        int pr = poll(&p, 1, 0);\n        if (pr <= 0) continue;\n        NurlReactorWait **pp = &nurl__np.byfd[fd].chain;\n        while (*pp) {\n            NurlReactorWait *w = *pp;\n            if (p.revents & (w->events | POLLERR | POLLHUP | POLLNVAL)) {\n                *pp = w->fd_next;\n                w->fd_next = NULL;\n                nurl__np_heap_remove(w);\n                w->result = 1;\n                w->wake_next = batch;\n                batch = w;\n            } else {\n                pp = &w->fd_next;\n            }\n        }\n    }\n    return batch;\n}\n\n#define NURL_NP_LOCAL_SHARE 8\n\n/* Recycle a consumed wake batch onto the freelist and hand the fibers\n * out. On an idle worker the FIRST fiber is returned to run right now;\n * the next NP_LOCAL_SHARE (oldest first) go to this worker's OWN local\n * queue; anything beyond that is appended to the GLOBAL queue as one\n * pre-linked chain, in event order, with wakes batched under a single\n * idle_lock acquisition (wake_n). On a busy worker nothing runs at\n * once: all of them join the queues' tails.\n *\n * Both extremes of this split were measured and lost:\n *   - Everything to the global queue, one wake apiece (the first cut):\n *     a stampede — global-lock convoy, one futex per fiber, a core\n *     migration per moved connection; c=50 throughput -15%.\n *   - Everything to the poller's local queue, one wake total (the\n *     second cut): great throughput, terrible tail — under saturation\n *     stealing never fires (it needs an empty thief queue), so a\n *     100-event burst drained at ONE core's speed and the batch tail\n *     waited multiple ms (measured directly as the c=200 p99).\n * The split keeps the small-batch case (c<=50: surplus rarely exceeds\n * the share) entirely on the polling core — warm cache, no wake chain\n * — while an overload burst spills into the pool-wide FIFO that every\n * worker drains fairly (nurl__rq_take_global_batch), which is what\n * bounds the tail. Called WITHOUT the lock. */\nstatic NurlFiber *nurl__np_hand_out(NurlWorker *w, NurlReactorWait *batch) {\n    NurlFiber *direct = NULL;\n    NurlFiber *admit = NULL;\n    NurlReactorWait *recycle = NULL;\n    NurlFiber *chain_head = NULL, *chain_tail = NULL;\n    int surplus = 0;\n    while (batch) {\n        NurlReactorWait *next = batch->wake_next;\n        NurlFiber *f = batch->fiber;\n        int r = batch->result;\n        int adm = batch->admit;\n        batch->fiber = NULL;\n        batch->wake_next = NULL;\n        batch->admit = 0;\n        batch->free_next = recycle;\n        recycle = batch;\n        if (f && adm && !admit) {\n            /* An accept runs now, ahead of everything (Admission). One is\n             * enough: it drains the listener's queue, and a second\n             * acceptor of the same listener finds it empty. */\n            f->last_park_result = r;\n            f->state = NF_RUNNABLE;\n            admit = f;\n        } else if (f) {\n            f->last_park_result = r;\n            f->state = NF_RUNNABLE;\n            f->next = chain_head;          /* batch list is newest-first;   */\n            chain_head = f;                /* prepend restores event order  */\n            if (!chain_tail) chain_tail = f;\n            surplus++;\n        }\n        batch = next;\n    }\n    /* The oldest woken fiber runs at once only on a worker with nothing\n     * queued, locally or globally: then it IS the oldest runnable fiber.\n     * A worker polling between dispatches (the cadence poll) has older\n     * fibers waiting, and running the polled one first put fresh I/O\n     * ahead of all of them (see \"Service order\"); there every woken\n     * fiber joins the tail. */\n    if (chain_head &&\n        __atomic_load_n(&w->rq_len, __ATOMIC_RELAXED) == 0 &&\n        __atomic_load_n(&nurl__sched.global_head_rseq, __ATOMIC_RELAXED) == 0) {\n        direct = chain_head;\n        chain_head = direct->next;\n        direct->next = NULL;\n        if (!chain_head) chain_tail = NULL;\n        surplus--;\n    }\n    /* Split the ordered surplus: the OLDEST few stay on this worker's\n     * local queue (warm cache, no wake needed; on an idle worker they\n     * run next), the rest\n     * go to the global FIFO in one chunk. Small batches — the c<=50\n     * case — never leave the polling core; an overload batch is bounded\n     * to NP_LOCAL_SHARE locally so no fiber ever sits behind a hundred\n     * others in one worker's private queue (that queue drains at one\n     * core's speed — measured as the p99 tail). */\n    int kept = 0;\n    while (chain_head && kept < NURL_NP_LOCAL_SHARE) {\n        NurlFiber *f = chain_head;\n        chain_head = f->next;\n        f->next = NULL;\n        nurl__rq_push_local(w, f);\n        kept++;\n    }\n    int spilled = surplus - kept;\n    if (chain_head) nurl__rq_push_global_chain(chain_head, chain_tail, spilled);\n    if (recycle) {\n        NurlReactorWait *tail = recycle;\n        while (tail->free_next) tail = tail->free_next;\n        pthread_mutex_lock(&nurl__np.lock);\n        tail->free_next = nurl__np.freelist;\n        nurl__np.freelist = recycle;\n        pthread_mutex_unlock(&nurl__np.lock);\n    }\n    /* A peer is worth waking for kept fibers only when it can steal\n     * some: stealing takes half of a queue of two or more. One kept\n     * fiber runs here right after `direct` — the poller role stays on\n     * this core (no replacement-poller wake, see the lease release) —\n     * and waking a peer for it was a futex wake, a futex wait and two\n     * context switches per request at c=10 for a peer that found\n     * nothing and went back to sleep. */\n    if (spilled > 0)      nurl__wake_n(spilled);\n    else if (kept >= 2)   nurl__wake_one();\n    if (admit) {\n        /* The fiber an idle worker would have run first is the oldest\n         * woken: it goes at the head of this worker's queue, to run\n         * right after the accept. */\n        if (direct) {\n            direct->rseq = NURL_RSEQ_ADMIT;\n            pthread_mutex_lock(&w->rq_lock);\n            direct->next = w->rq_head;\n            w->rq_head = direct;\n            if (!w->rq_tail) w->rq_tail = direct;\n            __atomic_fetch_add(&w->rq_len, 1, __ATOMIC_RELAXED);\n            pthread_mutex_unlock(&w->rq_lock);\n        }\n        return admit;\n    }\n    return direct;\n}\n\n/* One poller pass: take the lease, block in epoll_wait until an event,\n * a deadline or a poke, wake what completed. Returns a fiber to run\n * directly (NULL when the pass was pure bookkeeping); *held reports\n * whether the lease was ours — a caller that never held it parks on\n * idle_cv instead. */\nstatic NurlFiber *nurl__np_poll_at(NurlWorker *w, int *held, int block) {\n    *held = 0;\n    if (!nurl__np.started || nurl__np.shutdown || nurl__sched.shutdown)\n        return NULL;\n    int expect = 0;\n    if (!__atomic_compare_exchange_n(&nurl__np.lease, &expect, 1, 0,\n                                     __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))\n        return NULL;\n    *held = 1;\n\n    NurlReactorWait *batch = NULL;\n    pthread_mutex_lock(&nurl__np.lock);\n    long long now = nurl__now_ms_internal();\n    batch = nurl__np_expire_locked(now, batch);\n    long long next = (nurl__np.heap_len > 0)\n                   ? nurl__np.heap[0]->deadline_ms : -1;\n    pthread_mutex_unlock(&nurl__np.lock);\n\n    if (!batch) {\n        int timeout = -1;\n        if (next >= 0) {\n            long long d = next - now;\n            if (d < 0) d = 0;\n            if (d > 3600000LL) d = 3600000LL;\n            timeout = (int)d;\n        }\n        if (!block) timeout = 0;   /* opportunistic pass — never sleep */\n        struct epoll_event evs[128];\n        __atomic_store_n(&nurl__np.sleep_deadline, next, __ATOMIC_RELEASE);\n        if (block)\n            __atomic_store_n(&nurl__np.sleeping, 1, __ATOMIC_RELEASE);\n        int n = 0;\n        if (!nurl__np.shutdown && !nurl__sched.shutdown)\n            n = epoll_wait(nurl__np.ep, evs, 128, timeout);\n        if (block)\n            __atomic_store_n(&nurl__np.sleeping, 0, __ATOMIC_RELEASE);\n\n        pthread_mutex_lock(&nurl__np.lock);\n        for (int i = 0; i < n; i++) {\n            int fd = evs[i].data.fd;\n            if (fd == nurl__np.evfd) {\n                unsigned long long junk;\n                while (read(nurl__np.evfd, &junk, sizeof junk) > 0) {}\n                continue;\n            }\n            unsigned got = evs[i].events;\n            unsigned pr = 0;\n            if (got & (EPOLLIN  | EPOLLERR | EPOLLHUP)) pr |= POLLIN;\n            if (got & (EPOLLOUT | EPOLLERR | EPOLLHUP)) pr |= POLLOUT;\n            NurlNpFd *s = nurl__np_slot(fd, 0);\n            if (!s) continue;\n            /* A parked waiter for a direction consumes that edge; a\n             * direction nobody waits on is banked in the ready bits\n             * for the fast path (the next wait returns without\n             * parking). ERR/HUP wake both directions — the waiter's\n             * own read/write reports the story. */\n            unsigned unclaimed = pr;\n            NurlReactorWait **pp = &s->chain;\n            while (*pp) {\n                NurlReactorWait *wt = *pp;\n                if (wt->events & pr) {\n                    unclaimed &= (unsigned)~wt->events;\n                    *pp = wt->fd_next;\n                    wt->fd_next = NULL;\n                    nurl__np_heap_remove(wt);\n                    wt->result = 1;\n                    wt->wake_next = batch;\n                    batch = wt;\n                } else {\n                    pp = &wt->fd_next;\n                }\n            }\n            s->ready |= unclaimed;\n        }\n        if (nurl__np.rescan) {\n            nurl__np.rescan = 0;\n            batch = nurl__np_probe_all_locked(batch);\n        }\n        now = nurl__now_ms_internal();\n        batch = nurl__np_expire_locked(now, batch);\n        pthread_mutex_unlock(&nurl__np.lock);\n    }\n\n    NurlFiber *direct = nurl__np_hand_out(w, batch);\n    __atomic_store_n(&nurl__np.lease, 0, __ATOMIC_RELEASE);\n    /* No replacement-poller wake here: this worker runs its haul and\n     * re-takes the lease when its queue drains (driver stickiness —\n     * the poll pass tends to stay on one warm core). Events that\n     * arrive meanwhile wait in the epoll instance; a peer that goes\n     * idle takes the free lease on its own, and the opportunistic\n     * zero-timeout pass below keeps the flow moving under load. */\n    return direct;\n}\n\nstatic NurlFiber *nurl__np_poll(NurlWorker *w, int *held) {\n    return nurl__np_poll_at(w, held, 1);\n}\n\n/* Opportunistic pass: called between queue checks while the worker\n * still has (or is about to look for) other work. Zero timeout, so a\n * loaded pool keeps draining the epoll instance without waiting for a\n * worker to go fully idle — the Go scheduler's non-blocking netpoll\n * check in findrunnable, same reasoning. */\nstatic NurlFiber *nurl__np_poll_nowait(NurlWorker *w) {\n    int held = 0;\n    return nurl__np_poll_at(w, &held, 0);\n}\n\n/* Worker loop calls this AFTER the parking fiber's swap-out completes;\n * arming here (not at registration) closes the double-execution race —\n * with epoll the arm IS the point events can first fire. */\nvoid nurl__reactor_activate(NurlReactorWait *wt) {\n    if (!wt) return;\n    NurlFiber *fb = wt->fiber;\n    long long dl = wt->deadline_ms;\n    int verdict = -1;   /* -1 parked; 0/1 wake now with that result */\n    pthread_mutex_lock(&nurl__np.lock);\n    if (wt->fd >= 0) {\n        NurlNpFd *s = nurl__np_slot(wt->fd, 1);\n        if (!s || nurl__np_register_fd(s, wt->fd) != 0) {\n            /* Unpollable fd (closed / plain file / table OOM): answer\n             * \"ready\" now, the verdict poll(2) returned for these —\n             * the caller's next read/write reports the real story. */\n            verdict = 1;\n        } else if (s->ready & (unsigned)wt->events) {\n            /* The edge already arrived (between the fiber's EAGAIN and\n             * this activation, or banked from earlier) — consume it\n             * and skip the park entirely. */\n            s->ready &= (unsigned)~wt->events;\n            verdict = 1;\n        } else {\n            wt->fd_next = s->chain;\n            s->chain = wt;\n        }\n    } else if (wt->mutex_park) {\n        /* Woken between the mutex release and this activation: wake it\n         * now. Otherwise it is armed, and a wake from here on takes it\n         * out of the heap itself. */\n        if (wt->park_state == 2) verdict = 1;\n        else wt->park_state = 1;\n    }\n    if (verdict < 0 && dl >= 0) nurl__np_heap_push(wt);\n    if (verdict >= 0) {\n        wt->free_next = nurl__np.freelist;\n        nurl__np.freelist = wt;\n    }\n    pthread_mutex_unlock(&nurl__np.lock);\n    if (verdict >= 0) {\n        fb->last_park_result = verdict;\n        nurl_fiber_unpark((long long)(uintptr_t)fb);\n        return;\n    }\n    if (dl >= 0 &&\n        __atomic_load_n(&nurl__np.sleeping, __ATOMIC_ACQUIRE)) {\n        long long sd =\n            __atomic_load_n(&nurl__np.sleep_deadline, __ATOMIC_ACQUIRE);\n        if (sd < 0 || dl < sd) nurl__np_poke();\n    }\n}\n\n__attribute__((noinline))\nstatic int nurl__reactor_wait_k(int fd, short events, long long timeout_ms, int admit) {\n    NurlWorker *w = nurl__tls_worker;\n    if (!w || !w->current) return -1;  /* not on a fiber */\n    nurl__np_start_if_needed();\n    if (!nurl__np.started) return -1;\n\n    /* Spin-then-park (nurl__spin_ready); every way out of this wait\n     * reports its length to the fiber's estimate. */\n    long long spin_cap = nurl__spin_cap();\n    long long t0 = spin_cap > 0 ? nurl_monotonic_ns() : 0;\n    if (nurl__spin_ready(w, w->current, fd, events, t0, spin_cap)) return 1;\n\n    pthread_mutex_lock(&nurl__np.lock);\n    /* Fast path: the readiness edge already arrived (banked by the\n     * poller while this fiber was busy computing the response) —\n     * consume it and return without parking, no entry, no swap, no\n     * wake chain. Under load this is the common case: the peer's next\n     * request lands while the previous response is being written. */\n    if (fd >= 0) {\n        NurlNpFd *s = nurl__np_slot(fd, 0);\n        if (s && (s->ready & (unsigned)events)) {\n            s->ready &= (unsigned)~events;\n            pthread_mutex_unlock(&nurl__np.lock);\n            if (spin_cap > 0) nurl__spin_observe(w->current, nurl_monotonic_ns() - t0, spin_cap);\n            return 1;\n        }\n    }\n    NurlReactorWait *wt = nurl__np.freelist;\n    if (wt) nurl__np.freelist = wt->free_next;\n    pthread_mutex_unlock(&nurl__np.lock);\n    if (!wt) {\n        wt = (NurlReactorWait*)calloc(1, sizeof *wt);\n        if (!wt) return -1;\n    }\n    wt->fd = fd;\n    wt->events = events;\n    wt->deadline_ms = (timeout_ms < 0) ? -1\n                    : nurl__now_ms_internal() + timeout_ms;\n    wt->fiber = w->current;\n    wt->result = 0;\n    wt->fd_next = NULL;\n    wt->heap_idx = -1;\n    wt->free_next = NULL;\n    wt->wake_next = NULL;\n    wt->mutex_park = 0;\n    wt->park_state = 0;\n    wt->admit = admit;\n\n    NurlFiber *cur = w->current;\n    cur->pending_unlock = NULL;\n    cur->pending_reactor_wait = wt;\n    cur->state = NF_PARKED;\n    nurl__swap_to_loop(cur, w);\n    /* Resumed — possibly on another worker; the fiber carries its own\n     * estimate. */\n    if (spin_cap > 0) nurl__spin_observe(cur, nurl_monotonic_ns() - t0, spin_cap);\n    return cur->last_park_result;\n}\n\nstatic int nurl__reactor_wait(int fd, short events, long long timeout_ms) {\n    return nurl__reactor_wait_k(fd, events, timeout_ms, 0);\n}\n\n/* An I/O wait's fd as NURL hands it over (a `long long`), before it is\n * narrowed: a number that cannot name a descriptor — negative, which the\n * reactor would take for a pure timer and wait the whole timeout, or past\n * INT_MAX, which narrowed onto an unrelated fd (agora #133) — is\n * unpollable, and answers \"ready\" at once, as poll(2) does an fd it cannot\n * poll; the caller's read or write then says what the fd is. -1 off a\n * fiber, as every wait. */\nstatic int nurl__wait_fd_bad(long long fd, long long *verdict) {\n    if (fd >= 0 && fd <= INT_MAX) return 0;\n    NurlWorker *w = nurl__tls_worker;\n    *verdict = (w && w->current) ? 1 : -1;\n    return 1;\n}\n\nNURL_TLS_FN long long nurl_reactor_wait_read(long long fd, long long timeout_ms) {\n    long long v;\n    if (nurl__wait_fd_bad(fd, &v)) return v;\n    return (long long)nurl__reactor_wait((int)fd, POLLIN, timeout_ms);\n}\n\n/* nurl_reactor_wait_read for an accept on listener `fd` (Admission). */\nNURL_TLS_FN long long nurl_reactor_wait_accept(long long fd, long long timeout_ms) {\n    long long v;\n    if (nurl__wait_fd_bad(fd, &v)) return v;\n    return (long long)nurl__reactor_wait_k((int)fd, POLLIN, timeout_ms, 1);\n}\n\nNURL_TLS_FN long long nurl_reactor_wait_write(long long fd, long long timeout_ms) {\n    long long v;\n    if (nurl__wait_fd_bad(fd, &v)) return v;\n    return (long long)nurl__reactor_wait((int)fd, POLLOUT, timeout_ms);\n}\n\nNURL_TLS_FN long long nurl_reactor_wait_io(long long fd, long long events, long long timeout_ms) {\n    short mask = (short)(((events & 1) ? POLLIN : 0) | ((events & 2) ? POLLOUT : 0));\n    if (!mask) return -1;\n    long long v;\n    if (nurl__wait_fd_bad(fd, &v)) return v;\n    return (long long)nurl__reactor_wait((int)fd, mask, timeout_ms);\n}\n\nNURL_TLS_FN long long nurl_fiber_sleep_ms(long long ms) {\n    if (ms <= 0) { nurl_fiber_yield(); return 0; }\n    nurl__reactor_wait(-1, 0, ms);\n    return 0;\n}\n\n/* Park the current fiber on a mutex its caller holds, as\n * nurl_fiber_park_with_mutex does, but until nurl_fiber_wake() or\n * `timeout_ms` (< 0 = no deadline): 1 woken, 0 the deadline passed,\n * -1 not on a fiber (the mutex is then still held: wait on a condvar).\n *\n * The mutex is released after the swap-out (pending_unlock) and the\n * entry activated after that (pending_reactor_wait), in that order, by\n * the worker loop. A wake and the deadline race for the entry under the\n * netpoller lock: whichever takes `timed_park` first decides, so the\n * fiber is made runnable exactly once — a plain unpark racing a timer\n * would run it twice. A waker that lost the race (the wake answers 0)\n * has lost nothing: the fiber relocks the mutex and looks again at what\n * it waited for. */\nNURL_TLS_FN long long nurl_fiber_park_with_mutex_ms(long long mutex_h, long long timeout_ms) {\n    NurlWorker *w = nurl__tls_worker;\n    if (!w || !w->current) return -1;\n    nurl__np_start_if_needed();\n    if (!nurl__np.started) return -1;\n    pthread_mutex_lock(&nurl__np.lock);\n    NurlReactorWait *wt = nurl__np.freelist;\n    if (wt) nurl__np.freelist = wt->free_next;\n    pthread_mutex_unlock(&nurl__np.lock);\n    if (!wt) {\n        wt = (NurlReactorWait*)calloc(1, sizeof *wt);\n        if (!wt) return -1;\n    }\n    NurlFiber *cur = w->current;\n    wt->fd = -1;\n    wt->events = 0;\n    wt->deadline_ms = (timeout_ms < 0) ? -1\n                    : nurl__now_ms_internal() + timeout_ms;\n    wt->fiber = cur;\n    wt->result = 0;\n    wt->fd_next = NULL;\n    wt->heap_idx = -1;\n    wt->free_next = NULL;\n    wt->wake_next = NULL;\n    wt->mutex_park = 1;\n    wt->park_state = 0;\n    wt->admit = 0;\n    cur->timed_park = wt;\n    cur->pending_unlock = (pthread_mutex_t *)(uintptr_t)mutex_h;\n    cur->pending_reactor_wait = wt;\n    cur->state = NF_PARKED;\n    nurl__swap_to_loop(cur, w);\n    return cur->last_park_result;\n}\n\n/* Wake a fiber parked in nurl_fiber_park_with_mutex_ms — the caller\n * holds the mutex it parked on (that is how it found the fiber). 1: the\n * fiber was waiting and is woken (result 1); 0: its deadline claimed it\n * first, and it is already on its way back. */\nlong long nurl_fiber_wake(long long fiber_h) {\n    NurlFiber *f = (NurlFiber*)(uintptr_t)fiber_h;\n    if (!f) return 0;\n    pthread_mutex_lock(&nurl__np.lock);\n    NurlReactorWait *wt = f->timed_park;\n    if (!wt) {\n        pthread_mutex_unlock(&nurl__np.lock);\n        return 0;\n    }\n    f->timed_park = NULL;\n    if (wt->park_state == 0) {\n        /* Not activated yet: the activation wakes it (verdict 1). */\n        wt->park_state = 2;\n        pthread_mutex_unlock(&nurl__np.lock);\n        return 1;\n    }\n    nurl__np_heap_remove(wt);\n    wt->fiber = NULL;\n    wt->free_next = nurl__np.freelist;\n    nurl__np.freelist = wt;\n    pthread_mutex_unlock(&nurl__np.lock);\n    f->last_park_result = 1;\n    nurl_fiber_unpark((long long)(uintptr_t)f);\n    return 1;\n}\n\nvoid nurl__reactor_wake_if_started(void) {\n    if (!nurl__np.started) return;\n    pthread_mutex_lock(&nurl__np.lock);\n    nurl__np.rescan = 1;\n    pthread_mutex_unlock(&nurl__np.lock);\n    nurl__np_poke();\n}\n\n/* Pre-join half of shutdown: stop new blocking and kick a poller out\n * of epoll_wait. The teardown itself happens in nurl__reactor_cleanup\n * AFTER the workers are joined — a worker may still be inside the\n * netpoller here. */\nvoid nurl__reactor_shutdown(void) {\n    nurl__np.shutdown = 1;\n    if (nurl__np.started) nurl__np_poke();\n}\n\n/* Socket close (see nurl_close_sock): wake every waiter parked on this\n * fd — with the registration about to vanish no event can — reset the\n * slot so a REUSED fd number starts clean (a stale ready bit or\n * `registered` flag on a recycled fd would satisfy or starve the next\n * connection's first wait), and close the fd.\n *\n * The reset and the close(2) are ONE step under the lock. Reset first\n * and close after unlocking, and a fiber on another worker can park on\n * the still-open fd in between: its activation re-registers (EPOLL_CTL_ADD\n * answers EEXIST, the fd is still in the set) and sets `registered`\n * again, then close drops the kernel registration. The slot outlives\n * the fd claiming it is registered, and the next socket to get that fd\n * number is never added to the epoll set — its first park never wakes.\n * Observed: server_stop closing a listener from another thread while\n * its accept fiber re-parked left the next listener's accept fiber\n * asleep forever. */\nint nurl__np_close(int fd) {\n    if (!nurl__np.started) return close(fd);\n    NurlReactorWait *batch = NULL;\n    pthread_mutex_lock(&nurl__np.lock);\n    NurlNpFd *s = nurl__np_slot(fd, 0);\n    if (s) {\n        NurlReactorWait *w = s->chain;\n        s->chain = NULL;\n        s->ready = 0;\n        s->registered = 0;\n        while (w) {\n            NurlReactorWait *n = w->fd_next;\n            w->fd_next = NULL;\n            nurl__np_heap_remove(w);\n            w->result = 1;\n            w->wake_next = batch;\n            batch = w;\n            w = n;\n        }\n    }\n    int rc = close(fd);\n    pthread_mutex_unlock(&nurl__np.lock);\n    while (batch) {\n        NurlReactorWait *n = batch->wake_next;\n        NurlFiber *f = batch->fiber;\n        batch->fiber = NULL;\n        batch->wake_next = NULL;\n        pthread_mutex_lock(&nurl__np.lock);\n        batch->free_next = nurl__np.freelist;\n        nurl__np.freelist = batch;\n        pthread_mutex_unlock(&nurl__np.lock);\n        if (f) {\n            f->last_park_result = 1;\n            nurl_fiber_unpark((long long)(uintptr_t)f);\n        }\n        batch = n;\n    }\n    return rc;\n}\n\nvoid nurl__reactor_cleanup(void) {\n    if (!nurl__np.started) return;\n    nurl__np.started = 0;\n    close(nurl__np.ep);\n    close(nurl__np.evfd);\n    nurl__np.ep = nurl__np.evfd = -1;\n    for (int fd = 0; fd < nurl__np.byfd_cap; fd++) {\n        NurlReactorWait *w = nurl__np.byfd[fd].chain;\n        while (w) { NurlReactorWait *n = w->fd_next; free(w); w = n; }\n    }\n    free(nurl__np.byfd);\n    nurl__np.byfd = NULL;\n    nurl__np.byfd_cap = 0;\n    /* Timer-only entries live in the heap and no chain. */\n    for (int i = 0; i < nurl__np.heap_len; i++)\n        if (nurl__np.heap[i]->fd < 0) free(nurl__np.heap[i]);\n    free(nurl__np.heap);\n    nurl__np.heap = NULL;\n    nurl__np.heap_len = nurl__np.heap_cap = 0;\n    NurlReactorWait *w = nurl__np.freelist;\n    while (w) { NurlReactorWait *n = w->free_next; free(w); w = n; }\n    nurl__np.freelist = NULL;\n    pthread_mutex_destroy(&nurl__np.lock);\n}\n\n#else /* !__linux__ — portable poll(2) reactor thread (WSAPoll on Windows) */\n\ntypedef struct NurlReactorWait {\n    long long               fd;          /* -1 = pure timer */\n    short                   events;      /* POLLIN | POLLOUT */\n    long long               deadline_ms; /* -1 = infinite */\n    NurlFiber              *fiber;\n    int                     result;      /* 1 ready, 0 timeout */\n    int                     active;      /* set after fiber swap-out completes;\n                                          * inactive entries are skipped to\n                                          * close the park/unpark race. */\n    struct NurlReactorWait *next;\n    /* A nurl_fiber_park_with_mutex_ms park (fd = -1), as in the Linux\n     * backend: park_state 0 registered, 1 activated, 2 woken before its\n     * activation. Lock held for both. */\n    int                     mutex_park;\n    int                     park_state;\n    /* Windows: the read side of `fd` was shut (nurl__reactor_read_shut),\n     * which WSAPoll never reports — wake as ready, so the reader sees EOF. */\n    int                     forced;\n    /* An accept's wait (Admission, under \"Service order\"). */\n    int                     admit;\n    /* Its index in the poll round's set (0: not in it). Read only when\n     * that index still holds this entry, so a stale one is harmless. */\n    int                     pidx;\n} NurlReactorWait;\n\ntypedef struct NurlReactor {\n    pthread_t        thread;\n    /* [0] = read, [1] = write (nurl__signal_open; one socket, both\n     * slots, on Windows). */\n    nurl_sockfd_t    wake_pipe[2];\n    pthread_mutex_t  lock;           /* protects `waits` and `freelist` */\n    NurlReactorWait *waits;\n    /* Recycled wait entries. Every park used to calloc an entry on the\n     * worker thread and free it on the reactor thread — one\n     * cross-thread malloc/free pair per parked I/O operation (= per\n     * HTTP request on a keep-alive connection). The freelist reuses\n     * entries under the lock both sides already take; its size is\n     * bounded by the peak number of concurrently parked fibers. */\n    NurlReactorWait *freelist;\n    int              shutdown;\n    int              started;\n} NurlReactor;\n\nstatic NurlReactor nurl__reactor;\n\n#ifdef _WIN32\n/* The sockets whose read side nurl_tcp_conn_shutdown shut, until each is\n * closed (nurl__reactor_forget_fd): a fiber that starts waiting to read\n * one after the shutdown is woken at once, like one already waiting.\n * Its own lock, statically initialised, so a drain on a program that\n * never started the reactor needs nothing of it; taken after the\n * reactor's lock, never before. */\nstatic pthread_mutex_t nurl__shut_lock = PTHREAD_MUTEX_INITIALIZER;\nstatic long long      *nurl__shut_fds;\nstatic int             nurl__shut_n, nurl__shut_cap;\n\n/* Does `fd` name a socket? A wait on a number that does not (agora #133)\n * must not join the WSAPoll set: Windows can hold the whole call to its\n * timeout before saying so, and every other waiter with it. Asked once per\n * park, beside the cross-thread wakes a park costs anyway. */\nstatic int nurl__win_is_socket(long long fd) {\n    int type = 0;\n    int len = (int)sizeof type;\n    return getsockopt((SOCKET)fd, SOL_SOCKET, SO_TYPE, (char *)&type, &len) == 0;\n}\n\nstatic int nurl__shut_has(long long fd) {\n    int hit = 0;\n    pthread_mutex_lock(&nurl__shut_lock);\n    for (int i = 0; i < nurl__shut_n; i++) if (nurl__shut_fds[i] == fd) { hit = 1; break; }\n    pthread_mutex_unlock(&nurl__shut_lock);\n    return hit;\n}\n\nstatic void nurl__reactor_wake(void);\n\nvoid nurl__reactor_read_shut(long long fd) {\n    pthread_mutex_lock(&nurl__shut_lock);\n    int have = 0;\n    for (int i = 0; i < nurl__shut_n; i++) if (nurl__shut_fds[i] == fd) { have = 1; break; }\n    if (!have) {\n        if (nurl__shut_n == nurl__shut_cap) {\n            int cap = nurl__shut_cap ? nurl__shut_cap * 2 : 16;\n            long long *grown = (long long *)realloc(nurl__shut_fds, (size_t)cap * sizeof *grown);\n            if (!grown) { pthread_mutex_unlock(&nurl__shut_lock); abort(); }\n            nurl__shut_fds = grown;\n            nurl__shut_cap = cap;\n        }\n        nurl__shut_fds[nurl__shut_n++] = fd;\n    }\n    pthread_mutex_unlock(&nurl__shut_lock);\n    /* Then the readers already parked. One registering in between finds\n     * the fd in the set (nurl__reactor_wait), so none is missed. */\n    if (!nurl__reactor.started) return;\n    pthread_mutex_lock(&nurl__reactor.lock);\n    for (NurlReactorWait *w = nurl__reactor.waits; w; w = w->next)\n        if (w->fd == fd && (w->events & POLLIN)) w->forced = 1;\n    pthread_mutex_unlock(&nurl__reactor.lock);\n    nurl__reactor_wake();\n}\n\nvoid nurl__reactor_forget_fd(long long fd) {\n    pthread_mutex_lock(&nurl__shut_lock);\n    for (int i = 0; i < nurl__shut_n; i++)\n        if (nurl__shut_fds[i] == fd) { nurl__shut_fds[i] = nurl__shut_fds[--nurl__shut_n]; break; }\n    pthread_mutex_unlock(&nurl__shut_lock);\n}\n#endif\n\nstatic long long nurl__now_ms_internal(void) {\n    struct timespec ts;\n    clock_gettime(CLOCK_MONOTONIC, &ts);\n    return (long long)ts.tv_sec * 1000LL + (long long)(ts.tv_nsec / 1000000L);\n}\n\n/* The wake channel is nurl__signal_open's (§18-r): a pipe here, a\n * self-connected loopback UDP socket on Windows. Best-effort pokes;\n * several coalesce, and the loop drains them all after its poll. */\nstatic void nurl__reactor_wake(void) {\n    nurl__signal_raise(nurl__reactor.wake_pipe[0], nurl__reactor.wake_pipe[1]);\n}\n\nvoid nurl__reactor_wake_if_started(void) {\n    if (nurl__reactor.started) nurl__reactor_wake();\n}\n\n/* Reactor loop: build pollfd[], compute min-deadline, poll, wake\n * ready / timed-out fibers. */\nstatic void *nurl__reactor_loop(void *arg) {\n    (void)arg;\n    /* One pollfd per waiting fd, grown as the waits are counted: a fixed\n     * 256 left every waiter past the 255th out of the poll set — and new\n     * waits join the list's head, so the oldest starved for as long as\n     * 255 newer ones waited (agora #133). */\n    int cap = 256;\n    nurl_pollfd_t *fds = (nurl_pollfd_t *)malloc((size_t)cap * sizeof *fds);\n    NurlReactorWait **batch = (NurlReactorWait **)malloc((size_t)cap * sizeof *batch);\n    if (!fds || !batch) abort();\n    for (;;) {\n        if (nurl__reactor.shutdown) break;\n\n        pthread_mutex_lock(&nurl__reactor.lock);\n        int want = 1;\n        for (NurlReactorWait *w = nurl__reactor.waits; w; w = w->next)\n            if (w->active && w->fd >= 0) want++;\n        if (want > cap) {\n            while (cap < want) cap *= 2;\n            nurl_pollfd_t *nf = (nurl_pollfd_t *)realloc(fds, (size_t)cap * sizeof *fds);\n            if (!nf) abort();\n            fds = nf;\n            NurlReactorWait **nb = (NurlReactorWait **)realloc(batch, (size_t)cap * sizeof *batch);\n            if (!nb) abort();\n            batch = nb;\n        }\n        int nfds = 1;\n        fds[0].fd = nurl__reactor.wake_pipe[0];\n        fds[0].events = POLLIN;\n        fds[0].revents = 0;\n        batch[0] = NULL;\n\n        long long now = nurl__now_ms_internal();\n        long long min_deadline = -1;\n        for (NurlReactorWait *w = nurl__reactor.waits; w; w = w->next) {\n            if (!w->active) continue;   /* fiber hasn't swap-completed */\n            if (w->deadline_ms >= 0) {\n                if (min_deadline < 0 || w->deadline_ms < min_deadline)\n                    min_deadline = w->deadline_ms;\n            }\n            /* (A forced entry wakes without being polled: a Windows\n             * socket whose read side was shut, or a number that names no\n             * socket, which would hold WSAPoll to its timeout.) */\n            if (w->fd >= 0 && !w->forced) {\n                fds[nfds].fd = (nurl_sockfd_t)w->fd;\n                fds[nfds].events = w->events;\n                fds[nfds].revents = 0;\n                batch[nfds] = w;\n                w->pidx = nfds;\n                nfds++;\n            }\n        }\n        pthread_mutex_unlock(&nurl__reactor.lock);\n\n        int timeout = -1;\n        if (min_deadline >= 0) {\n            long long delta = min_deadline - now;\n            if (delta < 0) delta = 0;\n            if (delta > 1000000000LL) delta = 1000000000LL;\n            timeout = (int)delta;\n        }\n\n        int r = nurl__poll(fds, nfds, timeout);\n        if (r < 0) {\n#ifndef _WIN32\n            if (errno == EINTR) continue;\n#endif\n            /* One fd the call would not take (a number that is no socket:\n             * WSAPoll may fail the whole call for it) must not end the\n             * reactor — this thread is the only one that wakes a fiber\n             * waiting on an fd, and every such fiber would hang. Each fd is\n             * asked on its own instead, and one that cannot be polled is\n             * reported as poll(2) reports it, POLLNVAL: its waiter wakes\n             * and its own read or write says what is wrong (agora #133). */\n            fds[0].revents = 0;\n            for (int i = 1; i < nfds; i++) {\n                nurl_pollfd_t one = fds[i];\n                one.revents = 0;\n                if (nurl__poll(&one, 1, 0) < 0) one.revents = POLLNVAL;\n                fds[i].revents = one.revents;\n            }\n        }\n\n        /* Drain wake-pipe if signalled. */\n        if (fds[0].revents & POLLIN) nurl__signal_drain(nurl__reactor.wake_pipe[0]);\n\n        /* Walk the wait list, decide who wakes up. We snapshot the\n         * list under the lock, then process and re-attach the\n         * surviving entries. */\n        pthread_mutex_lock(&nurl__reactor.lock);\n        NurlReactorWait *survivors = NULL, *survivors_tail = NULL;\n        NurlReactorWait *to_unpark = NULL;\n        now = nurl__now_ms_internal();\n        for (NurlReactorWait *w = nurl__reactor.waits; w; ) {\n            NurlReactorWait *next = w->next;\n            int wake = 0;\n            int result = 0;\n            /* Inactive entries are invisible to the reactor — same\n             * predicate as the poll-set build above. */\n            if (!w->active) {\n                /* Survives unchanged. */\n                w->next = NULL;\n                if (survivors_tail) survivors_tail->next = w;\n                else                survivors = w;\n                survivors_tail = w;\n                w = next;\n                continue;\n            }\n            if (w->fd >= 0) {\n                /* This entry's place in the poll set, if it was in it. */\n                int i = w->pidx;\n                if (i > 0 && i < nfds && batch[i] == w &&\n                    (fds[i].revents & (w->events | POLLERR | POLLHUP | POLLNVAL))) {\n                    wake = 1; result = 1;\n                }\n            }\n            if (!wake && w->forced) { wake = 1; result = 1; }\n            if (!wake && w->deadline_ms >= 0 && now >= w->deadline_ms) {\n                wake = 1; result = 0;\n                /* The deadline claims a timed mutex park: a later\n                 * nurl_fiber_wake finds nothing to wake. */\n                if (w->mutex_park && w->fiber) w->fiber->timed_park = NULL;\n            }\n            if (wake) {\n                w->result = result;\n                w->next = to_unpark;\n                to_unpark = w;\n            } else {\n                w->next = NULL;\n                if (survivors_tail) survivors_tail->next = w;\n                else                survivors = w;\n                survivors_tail = w;\n            }\n            w = next;\n        }\n        nurl__reactor.waits = survivors;\n        pthread_mutex_unlock(&nurl__reactor.lock);\n\n        /* Unpark outside the lock. Write the result onto the fiber\n         * BEFORE unparking — once unparked it may resume on another\n         * worker and read the value. Entries are recycled onto the\n         * freelist in one batch afterwards instead of free()d one by\n         * one on this thread (the callocs happened on worker threads). */\n        NurlReactorWait *recycle = NULL;\n        for (NurlReactorWait *w = to_unpark; w; ) {\n            NurlReactorWait *next = w->next;\n            NurlFiber *fb = w->fiber;\n            int result = w->result;\n            w->fiber = NULL;\n            w->next = recycle;\n            recycle = w;\n            if (fb) {\n                fb->last_park_result = result;\n                if (w->admit) nurl__fiber_unpark_admit(fb);\n                else          nurl_fiber_unpark((long long)(uintptr_t)fb);\n            }\n            w = next;\n        }\n        if (recycle) {\n            NurlReactorWait *rt = recycle;\n            while (rt->next) rt = rt->next;\n            pthread_mutex_lock(&nurl__reactor.lock);\n            rt->next = nurl__reactor.freelist;\n            nurl__reactor.freelist = recycle;\n            pthread_mutex_unlock(&nurl__reactor.lock);\n        }\n    }\n    free(fds);\n    free(batch);\n    return NULL;\n}\n\nstatic void nurl__reactor_init_once(void) {\n    if (nurl__signal_open(&nurl__reactor.wake_pipe[0], &nurl__reactor.wake_pipe[1]) != 0) return;\n    pthread_mutex_init(&nurl__reactor.lock, NULL);\n    nurl__reactor.waits = NULL;\n    nurl__reactor.freelist = NULL;\n    nurl__reactor.shutdown = 0;\n    if (pthread_create(&nurl__reactor.thread, NULL,\n                       nurl__reactor_loop, NULL) == 0) {\n        nurl__reactor.started = 1;\n    }\n}\n\nstatic void nurl__reactor_start_if_needed(void) {\n    static pthread_once_t once = PTHREAD_ONCE_INIT;\n    pthread_once(&once, nurl__reactor_init_once);\n}\n\n/* Called by the worker loop AFTER the parking fiber's swap-out\n * completes; only then is it safe for the reactor to match. */\nvoid nurl__reactor_activate(NurlReactorWait *w) {\n    if (!w) return;\n    NurlFiber *woken = NULL;\n    pthread_mutex_lock(&nurl__reactor.lock);\n    if (w->mutex_park && w->park_state == 2) {\n        /* Woken between the mutex release and this activation: take the\n         * entry back out of the list and wake the fiber now. */\n        NurlReactorWait **pp = &nurl__reactor.waits;\n        while (*pp && *pp != w) pp = &(*pp)->next;\n        if (*pp) *pp = w->next;\n        woken = w->fiber;\n        w->fiber = NULL;\n        w->next = nurl__reactor.freelist;\n        nurl__reactor.freelist = w;\n    } else {\n        w->active = 1;\n        w->park_state = 1;\n    }\n    pthread_mutex_unlock(&nurl__reactor.lock);\n    if (woken) {\n        woken->last_park_result = 1;\n        nurl_fiber_unpark((long long)(uintptr_t)woken);\n        return;\n    }\n    nurl__reactor_wake();\n}\n\n/* Register (fd, events, deadline) → current fiber, park, return the\n * reactor-set result. timeout_ms < 0 = infinite. The entry is INACTIVE\n * until the worker loop activates it via nurl__reactor_activate after\n * swap-out, which closes the unpark-before-park double-execution race. */\n__attribute__((noinline))\nstatic int nurl__reactor_wait_k(long long fd, short events, long long timeout_ms, int admit) {\n    NurlWorker *w = nurl__tls_worker;\n    if (!w || !w->current) return -1;  /* not on a fiber */\n    nurl__reactor_start_if_needed();\n\n    /* Spin-then-park (nurl__spin_ready). Here a park costs two\n     * cross-thread wake chains (pipe poke to the reactor, then cond-var\n     * wake of a worker) — tens of microseconds per I/O wait — so a\n     * short wait caught by probing the fd skips the most. */\n    long long spin_cap = nurl__spin_cap();\n    long long t0 = spin_cap > 0 ? nurl_monotonic_ns() : 0;\n    if (nurl__spin_ready(w, w->current, fd, events, t0, spin_cap)) return 1;\n\n    pthread_mutex_lock(&nurl__reactor.lock);\n    NurlReactorWait *wt = nurl__reactor.freelist;\n    if (wt) nurl__reactor.freelist = wt->next;\n    else {\n        pthread_mutex_unlock(&nurl__reactor.lock);\n        wt = (NurlReactorWait*)calloc(1, sizeof(NurlReactorWait));\n        if (!wt) return -1;\n        pthread_mutex_lock(&nurl__reactor.lock);\n    }\n    wt->fd = fd;\n    wt->events = events;\n    wt->deadline_ms = (timeout_ms < 0) ? -1 :\n                      (nurl__now_ms_internal() + timeout_ms);\n    wt->fiber = w->current;\n    wt->result = 0;\n    wt->active = 0;\n    wt->mutex_park = 0;\n    wt->park_state = 0;\n    wt->forced = 0;\n    wt->admit = admit;\n#ifdef _WIN32\n    if ((events & POLLIN) && fd >= 0 && nurl__shut_has(fd)) wt->forced = 1;\n    /* …and so is one on a number that names no socket: ready, as poll(2)\n     * answers POLLNVAL, without ever reaching WSAPoll. */\n    if (fd >= 0 && !nurl__win_is_socket(fd)) wt->forced = 1;\n#endif\n    wt->next = nurl__reactor.waits;\n    nurl__reactor.waits = wt;\n    pthread_mutex_unlock(&nurl__reactor.lock);\n    /* Don't wake the reactor — entry is inactive; worker loop activates\n     * after swap-out completes. */\n\n    NurlFiber *cur = w->current;\n    cur->pending_unlock = NULL;\n    cur->pending_reactor_wait = wt;\n    cur->state = NF_PARKED;\n    nurl__swap_to_loop(cur, w);\n    /* Resumed — possibly on another worker; the fiber carries its own\n     * estimate. */\n    if (spin_cap > 0) nurl__spin_observe(cur, nurl_monotonic_ns() - t0, spin_cap);\n    return cur->last_park_result;\n}\n\nstatic int nurl__reactor_wait(long long fd, short events, long long timeout_ms) {\n    return nurl__reactor_wait_k(fd, events, timeout_ms, 0);\n}\n\n/* An I/O wait's fd that cannot name a descriptor answers \"ready\" at once\n * (the Linux backend's nurl__wait_fd_bad, agora #133): a negative one,\n * which the reactor would take for a pure timer, and on POSIX one past\n * INT_MAX, which pollfd's int would narrow onto an unrelated fd. (A\n * Windows SOCKET is a pointer-sized handle: only INVALID_SOCKET, -1 here,\n * names nothing.) -1 off a fiber, as every wait. */\nstatic int nurl__wait_fd_bad(long long fd, long long *verdict) {\n#ifdef _WIN32\n    if (fd >= 0) return 0;\n#else\n    if (fd >= 0 && fd <= INT_MAX) return 0;\n#endif\n    NurlWorker *w = nurl__tls_worker;\n    *verdict = (w && w->current) ? 1 : -1;\n    return 1;\n}\n\nNURL_TLS_FN long long nurl_reactor_wait_read(long long fd, long long timeout_ms) {\n    long long v;\n    if (nurl__wait_fd_bad(fd, &v)) return v;\n    return (long long)nurl__reactor_wait(fd, POLLIN, timeout_ms);\n}\n\n/* nurl_reactor_wait_read for an accept on listener `fd` (Admission). */\nNURL_TLS_FN long long nurl_reactor_wait_accept(long long fd, long long timeout_ms) {\n    long long v;\n    if (nurl__wait_fd_bad(fd, &v)) return v;\n    return (long long)nurl__reactor_wait_k(fd, POLLIN, timeout_ms, 1);\n}\n\nNURL_TLS_FN long long nurl_reactor_wait_write(long long fd, long long timeout_ms) {\n    long long v;\n    if (nurl__wait_fd_bad(fd, &v)) return v;\n    return (long long)nurl__reactor_wait(fd, POLLOUT, timeout_ms);\n}\n\nNURL_TLS_FN long long nurl_reactor_wait_io(long long fd, long long events, long long timeout_ms) {\n    short mask = (short)(((events & 1) ? POLLIN : 0) | ((events & 2) ? POLLOUT : 0));\n    if (!mask) return -1;\n    long long v;\n    if (nurl__wait_fd_bad(fd, &v)) return v;\n    return (long long)nurl__reactor_wait(fd, mask, timeout_ms);\n}\n\nNURL_TLS_FN long long nurl_fiber_sleep_ms(long long ms) {\n    if (ms <= 0) { nurl_fiber_yield(); return 0; }\n    nurl__reactor_wait(-1, 0, ms);\n    return 0;\n}\n\n/* nurl_fiber_park_with_mutex_ms / nurl_fiber_wake — the contract the\n * Linux backend documents, over this backend's wait list: the entry is\n * inactive until the worker loop activates it, after the mutex release. */\nNURL_TLS_FN long long nurl_fiber_park_with_mutex_ms(long long mutex_h, long long timeout_ms) {\n    NurlWorker *w = nurl__tls_worker;\n    if (!w || !w->current) return -1;\n    nurl__reactor_start_if_needed();\n    if (!nurl__reactor.started) return -1;\n    pthread_mutex_lock(&nurl__reactor.lock);\n    NurlReactorWait *wt = nurl__reactor.freelist;\n    if (wt) nurl__reactor.freelist = wt->next;\n    else {\n        pthread_mutex_unlock(&nurl__reactor.lock);\n        wt = (NurlReactorWait*)calloc(1, sizeof(NurlReactorWait));\n        if (!wt) return -1;\n        pthread_mutex_lock(&nurl__reactor.lock);\n    }\n    NurlFiber *cur = w->current;\n    wt->fd = -1;\n    wt->events = 0;\n    wt->deadline_ms = (timeout_ms < 0) ? -1 :\n                      (nurl__now_ms_internal() + timeout_ms);\n    wt->fiber = cur;\n    wt->result = 0;\n    wt->active = 0;\n    wt->mutex_park = 1;\n    wt->park_state = 0;\n    wt->forced = 0;\n    wt->admit = 0;\n    wt->next = nurl__reactor.waits;\n    nurl__reactor.waits = wt;\n    cur->timed_park = wt;\n    pthread_mutex_unlock(&nurl__reactor.lock);\n    cur->pending_unlock = (pthread_mutex_t *)(uintptr_t)mutex_h;\n    cur->pending_reactor_wait = wt;\n    cur->state = NF_PARKED;\n    nurl__swap_to_loop(cur, w);\n    return cur->last_park_result;\n}\n\nlong long nurl_fiber_wake(long long fiber_h) {\n    NurlFiber *f = (NurlFiber*)(uintptr_t)fiber_h;\n    if (!f) return 0;\n    pthread_mutex_lock(&nurl__reactor.lock);\n    NurlReactorWait *wt = f->timed_park;\n    if (!wt) {\n        pthread_mutex_unlock(&nurl__reactor.lock);\n        return 0;\n    }\n    f->timed_park = NULL;\n    if (wt->park_state == 0) {\n        wt->park_state = 2;\n        pthread_mutex_unlock(&nurl__reactor.lock);\n        return 1;\n    }\n    NurlReactorWait **pp = &nurl__reactor.waits;\n    while (*pp && *pp != wt) pp = &(*pp)->next;\n    if (*pp) *pp = wt->next;\n    wt->fiber = NULL;\n    wt->next = nurl__reactor.freelist;\n    nurl__reactor.freelist = wt;\n    pthread_mutex_unlock(&nurl__reactor.lock);\n    f->last_park_result = 1;\n    nurl_fiber_unpark((long long)(uintptr_t)f);\n    return 1;\n}\n\nvoid nurl__reactor_shutdown(void) {\n    if (!nurl__reactor.started) return;\n    nurl__reactor.shutdown = 1;\n    nurl__reactor_wake();\n    pthread_join(nurl__reactor.thread, NULL);\n    nurl__reactor.started = 0;\n    nurl__signal_close(nurl__reactor.wake_pipe[0], nurl__reactor.wake_pipe[1]);\n    pthread_mutex_destroy(&nurl__reactor.lock);\n    NurlReactorWait *w = nurl__reactor.waits;\n    while (w) { NurlReactorWait *n = w->next; free(w); w = n; }\n    nurl__reactor.waits = NULL;\n    w = nurl__reactor.freelist;\n    while (w) { NurlReactorWait *n = w->next; free(w); w = n; }\n    nurl__reactor.freelist = NULL;\n}\n\n\nvoid nurl__reactor_cleanup(void) {}\n\n#endif /* __linux__ */\n\n#else  /* WASI — Phase 5 stubs */\n\nvoid nurl__reactor_shutdown(void) {}\nvoid nurl__reactor_wake_if_started(void) {}\n#ifdef _WIN32\n/* Windows with no fiber backend (no Win64 x64 switch): nothing parks. */\nvoid nurl__reactor_read_shut(long long fd) { (void)fd; }\nvoid nurl__reactor_forget_fd(long long fd) { (void)fd; }\n#endif\n\nNURL_TLS_FN long long nurl_reactor_wait_read(long long fd, long long timeout_ms) {\n    (void)fd; (void)timeout_ms; return -1;\n}\nNURL_TLS_FN long long nurl_reactor_wait_accept(long long fd, long long timeout_ms) {\n    (void)fd; (void)timeout_ms; return -1;\n}\nNURL_TLS_FN long long nurl_reactor_wait_write(long long fd, long long timeout_ms) {\n    (void)fd; (void)timeout_ms; return -1;\n}\nNURL_TLS_FN long long nurl_reactor_wait_io(long long fd, long long events, long long timeout_ms) {\n    (void)fd; (void)events; (void)timeout_ms; return -1;\n}\nNURL_TLS_FN long long nurl_fiber_sleep_ms(long long ms) {\n    /* WASI fall-through; real sleep awaits port. */\n    (void)ms; return 0;\n}\n/* No fiber to park: the caller waits on its condvar instead. */\nNURL_TLS_FN long long nurl_fiber_park_with_mutex_ms(long long mutex_h, long long timeout_ms) {\n    (void)mutex_h; (void)timeout_ms; return -1;\n}\nlong long nurl_fiber_wake(long long fiber_h) { (void)fiber_h; return 0; }\n\n#endif\n","bytes":385153}