mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
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>
79 lines
3.4 KiB
C
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
|