backups - ensure to discard all on shutdown without dirty (#92962)

This commit is contained in:
Benjamin Pasero 2020-03-27 17:20:12 +01:00
parent 92b9e7f53e
commit 52fe5f21f5
5 changed files with 70 additions and 2 deletions

View file

@ -47,7 +47,8 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont
return this.onBeforeShutdownWithDirty(reason, dirtyWorkingCopies);
}
return false; // no veto (no dirty working copies)
// No dirty working copies
return this.onBeforeShutdownWithoutDirty();
}
protected async onBeforeShutdownWithDirty(reason: ShutdownReason, workingCopies: IWorkingCopy[]): Promise<boolean> {
@ -253,4 +254,21 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont
return Promise.all(backupsToDiscard.map(workingCopy => this.backupFileService.discardBackup(workingCopy.resource))).then(() => false, () => false);
}
private async onBeforeShutdownWithoutDirty(): Promise<boolean> {
// If we have proceeded enough that editors and dirty state
// has restored, we make sure that no backups lure around
// given we have no known dirty working copy. This helps
// to clean up stale backups as for example reported in
// https://github.com/microsoft/vscode/issues/92962
if (this.lifecycleService.phase >= LifecyclePhase.Restored) {
try {
await this.backupFileService.discardBackups();
} catch (error) {
this.logService.error(`[backup tracker] error discarding backups: ${error}`);
}
}
return false;
}
}

View file

@ -63,9 +63,14 @@ export interface IBackupFileService {
backup<T extends object>(resource: URI, content?: ITextSnapshot, versionId?: number, meta?: T): Promise<void>;
/**
* Discards the backup associated with a resource if it exists..
* Discards the backup associated with a resource if it exists.
*
* @param resource The resource whose backup is being discarded discard to back up.
*/
discardBackup(resource: URI): Promise<void>;
/**
* Discards all backups.
*/
discardBackups(): Promise<void>;
}

View file

@ -165,6 +165,10 @@ export class BackupFileService implements IBackupFileService {
return this.impl.discardBackup(resource);
}
discardBackups(): Promise<void> {
return this.impl.discardBackups();
}
getBackups(): Promise<URI[]> {
return this.impl.getBackups();
}
@ -260,6 +264,14 @@ class BackupFileServiceImpl extends Disposable implements IBackupFileService {
});
}
async discardBackups(): Promise<void> {
const model = await this.ready;
await this.deleteIgnoreFileNotFound(this.backupWorkspacePath);
model.clear();
}
discardBackup(resource: URI): Promise<void> {
const backupResource = this.toBackupResource(resource);
@ -429,6 +441,10 @@ export class InMemoryBackupFileService implements IBackupFileService {
this.backups.delete(this.toBackupResource(resource).toString());
}
async discardBackups(): Promise<void> {
this.backups.clear();
}
toBackupResource(resource: URI): URI {
return URI.file(join(resource.scheme, this.hashPath(resource)));
}

View file

@ -43,6 +43,7 @@ const barFile = URI.file(platform.isWindows ? 'c:\\Bar' : '/Bar');
const fooBarFile = URI.file(platform.isWindows ? 'c:\\Foo Bar' : '/Foo Bar');
const untitledFile = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' });
const fooBackupPath = path.join(workspaceBackupPath, 'file', hashPath(fooFile));
const barBackupPath = path.join(workspaceBackupPath, 'file', hashPath(barFile));
const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(untitledFile));
class TestBackupEnvironmentService extends NativeWorkbenchEnvironmentService {
@ -285,6 +286,33 @@ suite('BackupFileService', () => {
});
});
suite('discardBackups', () => {
test('text file', async () => {
await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
await service.backup(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 2);
await service.discardBackups();
assert.equal(fs.existsSync(fooBackupPath), false);
assert.equal(fs.existsSync(barBackupPath), false);
assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'file')), false);
});
test('untitled file', async () => {
await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
await service.discardBackups();
assert.equal(fs.existsSync(untitledBackupPath), false);
assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'untitled')), false);
});
test('can backup after discarding all', async () => {
await service.discardBackups();
await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));
assert.equal(fs.existsSync(workspaceBackupPath), true);
});
});
suite('getBackups', () => {
test('("file") - text file', async () => {
await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false));

View file

@ -811,6 +811,7 @@ export class TestBackupFileService implements IBackupFileService {
getBackups(): Promise<URI[]> { return Promise.resolve([]); }
resolve<T extends object>(_backup: URI): Promise<IResolvedBackup<T> | undefined> { return Promise.resolve(undefined); }
discardBackup(_resource: URI): Promise<void> { return Promise.resolve(); }
discardBackups(): Promise<void> { return Promise.resolve(); }
parseBackupContent(textBufferFactory: ITextBufferFactory): string {
const textBuffer = textBufferFactory.create(DefaultEndOfLine.LF);
const lineCount = textBuffer.getLineCount();