feat(pty): FFI breadcrumbs around the syscalls that wedge (T-434)
The freeze hypothesis is a wedged FFI call — a reader isolate blocked forever
in ReadFile, a waiter in WaitForSingleObject, Isolate.kill unable to interrupt
either. To NAME the wedge after a power-cycle, each backend now drops a
breadcrumb before/after every risky syscall.
- pty_log.dart (new, Flutter-free, tested): PtyLog — an injectable, no-op-by-
default breadcrumb hook for the MAIN isolate (wired to the kernel Logger,
source 'conpty'/'pty' = an eager FileLogSink source) — and IsolateCrumbFile,
which the SPAWNED reader/waiter isolates use to open their OWN append handle
and flushSync per line, so a wedged isolate's last crumb survives even a
frozen main isolate (the whole point). Bounded by a truncating size cap.
- native_pty.dart + windows_pty.dart: crumbs around posix_spawn/read and
CreatePseudoConsole/CreateProcessW/ReadFile/WaitForSingleObject; the reader/
waiter isolates carry a sendable crumb path + verbose flag. Per-syscall crumbs
only at debug/trace; lifecycle crumbs always.
- Wiring: startPtySession → PaneRegistry → buildDispatcher build the PtyLog from
the kernel Logger + a crumb file under logDirectory(); verbose follows the log
level. Default everywhere is PtyLog.none — zero behaviour change off the wire.
Tested: PtyLog/IsolateCrumbFile units (cap-truncation, append, no-op) + an
end-to-end real-PTY test asserting the reader isolate writes its own crumbs
('reader started' / 'read -> n=' / 'reader exiting'), which validates the
identical Windows structure that can't run here. Coverage gate 95.10%.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,14 +12,19 @@ import 'dart:io' show Platform;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import '../ipc/envelope.dart';
|
||||
import '../pty/pty_log.dart';
|
||||
import '../pty/pty_session.dart';
|
||||
import 'event_sink.dart';
|
||||
import 'pane.dart';
|
||||
|
||||
class PaneRegistry {
|
||||
PaneRegistry({required this.events});
|
||||
PaneRegistry({required this.events, this.ptyLog = PtyLog.none});
|
||||
|
||||
final DaemonEventSink events;
|
||||
|
||||
/// Breadcrumb hook handed to every PTY this registry spawns (T-434). Default
|
||||
/// no-op; production wires it to the kernel Logger + a crumb file.
|
||||
final PtyLog ptyLog;
|
||||
final Map<String, Pane> _panes = {};
|
||||
final Map<String, PtySession> _sessions = {};
|
||||
final Map<String, StreamSubscription<Uint8List>> _subs = {};
|
||||
@@ -55,7 +60,15 @@ class PaneRegistry {
|
||||
...?env,
|
||||
};
|
||||
|
||||
final session = startPtySession(executable: executable, arguments: arguments, columns: cols, rows: rows, workingDirectory: cwd, environment: fullEnv);
|
||||
final session = startPtySession(
|
||||
executable: executable,
|
||||
arguments: arguments,
|
||||
columns: cols,
|
||||
rows: rows,
|
||||
workingDirectory: cwd,
|
||||
environment: fullEnv,
|
||||
log: ptyLog,
|
||||
);
|
||||
final pane = Pane(id: id, kind: kind, pid: session.pid, argv: argv, cwd: cwd, title: title);
|
||||
_panes[id] = pane;
|
||||
_sessions[id] = session;
|
||||
|
||||
Reference in New Issue
Block a user