3
3
4
4
'use strict' ;
5
5
6
- import { execSync } from 'child_process' ;
6
+ import * as cp from 'child_process' ;
7
7
import * as os from 'os' ;
8
8
import * as path from 'path' ;
9
9
import * as uuid from 'uuid/v4' ;
@@ -14,7 +14,6 @@ import { IFileSystem, TemporaryDirectory } from '../../common/platform/types';
14
14
import { IPythonExecutionFactory , SpawnOptions } from '../../common/process/types' ;
15
15
import { IDisposable } from '../../common/types' ;
16
16
import * as localize from '../../common/utils/localize' ;
17
- import { noop } from '../../common/utils/misc' ;
18
17
import { StopWatch } from '../../common/utils/stopWatch' ;
19
18
import { EXTENSION_ROOT_DIR } from '../../constants' ;
20
19
import { IServiceContainer } from '../../ioc/types' ;
@@ -62,57 +61,11 @@ export class NotebookStarter implements Disposable {
62
61
let exitCode : number | null = 0 ;
63
62
try {
64
63
// 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 ( ) ;
113
66
114
67
// 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 ) ] ) ;
116
69
117
70
// Make sure we haven't canceled already.
118
71
if ( cancelToken && cancelToken . isCancellationRequested ) {
@@ -136,6 +89,7 @@ export class NotebookStarter implements Disposable {
136
89
}
137
90
138
91
// Wait for the connection information on this result
92
+ const tempDir = await tempDirPromise ;
139
93
const connection = await JupyterConnection . waitForConnection ( tempDir . path , this . getJupyterServerInfo , launchResult , this . serviceContainer , cancelToken ) ;
140
94
141
95
// Fire off telemetry for the process being talkable
@@ -158,6 +112,95 @@ export class NotebookStarter implements Disposable {
158
112
}
159
113
}
160
114
}
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
+ }
161
204
private async generateTempDir ( ) : Promise < TemporaryDirectory > {
162
205
const resultDir = path . join ( os . tmpdir ( ) , uuid ( ) ) ;
163
206
await this . fileSystem . createDirectory ( resultDir ) ;
0 commit comments