mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-15 12:51:00 +01:00
file working copy - prolong shutdown for pending saves
This commit is contained in:
@@ -103,7 +103,7 @@ export class BackupRestorer implements IWorkbenchContribution {
|
||||
|
||||
private async resolveEditor(resource: URI, index: number, hasOpenedEditors: boolean): Promise<IResourceEditorInput | IUntitledTextResourceEditorInput | IEditorInputWithOptions> {
|
||||
|
||||
// Set editor as `inatice` if we have other editors
|
||||
// Set editor as `inactive` if we have other editors
|
||||
const options = { pinned: true, preserveFocus: true, inactive: index > 0 || hasOpenedEditors };
|
||||
|
||||
// This is a (weak) strategy to find out if the untitled input had
|
||||
|
||||
@@ -26,7 +26,7 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura
|
||||
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { HotExitConfiguration } from 'vs/platform/files/common/files';
|
||||
import { ShutdownReason, ILifecycleService, BeforeShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { ShutdownReason, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { IFileDialogService, ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
@@ -35,7 +35,7 @@ import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { createEditorPart, registerTestFileEditor, TestFilesConfigurationService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { createEditorPart, registerTestFileEditor, TestBeforeShutdownEvent, TestFilesConfigurationService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
@@ -76,16 +76,6 @@ flakySuite('BackupTracker (native)', function () {
|
||||
}
|
||||
}
|
||||
|
||||
class BeforeShutdownEventImpl implements BeforeShutdownEvent {
|
||||
|
||||
value: boolean | Promise<boolean> | undefined;
|
||||
reason = ShutdownReason.CLOSE;
|
||||
|
||||
veto(value: boolean | Promise<boolean>): void {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
let testDir: string;
|
||||
let backupHome: string;
|
||||
let workspaceBackupPath: string;
|
||||
@@ -192,8 +182,8 @@ flakySuite('BackupTracker (native)', function () {
|
||||
const resource = toResource.call(this, '/path/index.txt');
|
||||
await accessor.editorService.openEditor({ resource, options: { pinned: true } });
|
||||
|
||||
const event = new BeforeShutdownEventImpl();
|
||||
accessor.lifecycleService.fireWillShutdown(event);
|
||||
const event = new TestBeforeShutdownEvent();
|
||||
accessor.lifecycleService.fireBeforeShutdown(event);
|
||||
|
||||
const veto = await event.value;
|
||||
assert.ok(!veto);
|
||||
@@ -216,8 +206,8 @@ flakySuite('BackupTracker (native)', function () {
|
||||
model?.textEditorModel?.setValue('foo');
|
||||
assert.strictEqual(accessor.workingCopyService.dirtyCount, 1);
|
||||
|
||||
const event = new BeforeShutdownEventImpl();
|
||||
accessor.lifecycleService.fireWillShutdown(event);
|
||||
const event = new TestBeforeShutdownEvent();
|
||||
accessor.lifecycleService.fireBeforeShutdown(event);
|
||||
|
||||
const veto = await event.value;
|
||||
assert.ok(veto);
|
||||
@@ -237,8 +227,8 @@ flakySuite('BackupTracker (native)', function () {
|
||||
model?.textEditorModel?.setValue('foo');
|
||||
assert.strictEqual(accessor.workingCopyService.dirtyCount, 1);
|
||||
|
||||
const event = new BeforeShutdownEventImpl();
|
||||
accessor.lifecycleService.fireWillShutdown(event);
|
||||
const event = new TestBeforeShutdownEvent();
|
||||
accessor.lifecycleService.fireBeforeShutdown(event);
|
||||
|
||||
const veto = await event.value;
|
||||
assert.ok(!veto);
|
||||
@@ -262,8 +252,8 @@ flakySuite('BackupTracker (native)', function () {
|
||||
await model?.resolve();
|
||||
model?.textEditorModel?.setValue('foo');
|
||||
assert.strictEqual(accessor.workingCopyService.dirtyCount, 1);
|
||||
const event = new BeforeShutdownEventImpl();
|
||||
accessor.lifecycleService.fireWillShutdown(event);
|
||||
const event = new TestBeforeShutdownEvent();
|
||||
accessor.lifecycleService.fireBeforeShutdown(event);
|
||||
|
||||
const veto = await event.value;
|
||||
assert.ok(!veto);
|
||||
@@ -286,8 +276,8 @@ flakySuite('BackupTracker (native)', function () {
|
||||
await model?.resolve();
|
||||
model?.textEditorModel?.setValue('foo');
|
||||
assert.strictEqual(accessor.workingCopyService.dirtyCount, 1);
|
||||
const event = new BeforeShutdownEventImpl();
|
||||
accessor.lifecycleService.fireWillShutdown(event);
|
||||
const event = new TestBeforeShutdownEvent();
|
||||
accessor.lifecycleService.fireBeforeShutdown(event);
|
||||
|
||||
const veto = await event.value;
|
||||
assert.ok(!veto);
|
||||
@@ -427,9 +417,9 @@ flakySuite('BackupTracker (native)', function () {
|
||||
model?.textEditorModel?.setValue('foo');
|
||||
assert.strictEqual(accessor.workingCopyService.dirtyCount, 1);
|
||||
|
||||
const event = new BeforeShutdownEventImpl();
|
||||
const event = new TestBeforeShutdownEvent();
|
||||
event.reason = shutdownReason;
|
||||
accessor.lifecycleService.fireWillShutdown(event);
|
||||
accessor.lifecycleService.fireBeforeShutdown(event);
|
||||
|
||||
const veto = await event.value;
|
||||
assert.strictEqual(accessor.backupFileService.discardedBackups.length, 0); // When hot exit is set, backups should never be cleaned since the confirm result is cancel
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { FileWorkingCopy, IFileWorkingCopy, IFileWorkingCopyModel, IFileWorkingCopyModelFactory, IFileWorkingCopySaveOptions } from 'vs/workbench/services/workingCopy/common/fileWorkingCopy';
|
||||
import { FileWorkingCopy, FileWorkingCopyState, IFileWorkingCopy, IFileWorkingCopyModel, IFileWorkingCopyModelFactory, IFileWorkingCopySaveOptions } from 'vs/workbench/services/workingCopy/common/fileWorkingCopy';
|
||||
import { SaveReason } from 'vs/workbench/common/editor';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
import { Promises, ResourceQueue } from 'vs/base/common/async';
|
||||
@@ -226,9 +226,21 @@ export class FileWorkingCopyManager<T extends IFileWorkingCopyModel> extends Dis
|
||||
this._register(this.workingCopyFileService.onDidRunWorkingCopyFileOperation(e => this.onDidRunWorkingCopyFileOperation(e)));
|
||||
|
||||
// Lifecycle
|
||||
this.lifecycleService.onWillShutdown(event => event.join(this.onWillShutdown(), 'join.fileWorkingCopyManager'));
|
||||
this.lifecycleService.onDidShutdown(() => this.dispose());
|
||||
}
|
||||
|
||||
private async onWillShutdown(): Promise<void> {
|
||||
let fileWorkingCopies: IFileWorkingCopy<T>[];
|
||||
|
||||
// As long as file working copies are pending to be saved, we prolong the shutdown
|
||||
// until that has happened to ensure we are not shutting down in the middle of
|
||||
// writing to the working copy (https://github.com/microsoft/vscode/issues/116600).
|
||||
while ((fileWorkingCopies = this.workingCopies.filter(workingCopy => workingCopy.hasState(FileWorkingCopyState.PENDING_SAVE))).length > 0) {
|
||||
await Promises.settled(fileWorkingCopies.map(workingCopy => workingCopy.joinState(FileWorkingCopyState.PENDING_SAVE)));
|
||||
}
|
||||
}
|
||||
|
||||
//#region Resolve from file changes
|
||||
|
||||
private onDidFilesChange(e: FileChangesEvent): void {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as assert from 'assert';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { workbenchInstantiationService, TestServiceAccessor, TestWillShutdownEvent } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||
import { FileWorkingCopyManager, IFileWorkingCopyManager } from 'vs/workbench/services/workingCopy/common/fileWorkingCopyManager';
|
||||
import { IFileWorkingCopy, IFileWorkingCopyModel } from 'vs/workbench/services/workingCopy/common/fileWorkingCopy';
|
||||
import { bufferToStream, VSBuffer } from 'vs/base/common/buffer';
|
||||
@@ -444,4 +444,34 @@ suite('FileWorkingCopyManager', () => {
|
||||
let canDispose2 = manager.canDispose(workingCopy);
|
||||
assert.strictEqual(canDispose2, true);
|
||||
});
|
||||
|
||||
test('pending saves join on shutdown', async () => {
|
||||
const resource1 = URI.file('/path/index_something1.txt');
|
||||
const resource2 = URI.file('/path/index_something2.txt');
|
||||
|
||||
const workingCopy1 = await manager.resolve(resource1);
|
||||
workingCopy1.model?.updateContents('make dirty');
|
||||
|
||||
const workingCopy2 = await manager.resolve(resource2);
|
||||
workingCopy2.model?.updateContents('make dirty');
|
||||
|
||||
let saved1 = false;
|
||||
workingCopy1.save().then(() => {
|
||||
saved1 = true;
|
||||
});
|
||||
|
||||
let saved2 = false;
|
||||
workingCopy2.save().then(() => {
|
||||
saved2 = true;
|
||||
});
|
||||
|
||||
const event = new TestWillShutdownEvent();
|
||||
accessor.lifecycleService.fireWillShutdown(event);
|
||||
|
||||
assert.ok(event.value.length > 0);
|
||||
await Promise.all(event.value);
|
||||
|
||||
assert.strictEqual(saved1, true);
|
||||
assert.strictEqual(saved2, true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1057,13 +1057,35 @@ export class TestLifecycleService implements ILifecycleService {
|
||||
});
|
||||
}
|
||||
|
||||
fireWillShutdown(event: BeforeShutdownEvent): void { this._onBeforeShutdown.fire(event); }
|
||||
fireBeforeShutdown(event: BeforeShutdownEvent): void { this._onBeforeShutdown.fire(event); }
|
||||
|
||||
fireWillShutdown(event: WillShutdownEvent): void { this._onWillShutdown.fire(event); }
|
||||
|
||||
shutdown(): void {
|
||||
this.fireShutdown();
|
||||
}
|
||||
}
|
||||
|
||||
export class TestBeforeShutdownEvent implements BeforeShutdownEvent {
|
||||
|
||||
value: boolean | Promise<boolean> | undefined;
|
||||
reason = ShutdownReason.CLOSE;
|
||||
|
||||
veto(value: boolean | Promise<boolean>): void {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
export class TestWillShutdownEvent implements WillShutdownEvent {
|
||||
|
||||
value: Promise<void>[] = [];
|
||||
reason = ShutdownReason.CLOSE;
|
||||
|
||||
join(promise: Promise<void>, id: string): void {
|
||||
this.value.push(promise);
|
||||
}
|
||||
}
|
||||
|
||||
export class TestTextResourceConfigurationService implements ITextResourceConfigurationService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
Reference in New Issue
Block a user