@@ -37,6 +37,11 @@ interface ShutdownStepOutcome<T> {
3737 error ?: string ;
3838}
3939
40+ type RunStepRaceOutcome < T > =
41+ | { kind : 'value' ; value : T }
42+ | { kind : 'error' ; error : string }
43+ | { kind : 'timed_out' } ;
44+
4045export interface McpShutdownResult {
4146 exitCode : number ;
4247 transportDisconnected : boolean ;
@@ -63,14 +68,19 @@ async function runStep<T>(
6368 let timeoutHandle : NodeJS . Timeout | null = null ;
6469
6570 try {
66- const timeoutPromise = new Promise < 'timed_out' > ( ( resolve ) => {
67- timeoutHandle = createTimer ( timeoutMs , ( ) => resolve ( 'timed_out' ) ) ;
71+ const timeoutPromise = new Promise < RunStepRaceOutcome < T > > ( ( resolve ) => {
72+ timeoutHandle = createTimer ( timeoutMs , ( ) => resolve ( { kind : 'timed_out' } ) ) ;
6873 } ) ;
69- const operationPromise = operation ( ) ;
70- const outcome = await Promise . race ( [
71- operationPromise . then ( ( value ) => ( { kind : 'value' as const , value } ) ) ,
72- timeoutPromise . then ( ( ) => ( { kind : 'timed_out' as const } ) ) ,
73- ] ) ;
74+
75+ const operationOutcome = operation ( )
76+ . then ( ( value ) : RunStepRaceOutcome < T > => ( { kind : 'value' , value } ) )
77+ . catch (
78+ ( error ) : RunStepRaceOutcome < T > => ( {
79+ kind : 'error' ,
80+ error : stringifyError ( error ) ,
81+ } ) ,
82+ ) ;
83+ const outcome = await Promise . race ( [ operationOutcome , timeoutPromise ] ) ;
7484
7585 if ( outcome . kind === 'timed_out' ) {
7686 return {
@@ -79,17 +89,19 @@ async function runStep<T>(
7989 } ;
8090 }
8191
92+ if ( outcome . kind === 'error' ) {
93+ return {
94+ status : 'failed' ,
95+ durationMs : Date . now ( ) - startedAt ,
96+ error : outcome . error ,
97+ } ;
98+ }
99+
82100 return {
83101 status : 'completed' ,
84102 durationMs : Date . now ( ) - startedAt ,
85103 value : outcome . value ,
86104 } ;
87- } catch ( error ) {
88- return {
89- status : 'failed' ,
90- durationMs : Date . now ( ) - startedAt ,
91- error : stringifyError ( error ) ,
92- } ;
93105 } finally {
94106 if ( timeoutHandle ) {
95107 clearTimeout ( timeoutHandle ) ;
@@ -152,19 +164,52 @@ export async function runMcpShutdown(input: {
152164 } ) ;
153165 pushStep ( 'server.close' , serverCloseOutcome ) ;
154166
155- const cleanupSteps : Array < [ string , ( ) => Promise < unknown > ] > = [
156- [ 'watcher.stop' , ( ) => stopXcodeStateWatcher ( ) ] ,
157- [ 'xcode-tools-bridge.shutdown' , ( ) => shutdownXcodeToolsBridge ( ) ] ,
158- [ 'debugger.dispose-all' , ( ) => getDefaultDebuggerManager ( ) . disposeAll ( ) ] ,
159- [ 'simulator-logs.stop-all' , ( ) => stopAllLogCaptures ( STEP_TIMEOUT_MS ) ] ,
160- [ 'device-logs.stop-all' , ( ) => stopAllDeviceLogCaptures ( STEP_TIMEOUT_MS ) ] ,
161- [ 'video-capture.stop-all' , ( ) => stopAllVideoCaptureSessions ( STEP_TIMEOUT_MS ) ] ,
162- [ 'swift-processes.stop-all' , ( ) => stopAllTrackedProcesses ( STEP_TIMEOUT_MS ) ] ,
167+ const perItemTimeoutMs = STEP_TIMEOUT_MS ;
168+ const bulkStepTimeoutMs = ( itemCount : number ) : number => {
169+ return Math . max ( STEP_TIMEOUT_MS , itemCount * perItemTimeoutMs ) ;
170+ } ;
171+
172+ const cleanupSteps : Array < {
173+ name : string ;
174+ timeoutMs : number ;
175+ operation : ( ) => Promise < unknown > ;
176+ } > = [
177+ { name : 'watcher.stop' , timeoutMs : STEP_TIMEOUT_MS , operation : ( ) => stopXcodeStateWatcher ( ) } ,
178+ {
179+ name : 'xcode-tools-bridge.shutdown' ,
180+ timeoutMs : STEP_TIMEOUT_MS ,
181+ operation : ( ) => shutdownXcodeToolsBridge ( ) ,
182+ } ,
183+ {
184+ name : 'debugger.dispose-all' ,
185+ timeoutMs : STEP_TIMEOUT_MS ,
186+ operation : ( ) => getDefaultDebuggerManager ( ) . disposeAll ( ) ,
187+ } ,
188+ {
189+ name : 'simulator-logs.stop-all' ,
190+ timeoutMs : bulkStepTimeoutMs ( input . snapshot . simulatorLogSessionCount ) ,
191+ operation : ( ) => stopAllLogCaptures ( perItemTimeoutMs ) ,
192+ } ,
193+ {
194+ name : 'device-logs.stop-all' ,
195+ timeoutMs : bulkStepTimeoutMs ( input . snapshot . deviceLogSessionCount ) ,
196+ operation : ( ) => stopAllDeviceLogCaptures ( perItemTimeoutMs ) ,
197+ } ,
198+ {
199+ name : 'video-capture.stop-all' ,
200+ timeoutMs : bulkStepTimeoutMs ( input . snapshot . videoCaptureSessionCount ) ,
201+ operation : ( ) => stopAllVideoCaptureSessions ( perItemTimeoutMs ) ,
202+ } ,
203+ {
204+ name : 'swift-processes.stop-all' ,
205+ timeoutMs : bulkStepTimeoutMs ( input . snapshot . swiftPackageProcessCount ) ,
206+ operation : ( ) => stopAllTrackedProcesses ( perItemTimeoutMs ) ,
207+ } ,
163208 ] ;
164209
165- for ( const [ name , operation ] of cleanupSteps ) {
166- const outcome = await runStep ( name , STEP_TIMEOUT_MS , operation ) ;
167- pushStep ( name , outcome ) ;
210+ for ( const cleanupStep of cleanupSteps ) {
211+ const outcome = await runStep ( cleanupStep . name , cleanupStep . timeoutMs , cleanupStep . operation ) ;
212+ pushStep ( cleanupStep . name , outcome ) ;
168213 }
169214
170215 const triggerError = input . error === undefined ? undefined : stringifyError ( input . error ) ;
0 commit comments