Fix: Logger-Flush konnte ungeschriebene Zeilen verlieren (Race mit 1MB-Cap)

flushAsync nahm eine Kopie der pending-Zeilen und entfernte sie nach dem await
per Index-Zaehlung (slice(snapshot.length)). Feuerte waehrend des awaits ein
write() den 1MB-Buffer-Cap, der vorne Zeilen wegshiftet, war die Zaehlung
desynchron und verwarf neu hinzugekommene, noch nicht geschriebene Zeilen.
Jetzt: pending-Zeilen per Move uebernehmen (Buffer auf [] zuruecksetzen) statt
kopieren; await-Zeit-writes laufen in einen frischen Buffer. Bei Schreibfehler
werden die Zeilen wieder vorn eingereiht und der Cap erneut angewandt.
This commit is contained in:
Sucukdeluxe 2026-06-08 22:33:11 +02:00
parent 272a41a4a7
commit 4432fa25e8

View File

@ -183,7 +183,14 @@ async function flushAsync(): Promise<void> {
} }
flushInFlight = true; flushInFlight = true;
const linesSnapshot = pendingLines.slice(); // Move (not copy) the pending lines out and take ownership. A concurrent write()
// during the await below pushes new lines AND can trim the 1MB cap from the FRONT
// of pendingLines; the old count-based removal (pendingLines.slice(snapshot.length))
// then sliced off the wrong lines and dropped unwritten ones. Resetting the buffer
// here means await-time writes queue independently and nothing desyncs.
const linesSnapshot = pendingLines;
pendingLines = [];
pendingChars = 0;
const chunk = linesSnapshot.join(""); const chunk = linesSnapshot.join("");
try { try {
@ -200,9 +207,19 @@ async function flushAsync(): Promise<void> {
} else if (!primary.ok) { } else if (!primary.ok) {
writeStderr(`LOGGER write failed: ${primary.errorText}\n`); writeStderr(`LOGGER write failed: ${primary.errorText}\n`);
} }
if (wroteAny) { if (!wroteAny) {
pendingLines = pendingLines.slice(linesSnapshot.length); // Write failed: requeue the unwritten lines AHEAD of anything that arrived
pendingChars = Math.max(0, pendingChars - chunk.length); // during the await (preserve order), then re-apply the buffer cap so a
// persistent write failure cannot grow the buffer without bound.
pendingLines = linesSnapshot.concat(pendingLines);
pendingChars += chunk.length;
while (pendingChars > LOG_BUFFER_LIMIT_CHARS && pendingLines.length > 1) {
const removed = pendingLines.shift();
if (!removed) {
break;
}
pendingChars = Math.max(0, pendingChars - removed.length);
}
} }
} finally { } finally {
flushInFlight = false; flushInFlight = false;