fix: resolve Python environment race condition (#142)
Implemented promise queue pattern in PythonEnvManager to handle concurrent initialization requests. Previously, multiple simultaneous requests (e.g., startup + merge) would fail with "Already initializing" error. Also fixed parsePythonCommand() to handle file paths with spaces by checking file existence before splitting on whitespace. Changes: - Added initializationPromise field to queue concurrent requests - Split initialize() into public and private _doInitialize() - Enhanced parsePythonCommand() with existsSync() check Co-authored-by: Joris Slagter <mail@jorisslagter.nl>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { execSync } from 'child_process';
|
||||
import { existsSync } from 'fs';
|
||||
|
||||
/**
|
||||
* Detect and return the best available Python command.
|
||||
@@ -48,12 +49,18 @@ export function getDefaultPythonCommand(): string {
|
||||
|
||||
/**
|
||||
* Parse a Python command string into command and base arguments.
|
||||
* Handles space-separated commands like "py -3".
|
||||
* Handles space-separated commands like "py -3" and file paths with spaces.
|
||||
*
|
||||
* @param pythonPath - The Python command string (e.g., "python3", "py -3")
|
||||
* @param pythonPath - The Python command string (e.g., "python3", "py -3", "/path/with spaces/python")
|
||||
* @returns Tuple of [command, baseArgs] ready for use with spawn()
|
||||
*/
|
||||
export function parsePythonCommand(pythonPath: string): [string, string[]] {
|
||||
// If the path points to an actual file, use it directly (handles paths with spaces)
|
||||
if (existsSync(pythonPath)) {
|
||||
return [pythonPath, []];
|
||||
}
|
||||
|
||||
// Otherwise, split on spaces for commands like "py -3"
|
||||
const parts = pythonPath.split(' ');
|
||||
const command = parts[0];
|
||||
const baseArgs = parts.slice(1);
|
||||
|
||||
@@ -24,6 +24,7 @@ export class PythonEnvManager extends EventEmitter {
|
||||
private pythonPath: string | null = null;
|
||||
private isInitializing = false;
|
||||
private isReady = false;
|
||||
private initializationPromise: Promise<PythonEnvStatus> | null = null;
|
||||
|
||||
/**
|
||||
* Get the path where the venv should be created.
|
||||
@@ -309,18 +310,42 @@ export class PythonEnvManager extends EventEmitter {
|
||||
/**
|
||||
* Initialize the Python environment.
|
||||
* Creates venv and installs deps if needed.
|
||||
*
|
||||
* If initialization is already in progress, this will wait for and return
|
||||
* the existing initialization promise instead of starting a new one.
|
||||
*/
|
||||
async initialize(autoBuildSourcePath: string): Promise<PythonEnvStatus> {
|
||||
if (this.isInitializing) {
|
||||
// If there's already an initialization in progress, wait for it
|
||||
if (this.initializationPromise) {
|
||||
console.warn('[PythonEnvManager] Initialization already in progress, waiting...');
|
||||
return this.initializationPromise;
|
||||
}
|
||||
|
||||
// If already ready and pointing to the same source, return cached status
|
||||
if (this.isReady && this.autoBuildSourcePath === autoBuildSourcePath) {
|
||||
return {
|
||||
ready: false,
|
||||
pythonPath: null,
|
||||
venvExists: false,
|
||||
depsInstalled: false,
|
||||
error: 'Already initializing'
|
||||
ready: true,
|
||||
pythonPath: this.pythonPath,
|
||||
venvExists: true,
|
||||
depsInstalled: true
|
||||
};
|
||||
}
|
||||
|
||||
// Start new initialization and store the promise
|
||||
this.initializationPromise = this._doInitialize(autoBuildSourcePath);
|
||||
|
||||
try {
|
||||
return await this.initializationPromise;
|
||||
} finally {
|
||||
this.initializationPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal initialization method that performs the actual setup.
|
||||
* This is separated from initialize() to support the promise queue pattern.
|
||||
*/
|
||||
private async _doInitialize(autoBuildSourcePath: string): Promise<PythonEnvStatus> {
|
||||
this.isInitializing = true;
|
||||
this.autoBuildSourcePath = autoBuildSourcePath;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user