fix(webcontainer): prevent double initialization and race conditions when booting WebContainer and loading editor workspace/file tree

This commit is contained in:
2025-12-30 15:47:15 +00:00
parent 26759a5b90
commit c27b532aaa
6 changed files with 63 additions and 9 deletions

View File

@@ -6,7 +6,10 @@ import type { IExecutionEnvironment, IFileEntry, IProcessHandle } from '../inter
* Runs Node.js and shell commands in the browser using WebContainer API.
*/
export class WebContainerEnvironment implements IExecutionEnvironment {
private container: webcontainer.WebContainer | null = null;
// Static shared state - WebContainer only allows ONE boot per page
private static sharedContainer: webcontainer.WebContainer | null = null;
private static bootPromise: Promise<webcontainer.WebContainer> | null = null;
private _ready: boolean = false;
public readonly type = 'webcontainer' as const;
@@ -15,11 +18,29 @@ export class WebContainerEnvironment implements IExecutionEnvironment {
return this._ready;
}
private get container(): webcontainer.WebContainer | null {
return WebContainerEnvironment.sharedContainer;
}
// ============ Lifecycle ============
public async init(): Promise<void> {
if (this._ready && this.container) {
return; // Already initialized
// Already initialized (this instance)
if (this._ready && WebContainerEnvironment.sharedContainer) {
return;
}
// If boot is in progress (by any instance), wait for it
if (WebContainerEnvironment.bootPromise) {
await WebContainerEnvironment.bootPromise;
this._ready = true;
return;
}
// If already booted by another instance, just mark ready
if (WebContainerEnvironment.sharedContainer) {
this._ready = true;
return;
}
// Check if SharedArrayBuffer is available (required for WebContainer)
@@ -32,14 +53,24 @@ export class WebContainerEnvironment implements IExecutionEnvironment {
);
}
this.container = await webcontainer.WebContainer.boot();
this._ready = true;
// Start boot process
WebContainerEnvironment.bootPromise = webcontainer.WebContainer.boot();
try {
WebContainerEnvironment.sharedContainer = await WebContainerEnvironment.bootPromise;
this._ready = true;
} catch (error) {
// Reset promise on failure so retry is possible
WebContainerEnvironment.bootPromise = null;
throw error;
}
}
public async destroy(): Promise<void> {
if (this.container) {
this.container.teardown();
this.container = null;
if (WebContainerEnvironment.sharedContainer) {
WebContainerEnvironment.sharedContainer.teardown();
WebContainerEnvironment.sharedContainer = null;
WebContainerEnvironment.bootPromise = null;
this._ready = false;
}
}