55
66import { ChildProcess } from 'child_process' ;
77import { Subject } from 'rxjs/Subject' ;
8- import { MessageConnection , NotificationType , RequestType0 } from 'vscode-jsonrpc' ;
9- import { BasePythonDaemon } from '../../common/process/baseDaemon' ;
8+ import { MessageConnection , NotificationType , RequestType , RequestType0 } from 'vscode-jsonrpc' ;
9+ import { BasePythonDaemon , ExecResponse } from '../../common/process/baseDaemon' ;
1010import {
1111 IPythonExecutionService ,
1212 ObservableExecutionResult ,
1313 Output ,
1414 SpawnOptions ,
1515 StdErrError
1616} from '../../common/process/types' ;
17+ import { noop } from '../../common/utils/misc' ;
1718import { IPythonKernelDaemon , PythonKernelDiedError } from './types' ;
1819
1920export class PythonKernelDaemon extends BasePythonDaemon implements IPythonKernelDaemon {
21+ private started ?: boolean ;
22+ private preWarmed ?: boolean ;
23+ private outputHooked ?: boolean ;
24+ private readonly subject = new Subject < Output < string > > ( ) ;
2025 constructor (
2126 pythonExecutionService : IPythonExecutionService ,
2227 pythonPath : string ,
@@ -33,21 +38,77 @@ export class PythonKernelDaemon extends BasePythonDaemon implements IPythonKerne
3338 const request = new RequestType0 < void , void , void > ( 'kill_kernel' ) ;
3439 await this . sendRequestWithoutArgs ( request ) ;
3540 }
41+ public dispose ( ) {
42+ this . kill ( ) . catch ( noop ) ;
43+ super . dispose ( ) ;
44+ }
45+ public async preWarm ( ) {
46+ if ( this . started ) {
47+ return ;
48+ }
49+ this . preWarmed = true ;
50+ this . monitorOutput ( ) ;
51+ const request = new RequestType0 < void , void , void > ( 'prewarm_kernel' ) ;
52+
53+ await this . sendRequestWithoutArgs ( request ) ;
54+ }
55+
3656 public async start (
3757 moduleName : string ,
3858 args : string [ ] ,
3959 options : SpawnOptions
4060 ) : Promise < ObservableExecutionResult < string > > {
41- const subject = new Subject < Output < string > > ( ) ;
42- let stdErr = '' ;
61+ if ( options . throwOnStdErr ) {
62+ throw new Error ( "'throwOnStdErr' not supported in spawnOptions for KernelDaemon.start" ) ;
63+ }
64+ if ( options . mergeStdOutErr ) {
65+ throw new Error ( "'mergeStdOutErr' not supported in spawnOptions for KernelDaemon.start" ) ;
66+ }
67+ if ( options . cwd ) {
68+ throw new Error ( "'cwd' not supported in spawnOptions for KernelDaemon.start" ) ;
69+ }
70+ if ( this . started ) {
71+ throw new Error ( 'Kernel has already been started in daemon' ) ;
72+ }
73+ this . started = true ;
74+ this . monitorOutput ( ) ;
75+
76+ if ( this . preWarmed ) {
77+ const request = new RequestType < { args : string [ ] } , ExecResponse , void , void > ( 'start_prewarmed_kernel' ) ;
78+ await this . sendRequest ( request , { args : [ moduleName ] . concat ( args ) } ) ;
79+ } else {
80+ // No need of the output here, we'll tap into the output coming from daemon `this.outputObservale`.
81+ // This is required because execModule will never end.
82+ // We cannot use `execModuleObservable` as that only works where the daemon is busy seeerving on request and we wait for it to finish.
83+ // In this case we're never going to wait for the module to run to end. Cuz when we run `pytohn -m ipykernel`, it never ends.
84+ // It only ends when the kernel dies, meaning the kernel process is dead.
85+ // What we need is to be able to run the module and keep getting a stream of stdout/stderr.
86+ // & also be able to execute other python code. I.e. we need a daemon.
87+ // For this we run the `ipykernel` code in a separate thread.
88+ // This is why when we run `execModule` in the Kernel daemon, it finishes (comes back) quickly.
89+ // However in reality it is running in the background.
90+ // See `m_exec_module_observable` in `kernel_launcher_daemon.py`.
91+ await this . execModule ( moduleName , args , options ) ;
92+ }
93+
94+ return {
95+ proc : this . proc ,
96+ dispose : ( ) => this . dispose ( ) ,
97+ out : this . subject
98+ } ;
99+ }
100+ private monitorOutput ( ) {
101+ if ( this . outputHooked ) {
102+ return ;
103+ }
104+ this . outputHooked = true ;
43105 // Message from daemon when kernel dies.
44106 const KernelDiedNotification = new NotificationType < { exit_code : string ; reason ?: string } , void > (
45107 'kernel_died'
46108 ) ;
47109 this . connection . onNotification ( KernelDiedNotification , ( output ) => {
48- // If we don't have a reason why things failed, then just include the stderr.
49- subject . error (
50- new PythonKernelDiedError ( { exitCode : parseInt ( output . exit_code , 10 ) , reason : output . reason || stdErr } )
110+ this . subject . error (
111+ new PythonKernelDiedError ( { exitCode : parseInt ( output . exit_code , 10 ) , reason : output . reason } )
51112 ) ;
52113 } ) ;
53114
@@ -56,39 +117,17 @@ export class PythonKernelDaemon extends BasePythonDaemon implements IPythonKerne
56117 // sptting stuff into stdout/stderr.
57118 this . outputObservale . subscribe (
58119 ( out ) => {
59- if ( out . source === 'stderr' && options . throwOnStdErr ) {
60- stdErr += out . out ;
61- subject . error ( new StdErrError ( out . out ) ) ;
62- } else if ( out . source === 'stderr' && options . mergeStdOutErr ) {
63- subject . next ( { source : 'stdout' , out : out . out } ) ;
120+ if ( out . source === 'stderr' ) {
121+ this . subject . error ( new StdErrError ( out . out ) ) ;
64122 } else {
65- subject . next ( out ) ;
123+ this . subject . next ( out ) ;
66124 }
67125 } ,
68- subject . error . bind ( subject ) ,
69- subject . complete . bind ( subject )
126+ this . subject . error . bind ( this . subject ) ,
127+ this . subject . complete . bind ( this . subject )
70128 ) ;
71129
72130 // If the daemon dies, then kernel is also dead.
73- this . closed . catch ( ( error ) => subject . error ( new PythonKernelDiedError ( { error } ) ) ) ;
74-
75- // No need of the output here, we'll tap into the output coming from daemon `this.outputObservale`.
76- // This is required because execModule will never end.
77- // We cannot use `execModuleObservable` as that only works where the daemon is busy seeerving on request and we wait for it to finish.
78- // In this case we're never going to wait for the module to run to end. Cuz when we run `pytohn -m ipykernel`, it never ends.
79- // It only ends when the kernel dies, meaning the kernel process is dead.
80- // What we need is to be able to run the module and keep getting a stream of stdout/stderr.
81- // & also be able to execute other python code. I.e. we need a daemon.
82- // For this we run the `ipykernel` code in a separate thread.
83- // This is why when we run `execModule` in the Kernel daemon, it finishes (comes back) quickly.
84- // However in reality it is running in the background.
85- // See `m_exec_module_observable` in `kernel_launcher_daemon.py`.
86- await this . execModule ( moduleName , args , options ) ;
87-
88- return {
89- proc : this . proc ,
90- dispose : ( ) => this . dispose ( ) ,
91- out : subject
92- } ;
131+ this . closed . catch ( ( error ) => this . subject . error ( new PythonKernelDiedError ( { error } ) ) ) ;
93132 }
94133}
0 commit comments