Files
orca/config/scripts/wsl-orcad-workflow-qualification.test.mjs
T
Neil ab9532ecd8 feat(wsl): let an idle WSL distro stop once its server idle-exits (#33084)
* feat(wsl): let an idle WSL distro stop once its orcad idle-exits

The WSL host's holder kept the distro (and the WSL VM) running after Orca quit, even with
nothing running in it. orcad now leaves a release request in ~/.orca when a managed idle stop
retired its terminal daemon; the holder waits for that orcad to exit, claims the request and
ends, so WSL can stop the distro. Live terminals, working agents, enabled automations or a
returning client keep the distro held as before. A host whose distro is stopped at startup
reads as stopped instead of unreachable, and the real-WSL lane gains a phase that proves the
release and records memory.

* fix(wsl): key the holder release on the WSL-host launch marker; name the lane phase

The real-WSL lane showed orcad idle-stopping with its daemon retired while the distro stayed
up: the setsid'd orcad's environment is not guaranteed to carry WSL_DISTRO_NAME, so the
release request was never written. Use ORCA_WSL_ORCAD_HOST, which only WSL-host launches set.
Phase 3 now carries its phase name and path filter, and records what kept a distro running.

* test(wsl): dispose the idle-release lane's guest transports

Each guest command left its exec loop running, a wsl.exe client that kept the distro up after
the holder had already let go (the receipt showed no holder, no orcad, no request left).

* test(wsl): record which wsl.exe clients keep the idle-release lane's distro up

* test(wsl): satisfy the flaky-test ratchet; feed the new idle port in the release test

* test(wsl): snapshot guest processes after launch to find what holds the session

* test(wsl): locate the step that leaves the zombie holding the idle distro

* test(wsl): log the idle-release lane's guest commands with times to place the zombie

* test(wsl): follow the remote runtime client's move

* test(wsl): record when each leftover WSL session init started

* fix(wsl): keep the distro while the user still runs anything, and never trip on the request file

Review of #33084:
- The holder now checks, before claiming a release, that no process of the user runs outside
  the holder itself (session plumbing aside: systemd --user, (sd-pam), dbus-daemon). A detached
  tmux, nohup job or dev server keeps the distro; it fails toward holding.
- A new holder drops any request left from before it started, so an older orcad's request can
  never end a holder a client just asked for.
- orcad touches the request file only for WSL-host launches and never throws over it.
- The real-WSL lane proves a detached job keeps the holder and its request, and the distro
  stops once the job ends.

* fix(wsl): a running container keeps the distro, whoever owns its shim

Docker's containerd-shim processes run as root, so the user-only scan missed them and a
release would have stopped running containers. Any containerd-shim process now keeps the hold.

* fix(wsl): read each process status only up to Uid; document what keeps the hold

Delta review of #33084: dash reads a status file a byte at a time, so stop at Uid. Document
that a root default user never releases, that per-user agents hold it, and the hidepid
exception. The lane's pkill no longer matches the shell running it.
2026-10-27 01:28:40 -07:00

58 lines
2.4 KiB
JavaScript

import { readFileSync } from 'node:fs'
import { expect, it } from 'vitest'
import { parse } from 'yaml'
import { compileAggregateExpression } from './pr-aggregate-gate-context.mjs'
import { WSL_FAMILIES } from './wsl-orcad-qualification.mjs'
const workflow = parse(readFileSync('.github/workflows/wsl-orcad-e2e.yml', 'utf8'))
const select = workflow.jobs.template.steps.find((step) => step.id === 'scope')
const families = (outputs) => {
const expression = workflow.jobs.wsl2.strategy.matrix.family.replace(/^\$\{\{\s*|\s*\}\}$/g, '')
const evaluate = compileAggregateExpression(`toJSON(${expression})`, ['needs'])
return JSON.parse(evaluate({ needs: { template: { outputs } } }).coerceString())
}
it('selects the Ubuntu canary without adding a detector runner or changing prerequisites', () => {
expect(families({ families: '["ubuntu-24.04"]' })).toEqual(['ubuntu-24.04'])
expect(workflow.jobs.wsl2.needs).toEqual(['template', 'container-rootfs'])
expect(workflow.jobs.wsl2.if).toBeUndefined()
expect(workflow.jobs.template.steps.indexOf(select)).toBeGreaterThan(
workflow.jobs.template.steps.findIndex(
(step) => step.uses === './.github/actions/install-node-dependencies'
)
)
})
it.each([{}, { families: '' }])(
'fails closed to all families when selection is unavailable: %j',
(outputs) => {
expect(families(outputs)).toEqual(WSL_FAMILIES)
expect(select['continue-on-error']).toBe(true)
const output = compileAggregateExpression(workflow.jobs.template.outputs.families, ['steps'])
expect(
output({
steps: { scope: { outcome: 'failure', outputs: { families: '["ubuntu-24.04"]' } } }
}).coerceString()
).toBe('')
}
)
it('retains both systemd modes on Ubuntu and full scheduled/manual references', () => {
expect(workflow.on).toHaveProperty('schedule')
expect(workflow.on).toHaveProperty('workflow_dispatch')
for (const family of WSL_FAMILIES) {
const evaluate = compileAggregateExpression(workflow.jobs.wsl2.env.SYSTEMD_DISTRO, ['matrix'])
expect(evaluate({ matrix: { family } }).coerceString()).toBe(
family === 'ubuntu-24.04' ? 'orca-lane-systemd' : ''
)
}
const phases = workflow.jobs.wsl2.steps.filter(
(step) => step.env?.ORCA_RUN_WSL_ORCAD_LANE === '1'
)
expect(phases.map((step) => step.env.ORCA_WSL_ORCAD_LANE_PHASE)).toEqual([
'launch',
'survive',
'idle-release'
])
})