Files
NX2navi/tests/handle_census_check.c
chrg6 26b7cfd658 Put the process's OS handle count on the sizes line and add a handle census by kind behind NX2NAVI_HANDLE_CENSUS.
A session of 2026-11-13 grew from 683 to 1095 OS handles in 11 minutes, rising at every area load and never falling,
while the guest's own handles stayed at 116-122 and the threads flat; the VMA block count fell back while the handle
count did not. The log could not show it: the 600-frame "mem: sizes" line had only the guest's handles, and nothing
told which kind of handle grows.

kernel.c kern_sizes now adds "process handles" (plat_handle_count, one system call every 600 frames under trace=perf)
beside the guest's "handles". platform.h gets plat_handle_census: on Windows the system's extended handle table
filtered by our process id, with the type names from NtQueryObject's ObjectTypesInformation; on Linux the
/proc/self/fd links grouped by kind. It never opens, duplicates or names a handle (a name query can block on a pipe).
With NX2NAVI_HANDLE_CENSUS=1 (default 0, read at start) and trace=perf, perf.c starts one long-lived thread that the
600-frame report wakes; when the count grew by 16 or more since its last census it logs "mem: handles frame N: T in
all (+D since the last census): Event a (+x), ...". The census reads a few MiB of the system's table in 2-5 ms, off
the drawing threads; one thread for the run keeps a new thread's own handle out of what it counts. New cfg rows:
HANDLE_CENSUS in known[], sections[] (diagnostics) and metas[] ("0", "0|1", restart), and an nx2navi.ini.example
entry.

New tests/handle_census_check.py builds the platform layer alone: 20 events and 5 sections show as Event +20 and
Section +5, the total stays within 2 of GetProcessHandleCount, the figures return once they are closed, and kinds
past the room given go into the last entry; docs/building.md describes it. Not reproduced before the change (the
growth needs a game run). handle_census_check, sync_wake_check and the cfg row check pass; every runtime file compiles
with the dev flags, and perf.c, kernel.c, win32.c and cfg.c add no -Wall -Wextra warnings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 18:41:39 -05:00

79 lines
3.4 KiB
C

// runtime/platform: the census of the process's handles by kind (platform.h plat_handle_census) sees the handles it is
// asked about. A census, then 20 events and 5 sections made, a census again (Event +20, Section +5, its total within 2
// of the process's handle count), the handles closed and a third census back at the first figures. Windows only: the
// Linux census groups file descriptors, which this check does not make. Built and run by tests/handle_census_check.py.
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "platform/platform.h"
void rt_log(const char *fmt, ...) { // the platform layer's lines
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputc('\n', stderr);
}
#ifdef _WIN32
#include <windows.h>
static int fails;
#define CHECK(c, ...) do { if (!(c)) { fails++; printf("FAIL line %d: ", __LINE__); printf(__VA_ARGS__); printf("\n"); } } while (0)
static uint32_t of(const PlatHandleKind *k, int n, const char *name) {
for (int i = 0; i < n; i++)
if (!strcmp(k[i].name, name)) return k[i].n;
return 0;
}
static uint32_t total(const PlatHandleKind *k, int n) {
uint32_t t = 0;
for (int i = 0; i < n; i++) t += k[i].n;
return t;
}
int main(void) {
static PlatHandleKind a[64], b[64], c[64];
int na = plat_handle_census(a, 64);
CHECK(na > 0, "the first census found %d kinds", na);
if (na <= 0) return 1;
uint32_t count = plat_handle_count();
CHECK(total(a, na) + 2 >= count && total(a, na) <= count + 2, "the census counted %u handles, the process holds %u",
total(a, na), count);
CHECK(of(a, na, "Event") > 0, "no Event handles named in the first census");
HANDLE ev[20], sec[5];
for (int i = 0; i < 20; i++) ev[i] = CreateEventW(NULL, TRUE, FALSE, NULL);
for (int i = 0; i < 5; i++) sec[i] = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 65536, NULL);
int nb = plat_handle_census(b, 64);
count = plat_handle_count();
CHECK(nb > 0, "the second census found %d kinds", nb);
CHECK(of(b, nb, "Event") == of(a, na, "Event") + 20, "Event %u after 20 made, %u before", of(b, nb, "Event"),
of(a, na, "Event"));
CHECK(of(b, nb, "Section") == of(a, na, "Section") + 5, "Section %u after 5 made, %u before", of(b, nb, "Section"),
of(a, na, "Section"));
CHECK(total(b, nb) + 2 >= count && total(b, nb) <= count + 2, "the census counted %u handles, the process holds %u",
total(b, nb), count);
for (int i = 0; i < 20; i++) CloseHandle(ev[i]);
for (int i = 0; i < 5; i++) CloseHandle(sec[i]);
int nc = plat_handle_census(c, 64);
CHECK(of(c, nc, "Event") == of(a, na, "Event") && of(c, nc, "Section") == of(a, na, "Section"),
"after closing: Event %u, Section %u; at first %u and %u", of(c, nc, "Event"), of(c, nc, "Section"),
of(a, na, "Event"), of(a, na, "Section"));
PlatHandleKind few[2];
int nf = plat_handle_census(few, 2);
CHECK(nf == 2 && !strcmp(few[1].name, "(more kinds)") && few[0].n + few[1].n == total(c, nc),
"a census into 2 entries: %d kinds, the second '%s', %u of %u handles", nf, nf == 2 ? few[1].name : "",
nf == 2 ? few[0].n + few[1].n : 0, total(c, nc));
if (fails) return 1;
printf("handle_census_check: all checks passed (%d kinds, %u handles)\n", nc, total(c, nc));
return 0;
}
#else
int main(void) {
printf("handle_census_check: skipped (Windows only)\n");
return 0;
}
#endif