33
44'use strict' ;
55
6- import { execSync } from 'child_process' ;
6+ import * as cp from 'child_process' ;
77import * as os from 'os' ;
88import * as path from 'path' ;
99import * as uuid from 'uuid/v4' ;
@@ -14,7 +14,6 @@ import { IFileSystem, TemporaryDirectory } from '../../common/platform/types';
1414import { IPythonExecutionFactory , SpawnOptions } from '../../common/process/types' ;
1515import { IDisposable } from '../../common/types' ;
1616import * as localize from '../../common/utils/localize' ;
17- import { noop } from '../../common/utils/misc' ;
1817import { StopWatch } from '../../common/utils/stopWatch' ;
1918import { EXTENSION_ROOT_DIR } from '../../constants' ;
2019import { IServiceContainer } from '../../ioc/types' ;
@@ -62,57 +61,11 @@ export class NotebookStarter implements Disposable {
6261 let exitCode : number | null = 0 ;
6362 try {
6463 // Generate a temp dir with a unique GUID, both to match up our started server and to easily clean up after
65- const tempDir = await this . generateTempDir ( ) ;
66- this . disposables . push ( tempDir ) ;
67-
68- // In the temp dir, create an empty config python file. This is the same
69- // as starting jupyter with all of the defaults.
70- const configFile = useDefaultConfig ? path . join ( tempDir . path , 'jupyter_notebook_config.py' ) : undefined ;
71- if ( configFile ) {
72- await this . fileSystem . writeFile ( configFile , '' ) ;
73- traceInfo ( `Generating custom default config at ${ configFile } ` ) ;
74- }
75-
76- // Create extra args based on if we have a config or not
77- const extraArgs : string [ ] = [ ] ;
78- if ( useDefaultConfig ) {
79- extraArgs . push ( `--config=${ configFile } ` ) ;
80- }
81- // Check for the debug environment variable being set. Setting this
82- // causes Jupyter to output a lot more information about what it's doing
83- // under the covers and can be used to investigate problems with Jupyter.
84- if ( process . env && process . env . VSCODE_PYTHON_DEBUG_JUPYTER ) {
85- extraArgs . push ( '--debug' ) ;
86- }
87-
88- // Modify the data rate limit if starting locally. The default prevents large dataframes from being returned.
89- extraArgs . push ( '--NotebookApp.iopub_data_rate_limit=10000000000.0' ) ;
90-
91- // Check for a docker situation.
92- try {
93- if ( await this . fileSystem . fileExists ( '/proc/self/cgroup' ) ) {
94- const cgroup = await this . fileSystem . readFile ( '/proc/self/cgroup' ) ;
95- if ( cgroup . includes ( 'docker' ) ) {
96- // We definitely need an ip address.
97- extraArgs . push ( '--ip' ) ;
98- extraArgs . push ( '127.0.0.1' ) ;
99-
100- // Now see if we need --allow-root.
101- const idResults = execSync ( 'id' , { encoding : 'utf-8' } ) ;
102- if ( idResults . includes ( '(root)' ) ) {
103- extraArgs . push ( '--allow-root' ) ;
104- }
105- }
106- }
107- } catch {
108- noop ( ) ;
109- }
110-
111- // Use this temp file and config file to generate a list of args for our command
112- const args : string [ ] = [ ...[ '--no-browser' , `--notebook-dir=${ tempDir . path } ` ] , ...extraArgs ] ;
64+ const tempDirPromise = this . generateTempDir ( ) ;
65+ tempDirPromise . then ( dir => this . disposables . push ( dir ) ) . ignoreErrors ( ) ;
11366
11467 // Before starting the notebook process, make sure we generate a kernel spec
115- const kernelSpec = await this . kernelService . getMatchingKernelSpec ( undefined , cancelToken ) ;
68+ const [ args , kernelSpec ] = await Promise . all ( [ this . generateArguments ( useDefaultConfig , tempDirPromise ) , this . kernelService . getMatchingKernelSpec ( undefined , cancelToken ) ] ) ;
11669
11770 // Make sure we haven't canceled already.
11871 if ( cancelToken && cancelToken . isCancellationRequested ) {
@@ -136,6 +89,7 @@ export class NotebookStarter implements Disposable {
13689 }
13790
13891 // Wait for the connection information on this result
92+ const tempDir = await tempDirPromise ;
13993 const connection = await JupyterConnection . waitForConnection ( tempDir . path , this . getJupyterServerInfo , launchResult , this . serviceContainer , cancelToken ) ;
14094
14195 // Fire off telemetry for the process being talkable
@@ -158,6 +112,95 @@ export class NotebookStarter implements Disposable {
158112 }
159113 }
160114 }
115+
116+ private async generateArguments ( useDefaultConfig : boolean , tempDirPromise : Promise < TemporaryDirectory > ) : Promise < string [ ] > {
117+ // Parallelize as much as possible.
118+ const promisedArgs : Promise < string > [ ] = [ ] ;
119+ promisedArgs . push ( Promise . resolve ( '--no-browser' ) ) ;
120+ promisedArgs . push ( this . getNotebookDirArgument ( tempDirPromise ) ) ;
121+ if ( useDefaultConfig ) {
122+ promisedArgs . push ( this . getConfigArgument ( tempDirPromise ) ) ;
123+ }
124+ // Modify the data rate limit if starting locally. The default prevents large dataframes from being returned.
125+ promisedArgs . push ( Promise . resolve ( '--NotebookApp.iopub_data_rate_limit=10000000000.0' ) ) ;
126+
127+ const [ args , dockerArgs ] = await Promise . all ( [ Promise . all ( promisedArgs ) , this . getDockerArguments ( ) ] ) ;
128+
129+ // Check for the debug environment variable being set. Setting this
130+ // causes Jupyter to output a lot more information about what it's doing
131+ // under the covers and can be used to investigate problems with Jupyter.
132+ const debugArgs = ( process . env && process . env . VSCODE_PYTHON_DEBUG_JUPYTER ) ? [ '--debug' ] : [ ] ;
133+
134+ // Use this temp file and config file to generate a list of args for our command
135+ return [ ...args , ...dockerArgs , ...debugArgs ] ;
136+ }
137+
138+ /**
139+ * Gets the `--notebook-dir` argument.
140+ *
141+ * @private
142+ * @param {Promise<TemporaryDirectory> } tempDirectory
143+ * @returns {Promise<void> }
144+ * @memberof NotebookStarter
145+ */
146+ private async getNotebookDirArgument ( tempDirectory : Promise < TemporaryDirectory > ) : Promise < string > {
147+ const tempDir = await tempDirectory ;
148+ return `--notebook-dir=${ tempDir . path } ` ;
149+ }
150+
151+ /**
152+ * Gets the `--config` argument.
153+ *
154+ * @private
155+ * @param {Promise<TemporaryDirectory> } tempDirectory
156+ * @returns {Promise<void> }
157+ * @memberof NotebookStarter
158+ */
159+ private async getConfigArgument ( tempDirectory : Promise < TemporaryDirectory > ) : Promise < string > {
160+ const tempDir = await tempDirectory ;
161+ // In the temp dir, create an empty config python file. This is the same
162+ // as starting jupyter with all of the defaults.
163+ const configFile = path . join ( tempDir . path , 'jupyter_notebook_config.py' ) ;
164+ await this . fileSystem . writeFile ( configFile , '' ) ;
165+ traceInfo ( `Generating custom default config at ${ configFile } ` ) ;
166+
167+ // Create extra args based on if we have a config or not
168+ return `--config=${ configFile } ` ;
169+ }
170+
171+ /**
172+ * Adds the `--ip` and `--allow-root` arguments when in docker.
173+ *
174+ * @private
175+ * @param {Promise<TemporaryDirectory> } tempDirectory
176+ * @returns {Promise<string[]> }
177+ * @memberof NotebookStarter
178+ */
179+ private async getDockerArguments ( ) : Promise < string [ ] > {
180+ const args : string [ ] = [ ] ;
181+ // Check for a docker situation.
182+ try {
183+ const cgroup = await this . fileSystem . readFile ( '/proc/self/cgroup' ) . catch ( ( ) => '' ) ;
184+ if ( ! cgroup . includes ( 'docker' ) ) {
185+ return args ;
186+ }
187+ // We definitely need an ip address.
188+ args . push ( '--ip' ) ;
189+ args . push ( '127.0.0.1' ) ;
190+
191+ // Now see if we need --allow-root.
192+ return new Promise ( resolve => {
193+ cp . exec ( 'id' , { encoding : 'utf-8' } , ( _ , stdout : string | Buffer ) => {
194+ if ( stdout && stdout . toString ( ) . includes ( '(root)' ) ) {
195+ args . push ( '--allow-root' ) ;
196+ }
197+ resolve ( [ ] ) ;
198+ } ) ;
199+ } ) ;
200+ } catch {
201+ return args ;
202+ }
203+ }
161204 private async generateTempDir ( ) : Promise < TemporaryDirectory > {
162205 const resultDir = path . join ( os . tmpdir ( ) , uuid ( ) ) ;
163206 await this . fileSystem . createDirectory ( resultDir ) ;
0 commit comments