@@ -15,6 +15,7 @@ import {
1515 emitRequestProgress ,
1616 findProjectRoot ,
1717 isCommandTimeoutError ,
18+ runnerRuntimeSlot ,
1819} from './host.ts' ;
1920import type { ExecBackgroundResult } from '@agent-device/host-kit/command' ;
2021import type { DeviceInfo } from '@agent-device/kernel/device' ;
@@ -119,7 +120,10 @@ type DeviceStartState = {
119120 inFlight : Set < StartAdmission > ;
120121} ;
121122
122- const deviceStartStates = new Map < string , DeviceStartState > ( ) ;
123+ const DEVICE_START_STATES_SLOT = Symbol ( 'apple-runner-device-start-states' ) ;
124+ // Per daemon runtime: a runtime's teardown retires only the start admissions it opened.
125+ const deviceStartStates = ( ) =>
126+ runnerRuntimeSlot ( DEVICE_START_STATES_SLOT , ( ) => new Map < string , DeviceStartState > ( ) ) ;
123127
124128/**
125129 * Opens the admission one start answers to, taken when a caller asks for a runner. Registration is
@@ -144,7 +148,7 @@ export function openRunnerStartLoopAdmission(deviceId: string): RunnerStartAdmis
144148/** The start settled: its token leaves the device's in-flight set. Verdicts live on the token. */
145149export function finishRunnerStartAdmission ( admission : RunnerStartAdmission ) : void {
146150 ( admission as StartAdmission ) . retryPending = false ;
147- const state = deviceStartStates . get ( admission . deviceId ) ;
151+ const state = deviceStartStates ( ) . get ( admission . deviceId ) ;
148152 if ( ! state ) return ;
149153 state . inFlight . delete ( admission as StartAdmission ) ;
150154 forgetDeviceStartState ( admission . deviceId , state ) ;
@@ -180,7 +184,10 @@ export function fenceRunnerStartAdmissionsForTeardown(deviceId: string): () => v
180184 * build past the sweep that is about to stop it.
181185 */
182186export function retireAllRunnerStartAdmissions ( additionalDeviceIds ?: Iterable < string > ) : ( ) => void {
183- const deviceIds = new Set < string > ( [ ...deviceStartStates . keys ( ) , ...( additionalDeviceIds ?? [ ] ) ] ) ;
187+ const deviceIds = new Set < string > ( [
188+ ...deviceStartStates ( ) . keys ( ) ,
189+ ...( additionalDeviceIds ?? [ ] ) ,
190+ ] ) ;
184191 const settles = [ ...deviceIds ] . map ( ( deviceId ) => fenceRunnerStartAdmissionsForTeardown ( deviceId ) ) ;
185192 return ( ) => {
186193 for ( const settle of settles . splice ( 0 ) ) settle ( ) ;
@@ -193,7 +200,7 @@ export function retireAllRunnerStartAdmissions(additionalDeviceIds?: Iterable<st
193200 * not that teardown's retry.
194201 */
195202export function runnerStartTeardownPending ( deviceId : string ) : boolean {
196- return ( deviceStartStates . get ( deviceId ) ?. pendingTeardowns ?? 0 ) > 0 ;
203+ return ( deviceStartStates ( ) . get ( deviceId ) ?. pendingTeardowns ?? 0 ) > 0 ;
197204}
198205
199206/**
@@ -299,17 +306,17 @@ export function releaseRunnerStartWaiter(
299306}
300307
301308function stateForDevice ( deviceId : string ) : DeviceStartState {
302- const existing = deviceStartStates . get ( deviceId ) ;
309+ const existing = deviceStartStates ( ) . get ( deviceId ) ;
303310 if ( existing ) return existing ;
304311 const state : DeviceStartState = { pendingTeardowns : 0 , inFlight : new Set ( ) } ;
305- deviceStartStates . set ( deviceId , state ) ;
312+ deviceStartStates ( ) . set ( deviceId , state ) ;
306313 return state ;
307314}
308315
309316/** Nothing fences the device and no start is in flight on it once the last of both is gone. */
310317function forgetDeviceStartState ( deviceId : string , state : DeviceStartState ) : void {
311318 if ( state . pendingTeardowns <= 0 && state . inFlight . size === 0 ) {
312- deviceStartStates . delete ( deviceId ) ;
319+ deviceStartStates ( ) . delete ( deviceId ) ;
313320 }
314321}
315322
@@ -360,7 +367,10 @@ type RunnerPrepProcess = Readonly<{
360367 child : ExecBackgroundResult [ 'child' ] ;
361368} > ;
362369
363- const runnerPrepProcessLedger = new Set < RunnerPrepProcess > ( ) ;
370+ const RUNNER_PREP_LEDGER_SLOT = Symbol ( 'apple-runner-prep-process-ledger' ) ;
371+ // Per daemon runtime: a runtime's stop signals only the prep children it started.
372+ const runnerPrepProcessLedger = ( ) =>
373+ runnerRuntimeSlot ( RUNNER_PREP_LEDGER_SLOT , ( ) => new Set < RunnerPrepProcess > ( ) ) ;
364374
365375/**
366376 * Records a prep subprocess (`xcodebuild build-for-testing`) against the device it builds for and
@@ -377,9 +387,9 @@ export function registerRunnerPrepProcess(
377387 startAdmission ?: RunnerStartAdmission ,
378388) : void {
379389 const entry : RunnerPrepProcess = { deviceId, startAdmission, child } ;
380- runnerPrepProcessLedger . add ( entry ) ;
390+ runnerPrepProcessLedger ( ) . add ( entry ) ;
381391 child . on ( 'close' , ( ) => {
382- runnerPrepProcessLedger . delete ( entry ) ;
392+ runnerPrepProcessLedger ( ) . delete ( entry ) ;
383393 } ) ;
384394}
385395
@@ -426,14 +436,14 @@ export function markRunnerStartRetryPending(admission: RunnerStartAdmission): vo
426436}
427437
428438function prepProcessEntries ( deviceId ?: string ) : readonly RunnerPrepProcess [ ] {
429- return [ ...runnerPrepProcessLedger ] . filter (
439+ return [ ...runnerPrepProcessLedger ( ) ] . filter (
430440 ( entry ) => deviceId === undefined || entry . deviceId === deviceId ,
431441 ) ;
432442}
433443
434444export function forgetRunnerPrepProcess ( child : ExecBackgroundResult [ 'child' ] ) : void {
435- for ( const entry of runnerPrepProcessLedger ) {
436- if ( entry . child === child ) runnerPrepProcessLedger . delete ( entry ) ;
445+ for ( const entry of runnerPrepProcessLedger ( ) ) {
446+ if ( entry . child === child ) runnerPrepProcessLedger ( ) . delete ( entry ) ;
437447 }
438448}
439449
0 commit comments