Skip to content

Commit a86bf11

Browse files
authored
Fix some intermittent test failures (microsoft#4973)
For #4951, #4936 <!-- If an item below does not apply to you, then go ahead and check it off as "done" and strikethrough the text, e.g.: - [x] ~Has unit tests & system/integration tests~ --> - [x] Pull request represents a single change (i.e. not fixing disparate/unrelated things in a single PR) - [x] Title summarizes what is changing - [x] Has a [news entry](https://github.com/Microsoft/vscode-python/tree/master/news) file (remember to thank yourself!) - [ ] Has sufficient logging. - [ ] Has telemetry for enhancements. - [x] Unit tests & system/integration tests are added/updated - [ ] [Test plan](https://github.com/Microsoft/vscode-python/blob/master/.github/test_plan.md) is updated as appropriate - [ ] [`package-lock.json`](https://github.com/Microsoft/vscode-python/blob/master/package-lock.json) has been regenerated by running `npm install` (if dependencies have changed) - [ ] The wiki is updated with any design decisions/details.
1 parent 1338fc3 commit a86bf11

File tree

6 files changed

+28
-43
lines changed

6 files changed

+28
-43
lines changed

news/3 Code Health/4936.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix intermittent test failure with listeners.

news/3 Code Health/4951.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix intermittent test failure with kernel shutdown.

src/client/common/utils/async.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,3 @@ export function createDeferredFromPromise<T>(promise: Promise<T>): Deferred<T> {
8484
.catch(deferred.reject.bind(deferred));
8585
return deferred;
8686
}
87-
export function callWithTimeout<T>(func: () => T, timeoutMS: number): Promise<T> {
88-
return new Promise<T>((resolve, reject) => {
89-
const timeout = setTimeout(() => {
90-
reject(new Error('Timed out'));
91-
}, timeoutMS);
92-
try {
93-
const result = func();
94-
resolve(result);
95-
timeout.unref();
96-
} catch (e) {
97-
reject(e);
98-
timeout.unref();
99-
}
100-
});
101-
}

src/client/datascience/jupyter/jupyterServer.ts

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { CancellationToken } from 'vscode-jsonrpc';
1515

1616
import { ILiveShareApi } from '../../common/application/types';
1717
import { CancellationError } from '../../common/cancellation';
18+
import { traceWarning } from '../../common/logger';
1819
import { IAsyncDisposableRegistry, IConfigurationService, IDisposableRegistry, ILogger } from '../../common/types';
1920
import { createDeferred, Deferred, sleep } from '../../common/utils/async';
2021
import * as localize from '../../common/utils/localize';
@@ -161,7 +162,7 @@ export class JupyterServerBase implements INotebookServer {
161162
await this.session.waitForIdle();
162163

163164
// Run our initial setup and plot magics
164-
this.initialNotebookSetup(cancelToken);
165+
await this.initialNotebookSetup(cancelToken);
165166
}
166167
}
167168

@@ -184,9 +185,6 @@ export class JupyterServerBase implements INotebookServer {
184185
}
185186

186187
public execute(code: string, file: string, line: number, id: string, cancelToken?: CancellationToken, silent?: boolean): Promise<ICell[]> {
187-
// Do initial setup if necessary
188-
this.initialNotebookSetup();
189-
190188
// Create a deferred that we'll fire when we're done
191189
const deferred = createDeferred<ICell[]>();
192190

@@ -271,7 +269,7 @@ export class JupyterServerBase implements INotebookServer {
271269

272270
// Rerun our initial setup for the notebook
273271
this.ranInitialSetup = false;
274-
this.initialNotebookSetup();
272+
await this.initialNotebookSetup();
275273

276274
return;
277275
}
@@ -386,9 +384,6 @@ export class JupyterServerBase implements INotebookServer {
386384
}
387385

388386
private executeSilently(code: string, cancelToken?: CancellationToken): Promise<ICell[]> {
389-
// Do initial setup if necessary
390-
this.initialNotebookSetup();
391-
392387
// Create a deferred that we'll fire when we're done
393388
const deferred = createDeferred<ICell[]>();
394389

@@ -438,9 +433,6 @@ export class JupyterServerBase implements INotebookServer {
438433
}
439434

440435
private executeObservableImpl(code: string, file: string, line: number, id: string, silent?: boolean): Observable<ICell[]> {
441-
// Do initial setup if necessary
442-
this.initialNotebookSetup();
443-
444436
// If we have a session, execute the code now.
445437
if (this.session) {
446438
// Generate our cells ahead of time
@@ -488,21 +480,25 @@ export class JupyterServerBase implements INotebookServer {
488480
}
489481

490482
// Set up our initial plotting and imports
491-
private initialNotebookSetup = (cancelToken?: CancellationToken) => {
483+
private async initialNotebookSetup(cancelToken?: CancellationToken) : Promise<void> {
492484
if (this.ranInitialSetup) {
493485
return;
494486
}
495487
this.ranInitialSetup = true;
496488

497-
// When we start our notebook initial, change to our workspace or user specified root directory
498-
if (this.launchInfo && this.launchInfo.workingDir && this.launchInfo.connectionInfo.localLaunch) {
499-
this.changeDirectoryIfPossible(this.launchInfo.workingDir).ignoreErrors();
500-
}
489+
try {
490+
// When we start our notebook initial, change to our workspace or user specified root directory
491+
if (this.launchInfo && this.launchInfo.workingDir && this.launchInfo.connectionInfo.localLaunch) {
492+
await this.changeDirectoryIfPossible(this.launchInfo.workingDir);
493+
}
501494

502-
this.executeSilently(
503-
`%matplotlib inline${os.EOL}import matplotlib.pyplot as plt${(this.launchInfo && this.launchInfo.usingDarkTheme) ? `${os.EOL}from matplotlib import style${os.EOL}style.use(\'dark_background\')` : ''}`,
504-
cancelToken
505-
).ignoreErrors();
495+
await this.executeSilently(
496+
`%matplotlib inline${os.EOL}import matplotlib.pyplot as plt${(this.launchInfo && this.launchInfo.usingDarkTheme) ? `${os.EOL}from matplotlib import style${os.EOL}style.use(\'dark_background\')` : ''}`,
497+
cancelToken
498+
);
499+
} catch (e) {
500+
traceWarning(e);
501+
}
506502
}
507503

508504
private combineObservables = (...args: Observable<ICell>[]): Observable<ICell[]> => {
@@ -574,7 +570,8 @@ export class JupyterServerBase implements INotebookServer {
574570
if (this.launchInfo && this.launchInfo.connectionInfo) {
575571
// If the server crashes, cancel the current observable
576572
exitHandlerDisposable = this.launchInfo.connectionInfo.disconnected((c) => {
577-
subscriber.error(this.sessionStartTime, new Error(localize.DataScience.jupyterServerCrashed().format(c.toString())));
573+
const str = c ? c.toString() : '';
574+
subscriber.error(this.sessionStartTime, new Error(localize.DataScience.jupyterServerCrashed().format(str)));
578575
subscriber.complete(this.sessionStartTime);
579576
});
580577
}

src/client/datascience/jupyter/jupyterSession.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { Event, EventEmitter } from 'vscode';
1616
import { CancellationToken } from 'vscode-jsonrpc';
1717

1818
import { Cancellation } from '../../common/cancellation';
19-
import { callWithTimeout, sleep } from '../../common/utils/async';
19+
import { sleep } from '../../common/utils/async';
2020
import * as localize from '../../common/utils/localize';
2121
import { noop } from '../../common/utils/misc';
2222
import { IConnection, IJupyterKernelSpec, IJupyterSession } from '../types';
@@ -182,18 +182,16 @@ export class JupyterSession implements IJupyterSession {
182182
if (this.session) {
183183
try {
184184
// Shutdown may fail if the process has been killed
185-
await Promise.race([this.session.shutdown(), sleep(100)]);
185+
await Promise.race([this.session.shutdown(), sleep(1000)]);
186186
} catch {
187187
noop();
188188
}
189-
// Dispose may not return. Wrap in a promise instead. Kernel futures can die if
190-
// process is already dead.
191189
if (this.session) {
192-
await callWithTimeout(this.session.dispose.bind(this.session), 100);
190+
this.session.dispose();
193191
}
194192
}
195193
if (this.sessionManager) {
196-
await callWithTimeout(this.sessionManager.dispose.bind(this.sessionManager), 100);
194+
this.sessionManager.dispose();
197195
}
198196
} catch {
199197
noop();

src/test/datascience/mockLiveShare.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,10 @@ class Emitter<T> {
126126
if (typeof item.listener === 'function') {
127127
result = item.listener.call(undefined, item.event);
128128
} else {
129-
result = item.listener[0].call(item.listener[1], item.event);
129+
const func = item.listener[0];
130+
if (func) {
131+
result = func.call(item.listener[1], item.event);
132+
}
130133
}
131134
}
132135
} catch (e) {

0 commit comments

Comments
 (0)