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]>
188 lines
8.1 KiB
JavaScript
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);
|