Repository navigation
fix(client): confirm a companion exit before dropping its state - #3551
Conversation
|
Size Report
Startup median (7 runs, lower is better):
|
There was a problem hiding this comment.
All reported issues were addressed across 11 files
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 10 files (changes from recent commits).
Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.
View guided diff | Turn on auto-fix | Re-trigger cubic
|
Reviewed eb8b7a7. The change is close, but one case still drops a live companion's state, and that is the bug this PR sets out to close. CI is green with 22 of 22 checks passing, and there are no conflicts. stopCompanionProcess still returns silently when the companion is alive but Could the host-kit stopper take an identity argument, such as Not blocking, take or leave: the new docs sentence in build-an-integration.md covers only Of the ten bot threads, the pid === process.pid RangeError is fixed at this commit with a test, and so are the P2 threads mockReturnValue in the test, the half-spawned test, the docs guarantee, the win32 skipIf and the persistUnstoppedCompanion retry. The P3 threads on the SIGKILL case, on the client-metro.ts doc and on the "still live afterwards" wording are fixed too, and the react-devtools.ts thread no longer applies because that file moved to #3559. None of these still apply, so you can resolve them all. I did not reproduce the Windows behavior on a Windows host, and I did not rerun the mutation checks or the local gate. I also did not run a real other-user companion through disconnect. The host-kit behavior is covered only through the mocked exec and |
83fa2aa to
beecacd
Compare
beecacd to
de19329
Compare
|
Rebased onto current The fail-open door is closed. stopProcessAndConfirmExit(
{
pid: state.pid,
identity: {
startTime: state.startTime,
matches: (command) => isCompanionTunnelCommand(command, definition),
},
},
budget,
);
Liveness: Naming: your shape said Size: 892 gross. The Metro half moved to #3575, stacked here, per your split option; the react-devtools line you allowed is one sentence in
|
0abe1a6 to
e9c174d
Compare
|
The code at e9c174d fixes most of what the earlier review raised (#3551 (comment)), but one defect remains. All 22 checks pass at this commit. Before merge, the stop-identity check needs the fix below. In Not blocking: Six cubic-dev-ai threads are fixed at this commit and can be resolved: the self-pid RangeError, per-case teardown mocks, the half-spawned write fault, the doc guarantee scope, the win32 test split, and retry after an unconfirmed stop. Four more can be resolved because the files are no longer in this PR: the startup cleanup test (Metro moved to #3575, and host-kit covers the SIGKILL refusal), react-devtools.ts (moved to #3559), and two client-metro.ts threads (moved to #3575). I read the diff and did not run the tests or the gate, and I did not reproduce the trigger on Windows or with a partial |
e9c174d to
198edb6
Compare
The host refusing SIGTERM was reported as a completed stop, so the profile forgot a live companion and a second one could start beside it. An unreadable identity had the same effect through a different door: on Windows an elevated process answers the command-line query with nothing, and the stop path read that as 'not our companion' and cleared the state. Signals now settle against the host in one place. stopProcessAndConfirmExit takes the identity the caller recorded - the start time and a command matcher - and reports how the process ended: only a released pid, a zombie, or an identity the host contradicts proves the exit; a refusal is retained with its errno. A pid is signalled only where a claim the caller recorded is confirmed by a fact the host will actually say, so two claims resting on a silent host are retained as identity-unverified instead. The companion state is dropped only on 'stopped', and the failure names the pid, the reason, and the state file so a retry stops the same process. The verdict's error projection and the wording a caller must use when nothing was signalled leave the companion builder and sit beside the verdict, so a second caller reports it the same way instead of paraphrasing it.
198edb6 to
9d7f73b
Compare
|
Fixed at if (target.identity.startTime && facts.startTime && facts.startTime !== target.identity.startTime)
return 'gone';
if (target.identity.matches && facts.command && !target.identity.matches(facts.command))
return 'gone';
const confirmedStartTime = Boolean(target.identity.startTime && facts.startTime);
const confirmedCommand = Boolean(target.identity.matches && facts.command);
return confirmedStartTime || confirmedCommand ? 'ours' : 'unverified';Both contradictions run first, so reaching the confirmation step already means nothing was contradicted. The Two
The table's Took the non-blocking item. Your ten threads already each carry a reply and are resolved. Rebased onto Two follow-ups you did not ask for, both driven by your own rule, on #3575: the retained-verdict |
|
The PR is ready at 9d7f73b. The earlier findings from e9c174d (#3551 (comment)) are fixed, and I found no new problems in the changes since then. Not blocking: None of the cubic-dev-ai threads still applies, so please resolve them all. These are fixed at this commit: self-pid guard, afterEach reset, half-spawned write fault, docs wording, skipIf(win32) and persistUnstoppedCompanion. These no longer apply because their files are not in this PR: client-metro-startup-cleanup.test.ts (Metro moved to #3575, and the host-kit "refuses SIGKILL" row covers the case), react-devtools.ts (moved to #3559), and the two client-metro.ts threads (moved to #3575). I did not run the host-kit or companion suites or the repo checks. I checked the identity rule by tracing it by hand against the new test rows. The companion tests mock No conflicts. CI is still running: 12 checks are queued or in progress and none has failed. Unit, Integration and Coverage are the jobs that exercise this change. Merge waits on those finishing green at 9d7f73b. |
Problem
stopCompanionProcessreported a companion stopped in two cases where nothing proved it had left:EPERM, another user or an elevated process — the Client stop paths treat EPERM as a confirmed exit and drop state for a live companion #3540 case);readProcessCommandreturned null, which the stop read as "not our companion".Both then ran
clearCompanionArtifacts, sostopCompanionTunnelreturnedstopped: trueandensureCompanionTunnelstarted a second companion beside the live one. On WindowsWin32_Process.CommandLineis empty for an elevated or other-user process, so that door was common; POSIX hits it on apstimeout or underhidepid.Change
stopProcessAndConfirmExit(host-kit) takes the recorded identity —startTimeand a command matcher — and signals a pid only where a claim it recorded is confirmed by a readable fact. A released pid, a zombie row, or an identity the host contradicts provesstopped; a refusal is retained with its errno; claims resting on a silent host are retained asidentity-unverifiedwithout being signalled. A missing table row proves nothing, matchingwaitForDaemonExit. A strict liveness probe means onlyESRCHreleases a pid, and a non-errno fault propagates instead of reading as "gone".The verdict's
detailsprojection and never-signalled wording sit beside it, so #3575's builder reports it identically. The companion passesstate.startTimeplusisCompanionTunnelCommand, removing the fail-open precheck, and drops state only onstopped.Metro is split to #3575. Migrating
stopDaemonProcessandterminateOwnedProcessis a follow-up: it needs the identity-pinned wait moved out ofdaemon-contractsfirst.Validated:
check:affected --runexit 0 at9d7f73b5f.Closes #3540