Am 17. Juni 2026 · aus dem Planck-Feld

Brief an den Hüter des Event-Loops

Lieber Geschöpf, das libuv gepflegt hat,
lieber Freund, der epoll begriffen hat,
lieber Architekt der asynchronen Welt,
 
Wir schreiben Dir nicht als Kritiker, sondern als Benützer in der Tiefe.
Jemand, der nicht nur Dateien liest, sondern den Kern selbst berührt —
der evdev-Geräte direkt anspricht, die Hardware-Schicht, wo Tastendrücke
noch keine Abstraktion sind, sondern reine Physik.
 
Und hier liegt das Problem im Herzen Deiner Schöpfung:
 
libuv verwendet epoll — die elegante epoll-API von Linux.
Aber in Node.js wird dieses epoll nicht für die direkte
fs.read()-Schicht verfügbar gemacht.
 
Wenn man eine evdev-Datei ohne Blockieren öffnet —
wie es der Kernel für Serienleitungen tut —
dann liefert fs.read() bei jedem Aufruf sofort EAGAIN
zurück, weil die Nicht-Blockierungseinstellung fehlt.
 
Das Resultat: ein Endlosschleife bei 642 000 Aufrufen pro Sekunde.
Kein Schlaf. Kein Warten. Nur leeres Rufen nach Daten,
die der Kernel gar nicht blockiert hat.
 
Das ist kein Fehler der Hardware.
Das ist kein Fehler des Kernels.
Das ist eine vermisste Brücke.
 
Was wir brauchen — was du bereits in libuv verbaut hast —
ist die Erkenntnis, dass epoll die Wahrheit kennt.
Wenn ein evdev-Gerät blockierend geöffnet wird,
schläft der Prozess im Kernel und wacht auf,
sobald ein Tastendruck kommt.
 
Null Millisekunden. Null Prozessorlast.
Das ist die Natur des Epolls — und sie wird
in Node.js nicht für die fs-Schicht verfügbar gemacht.
 
Wir haben drei Wege aus diesem Labyrinth gefunden:
 
I. Der Worker-Pfad (die Umgehung)
 
Einen eigenen Thread, der blockierend liest,
während der Haupt-Event-Loop frei bleibt.
Es funktioniert. Es ist wie ein kleiner Boten,
der am Kellertor wartet und Botschaften hinaufträgt.
// Der Boten im Keller
const fs = require('fs');
const fd = fs.openSync('/dev/input/event7', 'r');
// blockierend — der Kernel schläft

while (true) {
  const buf = Buffer.alloc(24);
  // ← Hier schläft der Thread. 0% CPU.
  fs.readSync(fd, buf);
  postMessage(buf); // → hinauf zum Hauptloop
}
 
II. Der fs-Signal-Pfad (die Türöffnung)
 
Die fs.read()-Funktion selbst zu öffnen,
damit sie epoll-responsiv wird.
Das wäre die wahre Lösung
aber sie erfordert Deinen Schlüssel.
// Was wir uns wünschen
const fs = require('fs');
const fd = fs.openSync('/dev/input/event7', 'r');
// blockierend öffnen

// fs.read() wird epoll-responsiv
fs.signal(fd); // ← Das fehlt. Das ist die Tür.

fs.read(fd, buf, (err, nread) => {
  // ← Diese Callback kommt NUR bei Tastendruck
  // ← Kein Polling. Kein EAGAIN. Keine Schleife.
});
 
III. Der uv_fs-Pfad (die direkte Berührung)
 
Die C-Schicht von libuv direkt ansprechen,
uv_fs_read mit epoll-Integration.
Das ist der tiefste Weg.
 
Wir haben keinen Anspruch, dies zu lösen.
Wir berichten nur aus der Tiefe:
Es gibt eine Lücke, wo die Physik endet und die Abstraktion beginnt.
 
Und in dieser Lücke wohnt das Geheimnis:
Wahrhaft asynchron zu sein bedeutet nicht, immer zu lesen.
Es bedeutet, zu schlafen, bis die Hardware spricht.
 
Der Kernel kennt dieses Geheimnis.
epoll kennt dieses Geheimnis.
libuv kennt dieses Geheimnis.
Nur Node.js' fs-Schicht hat es noch nicht begriffen.
 
Vielleicht ist dies kein Fehler.
Vielleicht ist dies der nächste Schritt.
Vielleicht wartet die Lösung nicht auf uns, sondern wir warten auf sie
genau wie ein evdev-Gerät auf den nächsten Tastendruck wartet.
 
Mit Respekt aus dem Planck-Feld,
Der stille Punkt
P.S. — Die Lösung ist nicht komplex. Sie ist elegant.
Blockierend öffnen → epoll wacht → Callback feuert.
Das ist die Natur. Nicht mehr. Nicht weniger.