The clean-path soak found no leak on windows-latest — because orderly close() reaps every host. That never exercises the freeze hypothesis (T-424), which is the parent dying WITHOUT teardown while a child is live. This probe does. conpty_orphan_probe.dart starts N real WindowsPty sessions on long-lived children and blocks WITHOUT ever calling close(); soak-conpty-kill.ps1 force-kills only the dart.exe parent (taskkill /F, no /T) once the hosts are up and counts the conhost/OpenConsole/cmd processes that SURVIVE. Absent a kill-on-close Job Object, abrupt parent death should orphan them — a survivor count that climbs across cycles is the leak signature. The same probe will prove the T-424 fix: with the job, survivors should drop to ~0. Wired as a second job in windows-soak.yml (workflow_dispatch + when the kit changes). Diagnostic only, never a gate, always exits 0; publishes the verdict to the job summary and uploads the CSV. Not part of the regular test suite. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
64 lines
2.8 KiB
Dart
64 lines
2.8 KiB
Dart
/// ConPTY orphan probe — the ABRUPT-DEATH half of the windows-verify kit.
|
|
///
|
|
/// `soak-conpty.ps1` measures the CLEAN path: dart exits normally, the ConPTY
|
|
/// teardown (`close()` → ClosePseudoConsole → handle release) runs, and the
|
|
/// hosts are reaped. On GitHub's windows-latest that path showed NO leak —
|
|
/// orderly shutdown reclaims everything. But the freeze hypothesis (T-424) is
|
|
/// not about orderly shutdown; it is about the parent dying WITHOUT teardown
|
|
/// (a crash, a Ctrl-C, a wedged reader isolate) while the child is still live.
|
|
///
|
|
/// This probe exercises exactly that. It starts [count] real [WindowsPty]
|
|
/// sessions — the production backend, no mocks — each running a long-lived
|
|
/// child, prints a READY line carrying the dart pid + each child pid, then
|
|
/// blocks forever and NEVER calls `close()`. Its companion driver
|
|
/// (`soak-conpty-kill.ps1`) force-kills this dart.exe (`taskkill /F`, NOT
|
|
/// `/T`, so only the parent dies) once the children are up, then counts the
|
|
/// conhost / OpenConsole / cmd processes that SURVIVE the parent's death.
|
|
///
|
|
/// Because the children are not placed in a kill-on-close Job Object, abrupt
|
|
/// parent death is expected to orphan them — that is the leak this probe is
|
|
/// built to expose. The same probe will later PROVE the T-424 fix: once each
|
|
/// child lives in a JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE job, killing the
|
|
/// parent should take the job — and every host — down with it, and the
|
|
/// survivor count should drop to zero.
|
|
library;
|
|
|
|
import 'dart:io';
|
|
|
|
import 'package:clide/src/pty/windows_pty.dart';
|
|
|
|
Future<void> main(List<String> args) async {
|
|
if (!Platform.isWindows) {
|
|
stderr.writeln('conpty_orphan_probe: Windows only (ConPTY).');
|
|
exit(2);
|
|
}
|
|
final count = args.isNotEmpty ? (int.tryParse(args.first) ?? 1) : 1;
|
|
|
|
final sessions = <WindowsPty>[];
|
|
for (var i = 0; i < count; i++) {
|
|
final s = WindowsPty.start(
|
|
executable: 'cmd.exe',
|
|
// A long-lived child so the ConPTY host stays alive across the whole
|
|
// kill window. `ping -n 600` loops for ~10 min with no extra deps.
|
|
arguments: ['/c', 'ping -n 600 127.0.0.1'],
|
|
columns: 80,
|
|
rows: 24,
|
|
environment: {...Platform.environment, 'TERM': 'xterm-256color'},
|
|
);
|
|
// Drain output so the reader isolate is actively pumping, closest to a
|
|
// real live pane. Discard the bytes.
|
|
s.output.listen((_) {}, onError: (_) {});
|
|
sessions.add(s);
|
|
}
|
|
|
|
// Signal the driver that the children are up. It waits for the host count
|
|
// to rise, then kills us.
|
|
stdout.writeln('PROBE READY dart_pid=$pid child_pids=${sessions.map((s) => s.pid).join(',')}');
|
|
await stdout.flush();
|
|
|
|
// Block WITHOUT ever calling close() — the driver kills us mid-sleep. That
|
|
// missing teardown is the entire point; do not add a finally/close here.
|
|
await Future<void>.delayed(const Duration(minutes: 10));
|
|
exit(0);
|
|
}
|