Skip to content

Commit 178801c

Browse files
correctly dispose of PythonRuntimeManager (#7337)
<!-- Thank you for submitting a pull request. If this is your first pull request you can find information about contributing here: * https://github.com/posit-dev/positron/blob/main/CONTRIBUTING.md We recommend synchronizing your branch with the latest changes in the main branch by either pulling or rebasing. --> <!-- Describe briefly what problem this pull request resolves, or what new feature it introduces. Include screenshots of any new or altered UI. Link to any GitHub issues but avoid "magic" keywords that will automatically close the issue. If there are any details about your approach that are unintuitive or you want to draw attention to, please describe them here. --> Addresses #7206. There is a lot of discussion in that issue for context. Basically, I think what was happening is that the `PythonRuntimeManager` was not being correctly disposed when the user trusts a workspace and everything gets deactivated/activated again. After this change, it does get disposed during that flow, and I can't reproduce the bug anymore. ### Release Notes <!-- Optionally, replace `N/A` with text to be included in the next release notes. The `N/A` bullets are ignored. If you refer to one or more Positron issues, these issues are used to collect information about the feature or bugfix, such as the relevant language pack as determined by Github labels of type `lang: `. The note will automatically be tagged with the language. These notes are typically filled by the Positron team. If you are an external contributor, you may ignore this section. --> #### New Features - N/A #### Bug Fixes - Fixed bug in Python startup (#7206) ### QA Notes 1. Create a new project. I used uv: ``` uv init proj cd proj uv venv ``` 2. Open a fresh Positron with no state, but with PET and multiconsole on. I do this with the following: ``` rm -rf ~/.vscode-oss-dev rm -rf ~/.positron-dev mkdir -p ~/.vscode-oss-dev/User echo '{"console.multipleConsoleSessions":true,"python.locator":"native"}' > ~/.vscode-oss-dev/User/settings.json ``` 4. No workspace should be selected. It should start "Discovering interpreters". 5. Open Folder to the `proj` workspace 6. Click "trust" 7. The uv interpreter should successfully open a session. Maybe try this a few times at varying levels of speed.
1 parent 64988df commit 178801c

File tree

4 files changed

+42
-14
lines changed

4 files changed

+42
-14
lines changed

extensions/positron-python/src/client/positron/manager.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,21 @@ import * as vscode from 'vscode';
1111
import * as path from 'path';
1212
import * as os from 'os';
1313

14-
import { Event, EventEmitter } from 'vscode';
14+
import { Event, EventEmitter, Disposable } from 'vscode';
1515
import { inject, injectable } from 'inversify';
1616
import * as fs from '../common/platform/fs-paths';
1717
import { IServiceContainer } from '../ioc/types';
1818
import { pythonRuntimeDiscoverer } from './discoverer';
1919
import { IInterpreterService } from '../interpreter/contracts';
2020
import { traceError, traceInfo, traceLog } from '../logging';
21-
import { IConfigurationService, IDisposable, IInstaller, InstallerResponse, Product } from '../common/types';
21+
import {
22+
IConfigurationService,
23+
IDisposable,
24+
IDisposableRegistry,
25+
IInstaller,
26+
InstallerResponse,
27+
Product,
28+
} from '../common/types';
2229
import { PythonRuntimeSession } from './session';
2330
import { createPythonRuntimeMetadata, PythonRuntimeExtraData } from './runtime';
2431
import { Commands, EXTENSION_ROOT_DIR } from '../common/constants';
@@ -51,7 +58,7 @@ export interface IPythonRuntimeManager extends positron.LanguageRuntimeManager {
5158
* implements positron.LanguageRuntimeManager.
5259
*/
5360
@injectable()
54-
export class PythonRuntimeManager implements IPythonRuntimeManager, vscode.Disposable {
61+
export class PythonRuntimeManager implements IPythonRuntimeManager, Disposable {
5562
/**
5663
* A map of Python interpreter paths to their language runtime metadata.
5764
*/
@@ -74,9 +81,11 @@ export class PythonRuntimeManager implements IPythonRuntimeManager, vscode.Dispo
7481
@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer,
7582
@inject(IInterpreterService) private readonly interpreterService: IInterpreterService,
7683
) {
77-
positron.runtime.registerLanguageRuntimeManager('python', this);
84+
const disposables = this.serviceContainer.get<Disposable[]>(IDisposableRegistry);
85+
disposables.push(this);
7886

7987
this.disposables.push(
88+
positron.runtime.registerLanguageRuntimeManager('python', this),
8089
// When an interpreter is added, register a corresponding language runtime.
8190
interpreterService.onDidChangeInterpreters(async (event) => {
8291
if (!event.old && event.new) {

extensions/positron-python/src/test/positron/manager.unit.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ import * as interpreterSettings from '../../client/positron/interpreterSettings'
2020
import * as environmentTypeComparer from '../../client/interpreter/configuration/environmentTypeComparer';
2121
import * as util from '../../client/positron/util';
2222
import { IEnvironmentVariablesProvider } from '../../client/common/variables/types';
23-
import { IConfigurationService, IDisposable, InspectInterpreterSettingType } from '../../client/common/types';
23+
import {
24+
IConfigurationService,
25+
IDisposable,
26+
IDisposableRegistry,
27+
InspectInterpreterSettingType,
28+
} from '../../client/common/types';
2429
import { IServiceContainer } from '../../client/ioc/types';
2530
import { PythonRuntimeManager } from '../../client/positron/manager';
2631
import { IInterpreterService } from '../../client/interpreter/contracts';
@@ -38,6 +43,7 @@ suite('Python runtime manager', () => {
3843
let interpreterService: TypeMoq.IMock<IInterpreterService>;
3944
let serviceContainer: TypeMoq.IMock<IServiceContainer>;
4045
let workspaceConfig: TypeMoq.IMock<WorkspaceConfiguration>;
46+
let disposableRegistry: TypeMoq.IMock<IDisposableRegistry>;
4147

4248
let getConfigurationStub: sinon.SinonStub;
4349
let isVersionSupportedStub: sinon.SinonStub;
@@ -53,6 +59,7 @@ suite('Python runtime manager', () => {
5359
interpreterService = createTypeMoq<IInterpreterService>();
5460
serviceContainer = createTypeMoq<IServiceContainer>();
5561
workspaceConfig = createTypeMoq<WorkspaceConfiguration>();
62+
disposableRegistry = createTypeMoq<IDisposableRegistry>();
5663

5764
runtimeMetadata.setup((r) => r.runtimeId).returns(() => 'runtimeId');
5865
runtimeMetadata.setup((r) => r.extraRuntimeData).returns(() => ({ pythonPath }));
@@ -64,6 +71,7 @@ suite('Python runtime manager', () => {
6471
serviceContainer.setup((s) => s.get(IConfigurationService)).returns(() => configService.object);
6572
serviceContainer.setup((s) => s.get(IEnvironmentVariablesProvider)).returns(() => envVarsProvider.object);
6673
serviceContainer.setup((s) => s.get(IInterpreterService)).returns(() => interpreterService.object);
74+
serviceContainer.setup((s) => s.get(IDisposableRegistry)).returns(() => disposableRegistry.object);
6775

6876
getConfigurationStub = sinon.stub(workspaceApis, 'getConfiguration');
6977
getConfigurationStub.callsFake((section?: string, _scope?: any) => {
@@ -191,6 +199,7 @@ suite('Python runtime manager - recommendedWorkspaceRuntime', () => {
191199
let interpreterService: TypeMoq.IMock<IInterpreterService>;
192200
let interpreter: TypeMoq.IMock<PythonEnvironment>;
193201
let runtimeMetadata: positron.LanguageRuntimeMetadata;
202+
let disposableRegistry: TypeMoq.IMock<IDisposableRegistry>;
194203

195204
let getUserDefaultInterpreterStub: sinon.SinonStub;
196205
let hasFilesStub: sinon.SinonStub;
@@ -201,9 +210,11 @@ suite('Python runtime manager - recommendedWorkspaceRuntime', () => {
201210
serviceContainer = createTypeMoq<IServiceContainer>();
202211
interpreterService = createTypeMoq<IInterpreterService>();
203212
interpreter = createTypeMoq<PythonEnvironment>();
213+
disposableRegistry = createTypeMoq<IDisposableRegistry>();
204214

205215
// Setup interpreter service
206216
serviceContainer.setup((s) => s.get(IInterpreterService)).returns(() => interpreterService.object);
217+
serviceContainer.setup((s) => s.get(IDisposableRegistry)).returns(() => disposableRegistry.object);
207218

208219
getUserDefaultInterpreterStub = sinon.stub(interpreterSettings, 'getUserDefaultInterpreter');
209220
hasFilesStub = sinon.stub(util, 'hasFiles');

extensions/vscode-api-tests/src/singlefolder-tests/positron/runtime.test.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -299,14 +299,11 @@ suite('positron API - runtime', () => {
299299

300300
managerDisposable.dispose();
301301

302-
// TODO: Unregistering a manager unregisters its runtimes, but doesn't remove them from
303-
// the list returned by positron.runtime.getRegisteredRuntimes. Is that a bug?
304-
// It also means that this test will currently fail if run out of order.
305-
// await poll(
306-
// getRegisteredRuntimes,
307-
// (runtimes) => runtimes.length === 0,
308-
// 'test runtimes should be unregistered',
309-
// );
302+
await poll(
303+
getRegisteredRuntimes,
304+
(runtimes) => runtimes.length === 0,
305+
'test runtimes should be unregistered',
306+
);
310307
});
311308

312309
});
@@ -585,7 +582,7 @@ suite('positron API - executeCode', () => {
585582
positron.runtime.onDidExecuteCode((e) => {
586583
event = e;
587584
})
588-
)
585+
);
589586

590587
// Execute the code
591588
await positron.runtime.executeCode(

src/vs/workbench/api/common/positron/extHostLanguageRuntime.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,17 @@ export class ExtHostLanguageRuntime implements extHostProtocol.ExtHostLanguageRu
10731073
if (index >= 0) {
10741074
this._runtimeManagers.splice(index, 1);
10751075
}
1076+
1077+
// Remove its associated runtimes
1078+
this._runtimeManagersByRuntimeId.forEach((m, runtimeId) => {
1079+
if (m.manager === manager) {
1080+
this._runtimeManagersByRuntimeId.delete(runtimeId);
1081+
const index = this._registeredRuntimes.findIndex(runtime => runtime.runtimeId === runtimeId);
1082+
if (index >= 0) {
1083+
this._registeredRuntimes.splice(index, 1);
1084+
}
1085+
}
1086+
});
10761087
});
10771088
}
10781089

0 commit comments

Comments
 (0)