/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
compiler-codegen/nova_rt/eventloop.c
324 строки
12 KB
Evgeniy Golovin
diag(221.1 №456, окно p456-hang, sonnet): диагноз зависания предъявлен наблюдением — UAF в net_tcp_close split_refcount, корень в компиляторе (D415 §4)
08 авг 2026, 20:23
08 авг 2026, 20:23
a203429
Код
Авторство
О чём код?
/* Plan 22 Ф.2: глобальный uv_loop_t lifecycle. * * Vela — M:N-движок конкурентности Nova; этот файл — событийный цикл (uv_loop). * Бренд-имя рантайма — docs/dev/naming-conventions.md §1.2, план 224 * (идентификаторы/ABI не переименованы). * * Простая реализация: * - один static uv_loop_t* (использует uv_default_loop()) * - флаг initialized: bool * - init и close — идемпотентные * * Close-логика: при uv_loop_close возможен UV_EBUSY если есть active * handles (forgot to close timer / socket / fs req etc.). В таком случае * walk-and-close с retry-loop, max 100 iterations против infinite hang. * * Под bootstrap (single-thread) — без локов. Под M:N (Plan 23) loop * станет per-worker, эта реализация сменится. */ /* Plan 22 F2: NOVA_USE_LIBUV mandatory — `#error` если не defined. */ #ifndef NOVA_USE_LIBUV # error "Plan 22 F2: NOVA_USE_LIBUV is mandatory." #endif #include "eventloop.h" #include <stdio.h> #include <stdlib.h> static uv_loop_t* _evloop = NULL; static int _evloop_state = 0; /* 0 = uninit, 1 = active, 2 = closed */ /* Plan 44.6 Layer 3: TLS storage. Initialised lazily — main thread в * nova_evloop_init(), worker thread в _worker_main (runtime.c). * Fallback path в nova_current_loop() — если NULL, set к global default. */ #ifdef _MSC_VER __declspec(thread) uv_loop_t* _nova_current_loop = NULL; #else __thread uv_loop_t* _nova_current_loop = NULL; #endif /* Plan 83.3 Ф.1: blocking-offload threadpool sizing. * * libuv's default threadpool is 4 threads — мало для blocking-FFI * offload (Plan 83.3: blocking { } уводит работу в этот пул). Читаем * NOVA_BLOCKING_THREADS и пробрасываем в UV_THREADPOOL_SIZE — libuv * читает его лениво при первом uv_queue_work через uv_once. Поэтому * выставлять обязательно ДО первого blocking-offload'а; nova_evloop_init * вызывается из main-prelude задолго до user-кода. * * Уважаем уже выставленный пользователем UV_THREADPOOL_SIZE. Default — * 64 (паритет с spec D50 §4). Невалидный NOVA_BLOCKING_THREADS → * warning + default. libuv сам клампит к [1, 1024]. */ static void _nova_init_blocking_threadpool(void) { char probe[16]; size_t probelen = sizeof(probe); if (uv_os_getenv("UV_THREADPOOL_SIZE", probe, &probelen) != UV_ENOENT) { return; /* пользователь уже задал — не перетираем */ } const char* size = "64"; char nbuf[16]; const char* n = getenv("NOVA_BLOCKING_THREADS"); if (n && *n) { char* end = NULL; long v = strtol(n, &end, 10); if (end && *end == '\0' && v >= 1 && v <= 1024) { snprintf(nbuf, sizeof(nbuf), "%ld", v); size = nbuf; } else { fprintf(stderr, "nova: warning: invalid NOVA_BLOCKING_THREADS='%s' " "(expected integer 1..1024), using default 64\n", n); } } uv_os_setenv("UV_THREADPOOL_SIZE", size); } void nova_evloop_init(void) { if (_evloop_state != 0) return; /* idempotent */ /* Plan 83.3 Ф.1: до создания loop'а и первого uv_queue_work. */ _nova_init_blocking_threadpool(); _evloop = uv_default_loop(); if (!_evloop) { fprintf(stderr, "nova: uv_default_loop() returned NULL\n"); abort(); } _evloop_state = 1; /* Plan 44.6 Layer 3: main thread current loop = global default. * Worker threads set'ят own loop в _worker_main. */ _nova_current_loop = _evloop; } uv_loop_t* nova_evloop(void) { if (_evloop_state == 0) { /* Lazy auto-init — обычно main-prelude вызывает init явно, * но защита на случай если что-то лезет в evloop раньше. */ nova_evloop_init(); } if (_evloop_state == 2) { fprintf(stderr, "nova: nova_evloop() called after close — use-after-close bug\n"); return NULL; } return _evloop; } uv_loop_t* nova_current_loop(void) { if (_nova_current_loop) return _nova_current_loop; /* Fallback: TLS не set'нут (main thread где evloop_init ещё не * fired либо thread без runtime.init). Lazily берём global default. */ _nova_current_loop = nova_evloop(); return _nova_current_loop; } bool nova_evloop_is_initialized(void) { return _evloop_state == 1; } /* Walk callback: close any handle that's not already closing. */ static void _evloop_close_walk_cb(uv_handle_t* handle, void* arg) { (void)arg; if (!uv_is_closing(handle)) { uv_close(handle, NULL); } } static void _nv456_walk_dump_cb(uv_handle_t* handle, void* arg) { (void)arg; fprintf(stderr, "[m456] evloop_close: handle=%p type=%d closing=%d active=%d\n", (void*)handle, (int)handle->type, uv_is_closing(handle), uv_is_active(handle)); fflush(stderr); } void nova_evloop_close(void) { if (_evloop_state != 1) return; /* not active, no-op */ int _nv456_diag = getenv("NOVA_DIAG_M456") != NULL; /* Попытка close — если active handles остались, walk-and-close, * затем drain pending callbacks через uv_run, повторить. Max 100 * iterations против infinite loop. */ int attempts = 0; while (attempts < 100) { if (_nv456_diag) { fprintf(stderr, "[m456] evloop_close: attempt=%d entering uv_loop_close\n", attempts); uv_walk(_evloop, _nv456_walk_dump_cb, NULL); fflush(stderr); } int rc = uv_loop_close(_evloop); if (_nv456_diag) { fprintf(stderr, "[m456] evloop_close: attempt=%d uv_loop_close rc=%d\n", attempts, rc); fflush(stderr); } if (rc == 0) { /* Чистый close. */ _evloop_state = 2; _evloop = NULL; return; } if (rc != UV_EBUSY) { fprintf(stderr, "nova: uv_loop_close returned %d (%s), giving up\n", rc, uv_strerror(rc)); _evloop_state = 2; _evloop = NULL; return; } /* UV_EBUSY — есть active handles. Закрыть все, drain. */ uv_walk(_evloop, _evloop_close_walk_cb, NULL); if (_nv456_diag) { fprintf(stderr, "[m456] evloop_close: attempt=%d before uv_run(DEFAULT)\n", attempts); fflush(stderr); } uv_run(_evloop, UV_RUN_DEFAULT); if (_nv456_diag) { fprintf(stderr, "[m456] evloop_close: attempt=%d after uv_run(DEFAULT)\n", attempts); fflush(stderr); } attempts++; } fprintf(stderr, "nova: nova_evloop_close: failed to close loop after 100 attempts " "(handles stuck?). Forcing exit.\n"); _evloop_state = 2; _evloop = NULL; } static void _evloop_count_walk_cb(uv_handle_t* handle, void* arg) { int* counter = (int*)arg; if (!uv_is_closing(handle)) { (*counter)++; } } int nova_evloop_active_handles(void) { if (_evloop_state != 1 || !_evloop) return 0; int count = 0; uv_walk(_evloop, _evloop_count_walk_cb, &count); return count; } /* Plan 22 Ф.10: SIGINT handler — graceful Ctrl+C → cancel main-scope. * * uv_signal_t handle регистрируется на SIGINT. При получении сигнала * callback ставит `cancel_requested = true` на main-scope. Все fiber'ы * на next yield-point бросают "scope cancelled" → unwind → defer'ы → * graceful exit. * * NB: Это работает **только** для fiber'ов внутри scope (D92 implicit * либо explicit supervised). Top-level main-flow exit'ит через scope- * drain после main-body, scope.cancel_requested срабатывает на pending * detach'ах. */ #include "nova_rt.h" /* для NovaFiberQueue (тут нужен complete type) */ static uv_signal_t _sigint_handle; static int _sigint_installed = 0; static NovaFiberQueue* _sigint_target = NULL; static void _sigint_cb(uv_signal_t* handle, int signum) { (void)handle; (void)signum; if (_sigint_target) { _sigint_target->cancel_requested = true; /* Plan 22 Ф.10: cancel-wake parked fiber'ов immediate через * generic stop_cb mechanism (D93). */ nova_sched_cancel_all_pending(_sigint_target); fprintf(stderr, "\nnova: SIGINT received — initiating graceful shutdown\n"); } } /* ── Plan 83.10.2 (2026-05-26): NovaDeferredCloseQueue impl ───────── * * These functions are the "per-queue" half of the deferred-close mechanism. * nova_loop_defer_close (the "routing" half) lives in runtime.c because it * needs access to _workers[] and _main_wake. */ void nova_close_queue_init(NovaDeferredCloseQueue* q) { if (!q) return; q->jobs = NULL; q->count = 0; q->cap = 0; nova_mutex_init(&q->mu); } void nova_close_queue_destroy(NovaDeferredCloseQueue* q) { if (!q) return; nova_mutex_lock(&q->mu); free(q->jobs); q->jobs = NULL; q->count = 0; q->cap = 0; nova_mutex_unlock(&q->mu); nova_mutex_destroy(&q->mu); } void nova_loop_drain_closes(NovaDeferredCloseQueue* q) { if (!q) return; /* Move jobs out under lock so producer can enqueue more in parallel. * Next uv_async wake will handle any jobs added during our drain. */ nova_mutex_lock(&q->mu); int n = q->count; NovaDeferredCloseJob* batch = q->jobs; q->jobs = NULL; q->count = 0; q->cap = 0; nova_mutex_unlock(&q->mu); for (int i = 0; i < n; i++) { uv_close(batch[i].handle, batch[i].close_cb); } free(batch); } /* [M-183-net2-loop-affinity-cross-thread-op] fix: generic deferred-call * queue — same shape as NovaDeferredCloseQueue above, generalised to an * arbitrary fn(arg) so net.c can marshal a uv_read_start/uv_write/uv_accept/ * uv_udp_send/uv_udp_recv_start issue call onto the loop-owning thread when * the calling fiber has been work-stolen since the handle was created. */ void nova_call_queue_init(NovaDeferredCallQueue* q) { if (!q) return; q->jobs = NULL; q->count = 0; q->cap = 0; nova_mutex_init(&q->mu); } void nova_call_queue_destroy(NovaDeferredCallQueue* q) { if (!q) return; nova_mutex_lock(&q->mu); free(q->jobs); q->jobs = NULL; q->count = 0; q->cap = 0; nova_mutex_unlock(&q->mu); nova_mutex_destroy(&q->mu); } void nova_loop_drain_calls(NovaDeferredCallQueue* q) { if (!q) return; /* Move jobs out under lock so producer can enqueue more in parallel * (mirrors nova_loop_drain_closes). */ nova_mutex_lock(&q->mu); int n = q->count; NovaDeferredCallJob* batch = q->jobs; q->jobs = NULL; q->count = 0; q->cap = 0; nova_mutex_unlock(&q->mu); for (int i = 0; i < n; i++) { batch[i].fn(batch[i].arg); } free(batch); } void nova_evloop_install_sigint(struct NovaFiberQueue* main_scope) { if (_sigint_installed) return; if (!main_scope) return; _sigint_target = (NovaFiberQueue*)main_scope; if (uv_signal_init(nova_evloop(), &_sigint_handle) != 0) { fprintf(stderr, "nova: uv_signal_init failed — SIGINT graceful shutdown disabled\n"); return; } /* SIGINT = 2 (POSIX standard, valid on Windows тоже через uv_signal). */ if (uv_signal_start(&_sigint_handle, _sigint_cb, 2) != 0) { fprintf(stderr, "nova: uv_signal_start failed — SIGINT graceful shutdown disabled\n"); uv_close((uv_handle_t*)&_sigint_handle, NULL); return; } /* unref'аем handle — он не должен держать loop alive. Loop exit'нет * когда все обычные handles закрыты, signal handler — passive. */ uv_unref((uv_handle_t*)&_sigint_handle); _sigint_installed = 1; }