提交 eaf3e0fd 编写于 作者: B Benjamin Pasero

storage - recover from backup upon exit when we detected a corrupt DB during runtime

上级 64585dd5
......@@ -6,7 +6,7 @@
import { Database, Statement } from 'vscode-sqlite3';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { ThrottledDelayer, timeout } from 'vs/base/common/async';
import { ThrottledDelayer, timeout, always } from 'vs/base/common/async';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { mapToString, setToString } from 'vs/base/common/map';
import { basename } from 'path';
......@@ -411,26 +411,38 @@ export class SQLiteStorageDatabase implements IStorageDatabase {
return this.whenOpened.then(result => {
return new Promise((resolve, reject) => {
result.db.close(error => {
if (error) {
this.handleSQLiteError(error, `[storage ${this.name}] close(): ${error}`);
result.db.close(closeError => {
if (closeError) {
this.handleSQLiteError(closeError, `[storage ${this.name}] close(): ${closeError}`);
}
return reject(error);
if (result.path === SQLiteStorageDatabase.IN_MEMORY_PATH) {
return resolve(); // return early for in-memory DBs
}
if (this.isCorrupt) {
// If the DB is corrupt, make sure to rename the file so that we can start
// from a fresh DB or a previous backup on the next startup and not be stuck
// with a corrupt DB for ever.
this.logger.error(`[storage ${this.name}] close(): removing corrupt DB and trying to restore backup`);
return always(rename(result.path, this.toCorruptPath(result.path))
.then(() => rename(this.toBackupPath(result.path), result.path)), () => closeError ? reject(closeError) : resolve());
}
if (closeError) {
return reject(closeError);
}
// If the DB closed successfully and we are not running in-memory
// and the DB did not get corrupted during runtime, make a backup
// of the DB so that we can use it as fallback in case the actual
// DB becomes corrupt.
if (result.path !== SQLiteStorageDatabase.IN_MEMORY_PATH && !this.isCorrupt) {
return this.backup(result).then(resolve, error => {
this.logger.error(`[storage ${this.name}] backup(): ${error}`);
return this.backup(result).then(resolve, error => {
this.logger.error(`[storage ${this.name}] backup(): ${error}`);
return resolve(); // ignore failing backup
});
}
return resolve();
return resolve(); // ignore failing backup
});
});
});
});
......@@ -512,6 +524,14 @@ export class SQLiteStorageDatabase implements IStorageDatabase {
.then(() => this.doOpen(path));
}
private handleSQLiteError(error: Error & { code?: string }, msg: string): void {
if (error.code === 'SQLITE_CORRUPT' || error.code === 'SQLITE_NOTADB') {
this.isCorrupt = true;
}
this.logger.error(msg);
}
private toCorruptPath(path: string): string {
const randomSuffix = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 4);
......@@ -652,14 +672,6 @@ export class SQLiteStorageDatabase implements IStorageDatabase {
stmt.removeListener('error', statementErrorListener);
});
}
private handleSQLiteError(error: Error & { code?: string }, msg: string): void {
if (error.code === 'SQLITE_CORRUPT' || error.code === 'SQLITE_NOTADB') {
this.isCorrupt = true;
}
this.logger.error(msg);
}
}
class SQLiteStorageDatabaseLogger {
......
......@@ -8,7 +8,7 @@ import { generateUuid } from 'vs/base/common/uuid';
import { join } from 'path';
import { tmpdir } from 'os';
import { equal, ok } from 'assert';
import { mkdirp, del, writeFile } from 'vs/base/node/pfs';
import { mkdirp, del, writeFile, exists } from 'vs/base/node/pfs';
import { timeout } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
import { isWindows } from 'vs/base/common/platform';
......@@ -427,7 +427,7 @@ suite('SQLite Storage Library', () => {
await del(storageDir, tmpdir());
});
test('basics (corrupt DB does not backup)', async () => {
test('basics (DB that becomes corrupt during runtime restores backup on close())', async () => {
if (isWindows) {
await Promise.resolve(); // Windows will fail to write to open DB due to locking
......@@ -449,6 +449,8 @@ suite('SQLite Storage Library', () => {
await storage.updateItems({ insert: items });
await storage.close();
equal(await exists(`${storagePath}.backup`), true);
storage = new SQLiteStorageDatabase(storagePath);
await storage.getItems();
......@@ -462,6 +464,8 @@ suite('SQLite Storage Library', () => {
await storage.close();
equal(await exists(`${storagePath}.backup`), false);
storage = new SQLiteStorageDatabase(storagePath);
const storedItems = await storage.getItems();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册