@@ -858,146 +858,180 @@ export class DeepnoteServerStarter implements IDeepnoteServerStarter, IExtension
858858 const processService = await this . processServiceFactory . create ( undefined ) ;
859859
860860 // Find all deepnote-toolkit server processes and related child processes
861- let command : string ;
862- let args : string [ ] ;
861+ const candidatePids : number [ ] = [ ] ;
863862
864863 if ( process . platform === 'win32' ) {
865- // Windows: use tasklist and findstr
866- command = 'tasklist' ;
867- args = [ '/FI' , 'IMAGENAME eq python.exe' , '/FO' , 'CSV' , '/NH' ] ;
868- } else {
869- // Unix-like: use ps and grep
870- command = 'ps' ;
871- args = [ 'aux' ] ;
872- }
864+ // Windows: tasklist CSV doesn't include command lines, so we need a two-step approach:
865+ // 1. Get all python.exe and pythonw.exe PIDs
866+ // 2. Check each PID's command line to see if it's deepnote-related
873867
874- const result = await processService . exec ( command , args , { throwOnStdErr : false } ) ;
868+ // Step 1: Get all Python process PIDs
869+ const pythonPids : number [ ] = [ ] ;
875870
876- if ( result . stdout ) {
877- const lines = result . stdout . split ( '\n' ) ;
878- const candidatePids : number [ ] = [ ] ;
871+ // Check python.exe
872+ const pythonResult = await processService . exec (
873+ 'tasklist' ,
874+ [ '/FI' , 'IMAGENAME eq python.exe' , '/FO' , 'CSV' , '/NH' ] ,
875+ { throwOnStdErr : false }
876+ ) ;
877+ if ( pythonResult . stdout ) {
878+ const lines = pythonResult . stdout . split ( '\n' ) ;
879+ for ( const line of lines ) {
880+ // Windows CSV format: "python.exe","12345",...
881+ const match = line . match ( / " p y t h o n \. e x e " , " ( \d + ) " / ) ;
882+ if ( match ) {
883+ const pid = parseInt ( match [ 1 ] , 10 ) ;
884+ if ( ! isNaN ( pid ) ) {
885+ pythonPids . push ( pid ) ;
886+ }
887+ }
888+ }
889+ }
890+
891+ // Check pythonw.exe
892+ const pythonwResult = await processService . exec (
893+ 'tasklist' ,
894+ [ '/FI' , 'IMAGENAME eq pythonw.exe' , '/FO' , 'CSV' , '/NH' ] ,
895+ { throwOnStdErr : false }
896+ ) ;
897+ if ( pythonwResult . stdout ) {
898+ const lines = pythonwResult . stdout . split ( '\n' ) ;
899+ for ( const line of lines ) {
900+ // Windows CSV format: "pythonw.exe","12345",...
901+ const match = line . match ( / " p y t h o n w \. e x e " , " ( \d + ) " / ) ;
902+ if ( match ) {
903+ const pid = parseInt ( match [ 1 ] , 10 ) ;
904+ if ( ! isNaN ( pid ) ) {
905+ pythonPids . push ( pid ) ;
906+ }
907+ }
908+ }
909+ }
879910
880- for ( const line of lines ) {
881- // Look for processes running deepnote_toolkit server or related child processes
882- // This includes:
883- // - deepnote_toolkit server (main server process)
884- // - pylsp (Python LSP server child process)
885- // - jupyter (Jupyter server child process)
886- const isDeepnoteRelated =
887- ( line . includes ( 'deepnote_toolkit' ) && line . includes ( 'server' ) ) ||
888- ( line . includes ( 'pylsp' ) && line . includes ( '2087' ) ) || // LSP server on port 2087
889- ( line . includes ( 'jupyter' ) && line . includes ( 'deepnote' ) ) ;
911+ logger . debug ( `Found ${ pythonPids . length } Python process(es) on Windows` ) ;
890912
913+ // Step 2: Check each Python PID to see if it's deepnote-related
914+ for ( const pid of pythonPids ) {
915+ const isDeepnoteRelated = await this . isDeepnoteRelatedProcess ( pid ) ;
891916 if ( isDeepnoteRelated ) {
892- // Extract PID based on platform
893- let pid : number | undefined ;
894-
895- if ( process . platform === 'win32' ) {
896- // Windows CSV format: "python.exe","12345",...
897- const match = line . match ( / " p y t h o n \. e x e " , " ( \d + ) " / ) ;
898- if ( match ) {
899- pid = parseInt ( match [ 1 ] , 10 ) ;
900- }
901- } else {
917+ candidatePids . push ( pid ) ;
918+ }
919+ }
920+ } else {
921+ // Unix-like: use ps with full command line
922+ const result = await processService . exec ( 'ps' , [ 'aux' ] , { throwOnStdErr : false } ) ;
923+
924+ if ( result . stdout ) {
925+ const lines = result . stdout . split ( '\n' ) ;
926+
927+ for ( const line of lines ) {
928+ // Look for processes running deepnote_toolkit server or related child processes
929+ // This includes:
930+ // - deepnote_toolkit server (main server process)
931+ // - pylsp (Python LSP server child process)
932+ // - jupyter (Jupyter server child process)
933+ const isDeepnoteRelated =
934+ ( line . includes ( 'deepnote_toolkit' ) && line . includes ( 'server' ) ) ||
935+ ( line . includes ( 'pylsp' ) && line . includes ( '2087' ) ) || // LSP server on port 2087
936+ ( line . includes ( 'jupyter' ) && line . includes ( 'deepnote' ) ) ;
937+
938+ if ( isDeepnoteRelated ) {
902939 // Unix format: user PID ...
903940 const parts = line . trim ( ) . split ( / \s + / ) ;
904941 if ( parts . length > 1 ) {
905- pid = parseInt ( parts [ 1 ] , 10 ) ;
942+ const pid = parseInt ( parts [ 1 ] , 10 ) ;
943+ if ( ! isNaN ( pid ) ) {
944+ candidatePids . push ( pid ) ;
945+ }
906946 }
907947 }
908-
909- if ( pid && ! isNaN ( pid ) ) {
910- candidatePids . push ( pid ) ;
911- }
912948 }
913949 }
950+ }
914951
915- if ( candidatePids . length > 0 ) {
916- logger . info (
917- `Found ${ candidatePids . length } deepnote-related process(es): ${ candidatePids . join ( ', ' ) } `
918- ) ;
952+ if ( candidatePids . length > 0 ) {
953+ logger . info ( `Found ${ candidatePids . length } deepnote-related process(es): ${ candidatePids . join ( ', ' ) } ` ) ;
919954
920- const pidsToKill : number [ ] = [ ] ;
921- const pidsToSkip : Array < { pid : number ; reason : string } > = [ ] ;
922-
923- // Check each process to determine if it should be killed
924- for ( const pid of candidatePids ) {
925- // Check if there's a lock file for this PID (only main server processes have lock files)
926- const lockData = await this . readLockFile ( pid ) ;
927-
928- if ( lockData ) {
929- // Lock file exists - this is a main server process
930- // Check if it belongs to a different session
931- if ( lockData . sessionId !== this . sessionId ) {
932- // Different session - check if the process is actually orphaned
933- const isOrphaned = await this . isProcessOrphaned ( pid ) ;
934- if ( isOrphaned ) {
935- logger . info (
936- `PID ${ pid } belongs to session ${ lockData . sessionId } and is orphaned - will kill`
937- ) ;
938- pidsToKill . push ( pid ) ;
939- } else {
940- pidsToSkip . push ( {
941- pid,
942- reason : `belongs to active session ${ lockData . sessionId . substring ( 0 , 8 ) } ...`
943- } ) ;
944- }
945- } else {
946- // Same session - this shouldn't happen during startup, but skip it
947- pidsToSkip . push ( { pid, reason : 'belongs to current session' } ) ;
948- }
949- } else {
950- // No lock file - could be a child process (LSP, Jupyter) or orphaned main process
951- // Check if orphaned before killing
955+ const pidsToKill : number [ ] = [ ] ;
956+ const pidsToSkip : Array < { pid : number ; reason : string } > = [ ] ;
957+
958+ // Check each process to determine if it should be killed
959+ for ( const pid of candidatePids ) {
960+ // Check if there's a lock file for this PID (only main server processes have lock files)
961+ const lockData = await this . readLockFile ( pid ) ;
962+
963+ if ( lockData ) {
964+ // Lock file exists - this is a main server process
965+ // Check if it belongs to a different session
966+ if ( lockData . sessionId !== this . sessionId ) {
967+ // Different session - check if the process is actually orphaned
952968 const isOrphaned = await this . isProcessOrphaned ( pid ) ;
953969 if ( isOrphaned ) {
954- logger . info ( `PID ${ pid } has no lock file and is orphaned - will kill` ) ;
970+ logger . info (
971+ `PID ${ pid } belongs to session ${ lockData . sessionId } and is orphaned - will kill`
972+ ) ;
955973 pidsToKill . push ( pid ) ;
956974 } else {
957- pidsToSkip . push ( { pid, reason : 'no lock file but has active parent process' } ) ;
975+ pidsToSkip . push ( {
976+ pid,
977+ reason : `belongs to active session ${ lockData . sessionId . substring ( 0 , 8 ) } ...`
978+ } ) ;
958979 }
980+ } else {
981+ // Same session - this shouldn't happen during startup, but skip it
982+ pidsToSkip . push ( { pid, reason : 'belongs to current session' } ) ;
959983 }
960- }
961-
962- // Log skipped processes
963- if ( pidsToSkip . length > 0 ) {
964- for ( const { pid, reason } of pidsToSkip ) {
965- logger . info ( `Skipping PID ${ pid } : ${ reason } ` ) ;
984+ } else {
985+ // No lock file - could be a child process (LSP, Jupyter) or orphaned main process
986+ // Check if orphaned before killing
987+ const isOrphaned = await this . isProcessOrphaned ( pid ) ;
988+ if ( isOrphaned ) {
989+ logger . info ( `PID ${ pid } has no lock file and is orphaned - will kill` ) ;
990+ pidsToKill . push ( pid ) ;
991+ } else {
992+ pidsToSkip . push ( { pid, reason : 'no lock file but has active parent process' } ) ;
966993 }
967994 }
995+ }
968996
969- // Kill orphaned processes
970- if ( pidsToKill . length > 0 ) {
971- logger . info ( `Killing ${ pidsToKill . length } orphaned process(es): ${ pidsToKill . join ( ', ' ) } ` ) ;
972- this . outputChannel . appendLine (
973- `Cleaning up ${ pidsToKill . length } orphaned deepnote-related process(es)...`
974- ) ;
997+ // Log skipped processes
998+ if ( pidsToSkip . length > 0 ) {
999+ for ( const { pid , reason } of pidsToSkip ) {
1000+ logger . info ( `Skipping PID ${ pid } : ${ reason } ` ) ;
1001+ }
1002+ }
9751003
976- for ( const pid of pidsToKill ) {
977- try {
978- if ( process . platform === 'win32' ) {
979- await processService . exec ( 'taskkill' , [ '/F' , '/T' , '/PID' , pid . toString ( ) ] , {
980- throwOnStdErr : false
981- } ) ;
982- } else {
983- await processService . exec ( 'kill' , [ '-9' , pid . toString ( ) ] , { throwOnStdErr : false } ) ;
984- }
985- logger . info ( `Killed orphaned process ${ pid } ` ) ;
1004+ // Kill orphaned processes
1005+ if ( pidsToKill . length > 0 ) {
1006+ logger . info ( `Killing ${ pidsToKill . length } orphaned process(es): ${ pidsToKill . join ( ', ' ) } ` ) ;
1007+ this . outputChannel . appendLine (
1008+ `Cleaning up ${ pidsToKill . length } orphaned deepnote-related process(es)...`
1009+ ) ;
9861010
987- // Clean up the lock file after killing (if it exists)
988- await this . deleteLockFile ( pid ) ;
989- } catch ( ex ) {
990- logger . warn ( `Failed to kill process ${ pid } : ${ ex } ` ) ;
1011+ for ( const pid of pidsToKill ) {
1012+ try {
1013+ if ( process . platform === 'win32' ) {
1014+ await processService . exec ( 'taskkill' , [ '/F' , '/T' , '/PID' , pid . toString ( ) ] , {
1015+ throwOnStdErr : false
1016+ } ) ;
1017+ } else {
1018+ await processService . exec ( 'kill' , [ '-9' , pid . toString ( ) ] , { throwOnStdErr : false } ) ;
9911019 }
992- }
1020+ logger . info ( `Killed orphaned process ${ pid } ` ) ;
9931021
994- this . outputChannel . appendLine ( '✓ Cleanup complete' ) ;
995- } else {
996- logger . info ( 'No orphaned deepnote-related processes found (all processes are active)' ) ;
1022+ // Clean up the lock file after killing (if it exists)
1023+ await this . deleteLockFile ( pid ) ;
1024+ } catch ( ex ) {
1025+ logger . warn ( `Failed to kill process ${ pid } : ${ ex } ` ) ;
1026+ }
9971027 }
1028+
1029+ this . outputChannel . appendLine ( '✓ Cleanup complete' ) ;
9981030 } else {
999- logger . info ( 'No deepnote-related processes found' ) ;
1031+ logger . info ( 'No orphaned deepnote-related processes found (all processes are active) ' ) ;
10001032 }
1033+ } else {
1034+ logger . info ( 'No deepnote-related processes found' ) ;
10011035 }
10021036 } catch ( ex ) {
10031037 // Don't fail startup if cleanup fails
0 commit comments