@@ -15,7 +15,7 @@ import { isNewOsSession } from './utilities/osUtils'
1515import nodeFs from 'fs/promises'
1616import fs from './fs/fs'
1717import { getLogger } from './logger/logger'
18- import { crashMonitoringDirNames } from './constants'
18+ import { crashMonitoringDirName } from './constants'
1919import { throwOnUnstableFileSystem } from './filesystemUtilities'
2020import { withRetries } from './utilities/functionUtils'
2121import { TimeLag } from './utilities/timeoutUtils'
@@ -307,35 +307,26 @@ class CrashChecker {
307307 continue
308308 }
309309
310- // Ext is not running anymore, handle appropriately depending on why it stopped running
311- await state . handleExtNotRunning ( ext , {
312- onShutdown : async ( ) => {
313- // Nothing to do, just log info if necessary
314- devLogger ?. debug (
315- `crashMonitoring: SHUTDOWN: following has gracefully shutdown: pid ${ ext . extHostPid } + sessionId: ${ ext . sessionId } `
316- )
317- } ,
318- onCrash : async ( ) => {
319- // Debugger instances may incorrectly look like they crashed, so don't emit.
320- // Example is if I hit the red square in the debug menu, it is a non-graceful shutdown. But the regular
321- // 'x' button in the Debug IDE instance is a graceful shutdown.
322- if ( ext . isDebug ) {
323- devLogger ?. debug ( `crashMonitoring: DEBUG instance crashed: ${ JSON . stringify ( ext ) } ` )
324- return
325- }
326-
327- // This is the metric to let us know the extension crashed
328- telemetry . session_end . emit ( {
329- result : 'Failed' ,
330- proxiedSessionId : ext . sessionId ,
331- reason : 'ExtHostCrashed' ,
332- passive : true ,
333- } )
334-
335- devLogger ?. debug (
336- `crashMonitoring: CRASH: following has crashed: pid ${ ext . extHostPid } + sessionId: ${ ext . sessionId } `
337- )
338- } ,
310+ await state . handleCrashedExt ( ext , ( ) => {
311+ // Debugger instances may incorrectly look like they crashed, so don't emit.
312+ // Example is if I hit the red square in the debug menu, it is a non-graceful shutdown. But the regular
313+ // 'x' button in the Debug IDE instance is a graceful shutdown.
314+ if ( ext . isDebug ) {
315+ devLogger ?. debug ( `crashMonitoring: DEBUG instance crashed: ${ JSON . stringify ( ext ) } ` )
316+ return
317+ }
318+
319+ // This is the metric to let us know the extension crashed
320+ telemetry . session_end . emit ( {
321+ result : 'Failed' ,
322+ proxiedSessionId : ext . sessionId ,
323+ reason : 'ExtHostCrashed' ,
324+ passive : true ,
325+ } )
326+
327+ devLogger ?. debug (
328+ `crashMonitoring: CRASH: following has crashed: pid ${ ext . extHostPid } + sessionId: ${ ext . sessionId } `
329+ )
339330 } )
340331 }
341332
@@ -421,7 +412,7 @@ export class FileSystemState {
421412 * IMORTANT: Use {@link crashMonitoringStateFactory} to make an instance
422413 */
423414 constructor ( protected readonly deps : MementoStateDependencies ) {
424- this . stateDirPath = path . join ( this . deps . workDirPath , crashMonitoringDirNames . root )
415+ this . stateDirPath = path . join ( this . deps . workDirPath , crashMonitoringDirName )
425416
426417 this . deps . devLogger ?. debug ( `crashMonitoring: pid: ${ this . deps . pid } ` )
427418 this . deps . devLogger ?. debug ( `crashMonitoring: sessionId: ${ this . deps . sessionId . slice ( 0 , 8 ) } -...` )
@@ -447,17 +438,16 @@ export class FileSystemState {
447438 if ( await this . deps . isStateStale ( ) ) {
448439 await this . clearState ( )
449440 }
441+
442+ await withFailCtx ( 'init' , ( ) => fs . mkdir ( this . stateDirPath ) )
450443 }
451444
452445 // ------------------ Heartbeat methods ------------------
453446 public async sendHeartbeat ( ) {
454- const extId = this . createExtId ( this . ext )
455-
456447 try {
457448 const func = async ( ) => {
458- const dir = await this . runningExtsDir ( )
459449 await fs . writeFile (
460- path . join ( dir , extId ) ,
450+ this . makeStateFilePath ( this . extId ) ,
461451 JSON . stringify ( { ...this . ext , lastHeartbeat : this . deps . now ( ) } , undefined , 4 )
462452 )
463453 this . deps . devLogger ?. debug (
@@ -479,7 +469,7 @@ export class FileSystemState {
479469 }
480470
481471 /**
482- * Signal that this extension is gracefully shutting down. This will prevent the IDE from thinking it crashed .
472+ * Indicates that this extension instance has gracefully shutdown .
483473 *
484474 * IMPORTANT: This code is being run in `deactivate()` where VS Code api is not available. Due to this we cannot
485475 * easily update the state to indicate a graceful shutdown. So the next best option is to write to a file on disk,
@@ -489,46 +479,24 @@ export class FileSystemState {
489479 * function touches.
490480 */
491481 public async indicateGracefulShutdown ( ) : Promise < void > {
492- const dir = await this . shutdownExtsDir ( )
493- await withFailCtx ( 'writeShutdownFile' , ( ) => nodeFs . writeFile ( path . join ( dir , this . extId ) , '' ) )
482+ // By removing the heartbeat entry, the crash checkers will not be able to find this entry anymore, making it
483+ // impossible to report on since the file system is the source of truth
484+ await withFailCtx ( 'indicateGracefulShutdown' , ( ) => nodeFs . rm ( this . makeStateFilePath ( this . extId ) ) )
494485 }
495486
496487 // ------------------ Checker Methods ------------------
497488
498- /**
499- * Signals the state that the given extension is not running, allowing the state to appropriately update
500- * depending on a graceful shutdown or crash.
501- *
502- * NOTE: This does NOT run in the `deactivate()` method, so it CAN reliably use the VS Code FS api
503- *
504- * @param opts - functions to run depending on why the extension stopped running
505- */
506- public async handleExtNotRunning (
507- ext : ExtInstance ,
508- opts : { onShutdown : ( ) => Promise < void > ; onCrash : ( ) => Promise < void > }
509- ) : Promise < void > {
510- const extId = this . createExtId ( ext )
511- const shutdownFilePath = path . join ( await this . shutdownExtsDir ( ) , extId )
512-
513- if ( await withFailCtx ( 'existsShutdownFile' , ( ) => fs . exists ( shutdownFilePath ) ) ) {
514- await opts . onShutdown ( )
515- // We intentionally do not clean up the file in shutdown since there may be another
516- // extension may be doing the same thing in parallel, and would read the extension as
517- // crashed since the file was missing. The file will be cleared on computer restart though.
518-
519- // TODO: Be smart and clean up the file after some time.
520- } else {
521- await opts . onCrash ( )
522- }
523-
524- // Clean up the running extension file since it no longer exists
525- await this . deleteHeartbeatFile ( extId )
489+ public async handleCrashedExt ( ext : ExtInstance , fn : ( ) => void ) {
490+ await withFailCtx ( 'handleCrashedExt' , async ( ) => {
491+ await this . deleteHeartbeatFile ( ext )
492+ fn ( )
493+ } )
526494 }
527- public async deleteHeartbeatFile ( extId : ExtInstanceId ) {
528- const dir = await this . runningExtsDir ( )
495+
496+ private async deleteHeartbeatFile ( ext : ExtInstanceId | ExtInstance ) {
529497 // Retry file deletion to prevent incorrect crash reports. Common Windows errors seen in telemetry: EPERM/EBUSY.
530498 // See: https://github.com/aws/aws-toolkit-vscode/pull/5335
531- await withRetries ( ( ) => withFailCtx ( 'deleteStaleRunningFile' , ( ) => fs . delete ( path . join ( dir , extId ) ) ) , {
499+ await withRetries ( ( ) => withFailCtx ( 'deleteStaleRunningFile' , ( ) => fs . delete ( this . makeStateFilePath ( ext ) ) ) , {
532500 maxRetries : 8 ,
533501 delay : 100 ,
534502 backoff : 2 ,
@@ -557,17 +525,9 @@ export class FileSystemState {
557525 protected createExtId ( ext : ExtInstance ) : ExtInstanceId {
558526 return `${ ext . extHostPid } _${ ext . sessionId } `
559527 }
560- private async runningExtsDir ( ) : Promise < string > {
561- const p = path . join ( this . stateDirPath , crashMonitoringDirNames . running )
562- // ensure the dir exists
563- await withFailCtx ( 'ensureRunningExtsDir' , ( ) => nodeFs . mkdir ( p , { recursive : true } ) )
564- return p
565- }
566- private async shutdownExtsDir ( ) {
567- const p = path . join ( this . stateDirPath , crashMonitoringDirNames . shutdown )
568- // Since this runs in `deactivate()` it cannot use the VS Code FS api
569- await withFailCtx ( 'ensureShutdownExtsDir' , ( ) => nodeFs . mkdir ( p , { recursive : true } ) )
570- return p
528+ private makeStateFilePath ( ext : ExtInstance | ExtInstanceId ) {
529+ const extId = typeof ext === 'string' ? ext : this . createExtId ( ext )
530+ return path . join ( this . stateDirPath , extId )
571531 }
572532 public async clearState ( ) : Promise < void > {
573533 this . deps . devLogger ?. debug ( 'crashMonitoring: CLEAR_STATE: Started' )
@@ -580,7 +540,7 @@ export class FileSystemState {
580540 const res = await withFailCtx ( 'getAllExts' , async ( ) => {
581541 // The file names are intentionally the IDs for easy mapping
582542 const allExtIds : ExtInstanceId [ ] = await withFailCtx ( 'readdir' , async ( ) =>
583- ( await fs . readdir ( await this . runningExtsDir ( ) ) ) . map ( ( k ) => k [ 0 ] )
543+ ( await fs . readdir ( this . stateDirPath ) ) . map ( ( k ) => k [ 0 ] )
584544 )
585545
586546 const allExts = allExtIds . map < Promise < ExtInstanceHeartbeat | undefined > > ( async ( extId : string ) => {
@@ -591,7 +551,7 @@ export class FileSystemState {
591551 ( ) =>
592552 withFailCtx ( 'parseRunningExtFile' , async ( ) =>
593553 ignoreBadFileError ( async ( ) => {
594- const text = await fs . readFileText ( path . join ( await this . runningExtsDir ( ) , extId ) )
554+ const text = await fs . readFileText ( this . makeStateFilePath ( extId ) )
595555
596556 if ( ! text ) {
597557 return undefined
0 commit comments