forked from DonJayamanne/pythonVSCode
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Check our saved jupyter interpreters before allowing any of them to be used as active interpreters #10113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
IanMatthewHuff
merged 5 commits into
microsoft:master
from
IanMatthewHuff:dev/ianhu/savedServerFail
Feb 14, 2020
Merged
Check our saved jupyter interpreters before allowing any of them to be used as active interpreters #10113
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
5ec1a99
initial idea
IanMatthewHuff 8bc139a
refactor to save check promise
IanMatthewHuff b25c387
revamp of initial loading
IanMatthewHuff f0eb2be
unit tests added
IanMatthewHuff 331e7f7
slight refactor for setting active jupyter interpreter
IanMatthewHuff File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ import { Event, EventEmitter } from 'vscode'; | |
import { CancellationToken } from 'vscode-jsonrpc'; | ||
import { createPromiseFromCancellation } from '../../../common/cancellation'; | ||
import '../../../common/extensions'; | ||
import { noop } from '../../../common/utils/misc'; | ||
import { IInterpreterService, PythonInterpreter } from '../../../interpreter/contracts'; | ||
import { sendTelemetryEvent } from '../../../telemetry'; | ||
import { Telemetry } from '../../constants'; | ||
|
@@ -22,8 +23,8 @@ import { JupyterInterpreterStateStore } from './jupyterInterpreterStateStore'; | |
@injectable() | ||
export class JupyterInterpreterService { | ||
private _selectedInterpreter?: PythonInterpreter; | ||
private _selectedInterpreterPath?: string; | ||
private _onDidChangeInterpreter = new EventEmitter<PythonInterpreter>(); | ||
private getInitialInterpreterPromise: Promise<PythonInterpreter | undefined> | undefined; | ||
public get onDidChangeInterpreter(): Event<PythonInterpreter> { | ||
return this._onDidChangeInterpreter.event; | ||
} | ||
|
@@ -45,50 +46,30 @@ export class JupyterInterpreterService { | |
* @memberof JupyterInterpreterService | ||
*/ | ||
public async getSelectedInterpreter(token?: CancellationToken): Promise<PythonInterpreter | undefined> { | ||
if (this._selectedInterpreter) { | ||
return this._selectedInterpreter; | ||
} | ||
// Before we return _selected interpreter make sure that we have run our initial set interpreter once | ||
// because _selectedInterpreter can be changed by other function and at other times, this promise | ||
// is cached to only run once | ||
await this.setInitialInterpreter(token); | ||
|
||
const resolveToUndefinedWhenCancelled = createPromiseFromCancellation({ | ||
cancelAction: 'resolve', | ||
defaultValue: undefined, | ||
token | ||
}); | ||
// For backwards compatiblity check if we have a cached interpreter (older version of extension). | ||
// If that interpreter has everything we need then use that. | ||
let interpreter = await Promise.race([ | ||
this.getInterpreterFromChangeOfOlderVersionOfExtension(), | ||
resolveToUndefinedWhenCancelled | ||
]); | ||
if (interpreter) { | ||
return interpreter; | ||
} | ||
return this._selectedInterpreter; | ||
} | ||
|
||
const pythonPath = this._selectedInterpreterPath || this.interpreterSelectionState.selectedPythonPath; | ||
if (!pythonPath) { | ||
// Check if current interpreter has all of the required dependencies. | ||
// If yes, then use that. | ||
interpreter = await this.interpreterService.getActiveInterpreter(undefined); | ||
if (!interpreter) { | ||
return; | ||
} | ||
// Use this interpreter going forward. | ||
if (await this.interpreterConfiguration.areDependenciesInstalled(interpreter)) { | ||
this.setAsSelectedInterpreter(interpreter); | ||
return interpreter; | ||
} | ||
return; | ||
// To be run one initial time. Check our saved locations and then current interpreter to try to start off | ||
// with a valid jupyter interpreter | ||
public async setInitialInterpreter(token?: CancellationToken): Promise<PythonInterpreter | undefined> { | ||
if (!this.getInitialInterpreterPromise) { | ||
this.getInitialInterpreterPromise = this.getInitialInterpreterImpl(token).then(result => { | ||
// Set ourselves as a valid interpreter if we found something | ||
if (result) { | ||
this.changeSelectedInterpreterProperty(result); | ||
} | ||
return result; | ||
}); | ||
} | ||
|
||
const interpreterDetails = await Promise.race([ | ||
this.interpreterService.getInterpreterDetails(pythonPath, undefined), | ||
resolveToUndefinedWhenCancelled | ||
]); | ||
if (interpreterDetails) { | ||
this._selectedInterpreter = interpreterDetails; | ||
} | ||
return interpreterDetails; | ||
return this.getInitialInterpreterPromise; | ||
} | ||
|
||
/** | ||
* Selects and interpreter to run jupyter server. | ||
* Validates and configures the interpreter. | ||
|
@@ -116,7 +97,7 @@ export class JupyterInterpreterService { | |
const result = await this.interpreterConfiguration.installMissingDependencies(interpreter, undefined, token); | ||
switch (result) { | ||
case JupyterInterpreterDependencyResponse.ok: { | ||
this.setAsSelectedInterpreter(interpreter); | ||
await this.setAsSelectedInterpreter(interpreter); | ||
return interpreter; | ||
} | ||
case JupyterInterpreterDependencyResponse.cancel: | ||
|
@@ -126,30 +107,97 @@ export class JupyterInterpreterService { | |
return this.selectInterpreter(token); | ||
} | ||
} | ||
private async getInterpreterFromChangeOfOlderVersionOfExtension(): Promise<PythonInterpreter | undefined> { | ||
|
||
// Check the location that we stored jupyter launch path in the old version | ||
// if it's there, return it and clear the location | ||
private getInterpreterFromChangeOfOlderVersionOfExtension(): string | undefined { | ||
const pythonPath = this.oldVersionCacheStateStore.getCachedInterpreterPath(); | ||
if (!pythonPath) { | ||
return; | ||
} | ||
|
||
// Clear the cache to not check again | ||
this.oldVersionCacheStateStore.clearCache().ignoreErrors(); | ||
return pythonPath; | ||
} | ||
|
||
// Set the specified interpreter as our current selected interpreter | ||
private async setAsSelectedInterpreter(interpreter: PythonInterpreter): Promise<void> { | ||
// Make sure that our initial set has happened before we allow a set so that | ||
// calculation of the initial interpreter doesn't clobber the existing one | ||
await this.setInitialInterpreter(); | ||
this.changeSelectedInterpreterProperty(interpreter); | ||
} | ||
|
||
private changeSelectedInterpreterProperty(interpreter: PythonInterpreter) { | ||
this._selectedInterpreter = interpreter; | ||
this._onDidChangeInterpreter.fire(interpreter); | ||
this.interpreterSelectionState.updateSelectedPythonPath(interpreter.path); | ||
sendTelemetryEvent(Telemetry.SelectJupyterInterpreter, undefined, { result: 'selected' }); | ||
} | ||
|
||
// For a given python path check if it can run jupyter for us | ||
// if so, return the interpreter | ||
private async validateInterpreterPath( | ||
pythonPath: string, | ||
token?: CancellationToken | ||
): Promise<PythonInterpreter | undefined> { | ||
try { | ||
const interpreter = await this.interpreterService.getInterpreterDetails(pythonPath, undefined); | ||
const resolveToUndefinedWhenCancelled = createPromiseFromCancellation({ | ||
cancelAction: 'resolve', | ||
defaultValue: undefined, | ||
token | ||
}); | ||
|
||
// First see if we can get interpreter details | ||
const interpreter = await Promise.race([ | ||
this.interpreterService.getInterpreterDetails(pythonPath, undefined), | ||
resolveToUndefinedWhenCancelled | ||
]); | ||
if (interpreter) { | ||
// Then check that dependencies are installed | ||
if (await this.interpreterConfiguration.areDependenciesInstalled(interpreter, token)) { | ||
return interpreter; | ||
} | ||
} | ||
} catch (_err) { | ||
// For any errors we are ok with just returning undefined for an invalid interpreter | ||
noop(); | ||
} | ||
return undefined; | ||
} | ||
|
||
private async getInitialInterpreterImpl(token?: CancellationToken): Promise<PythonInterpreter | undefined> { | ||
let interpreter: PythonInterpreter | undefined; | ||
|
||
// Check the old version location first, we will clear it if we find it here | ||
const oldVersionPythonPath = this.getInterpreterFromChangeOfOlderVersionOfExtension(); | ||
if (oldVersionPythonPath) { | ||
interpreter = await this.validateInterpreterPath(oldVersionPythonPath, token); | ||
} | ||
|
||
// Next check the saved global path | ||
if (!interpreter && this.interpreterSelectionState.selectedPythonPath) { | ||
interpreter = await this.validateInterpreterPath(this.interpreterSelectionState.selectedPythonPath, token); | ||
|
||
// If we had a global path, but it's not valid, trash it | ||
if (!interpreter) { | ||
return; | ||
this.interpreterSelectionState.updateSelectedPythonPath(undefined); | ||
} | ||
if (await this.interpreterConfiguration.areDependenciesInstalled(interpreter)) { | ||
this.setAsSelectedInterpreter(interpreter); | ||
return interpreter; | ||
} | ||
|
||
// Nothing saved found, so check our current interpreter | ||
if (!interpreter) { | ||
const currentInterpreter = await this.interpreterService.getActiveInterpreter(undefined); | ||
|
||
if (currentInterpreter) { | ||
// Ask and give a chance to install dependencies in current interpreter | ||
if (await this.interpreterConfiguration.areDependenciesInstalled(currentInterpreter, token)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should you use validate here instead of checking dependencies? Or is that overkill because you already have th einterpreter? |
||
interpreter = currentInterpreter; | ||
} | ||
} | ||
// If dependencies are not installed, then ignore it. lets continue with the current logic. | ||
} finally { | ||
// Don't perform this check again, just clear the cache. | ||
this.oldVersionCacheStateStore.clearCache().ignoreErrors(); | ||
} | ||
} | ||
private setAsSelectedInterpreter(interpreter: PythonInterpreter): void { | ||
this._selectedInterpreter = interpreter; | ||
this._onDidChangeInterpreter.fire(interpreter); | ||
this.interpreterSelectionState.updateSelectedPythonPath((this._selectedInterpreterPath = interpreter.path)); | ||
sendTelemetryEvent(Telemetry.SelectJupyterInterpreter, undefined, { result: 'selected' }); | ||
|
||
return interpreter; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I liked the promise at this level better. getSelectedInterpreter is basically a property here, having to swap out the promise results if you set the interpreter later feels like a weird pattern. Instead, just promise wrap the unit of work that we expect to only perform once per extension activation which will always have the same result, and make sure that it happens before we return the property for the first time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you just return the value of the promise?
In reply to: 379544662 [](ancestors = 379544662)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or is that not true because the promise may finish and then later something else updates the value?
In reply to: 379547528 [](ancestors = 379547528,379544662)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That feels kinda confusing. Maybe a comment explaining how the two are separate. The initial promise must always be run, but the actual selection can be updated at any time.
In reply to: 379548204 [](ancestors = 379548204,379547528,379544662)