Files
dogfather-universe/server/pruef-ports.mjs
T
DogFatherGitandClaude Opus 5 dc355ad05b pruef-ports: kann jede Pruefung ihren Port ueberhaupt bekommen?
ENTSTANDEN AUS EINEM ABSTURZ, DEN ICH SELBST AUSGELOEST HABE

Die Portnummer einer Pruefung wird aus ihrer STELLE IM ALPHABET
abgeleitet. Das ist eindeutig von der Bauart her und deshalb gut --
hat aber eine Nebenwirkung, die in helfer-port.mjs auch ausdruecklich
steht: Kommt eine neue Pruefung dazu, verschieben sich ALLE Nummern
dahinter.

In der Nacht zum 23.09. sind sechs neue Pruefdateien entstanden.
`pruef-bereiche-lesend` rutschte dadurch auf Port 5040, und den haelt
auf diesem Rechner ein Windows-Dienst. Der Lauf endete mit

    [uncaughtException] Error: listen EACCES 127.0.0.1:5040

also einem Stapelauszug aus node:net, der wie ein Fehler im Code
aussieht. GEFUNDEN HABE ICH ES DURCH ZUFALL -- ich wollte nur sehen,
ob eine andere Aenderung etwas kaputt gemacht hat.

Nachgemessen habe ich danach ALLE 366 Nummern: Genau eine ist
betroffen. Es lag also kein weiterer Schaden herum. Aber der naechste
Umbau verschiebt wieder alles, und dann faellt es wieder jemandem
zufaellig auf -- oder eben nicht.

WAS DIE PRUEFUNG TUT (unter einer Sekunde)

  * Sie leitet die Liste mit DERSELBEN Regel ab wie helfer-port.mjs
    (alle pruef-*.mjs im Serverordner, sortiert) -- eine eigene Liste
    waere die, die beim Hinzufuegen der naechsten nicht mitwaechst,
    also genau der Fehler, den sie verhindern soll.
  * Keine Nummer doppelt, keine auf einem gesperrten Port.
  * Und sie probiert jeden Port am echten Betriebssystem aus.

BELEGT IST ETWAS ANDERES ALS GESPERRT, und nur eines ist ein Befund:

  EACCES      Das System gibt den Port nicht her. Das geht nicht
              vorbei. Hier muss der Ausweichport frei sein, sonst ist
              die betroffene Pruefung auf diesem Rechner nicht
              lauffaehig.
  EADDRINUSE  Jemand hoert gerade darauf -- meist ein Lauf, dessen
              Server noch schliesst. Das geht vorbei, und es als
              Fehler zu melden waere eine Warnung, die immer kommt.
              Dritter Ausgang.

Der Unterschied ist der ganze Punkt. Wuerde beides gleich behandelt,
waere diese Pruefung im Gesamtlauf regelmaessig rot -- und dann liest
niemand mehr, wenn sie einmal recht hat.

Dazu eine Gegenprobe zur Ableitung: Ein bekannter gesperrter Port
(5060, SIP) darf in keiner Nummer vorkommen. Ohne sie waere „keine
liegt auf einem gesperrten" auch dann wahr, wenn die Sperrliste gar
nicht angesehen wird.

AUSWEICHEN und GESPERRT sind dafuer aus helfer-port.mjs ausgeleitet --
eine 4000 in der Pruefung waere die zweite Fassung, und beim naechsten
Mal stuende in einer der beiden eine andere Zahl.

Ergebnis heute: 183 Dateien, 366 Nummern, 0 doppelt, 1 vom System
gesperrt (5040 -> weicht auf 9040 aus, frei). 8 Pruefungen, 0 Fehler.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-23 00:35:52 +02:00

188 lines
8.1 KiB
JavaScript

/* =====================================================================
KANN JEDE PRÜFUNG IHREN PORT ÜBERHAUPT BEKOMMEN?
=====================================================================
ENTSTANDEN AUS EINEM ABSTURZ, DEN ICH SELBST AUSGELÖST HABE
(22./23.09.2026).
Die Portnummer einer Prüfung wird aus ihrer STELLE IM ALPHABET
abgeleitet (helfer-port.mjs). Das ist eindeutig von der Bauart her
— zwei Dateien können nicht dieselbe Stelle haben — und genau
deshalb gut. Es hat aber eine Nebenwirkung, die dort auch
ausdrücklich steht: Kommt eine neue Prüfung dazu, verschieben sich
ALLE Nummern dahinter.
In der Nacht zum 23.09. sind sechs neue Prüfdateien entstanden.
`pruef-bereiche-lesend` rutschte dadurch auf Port 5040 — und den
hält auf diesem Rechner ein Windows-Dienst. Der Lauf endete mit
[uncaughtException] Error: listen EACCES 127.0.0.1:5040
also einem Stapelauszug aus node:net, der wie ein Fehler im Code
aussieht. Gefunden habe ich es durch Zufall: Ich wollte nur sehen,
ob eine andere Änderung etwas kaputt gemacht hat.
---------------------------------------------------------------------
WARUM DAS EINE EIGENE PRÜFUNG IST UND KEINE ZEILE IN EINER ANDEREN
Weil die Frage nicht „ist mein Code richtig" lautet, sondern „kann
hier überhaupt gemessen werden". Das ist der dritte Ausgang, und er
betrifft ALLE Prüfungen gleichzeitig. Eine einzelne kann ihn für
sich beantworten (das tut `portMussFreiSein` seit dem 23.09. auch);
dass der ganze Satz durchläuft, sieht man erst, wenn man den ganzen
Satz ansieht. Das dauert unter einer Sekunde.
---------------------------------------------------------------------
BELEGT IST ETWAS ANDERES ALS GESPERRT — und nur eines davon ist ein
Befund:
EACCES Das System gibt den Port nicht her. Das geht nicht
vorbei. Hier MUSS der Ausweichport frei sein, sonst
ist diese Prüfung auf diesem Rechner nicht lauffähig.
EADDRINUSE Jemand hört gerade darauf — meist ein Lauf, der eben
zu Ende ging und dessen Server noch schließt. Das
geht vorbei, und es als Fehler zu melden wäre eine
Warnung, die immer kommt. Deshalb der dritte Ausgang.
Der Unterschied ist der ganze Punkt. Würde hier beides gleich
behandelt, wäre diese Prüfung im Gesamtlauf regelmäßig rot — und
dann liest niemand mehr, wenn sie einmal recht hat.
===================================================================== */
import { readdirSync } from "node:fs";
import { createServer } from "node:net";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { notbremse } from "./helfer-notbremse.mjs";
notbremse(60_000, "pruef-ports");
const HIER = dirname(fileURLToPath(import.meta.url));
let fehler = 0, geprueft = 0, offen = 0;
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
const unklar = (t) => { offen++; console.log(" -- " + t); };
const { portNummer, AUSWEICHEN, GESPERRT } = await import("./helfer-port.mjs");
console.log("=== Kann jede Pruefung ihren Port bekommen? ===");
console.log("");
/* DIE LISTE WIRD GELESEN, NICHT GEPFLEGT -- und zwar mit derselben
Regel wie in helfer-port.mjs selbst: alle `pruef-*.mjs` im
Serverordner, sortiert. Eine eigene Liste hier wäre die, die beim
Hinzufügen der nächsten Prüfung nicht mitwächst -- also genau der
Fehler, den diese Datei verhindern soll. */
const dateien = readdirSync(HIER)
.filter((f) => f.startsWith("pruef-") && f.endsWith(".mjs"))
.sort();
ok(dateien.length > 50, `${dateien.length} Pruefdateien gefunden`);
ok(dateien.includes("pruef-ports.mjs"),
"diese Datei steht selbst in der Liste -- sonst waere die Regel eine andere"
+ " als die, nach der die Nummern vergeben werden");
/** Was sagt das Betriebssystem zu diesem Port? */
const lage = (port) => new Promise((fertig) => {
const s = createServer();
s.once("error", (f) => fertig(f.code || "FEHLER"));
s.once("listening", () => s.close(() => fertig(null)));
s.listen(port, "127.0.0.1");
});
/* =====================================================================
1. DIE ABLEITUNG SELBST
===================================================================== */
console.log("--- Die Nummern ---");
const nummern = [];
for (let i = 0; i < dateien.length; i++) {
for (const nr of [0, 1]) nummern.push({ datei: dateien[i], nr, port: portNummer(i * 2 + nr) });
}
ok(nummern.length === dateien.length * 2,
`${nummern.length} Nummern fuer ${dateien.length} Dateien (je zwei)`);
const doppelt = nummern
.map((x) => x.port)
.filter((p, i, alle) => alle.indexOf(p) !== i);
ok(nummern.length > 0 && doppelt.length === 0,
`keine Nummer doppelt (${doppelt.length} Ueberschneidungen)`);
const inGesperrt = nummern.filter((x) => GESPERRT.has(x.port));
ok(nummern.length > 0 && inGesperrt.length === 0,
`keine liegt auf einem gesperrten Port (${inGesperrt.map((x) => x.port).join(", ") || "keine"})`);
/* GEGENPROBE ZUR ABLEITUNG: Ein gesperrter Port MUSS uebersprungen
werden. Ohne diese Zeile waere „keine liegt auf einem gesperrten"
auch dann wahr, wenn die Sperrliste gar nicht angesehen wird. */
{
const bekannt = [...GESPERRT].filter((p) => p >= 5000).sort((a, b) => a - b)[0];
if (bekannt === undefined) {
unklar("kein gesperrter Port oberhalb von 5000 in der Liste --"
+ " die Gegenprobe hat nichts, woran sie sich zeigen koennte");
} else {
const alle = new Set(nummern.map((x) => x.port));
ok(!alle.has(bekannt),
`Gegenprobe: der gesperrte Port ${bekannt} kommt in keiner Nummer vor`);
}
}
/* =====================================================================
2. UND WAS DAS SYSTEM DAZU SAGT
===================================================================== */
console.log("");
console.log("--- Was der Rechner hergibt ---");
const gesperrt = [], belegt = [];
for (const x of nummern) {
const l = await lage(x.port);
if (l === "EACCES") gesperrt.push(x);
else if (l !== null) belegt.push({ ...x, grund: l });
}
ok(nummern.length > 0,
`${nummern.length} Ports durchprobiert, ${gesperrt.length} vom System gesperrt,`
+ ` ${belegt.length} gerade belegt`);
/* EIN GESPERRTER PORT IST KEIN BEFUND -- solange das Ausweichen
greift. `portMussFreiSein` nimmt dann Port + AUSWEICHEN. Was hier
geprueft wird, ist genau das: dass dieser Weg offensteht. */
for (const x of gesperrt) {
const ersatz = x.port + AUSWEICHEN;
const l2 = await lage(ersatz);
ok(l2 === null,
`${x.datei} (Nr. ${x.nr}): Port ${x.port} ist vom System gesperrt,`
+ ` der Ausweichport ${ersatz} ist ${l2 === null ? "frei" : "es auch nicht (" + l2 + ")"}`);
}
if (!gesperrt.length) {
ok(true, "kein einziger Port ist vom System gesperrt");
}
/* BELEGT GEHT VORBEI. Kein Fehler, sondern der dritte Ausgang: Hier
laesst sich gerade nicht sagen, ob die Nummer brauchbar ist. */
for (const x of belegt.slice(0, 5)) {
unklar(`${x.datei} (Nr. ${x.nr}): Port ${x.port} ist gerade belegt (${x.grund})`
+ " -- das geht vorbei, hier ist nichts zu entscheiden");
}
if (belegt.length > 5) {
unklar(`... und ${belegt.length - 5} weitere belegte Ports`);
}
/* =====================================================================
3. DER TEUERSTE FALL: ZWEI LAEUFE GLEICHZEITIG
=====================================================================
Sind SEHR viele Ports belegt, laeuft vermutlich ein zweiter
Prueflauf. Das ist keine Aussage ueber den Code, aber eine, die man
sehen will -- am 06.09.2026 haben zwei gleichzeitige Laeufe im
anderen Projekt 16 Fehlschlaege erzeugt, keiner davon echt.
===================================================================== */
if (belegt.length > nummern.length / 4) {
unklar(`${belegt.length} von ${nummern.length} Ports sind belegt --`
+ " laeuft parallel noch ein zweiter Prueflauf? Dessen Ergebnisse"
+ " und diese hier waeren beide unbrauchbar.");
}
console.log("");
console.log(`${fehler === 0 ? "BESTANDEN" : "FEHLGESCHLAGEN"}`
+ ` -- ${geprueft} geprueft, ${fehler} Fehler, ${offen} nicht nachsehbar`);
process.exit(fehler === 0 ? 0 : 1);