Closes the observability loop: the log + breadcrumb + watchdog files are now collected by CI so a wedged run leaves downloadable evidence instead of nothing. - logDirectory(): CLIDE_LOG_DIR overrides the per-platform default, so CI can point the logs at an uploadable workspace dir (and tests at a temp dir). Now takes an injectable env map; tested. - test_app.dart: when CLIDE_LOG_DIR is set, the testmode harness tees its logger to a FileLogSink + spawns the watchdog (off by default — normal run-testmode keeps the stderr-only path, no isolate). _say breadcrumbs each test into the file. - conpty_orphan_probe.dart: with CLIDE_LOG_DIR set it passes a verbose PtyLog, so when soak-conpty-kill.ps1 force-kills the parent, the reader/waiter isolates' LAST crumb is fsynced to disk — naming what the wedged isolate was doing at the instant of death. - bundle-smoke job: runs the real release app with CLIDE_LOG=debug + CLIDE_LOG_DIR, uploads clide-logs (watchdog heartbeat/sample + FileLogSink) in an always() step. - windows-soak kill-probe job: sets CLIDE_LOG_DIR, uploads the FFI crumbs. Coverage gate 95.08%. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
116 lines
4.7 KiB
YAML
116 lines
4.7 KiB
YAML
name: windows-soak
|
|
|
|
# ConPTY orphan-leak soak on a GitHub-hosted Windows runner — the cheap
|
|
# alternative to a dedicated Windows VM. The freeze hypothesis (T-424) is that
|
|
# each WindowsPty.start() leaks its conhost/OpenConsole host because the child
|
|
# is not in a kill-on-close Job Object; across many runs those hosts pile up
|
|
# until the box starves. tools/windows-verify/soak-conpty.ps1 reproduces that
|
|
# WITHOUT crashing: it runs the ConPTY suite many times IN ONE job and counts
|
|
# the hosts that survive each dart.exe exit. A throwaway runner is fine — we
|
|
# watch the accumulation (the leading indicator), not the reboot. The repeated
|
|
# runs happen inside this single job, so the leak can build up here even though
|
|
# the runner is discarded afterwards (cf. the note in windows.yml, which only
|
|
# runs the suite once).
|
|
#
|
|
# Diagnostic, never a gate: it always exits 0 and just publishes the verdict +
|
|
# CSV. Runs on demand (workflow_dispatch) and when the soak kit itself changes.
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
iterations:
|
|
description: How many times to run the ConPTY suite (clean-path soak)
|
|
default: "25"
|
|
kill_iterations:
|
|
description: Spawn+force-kill cycles (abrupt-death orphan probe)
|
|
default: "15"
|
|
ptys_per_iter:
|
|
description: WindowsPty sessions spawned per kill cycle
|
|
default: "2"
|
|
push:
|
|
branches: [windows-support]
|
|
paths:
|
|
- tools/windows-verify/**
|
|
- .github/workflows/windows-soak.yml
|
|
|
|
jobs:
|
|
conpty-soak:
|
|
runs-on: windows-latest
|
|
timeout-minutes: 30
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: subosito/flutter-action@v2
|
|
with:
|
|
channel: stable
|
|
- run: flutter --version
|
|
- run: flutter pub get
|
|
- name: ConPTY orphan-leak soak
|
|
shell: pwsh
|
|
run: |
|
|
$iters = "${{ github.event.inputs.iterations }}"
|
|
if (-not $iters) { $iters = "25" }
|
|
tools/windows-verify/soak-conpty.ps1 -Iterations ([int]$iters) -OutDir "$env:GITHUB_WORKSPACE/soak-out"
|
|
- name: Publish verdict to job summary
|
|
if: always()
|
|
shell: pwsh
|
|
run: |
|
|
$s = Get-ChildItem "$env:GITHUB_WORKSPACE/soak-out/*.summary.txt" -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
if ($s) { Get-Content $s.FullName | Add-Content $env:GITHUB_STEP_SUMMARY }
|
|
- name: Upload soak CSV + summary
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: conpty-soak
|
|
path: soak-out
|
|
if-no-files-found: warn
|
|
|
|
conpty-kill-probe:
|
|
# Abrupt-death half: force-kill the parent dart.exe mid-life (no close(),
|
|
# no Job Object) and count the ConPTY hosts that survive. This is the path
|
|
# the freeze hypothesis (T-424) actually implicates — the clean-path soak
|
|
# above never exercises it. Diagnostic only; always succeeds.
|
|
runs-on: windows-latest
|
|
timeout-minutes: 30
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: subosito/flutter-action@v2
|
|
with:
|
|
channel: stable
|
|
- run: flutter --version
|
|
- run: flutter pub get
|
|
- name: ConPTY abrupt-death orphan probe
|
|
shell: pwsh
|
|
# CLIDE_LOG_DIR makes the probe emit FFI breadcrumbs (T-436): when a
|
|
# parent is force-killed mid-life, its reader/waiter isolates' last
|
|
# crumb ("ReadFile enter" / "WaitForSingleObject enter") is fsynced to
|
|
# clide-pty.crumbs.log and uploaded below — naming what the wedged
|
|
# isolate was doing at the instant of death.
|
|
env:
|
|
CLIDE_LOG_DIR: ${{ github.workspace }}/kill-crumbs
|
|
run: |
|
|
$iters = "${{ github.event.inputs.kill_iterations }}"
|
|
if (-not $iters) { $iters = "15" }
|
|
$ptys = "${{ github.event.inputs.ptys_per_iter }}"
|
|
if (-not $ptys) { $ptys = "2" }
|
|
tools/windows-verify/soak-conpty-kill.ps1 -Iterations ([int]$iters) -PtysPerIter ([int]$ptys) -OutDir "$env:GITHUB_WORKSPACE/kill-out"
|
|
- name: Publish verdict to job summary
|
|
if: always()
|
|
shell: pwsh
|
|
run: |
|
|
$s = Get-ChildItem "$env:GITHUB_WORKSPACE/kill-out/*.summary.txt" -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
if ($s) { Get-Content $s.FullName | Add-Content $env:GITHUB_STEP_SUMMARY }
|
|
- name: Upload kill-probe CSV + summary
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: conpty-kill-probe
|
|
path: kill-out
|
|
if-no-files-found: warn
|
|
- name: Upload FFI breadcrumbs (last act of each killed reader/waiter)
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: conpty-kill-crumbs
|
|
path: ${{ github.workspace }}/kill-crumbs
|
|
if-no-files-found: ignore
|