Skip to content

Commit 2e47093

Browse files
Fix linux tests to report correctly, get rid of stream destroyed messages on raw kernel shutdown (microsoft#12643)
1 parent 14acf98 commit 2e47093

File tree

15 files changed

+101
-30
lines changed

15 files changed

+101
-30
lines changed

news/3 Code Health/12539.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix linux nightly tests so they run and report results. Also seems to get rid of stream destroyed messages for raw kernel.

src/client/common/process/baseDaemon.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as os from 'os';
88
import { Subject } from 'rxjs/Subject';
99
import * as util from 'util';
1010
import { MessageConnection, NotificationType, RequestType, RequestType0 } from 'vscode-jsonrpc';
11+
import { IPlatformService } from '../../common/platform/types';
1112
import { traceError, traceInfo, traceVerbose, traceWarning } from '../logger';
1213
import { IDisposable } from '../types';
1314
import { createDeferred, Deferred } from '../utils/async';
@@ -49,6 +50,7 @@ export abstract class BasePythonDaemon {
4950
private disposed = false;
5051
constructor(
5152
protected readonly pythonExecutionService: IPythonExecutionService,
53+
protected readonly platformService: IPlatformService,
5254
protected readonly pythonPath: string,
5355
public readonly proc: ChildProcess,
5456
public readonly connection: MessageConnection
@@ -61,15 +63,27 @@ export abstract class BasePythonDaemon {
6163
this.monitorConnection();
6264
}
6365
public dispose() {
64-
try {
65-
this.disposed = true;
66-
// The daemon should die as a result of this.
67-
this.connection.sendNotification(new NotificationType('exit'));
68-
this.proc.kill();
69-
} catch {
70-
noop();
66+
// Make sure that we only dispose once so we are not sending multiple kill signals or notifications
67+
// This daemon can be held by multiple disposes such as a jupyter server daemon process which can
68+
// be disposed by both the connection and the main async disposable
69+
if (!this.disposed) {
70+
try {
71+
this.disposed = true;
72+
73+
// Proc.kill uses a 'SIGTERM' signal by default to kill. This was failing to kill the process
74+
// sometimes on Mac and Linux. Changing this over to a 'SIGKILL' to fully kill the process.
75+
// Windows closes with a different non-signal message, so keep that the same
76+
// See kill_kernel message of kernel_launcher_daemon.py for and example of this.
77+
if (this.platformService.isWindows) {
78+
this.proc.kill();
79+
} else {
80+
this.proc.kill('SIGKILL');
81+
}
82+
} catch {
83+
noop();
84+
}
85+
this.disposables.forEach((item) => item.dispose());
7186
}
72-
this.disposables.forEach((item) => item.dispose());
7387
}
7488
public execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult<string> {
7589
if (this.isAlive && this.canExecFileUsingDaemon(args, options)) {

src/client/common/process/pythonDaemon.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { PythonExecInfo } from '../../pythonEnvironments/exec';
99
import { InterpreterInformation } from '../../pythonEnvironments/info';
1010
import { extractInterpreterInfo } from '../../pythonEnvironments/info/interpreter';
1111
import { traceWarning } from '../logger';
12+
import { IPlatformService } from '../platform/types';
1213
import { BasePythonDaemon } from './baseDaemon';
1314
import { PythonEnvInfo } from './internal/scripts';
1415
import {
@@ -34,11 +35,12 @@ export class DaemonError extends Error {
3435
export class PythonDaemonExecutionService extends BasePythonDaemon implements IPythonDaemonExecutionService {
3536
constructor(
3637
pythonExecutionService: IPythonExecutionService,
38+
platformService: IPlatformService,
3739
pythonPath: string,
3840
proc: ChildProcess,
3941
connection: MessageConnection
4042
) {
41-
super(pythonExecutionService, pythonPath, proc, connection);
43+
super(pythonExecutionService, platformService, pythonPath, proc, connection);
4244
}
4345
public async getInterpreterInformation(): Promise<InterpreterInformation | undefined> {
4446
try {

src/client/common/process/pythonDaemonFactory.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { EXTENSION_ROOT_DIR } from '../../constants';
1414
import { PYTHON_WARNINGS } from '../constants';
1515
import { traceDecorators, traceError } from '../logger';
16+
import { IPlatformService } from '../platform/types';
1617
import { IDisposable, IDisposableRegistry } from '../types';
1718
import { createDeferred } from '../utils/async';
1819
import { BasePythonDaemon } from './baseDaemon';
@@ -26,6 +27,7 @@ export class PythonDaemonFactory {
2627
protected readonly disposables: IDisposableRegistry,
2728
protected readonly options: DaemonExecutionFactoryCreationOptions,
2829
protected readonly pythonExecutionService: IPythonExecutionService,
30+
protected readonly platformService: IPlatformService,
2931
protected readonly activatedEnvVariables?: NodeJS.ProcessEnv
3032
) {
3133
if (!options.pythonPath) {
@@ -82,7 +84,13 @@ export class PythonDaemonFactory {
8284
await this.testDaemon(connection);
8385

8486
const cls = this.options.daemonClass ?? PythonDaemonExecutionService;
85-
const instance = new cls(this.pythonExecutionService, this.pythonPath, daemonProc.proc, connection);
87+
const instance = new cls(
88+
this.pythonExecutionService,
89+
this.platformService,
90+
this.pythonPath,
91+
daemonProc.proc,
92+
connection
93+
);
8694
if (instance instanceof BasePythonDaemon) {
8795
this.disposables.push(instance);
8896
return (instance as unknown) as T;

src/client/common/process/pythonDaemonPool.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
import { IPlatformService } from '../../common/platform/types';
45
import { PythonExecInfo } from '../../pythonEnvironments/exec';
56
import { InterpreterInformation } from '../../pythonEnvironments/info';
67
import { IDisposableRegistry } from '../types';
@@ -32,10 +33,11 @@ export class PythonDaemonExecutionServicePool extends PythonDaemonFactory implem
3233
disposables: IDisposableRegistry,
3334
options: PooledDaemonExecutionFactoryCreationOptions,
3435
pythonExecutionService: IPythonExecutionService,
36+
platformService: IPlatformService,
3537
activatedEnvVariables?: NodeJS.ProcessEnv,
3638
private readonly timeoutWaitingForDaemon: number = 1_000
3739
) {
38-
super(disposables, options, pythonExecutionService, activatedEnvVariables);
40+
super(disposables, options, pythonExecutionService, platformService, activatedEnvVariables);
3941
this.disposables.push(this);
4042
}
4143
public async initialize() {

src/client/common/process/pythonExecutionFactory.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { inject, injectable } from 'inversify';
44
import { gte } from 'semver';
55

66
import { Uri } from 'vscode';
7+
import { IPlatformService } from '../../common/platform/types';
78
import { IEnvironmentActivationService } from '../../interpreter/activation/types';
89
import { ICondaService, IInterpreterService } from '../../interpreter/contracts';
910
import { IWindowsStoreInterpreter } from '../../interpreter/locators/types';
@@ -50,7 +51,8 @@ export class PythonExecutionFactory implements IPythonExecutionFactory {
5051
@inject(IConfigurationService) private readonly configService: IConfigurationService,
5152
@inject(ICondaService) private readonly condaService: ICondaService,
5253
@inject(IBufferDecoder) private readonly decoder: IBufferDecoder,
53-
@inject(WindowsStoreInterpreter) private readonly windowsStoreInterpreter: IWindowsStoreInterpreter
54+
@inject(WindowsStoreInterpreter) private readonly windowsStoreInterpreter: IWindowsStoreInterpreter,
55+
@inject(IPlatformService) private readonly platformService: IPlatformService
5456
) {
5557
// Acquire other objects here so that if we are called during dispose they are available.
5658
this.disposables = this.serviceContainer.get<IDisposableRegistry>(IDisposableRegistry);
@@ -109,6 +111,7 @@ export class PythonExecutionFactory implements IPythonExecutionFactory {
109111
this.disposables,
110112
{ ...options, pythonPath },
111113
activatedProc!,
114+
this.platformService,
112115
activatedEnvVars
113116
);
114117
await daemon.initialize();
@@ -119,6 +122,7 @@ export class PythonExecutionFactory implements IPythonExecutionFactory {
119122
this.disposables,
120123
{ ...options, pythonPath },
121124
activatedProc!,
125+
this.platformService,
122126
activatedEnvVars
123127
);
124128
return factory.createDaemonService<T>();

src/client/datascience/kernel-launcher/kernelDaemon.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { ChildProcess } from 'child_process';
77
import { Subject } from 'rxjs/Subject';
88
import { MessageConnection, NotificationType, RequestType, RequestType0 } from 'vscode-jsonrpc';
9+
import { IPlatformService } from '../../common/platform/types';
910
import { BasePythonDaemon, ExecResponse } from '../../common/process/baseDaemon';
1011
import {
1112
IPythonExecutionService,
@@ -14,7 +15,6 @@ import {
1415
SpawnOptions,
1516
StdErrError
1617
} from '../../common/process/types';
17-
import { noop } from '../../common/utils/misc';
1818
import { IPythonKernelDaemon, PythonKernelDiedError } from './types';
1919

2020
export class PythonKernelDaemon extends BasePythonDaemon implements IPythonKernelDaemon {
@@ -25,11 +25,12 @@ export class PythonKernelDaemon extends BasePythonDaemon implements IPythonKerne
2525
private readonly subject = new Subject<Output<string>>();
2626
constructor(
2727
pythonExecutionService: IPythonExecutionService,
28+
platformService: IPlatformService,
2829
pythonPath: string,
2930
proc: ChildProcess,
3031
connection: MessageConnection
3132
) {
32-
super(pythonExecutionService, pythonPath, proc, connection);
33+
super(pythonExecutionService, platformService, pythonPath, proc, connection);
3334
}
3435
public async interrupt() {
3536
const request = new RequestType0<void, void, void>('interrupt_kernel');
@@ -43,10 +44,6 @@ export class PythonKernelDaemon extends BasePythonDaemon implements IPythonKerne
4344
const request = new RequestType0<void, void, void>('kill_kernel');
4445
await this.sendRequestWithoutArgs(request);
4546
}
46-
public dispose() {
47-
this.kill().catch(noop);
48-
super.dispose();
49-
}
5047
public async preWarm() {
5148
if (this.started) {
5249
return;

src/test/common/process/pythonDaemon.functional.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
StreamMessageReader,
1919
StreamMessageWriter
2020
} from 'vscode-jsonrpc/node';
21+
import { IPlatformService } from '../../../client/common/platform/types';
2122
import { PythonDaemonExecutionService } from '../../../client/common/process/pythonDaemon';
2223
import { IPythonExecutionService } from '../../../client/common/process/types';
2324
import { IDisposable } from '../../../client/common/types';
@@ -44,6 +45,7 @@ suite('Daemon', () => {
4445
let fullyQualifiedPythonPath: string = PYTHON_PATH;
4546
let pythonDaemon: PythonDaemonExecutionService;
4647
let pythonExecutionService: IPythonExecutionService;
48+
let platformService: IPlatformService;
4749
let disposables: IDisposable[] = [];
4850
suiteSetup(() => {
4951
// When running locally.
@@ -67,8 +69,10 @@ suite('Daemon', () => {
6769
);
6870
connection.listen();
6971
pythonExecutionService = mock<IPythonExecutionService>();
72+
platformService = mock<IPlatformService>();
7073
pythonDaemon = new PythonDaemonExecutionService(
7174
instance(pythonExecutionService),
75+
instance(platformService),
7276
fullyQualifiedPythonPath,
7377
pythonProc,
7478
connection

src/test/common/process/pythonDaemonPool.functional.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { Observable } from 'rxjs/Observable';
1515
import * as sinon from 'sinon';
1616
import { anything, deepEqual, instance, mock, verify, when } from 'ts-mockito';
1717
import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node';
18+
import { IPlatformService } from '../../../client/common/platform/types';
1819
import { ProcessLogger } from '../../../client/common/process/logger';
1920
import { PythonDaemonExecutionServicePool } from '../../../client/common/process/pythonDaemonPool';
2021
import {
@@ -49,6 +50,7 @@ suite('Daemon - Python Daemon Pool', () => {
4950
let fullyQualifiedPythonPath: string = PYTHON_PATH;
5051
let pythonDaemonPool: PythonDaemonExecutionServicePool;
5152
let pythonExecutionService: IPythonExecutionService;
53+
let platformService: IPlatformService;
5254
let disposables: IDisposable[] = [];
5355
let createDaemonServicesSpy: sinon.SinonSpy<[], Promise<IPythonDaemonExecutionService | IDisposable>>;
5456
let logger: IProcessLogger;
@@ -74,6 +76,7 @@ suite('Daemon - Python Daemon Pool', () => {
7476
logger = mock(ProcessLogger);
7577
createDaemonServicesSpy = sinon.spy(DaemonPool.prototype, 'createDaemonService');
7678
pythonExecutionService = mock<IPythonExecutionService>();
79+
platformService = mock<IPlatformService>();
7780
when(
7881
pythonExecutionService.execModuleObservable('vscode_datascience_helpers.daemon', anything(), anything())
7982
).thenCall(() => {
@@ -94,7 +97,15 @@ suite('Daemon - Python Daemon Pool', () => {
9497
daemonCount: 2,
9598
observableDaemonCount: 1
9699
};
97-
pythonDaemonPool = new DaemonPool(logger, [], options, instance(pythonExecutionService), {}, 100);
100+
pythonDaemonPool = new DaemonPool(
101+
logger,
102+
[],
103+
options,
104+
instance(pythonExecutionService),
105+
instance(platformService),
106+
{},
107+
100
108+
);
98109
await pythonDaemonPool.initialize();
99110
disposables.push(pythonDaemonPool);
100111
});

src/test/common/process/pythonDaemonPool.unit.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Observable } from 'rxjs/Observable';
1212
import * as sinon from 'sinon';
1313
import { anything, instance, mock, reset, verify, when } from 'ts-mockito';
1414
import { MessageConnection } from 'vscode-jsonrpc';
15+
import { IPlatformService } from '../../../client/common/platform/types';
1516
import { ProcessLogger } from '../../../client/common/process/logger';
1617
import { PythonDaemonExecutionService } from '../../../client/common/process/pythonDaemon';
1718
import { PythonDaemonExecutionServicePool } from '../../../client/common/process/pythonDaemonPool';
@@ -34,11 +35,13 @@ suite('Daemon - Python Daemon Pool', () => {
3435
// tslint:disable-next-line: no-any use-default-type-parameter
3536
let listenStub: sinon.SinonStub<any[], any>;
3637
let pythonExecService: IPythonExecutionService;
38+
let platformService: IPlatformService;
3739
let logger: IProcessLogger;
3840
let clock: fakeTimers.InstalledClock;
3941
setup(() => {
4042
logger = instance(mock(ProcessLogger));
4143
pythonExecService = mock<IPythonExecutionService>();
44+
platformService = mock<IPlatformService>();
4245
(instance(pythonExecService) as any).then = undefined;
4346
sendRequestStub = sinon.stub();
4447
listenStub = sinon.stub();
@@ -83,7 +86,14 @@ suite('Daemon - Python Daemon Pool', () => {
8386
}
8487
test('Create daemons when initializing', async () => {
8588
// Create and initialize the pool.
86-
const pool = new DaemonPool(logger, [], { pythonPath: 'py.exe' }, instance(pythonExecService), undefined);
89+
const pool = new DaemonPool(
90+
logger,
91+
[],
92+
{ pythonPath: 'py.exe' },
93+
instance(pythonExecService),
94+
instance(platformService),
95+
undefined
96+
);
8797
await setupDaemon(pool);
8898

8999
// 2 = 2 for standard daemon + 1 observable daemon.
@@ -97,6 +107,7 @@ suite('Daemon - Python Daemon Pool', () => {
97107
[],
98108
{ daemonCount: 5, observableDaemonCount: 3, pythonPath: 'py.exe' },
99109
instance(pythonExecService),
110+
instance(platformService),
100111
undefined
101112
);
102113
await setupDaemon(pool);
@@ -115,6 +126,7 @@ suite('Daemon - Python Daemon Pool', () => {
115126
[],
116127
{ daemonCount: 5, observableDaemonCount: 3, pythonPath: 'py.exe' },
117128
instance(pythonExecService),
129+
instance(platformService),
118130
undefined
119131
);
120132
const promise = setupDaemon(pool);
@@ -143,6 +155,7 @@ suite('Daemon - Python Daemon Pool', () => {
143155
[],
144156
{ daemonCount: 1, observableDaemonCount: 1, pythonPath: 'py.exe' },
145157
instance(pythonExecService),
158+
instance(platformService),
146159
undefined
147160
);
148161
await setupDaemon(pool);
@@ -194,6 +207,7 @@ suite('Daemon - Python Daemon Pool', () => {
194207
[],
195208
{ daemonCount: 2, observableDaemonCount: 1, pythonPath: 'py.exe' },
196209
instance(pythonExecService),
210+
instance(platformService),
197211
undefined
198212
);
199213

@@ -272,6 +286,7 @@ suite('Daemon - Python Daemon Pool', () => {
272286
[],
273287
{ daemonCount: 1, observableDaemonCount: 1, pythonPath: 'py.exe' },
274288
instance(pythonExecService),
289+
instance(platformService),
275290
undefined
276291
);
277292
await setupDaemon(pool);

0 commit comments

Comments
 (0)