diff --git a/src/main/wsl/wsl-host-service.test.ts b/src/main/wsl/wsl-host-service.test.ts index 7df9c2b9e6c..c8ca64eb150 100644 --- a/src/main/wsl/wsl-host-service.test.ts +++ b/src/main/wsl/wsl-host-service.test.ts @@ -352,6 +352,26 @@ describe('WslHostService', () => { expect(listEnvironments(userDataPath)).toEqual([]) }) + it('keeps a host whose removal failed usable and removable again', async () => { + const connector = makeConnector([UBUNTU]) + const service = makeService(connector) + await service.add({ distro: 'Ubuntu' }) + service.setVisibleWork(['wsl:ubuntu']) + connector.releaseHolder.mockRejectedValueOnce( + Object.assign(new Error('EBUSY: resource busy or locked'), { code: 'EBUSY' }) + ) + + await expect(service.remove('wsl:ubuntu')).rejects.toThrow('EBUSY') + + expect(listEnvironments(userDataPath)).toHaveLength(1) + expect(removed).not.toHaveBeenCalled() + await expect(service.ensureForRuntimeCall('wsl:ubuntu')).resolves.toBeUndefined() + await expect(service.connect('wsl:ubuntu')).resolves.toBeUndefined() + await expect(service.remove('wsl:ubuntu')).resolves.toEqual({ stopped: true }) + expect(listEnvironments(userDataPath)).toEqual([]) + expect(removed).toHaveBeenCalledWith('wsl:ubuntu') + }) + it('turning the experiment off closes every connection and releases holders; on resumes lazily', async () => { const connector = makeConnector([UBUNTU]) const service = makeService(connector) diff --git a/src/main/wsl/wsl-host-service.ts b/src/main/wsl/wsl-host-service.ts index 49832a52f9d..b2a3625275f 100644 --- a/src/main/wsl/wsl-host-service.ts +++ b/src/main/wsl/wsl-host-service.ts @@ -139,19 +139,23 @@ export class WslHostService { } // First: nothing may reconnect it while it is being stopped (open work, liveness, calls). this.removing.add(environmentId) - this.visible.delete(environmentId) const connector = this.connector let serverStop: WslHostServerStop = { stopped: true } - if (connector) { - serverStop = await connector - .stopServer(record.distro, environmentId) - .catch((error: unknown) => ({ stopped: false, reason: errorMessage(error) })) - await connector.disconnect(record.distro) - // Without its holder the distro idles out. - await connector.releaseHolder(record.distro) + try { + if (connector) { + serverStop = await connector + .stopServer(record.distro, environmentId) + .catch((error: unknown) => ({ stopped: false, reason: errorMessage(error) })) + await connector.disconnect(record.distro) + // Without its holder the distro idles out. + await connector.releaseHolder(record.distro) + } + removeWslHostEnvironment(this.deps.userDataPath(), environmentId) + this.visible.delete(environmentId) + } finally { + // Why: a failed removal (e.g. EBUSY on Windows) must leave the host usable and removable. + this.removing.delete(environmentId) } - removeWslHostEnvironment(this.deps.userDataPath(), environmentId) - this.removing.delete(environmentId) this.statuses.delete(environmentId) await this.deps.removed(environmentId) return serverStop @@ -236,7 +240,8 @@ export class WslHostService { hasUserVisibleWork(distro: string): boolean { const record = this.recordForDistro(distro) // Why the gate: liveness relaunches only for visible work, and none may run while off. - return record ? this.canRun() && this.visible.has(record.environmentId) : false + const id = record?.environmentId + return id ? this.canRun() && this.visible.has(id) && !this.removing.has(id) : false } /** Startup: keep the holders a previous run left, without booting anything. */