Skip to content

Commit dbdd5cf

Browse files
author
David Kutugata
authored
Davidkutu/finder speedup (microsoft#11311)
* search for the kernel by name in the path before opening the json file * Race the slow search options and wait for both in case an undefined wins. Use interpreterLocator to search for cached interpreters. * updated unit tests * reuse duplicated code
1 parent f95171e commit dbdd5cf

File tree

2 files changed

+45
-53
lines changed

2 files changed

+45
-53
lines changed

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

Lines changed: 29 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22
// Licensed under the MIT License.
33
'use strict';
44

5-
import { inject, injectable } from 'inversify';
5+
import { inject, injectable, named } from 'inversify';
66
import * as path from 'path';
77
import { InterpreterUri } from '../../common/installer/types';
88
import { traceError, traceInfo } from '../../common/logger';
99
import { IFileSystem, IPlatformService } from '../../common/platform/types';
1010
import { IExtensionContext, IPathUtils, Resource } from '../../common/types';
1111
import { isResource } from '../../common/utils/misc';
12-
import { IInterpreterService, PythonInterpreter } from '../../interpreter/contracts';
12+
import {
13+
IInterpreterLocatorService,
14+
IInterpreterService,
15+
KNOWN_PATH_SERVICE,
16+
PythonInterpreter
17+
} from '../../interpreter/contracts';
1318
import { IJupyterKernelSpec } from '../types';
1419
import { IKernelFinder } from './types';
1520

@@ -38,6 +43,9 @@ export class KernelFinder implements IKernelFinder {
3843

3944
constructor(
4045
@inject(IInterpreterService) private interpreterService: IInterpreterService,
46+
@inject(IInterpreterLocatorService)
47+
@named(KNOWN_PATH_SERVICE)
48+
private readonly interpreterLocator: IInterpreterLocatorService,
4149
@inject(IPlatformService) private platformService: IPlatformService,
4250
@inject(IFileSystem) private file: IFileSystem,
4351
@inject(IPathUtils) private readonly pathUtils: IPathUtils,
@@ -62,20 +70,24 @@ export class KernelFinder implements IKernelFinder {
6270
}
6371

6472
if (kernelSpec) {
73+
// tslint:disable-next-line: no-floating-promises
74+
this.writeCache(this.cache);
6575
return kernelSpec;
6676
}
6777

68-
const kernelSearches = [
69-
this.interpreterService.getInterpreters(resource).then((interpreters) => {
70-
const interpreterPaths = interpreters.map((interp) => interp.path);
71-
return this.findInterpreterPath(interpreterPaths, kernelName);
72-
}),
73-
this.findDiskPath(kernelName)
74-
];
78+
const diskSearch = this.findDiskPath(kernelName);
79+
const interpreterSearch = this.interpreterLocator.getInterpreters(resource, false).then((interpreters) => {
80+
const interpreterPaths = interpreters.map((interp) => interp.path);
81+
return this.findInterpreterPath(interpreterPaths, kernelName);
82+
});
83+
84+
let result = await Promise.race([diskSearch, interpreterSearch]);
85+
if (!result) {
86+
const both = await Promise.all([diskSearch, interpreterSearch]);
87+
result = both[0] ? both[0] : both[1];
88+
}
7589

76-
const result = await Promise.all(kernelSearches);
77-
const spec = result.find((sp) => sp?.name === kernelName);
78-
foundKernel = spec ? spec : await this.getDefaultKernelSpec(resource);
90+
foundKernel = result ? result : await this.getDefaultKernelSpec(resource);
7991
} else {
8092
foundKernel = await this.getDefaultKernelSpec(resource);
8193
}
@@ -87,7 +99,7 @@ export class KernelFinder implements IKernelFinder {
8799

88100
private async getKernelSpecFromActiveInterpreter(
89101
resource: Resource,
90-
kernelName?: string
102+
kernelName: string
91103
): Promise<IJupyterKernelSpec | undefined> {
92104
this.activeInterpreter = await this.interpreterService.getActiveInterpreter(resource);
93105

@@ -99,47 +111,15 @@ export class KernelFinder implements IKernelFinder {
99111
}
100112
}
101113

102-
private async getKernelSpec(kernelPath: string, kernelName?: string): Promise<IJupyterKernelSpec | undefined> {
114+
private async getKernelSpec(kernelPath: string, kernelName: string): Promise<IJupyterKernelSpec | undefined> {
103115
try {
104-
const kernels = await this.file.getSubDirectories(kernelPath);
116+
const paths = await this.file.getSubDirectories(kernelPath);
105117

106-
if (kernels.length === 0) {
118+
if (paths.length === 0) {
107119
return undefined;
108120
}
109121

110-
// If no kernel name is included, return the first kernel
111-
if (!kernelName) {
112-
try {
113-
const kernelSpec: IJupyterKernelSpec = JSON.parse(
114-
await this.file.readFile(path.join(kernels[0], 'kernel.json'))
115-
);
116-
this.cache.push(kernelSpec);
117-
return kernelSpec;
118-
} catch (e) {
119-
traceError('Invalid kernel.json', e);
120-
return undefined;
121-
}
122-
}
123-
124-
let spec: IJupyterKernelSpec | undefined;
125-
const promises = kernels.map(async (kernel) => {
126-
try {
127-
const kernelSpec: IJupyterKernelSpec = JSON.parse(
128-
await this.file.readFile(path.join(kernel, 'kernel.json'))
129-
);
130-
this.cache.push(kernelSpec);
131-
if (kernelSpec.name === kernelName) {
132-
spec = kernelSpec;
133-
}
134-
return kernelSpec;
135-
} catch (e) {
136-
traceError('Invalid kernel.json', e);
137-
return undefined;
138-
}
139-
});
140-
141-
await Promise.all(promises);
142-
return spec;
122+
return this.getKernelSpecFromDisk(paths, kernelName);
143123
} catch {
144124
traceInfo(`The path ${kernelPath} does not exist.`);
145125
return undefined;

src/test/datascience/kernelFinder.unit.test.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,16 @@ import { Architecture } from '../../client/common/utils/platform';
1212
import { KernelFinder } from '../../client/datascience/kernel-launcher/kernelFinder';
1313
import { IKernelFinder } from '../../client/datascience/kernel-launcher/types';
1414
import { IJupyterKernelSpec } from '../../client/datascience/types';
15-
import { IInterpreterService, InterpreterType, PythonInterpreter } from '../../client/interpreter/contracts';
15+
import {
16+
IInterpreterLocatorService,
17+
IInterpreterService,
18+
InterpreterType,
19+
PythonInterpreter
20+
} from '../../client/interpreter/contracts';
1621

1722
suite('Kernel Finder', () => {
1823
let interpreterService: typemoq.IMock<IInterpreterService>;
24+
let interpreterLocator: typemoq.IMock<IInterpreterLocatorService>;
1925
let fileSystem: typemoq.IMock<IFileSystem>;
2026
let platformService: typemoq.IMock<IPlatformService>;
2127
let pathUtils: typemoq.IMock<IPathUtils>;
@@ -39,17 +45,22 @@ suite('Kernel Finder', () => {
3945
.setup((fs) => fs.writeFile(typemoq.It.isAnyString(), typemoq.It.isAnyString()))
4046
.returns(() => Promise.resolve());
4147
fileSystem.setup((fs) => fs.getSubDirectories(typemoq.It.isAnyString())).returns(() => Promise.resolve(['']));
48+
fileSystem
49+
.setup((fs) => fs.search(typemoq.It.isAnyString(), typemoq.It.isAnyString()))
50+
.returns((param: string) => Promise.resolve([param]));
4251
}
4352

4453
setup(() => {
4554
interpreterService = typemoq.Mock.ofType<IInterpreterService>();
46-
interpreterService
47-
.setup((is) => is.getInterpreters(typemoq.It.isAny()))
48-
.returns(() => Promise.resolve(interpreters));
4955
interpreterService
5056
.setup((is) => is.getActiveInterpreter(typemoq.It.isAny()))
5157
.returns(() => Promise.resolve(activeInterpreter));
5258

59+
interpreterLocator = typemoq.Mock.ofType<IInterpreterLocatorService>();
60+
interpreterLocator
61+
.setup((il) => il.getInterpreters(typemoq.It.isAny(), typemoq.It.isAny()))
62+
.returns(() => Promise.resolve(interpreters));
63+
5364
fileSystem = typemoq.Mock.ofType<IFileSystem>();
5465
platformService = typemoq.Mock.ofType<IPlatformService>();
5566
platformService.setup((ps) => ps.isWindows).returns(() => true);
@@ -84,6 +95,7 @@ suite('Kernel Finder', () => {
8495

8596
kernelFinder = new KernelFinder(
8697
interpreterService.object,
98+
interpreterLocator.object,
8799
platformService.object,
88100
fileSystem.object,
89101
pathUtils.object,

0 commit comments

Comments
 (0)