diff --git a/src/vs/platform/storage/common/storageIpc.ts b/src/vs/platform/storage/common/storageIpc.ts index 105f15b1c3a..5669ec1f22f 100644 --- a/src/vs/platform/storage/common/storageIpc.ts +++ b/src/vs/platform/storage/common/storageIpc.ts @@ -7,17 +7,17 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest } from 'vs/base/parts/storage/common/storage'; -import { ISerializedWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IEmptyWorkspaceIdentifier, ISerializedSingleFolderWorkspaceIdentifier, ISerializedWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; export type Key = string; export type Value = string; export type Item = [Key, Value]; -export interface ISerializableWorkspaceArgument { - readonly workspace: ISerializedWorkspaceIdentifier | undefined +export interface IBaseSerializableStorageRequest { + readonly workspace: ISerializedWorkspaceIdentifier | ISerializedSingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined } -export interface ISerializableUpdateRequest extends ISerializableWorkspaceArgument { +export interface ISerializableUpdateRequest extends IBaseSerializableStorageRequest { insert?: Item[]; delete?: Key[]; } @@ -31,12 +31,12 @@ abstract class BaseStorageDatabaseClient extends Disposable implements IStorageD abstract onDidChangeItemsExternal: Event; - constructor(protected channel: IChannel, private workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined) { + constructor(protected channel: IChannel, private workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined) { super(); } async getItems(): Promise> { - const serializableRequest: ISerializableWorkspaceArgument = { workspace: this.workspace }; + const serializableRequest: IBaseSerializableStorageRequest = { workspace: this.workspace }; const items: Item[] = await this.channel.call('getItems', serializableRequest); return new Map(items); @@ -57,7 +57,7 @@ abstract class BaseStorageDatabaseClient extends Disposable implements IStorageD } async close(): Promise { - const serializableRequest: ISerializableWorkspaceArgument = { workspace: this.workspace }; + const serializableRequest: IBaseSerializableStorageRequest = { workspace: this.workspace }; return this.channel.call('close', serializableRequest); } @@ -101,7 +101,7 @@ class WorkspaceStorageDatabaseClient extends BaseStorageDatabaseClient implement readonly onDidChangeItemsExternal = Event.None; // unsupported for workspace storage because we only ever write from one window - constructor(channel: IChannel, workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier) { + constructor(channel: IChannel, workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier) { super(channel, workspace); } } @@ -113,7 +113,7 @@ export class StorageDatabaseChannelClient extends Disposable { constructor( private channel: IChannel, - private workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined + private workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined ) { super(); } diff --git a/src/vs/platform/storage/electron-main/storageIpc.ts b/src/vs/platform/storage/electron-main/storageIpc.ts index 486a64c06ab..27084af2ee6 100644 --- a/src/vs/platform/storage/electron-main/storageIpc.ts +++ b/src/vs/platform/storage/electron-main/storageIpc.ts @@ -7,10 +7,10 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { ILogService } from 'vs/platform/log/common/log'; -import { ISerializableItemsChangeEvent, ISerializableUpdateRequest, ISerializableWorkspaceArgument, Key, Value } from 'vs/platform/storage/common/storageIpc'; +import { ISerializableItemsChangeEvent, ISerializableUpdateRequest, IBaseSerializableStorageRequest, Key, Value } from 'vs/platform/storage/common/storageIpc'; import { IStorageChangeEvent, IStorageMain } from 'vs/platform/storage/electron-main/storageMain'; import { IStorageMainService } from 'vs/platform/storage/electron-main/storageMainService'; -import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, reviveIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, reviveIdentifier } from 'vs/platform/workspaces/common/workspaces'; export class StorageDatabaseChannel extends Disposable implements IServerChannel { @@ -77,7 +77,7 @@ export class StorageDatabaseChannel extends Disposable implements IServerChannel //#endregion - async call(_: unknown, command: string, arg: ISerializableWorkspaceArgument): Promise { + async call(_: unknown, command: string, arg: IBaseSerializableStorageRequest): Promise { const workspace = reviveIdentifier(arg.workspace); // Get storage to be ready @@ -119,7 +119,7 @@ export class StorageDatabaseChannel extends Disposable implements IServerChannel } } - private async withStorageInitialized(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined): Promise { + private async withStorageInitialized(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined): Promise { const storage = workspace ? this.storageMainService.workspaceStorage(workspace) : this.storageMainService.globalStorage; try { diff --git a/src/vs/platform/storage/electron-main/storageMain.ts b/src/vs/platform/storage/electron-main/storageMain.ts index 3f75df7deb5..18494b1b00b 100644 --- a/src/vs/platform/storage/electron-main/storageMain.ts +++ b/src/vs/platform/storage/electron-main/storageMain.ts @@ -3,17 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { promises } from 'fs'; +import { exists, writeFile } from 'vs/base/node/pfs'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { SQLiteStorageDatabase, ISQLiteStorageDatabaseLoggingOptions } from 'vs/base/parts/storage/node/storage'; -import { Storage, InMemoryStorageDatabase } from 'vs/base/parts/storage/common/storage'; +import { Storage, InMemoryStorageDatabase, StorageHint, IStorage } from 'vs/base/parts/storage/common/storage'; import { join } from 'vs/base/common/path'; import { IS_NEW_KEY } from 'vs/platform/storage/common/storage'; import { currentSessionDateStorageKey, firstSessionDateStorageKey, instanceStorageKey, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; import { generateUuid } from 'vs/base/common/uuid'; -import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; /** * Provides access to global and workspace storage from the @@ -107,30 +109,34 @@ abstract class BaseStorageMain extends Disposable implements IStorageMain { private readonly _onDidCloseStorage = this._register(new Emitter()); readonly onDidCloseStorage = this._onDidCloseStorage.event; - private storage = new Storage(new InMemoryStorageDatabase()); // storage is in-memory until initialized + private storage: IStorage = new Storage(new InMemoryStorageDatabase()); // storage is in-memory until initialized private initializePromise: Promise | undefined = undefined; - constructor() { + constructor(protected readonly logService: ILogService) { super(); } initialize(): Promise { if (!this.initializePromise) { this.initializePromise = (async () => { - const storage = await this.doInitialize(); + try { + const storage = await this.doInitialize(); - // Replace our in-memory storage with the initialized - // one once that is finished and use it from then on - this.storage.dispose(); - this.storage = storage; + // Replace our in-memory storage with the initialized + // one once that is finished and use it from then on + this.storage.dispose(); + this.storage = storage; - // Ensure we track wether storage is new or not - const isNewStorage = storage.getBoolean(IS_NEW_KEY); - if (isNewStorage === undefined) { - storage.set(IS_NEW_KEY, true); - } else if (isNewStorage) { - storage.set(IS_NEW_KEY, false); + // Ensure we track wether storage is new or not + const isNewStorage = storage.getBoolean(IS_NEW_KEY); + if (isNewStorage === undefined) { + storage.set(IS_NEW_KEY, true); + } else if (isNewStorage) { + storage.set(IS_NEW_KEY, false); + } + } catch (error) { + this.logService.error(`StorageMain#initialize(): Unable to init storage due to ${error}`); } })(); } @@ -138,7 +144,14 @@ abstract class BaseStorageMain extends Disposable implements IStorageMain { return this.initializePromise; } - protected abstract doInitialize(): Promise; + protected createLogginOptions(): ISQLiteStorageDatabaseLoggingOptions { + return { + logTrace: (this.logService.getLevel() === LogLevel.Trace) ? msg => this.logService.trace(msg) : undefined, + logError: error => this.logService.error(error) + }; + } + + protected abstract doInitialize(): Promise; get items(): Map { return this.storage.items; } @@ -183,20 +196,13 @@ export class GlobalStorageMain extends BaseStorageMain implements IStorageMain { private static readonly STORAGE_NAME = 'state.vscdb'; constructor( - @ILogService private readonly logService: ILogService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + logService: ILogService, + private readonly environmentService: IEnvironmentService ) { - super(); + super(logService); } - private createLogginOptions(): ISQLiteStorageDatabaseLoggingOptions { - return { - logTrace: (this.logService.getLevel() === LogLevel.Trace) ? msg => this.logService.trace(msg) : undefined, - logError: error => this.logService.error(error) - }; - } - - protected async doInitialize(): Promise { + protected async doInitialize(): Promise { let storagePath: string; if (!!this.environmentService.extensionTestsLocationURI) { storagePath = SQLiteStorageDatabase.IN_MEMORY_PATH; // no storage during extension tests! @@ -204,6 +210,7 @@ export class GlobalStorageMain extends BaseStorageMain implements IStorageMain { storagePath = join(this.environmentService.globalStorageHome.fsPath, GlobalStorageMain.STORAGE_NAME); } + // Create Storage const storage = new Storage(new SQLiteStorageDatabase(storagePath, { logging: this.createLogginOptions() })); @@ -255,103 +262,80 @@ export class GlobalStorageMain extends BaseStorageMain implements IStorageMain { export class WorkspaceStorageMain extends BaseStorageMain implements IStorageMain { - constructor(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier) { - super(); + private static readonly WORKSPACE_STORAGE_NAME = 'state.vscdb'; + private static readonly WORKSPACE_META_NAME = 'workspace.json'; + + constructor( + private workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier, + logService: ILogService, + private readonly environmentService: IEnvironmentService + ) { + super(logService); } - protected async doInitialize(): Promise { - const storage = new Storage(new InMemoryStorageDatabase()); + protected async doInitialize(): Promise { + + // Prepare workspace storage folder for DB + const { storageFilePath, wasCreated } = await this.prepareWorkspaceStorageFolder(); + + // Create Storage + const storage = new Storage(new SQLiteStorageDatabase(storageFilePath, { + logging: this.createLogginOptions() + }), { hint: wasCreated ? StorageHint.STORAGE_DOES_NOT_EXIST : undefined }); // Re-emit storage changes via event this._register(storage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key }))); + // Forward init to SQLite DB + await storage.init(); + return storage; + } - // private async initializeWorkspaceStorage(payload: IWorkspaceInitializationPayload): Promise { + private async prepareWorkspaceStorageFolder(): Promise<{ storageFilePath: string, wasCreated: boolean }> { - // // Prepare workspace storage folder for DB - // try { - // const result = await this.prepareWorkspaceStorageFolder(payload); + // Return early with in-memory when running extension tests + if (!!this.environmentService.extensionTestsLocationURI) { + return { storageFilePath: SQLiteStorageDatabase.IN_MEMORY_PATH, wasCreated: true }; + } - // const useInMemoryStorage = !!this.environmentService.extensionTestsLocationURI; // no storage during extension tests! + // Otherwise, ensure the storage folder exists on disk + const workspaceStorageFolderPath = join(this.environmentService.workspaceStorageHome.fsPath, this.workspace.id); + const workspaceStorageDatabasePath = join(workspaceStorageFolderPath, WorkspaceStorageMain.WORKSPACE_STORAGE_NAME); - // // Create workspace storage and initialize - // mark('code/willInitWorkspaceStorage'); - // try { - // const workspaceStorage = this.createWorkspaceStorage( - // useInMemoryStorage ? SQLiteStorageDatabase.IN_MEMORY_PATH : join(result.path, NativeStorageService.WORKSPACE_STORAGE_NAME), - // result.wasCreated ? StorageHint.STORAGE_DOES_NOT_EXIST : undefined - // ); - // await workspaceStorage.init(); - // } finally { - // mark('code/didInitWorkspaceStorage'); - // } - // } catch (error) { - // this.logService.error(`[storage] initializeWorkspaceStorage(): Unable to init workspace storage due to ${error}`); - // } - // } + const storageExists = await exists(workspaceStorageFolderPath); + if (storageExists) { + return { storageFilePath: workspaceStorageDatabasePath, wasCreated: false }; + } - // private createWorkspaceStorage(workspaceStoragePath: string, hint?: StorageHint): IStorage { + await promises.mkdir(workspaceStorageFolderPath, { recursive: true }); - // // Logger for workspace storage - // const workspaceLoggingOptions: ISQLiteStorageDatabaseLoggingOptions = { - // logTrace: (this.logService.getLevel() === LogLevel.Trace) ? msg => this.logService.trace(msg) : undefined, - // logError: error => this.logService.error(error) - // }; + // Write metadata into folder + this.ensureWorkspaceStorageFolderMeta(workspaceStorageFolderPath); - // // Dispose old (if any) - // dispose(this.workspaceStorage); - // dispose(this.workspaceStorageListener); + return { storageFilePath: workspaceStorageDatabasePath, wasCreated: true }; + } - // // Create new - // this.workspaceStoragePath = workspaceStoragePath; - // this.workspaceStorage = new Storage(new SQLiteStorageDatabase(workspaceStoragePath, { logging: workspaceLoggingOptions }), { hint }); - // this.workspaceStorageListener = this.workspaceStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.WORKSPACE, key)); + private ensureWorkspaceStorageFolderMeta(workspaceStorageFolderPath: string): void { + let meta: object | undefined = undefined; + if (isSingleFolderWorkspaceIdentifier(this.workspace)) { + meta = { folder: this.workspace.uri.toString() }; + } else if (isWorkspaceIdentifier(this.workspace)) { + meta = { workspace: this.workspace.configPath.toString() }; + } - // return this.workspaceStorage; - // } - - // private getWorkspaceStorageFolderPath(payload: IWorkspaceInitializationPayload): string { - // return join(this.environmentService.workspaceStorageHome.fsPath, payload.id); // workspace home + workspace id; - // } - - // private async prepareWorkspaceStorageFolder(payload: IWorkspaceInitializationPayload): Promise<{ path: string, wasCreated: boolean }> { - // const workspaceStorageFolderPath = this.getWorkspaceStorageFolderPath(payload); - - // const storageExists = await exists(workspaceStorageFolderPath); - // if (storageExists) { - // return { path: workspaceStorageFolderPath, wasCreated: false }; - // } - - // await promises.mkdir(workspaceStorageFolderPath, { recursive: true }); - - // // Write metadata into folder - // this.ensureWorkspaceStorageFolderMeta(payload); - - // return { path: workspaceStorageFolderPath, wasCreated: true }; - // } - - // private ensureWorkspaceStorageFolderMeta(payload: IWorkspaceInitializationPayload): void { - // let meta: object | undefined = undefined; - // if (isSingleFolderWorkspaceIdentifier(payload)) { - // meta = { folder: payload.uri.toString() }; - // } else if (isWorkspaceIdentifier(payload)) { - // meta = { workspace: payload.configPath.toString() }; - // } - - // if (meta) { - // (async () => { - // try { - // const workspaceStorageMetaPath = join(this.getWorkspaceStorageFolderPath(payload), NativeStorageService.WORKSPACE_META_NAME); - // const storageExists = await exists(workspaceStorageMetaPath); - // if (!storageExists) { - // await writeFile(workspaceStorageMetaPath, JSON.stringify(meta, undefined, 2)); - // } - // } catch (error) { - // this.logService.error(error); - // } - // })(); - // } - // } + if (meta) { + (async () => { + try { + const workspaceStorageMetaPath = join(workspaceStorageFolderPath, WorkspaceStorageMain.WORKSPACE_META_NAME); + const storageExists = await exists(workspaceStorageMetaPath); + if (!storageExists) { + await writeFile(workspaceStorageMetaPath, JSON.stringify(meta, undefined, 2)); + } + } catch (error) { + this.logService.error(`StorageMain#ensureWorkspaceStorageFolderMeta(): Unable to create workspace storage metadata due to ${error}`); + } + })(); + } } } diff --git a/src/vs/platform/storage/electron-main/storageMainService.ts b/src/vs/platform/storage/electron-main/storageMainService.ts index 3e4c232099d..38e8d235a33 100644 --- a/src/vs/platform/storage/electron-main/storageMainService.ts +++ b/src/vs/platform/storage/electron-main/storageMainService.ts @@ -8,7 +8,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { GlobalStorageMain, IStorageMain, WorkspaceStorageMain } from 'vs/platform/storage/electron-main/storageMain'; -import { ISingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; export const IStorageMainService = createDecorator('storageMainService'); @@ -24,7 +24,7 @@ export interface IStorageMainService { /** * Provides access to the workspace storage specific to a single window. */ - workspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): IStorageMain; + workspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier): IStorageMain; } export class StorageMainService implements IStorageMainService { @@ -63,22 +63,22 @@ export class StorageMainService implements IStorageMainService { private readonly mapWorkspaceToStorage = new Map(); - private createWorkspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): IStorageMain { - const workspaceStorage = new WorkspaceStorageMain(workspace); + private createWorkspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier): IStorageMain { + const workspaceStorage = new WorkspaceStorageMain(workspace, this.logService, this.environmentService); return workspaceStorage; } - workspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): IStorageMain { + workspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier): IStorageMain { let workspaceStorage = this.mapWorkspaceToStorage.get(workspace.id); if (!workspaceStorage) { - this.logService.trace(`StorageMainService: creating workspace storage (${isWorkspaceIdentifier(workspace) ? workspace.configPath : workspace.uri})`); + this.logService.trace(`StorageMainService: creating workspace storage (${workspace.id})`); workspaceStorage = this.createWorkspaceStorage(workspace); this.mapWorkspaceToStorage.set(workspace.id, workspaceStorage); once(workspaceStorage.onDidCloseStorage)(() => { - this.logService.trace(`StorageMainService: closed workspace storage (${isWorkspaceIdentifier(workspace) ? workspace.configPath : workspace.uri})`); + this.logService.trace(`StorageMainService: closed workspace storage (${workspace.id})`); this.mapWorkspaceToStorage.delete(workspace.id); }); diff --git a/src/vs/platform/storage/electron-sandbox/storageService2.ts b/src/vs/platform/storage/electron-sandbox/storageService2.ts index e5748a183f0..97fc6da4e21 100644 --- a/src/vs/platform/storage/electron-sandbox/storageService2.ts +++ b/src/vs/platform/storage/electron-sandbox/storageService2.ts @@ -10,6 +10,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { assertIsDefined } from 'vs/base/common/types'; import { Promises, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; +import { mark } from 'vs/base/common/performance'; export class NativeStorageService2 extends AbstractStorageService { @@ -47,10 +48,15 @@ export class NativeStorageService2 extends AbstractStorageService { private async doInitialize(): Promise { // Init all storage locations - await Promises.settled([ - this.globalStorage.init(), - this.workspaceStorage?.init() ?? Promise.resolve() - ]); + mark('code/willInitStorage'); + try { + await Promises.settled([ + this.globalStorage.init(), + this.workspaceStorage?.init() ?? Promise.resolve() + ]); + } finally { + mark('code/didInitStorage'); + } // On some OS we do not get enough time to persist state on shutdown (e.g. when // Windows restarts after applying updates). In other cases, VSCode might crash, diff --git a/src/vs/platform/storage/test/electron-main/storageMainService.test.ts b/src/vs/platform/storage/test/electron-main/storageMainService.test.ts index 99067897869..025bbf6226c 100644 --- a/src/vs/platform/storage/test/electron-main/storageMainService.test.ts +++ b/src/vs/platform/storage/test/electron-main/storageMainService.test.ts @@ -12,18 +12,19 @@ import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; import { OPTIONS, parseArgs } from 'vs/platform/environment/node/argv'; import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { NullLogService } from 'vs/platform/log/common/log'; -import { IStorageMainService, StorageMainService } from 'vs/platform/storage/electron-main/storageMainService'; +import { StorageMainService } from 'vs/platform/storage/electron-main/storageMainService'; import { currentSessionDateStorageKey, firstSessionDateStorageKey, instanceStorageKey } from 'vs/platform/telemetry/common/telemetry'; import { IStorageChangeEvent, IStorageMain } from 'vs/platform/storage/electron-main/storageMain'; import { generateUuid } from 'vs/base/common/uuid'; import { isWindows } from 'vs/base/common/platform'; import { IS_NEW_KEY } from 'vs/platform/storage/common/storage'; +import { joinPath } from 'vs/base/common/resources'; flakySuite('StorageMainService (native)', function () { class StorageTestEnvironmentService extends NativeEnvironmentService { - constructor(private globalStorageFolderPath: URI, private _extensionsPath: string) { + constructor(private globalStorageFolderPath: URI, private workspaceStorageFolderPath: URI, private _extensionsPath: string) { super(parseArgs(process.argv, OPTIONS)); } @@ -31,29 +32,37 @@ flakySuite('StorageMainService (native)', function () { return this.globalStorageFolderPath; } + get workspaceStorageHome(): URI { + return this.workspaceStorageFolderPath; + } + get extensionsPath(): string { return this._extensionsPath; } } let testDir: string; - let storageMainService: IStorageMainService; + let environmentService: StorageTestEnvironmentService; setup(async () => { testDir = getRandomTestPath(tmpdir(), 'vsctests', 'storageMainService'); await promises.mkdir(testDir, { recursive: true }); - storageMainService = new StorageMainService(new NullLogService(), new StorageTestEnvironmentService(URI.file(testDir), testDir)); + const globalStorageFolder = joinPath(URI.file(testDir), 'globalStorage'); + const workspaceStorageFolder = joinPath(URI.file(testDir), 'workspaceStorage'); + + await promises.mkdir(globalStorageFolder.fsPath, { recursive: true }); + + environmentService = new StorageTestEnvironmentService(globalStorageFolder, workspaceStorageFolder, testDir); }); - teardown(async () => { - await storageMainService.globalStorage.close(); - + teardown(() => { return rimraf(testDir); }); - async function testStorage(storage: IStorageMain, isGlobal: boolean): Promise { + async function testStorage(storageFn: () => IStorageMain, isGlobal: boolean): Promise { + let storage = storageFn(); // Telemetry: added after init if (isGlobal) { @@ -63,6 +72,8 @@ flakySuite('StorageMainService (native)', function () { strictEqual(typeof storage.get(instanceStorageKey), 'string'); strictEqual(typeof storage.get(firstSessionDateStorageKey), 'string'); strictEqual(typeof storage.get(currentSessionDateStorageKey), 'string'); + } else { + await storage.initialize(); } let storageChangeEvent: IStorageChangeEvent | undefined = undefined; @@ -93,7 +104,7 @@ flakySuite('StorageMainService (native)', function () { strictEqual(storage.items.size, size + 2); // IS_NEW - strictEqual(storage.get(IS_NEW_KEY), true); + strictEqual(storage.getBoolean(IS_NEW_KEY), true); // Close await storage.close(); @@ -102,13 +113,32 @@ flakySuite('StorageMainService (native)', function () { storageChangeListener.dispose(); storageCloseListener.dispose(); + + // Reopen + storage = storageFn(); + await storage.initialize(); + + strictEqual(storage.getNumber('barNumber'), 55); + strictEqual(storage.getBoolean('barBoolean'), true); + + await storage.close(); } test('basics (global)', function () { - return testStorage(storageMainService.globalStorage, true); + return testStorage(() => { + const storageMainService = new StorageMainService(new NullLogService(), environmentService); + + return storageMainService.globalStorage; + }, true); }); test('basics (workspace)', function () { - return testStorage(storageMainService.workspaceStorage({ id: generateUuid(), uri: URI.file(isWindows ? 'C:\\testWorkspace' : '/testWorkspace') }), false); + const workspace = { id: generateUuid(), uri: URI.file(isWindows ? 'C:\\testWorkspace' : '/testWorkspace') }; + + return testStorage(() => { + const storageMainService = new StorageMainService(new NullLogService(), environmentService); + + return storageMainService.workspaceStorage(workspace); + }, false); }); }); diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index 491630b4764..897a204578e 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -116,6 +116,10 @@ export interface ISingleFolderWorkspaceIdentifier extends IBaseWorkspaceIdentifi uri: URI; } +export interface ISerializedSingleFolderWorkspaceIdentifier extends IBaseWorkspaceIdentifier { + uri: UriComponents; +} + export function isSingleFolderWorkspaceIdentifier(obj: unknown): obj is ISingleFolderWorkspaceIdentifier { const singleFolderIdentifier = obj as ISingleFolderWorkspaceIdentifier | undefined; @@ -133,6 +137,10 @@ export interface IWorkspaceIdentifier extends IBaseWorkspaceIdentifier { configPath: URI; } +export interface ISerializedWorkspaceIdentifier extends IBaseWorkspaceIdentifier { + configPath: UriComponents; +} + export function toWorkspaceIdentifier(workspace: IWorkspace): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined { // Multi root @@ -161,19 +169,28 @@ export function isWorkspaceIdentifier(obj: unknown): obj is IWorkspaceIdentifier return typeof workspaceIdentifier?.id === 'string' && URI.isUri(workspaceIdentifier.configPath); } -export interface ISerializedWorkspaceIdentifier { - id: string; - uri?: UriComponents; - configPath?: UriComponents; -} +export function reviveIdentifier(identifier: undefined): undefined; +export function reviveIdentifier(identifier: ISerializedWorkspaceIdentifier): IWorkspaceIdentifier; +export function reviveIdentifier(identifier: ISerializedSingleFolderWorkspaceIdentifier): ISingleFolderWorkspaceIdentifier; +export function reviveIdentifier(identifier: IEmptyWorkspaceIdentifier): IEmptyWorkspaceIdentifier; +export function reviveIdentifier(identifier: ISerializedWorkspaceIdentifier | ISerializedSingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined; +export function reviveIdentifier(identifier: ISerializedWorkspaceIdentifier | ISerializedSingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined { -export function reviveIdentifier(identifier: ISerializedWorkspaceIdentifier | undefined): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined { - if (identifier?.uri) { - return { id: identifier.id, uri: URI.revive(identifier.uri) }; + // Single Folder + const singleFolderIdentifierCandidate = identifier as ISerializedSingleFolderWorkspaceIdentifier | undefined; + if (singleFolderIdentifierCandidate?.uri) { + return { id: singleFolderIdentifierCandidate.id, uri: URI.revive(singleFolderIdentifierCandidate.uri) }; } - if (identifier?.configPath) { - return { id: identifier.id, configPath: URI.revive(identifier.configPath) }; + // Multi folder + const workspaceIdentifierCandidate = identifier as ISerializedWorkspaceIdentifier | undefined; + if (workspaceIdentifierCandidate?.configPath) { + return { id: workspaceIdentifierCandidate.id, configPath: URI.revive(workspaceIdentifierCandidate.configPath) }; + } + + // Empty + if (identifier?.id) { + return { id: identifier.id }; } return undefined; @@ -183,9 +200,9 @@ export function isUntitledWorkspace(path: URI, environmentService: IEnvironmentS return extUriBiasedIgnorePathCase.isEqualOrParent(path, environmentService.untitledWorkspacesHome); } -export interface IEmptyWorkspaceInitializationPayload extends IBaseWorkspaceIdentifier { } +export interface IEmptyWorkspaceIdentifier extends IBaseWorkspaceIdentifier { } -export type IWorkspaceInitializationPayload = IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceInitializationPayload; +export type IWorkspaceInitializationPayload = IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier; //#endregion diff --git a/src/vs/platform/workspaces/test/common/workspaces.test.ts b/src/vs/platform/workspaces/test/common/workspaces.test.ts index 270eaee4899..cb8c60e8668 100644 --- a/src/vs/platform/workspaces/test/common/workspaces.test.ts +++ b/src/vs/platform/workspaces/test/common/workspaces.test.ts @@ -5,10 +5,25 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { hasWorkspaceFileExtension, toWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { hasWorkspaceFileExtension, toWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, ISerializedWorkspaceIdentifier, reviveIdentifier, ISerializedSingleFolderWorkspaceIdentifier, IEmptyWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; suite('Workspaces', () => { + test('reviveIdentifier', () => { + let serializedWorkspaceIdentifier: ISerializedWorkspaceIdentifier = { id: 'id', configPath: URI.file('foo').toJSON() }; + assert.strictEqual(isWorkspaceIdentifier(reviveIdentifier(serializedWorkspaceIdentifier)), true); + + let serializedSingleFolderWorkspaceIdentifier: ISerializedSingleFolderWorkspaceIdentifier = { id: 'id', uri: URI.file('foo').toJSON() }; + assert.strictEqual(isSingleFolderWorkspaceIdentifier(reviveIdentifier(serializedSingleFolderWorkspaceIdentifier)), true); + + let serializedEmptyWorkspaceIdentifier: IEmptyWorkspaceIdentifier = { id: 'id' }; + assert.strictEqual(reviveIdentifier(serializedEmptyWorkspaceIdentifier).id, serializedEmptyWorkspaceIdentifier.id); + assert.strictEqual(isWorkspaceIdentifier(serializedEmptyWorkspaceIdentifier), false); + assert.strictEqual(isSingleFolderWorkspaceIdentifier(serializedEmptyWorkspaceIdentifier), false); + + assert.strictEqual(reviveIdentifier(undefined), undefined); + }); + test('hasWorkspaceFileExtension', () => { assert.strictEqual(hasWorkspaceFileExtension('something'), false); assert.strictEqual(hasWorkspaceFileExtension('something.code-workspace'), true); diff --git a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts index 93974d117c3..51d3d495873 100644 --- a/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/browser/perfviewEditor.ts @@ -174,6 +174,7 @@ class PerfModelContentProvider implements ITextModelContentProvider { table.push(['window.loadUrl() => begin to require(workbench.desktop.main.js)', metrics.timers.ellapsedWindowLoadToRequire, '[main->renderer]', StartupKindToString(metrics.windowKind)]); table.push(['require(workbench.desktop.main.js)', metrics.timers.ellapsedRequire, '[renderer]', `cached data: ${(metrics.didUseCachedData ? 'YES' : 'NO')}${stats ? `, node_modules took ${stats.nodeRequireTotal}ms` : ''}`]); table.push(['wait for shell environment', metrics.timers.ellapsedWaitForShellEnv, '[renderer]', undefined]); + table.push(['init storage (global & workspace)', metrics.timers.ellapsedStorageInit, '[renderer]', undefined]); table.push(['require & init workspace storage', metrics.timers.ellapsedWorkspaceStorageInit, '[renderer]', undefined]); table.push(['init workspace service', metrics.timers.ellapsedWorkspaceServiceInit, '[renderer]', undefined]); if (isWeb) { diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index 8b6823290e0..411501667ec 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -84,7 +84,10 @@ class DesktopMain extends Disposable { private reviveUris() { // Workspace - this.configuration.workspace = reviveIdentifier(this.configuration.workspace); + const workspace = reviveIdentifier(this.configuration.workspace); + if (isWorkspaceIdentifier(workspace) || isSingleFolderWorkspaceIdentifier(workspace)) { + this.configuration.workspace = workspace; + } // Files const filesToWait = this.configuration.filesToWait; @@ -321,7 +324,7 @@ class DesktopMain extends Disposable { } private async createStorageService(payload: IWorkspaceInitializationPayload, logService: ILogService, mainProcessService: IMainProcessService): Promise { - const storageDataBaseClient = new StorageDatabaseChannelClient(mainProcessService.getChannel('storage'), isWorkspaceIdentifier(payload) || isSingleFolderWorkspaceIdentifier(payload) ? payload : undefined); + const storageDataBaseClient = new StorageDatabaseChannelClient(mainProcessService.getChannel('storage'), payload); let storageService: NativeStorageService | NativeStorageService2; if (this.configuration.enableExperimentalMainProcessWorkspaceStorage) { diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index a28d6151bf8..6edfbc6d1d4 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -73,7 +73,10 @@ class DesktopMain extends Disposable { private reviveUris() { // Workspace - this.configuration.workspace = reviveIdentifier(this.configuration.workspace); + const workspace = reviveIdentifier(this.configuration.workspace); + if (isWorkspaceIdentifier(workspace) || isSingleFolderWorkspaceIdentifier(workspace)) { + this.configuration.workspace = workspace; + } // Files const filesToWait = this.configuration.filesToWait; @@ -294,7 +297,7 @@ class DesktopMain extends Disposable { } private async createStorageService(payload: IWorkspaceInitializationPayload, mainProcessService: IMainProcessService): Promise { - const storageDataBaseClient = new StorageDatabaseChannelClient(mainProcessService.getChannel('storage'), isWorkspaceIdentifier(payload) || isSingleFolderWorkspaceIdentifier(payload) ? payload : undefined); + const storageDataBaseClient = new StorageDatabaseChannelClient(mainProcessService.getChannel('storage'), payload); const storageService = new NativeStorageService2(storageDataBaseClient.globalStorage, storageDataBaseClient.workspaceStorage, this.environmentService); try { diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 5577ffbb2b0..fa4552e36f9 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -17,7 +17,7 @@ import { Configuration } from 'vs/workbench/services/configuration/common/config import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId, IConfigurationCache, machineSettingsSchemaId, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions, allSettings, windowSettings, resourceSettings, applicationSettings, machineSettings, machineOverridableSettings, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, useSlashForPath, getStoredWorkspaceFolder, isSingleFolderWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IWorkspaceInitializationPayload, IEmptyWorkspaceIdentifier, useSlashForPath, getStoredWorkspaceFolder, isSingleFolderWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ConfigurationEditingService, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService'; import { WorkspaceConfiguration, FolderConfiguration, RemoteUserConfiguration, UserConfiguration } from 'vs/workbench/services/configuration/browser/configuration'; @@ -418,8 +418,8 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat return Promise.resolve(workspace); } - private createEmptyWorkspace(emptyWorkspacePayload: IEmptyWorkspaceInitializationPayload): Promise { - const workspace = new Workspace(emptyWorkspacePayload.id, [], null, uri => this.uriIdentityService.extUri.ignorePathCasing(uri)); + private createEmptyWorkspace(emptyWorkspaceIdentifier: IEmptyWorkspaceIdentifier): Promise { + const workspace = new Workspace(emptyWorkspaceIdentifier.id, [], null, uri => this.uriIdentityService.extUri.ignorePathCasing(uri)); workspace.initialized = true; return Promise.resolve(workspace); } diff --git a/src/vs/workbench/services/timer/browser/timerService.ts b/src/vs/workbench/services/timer/browser/timerService.ts index f4bc25a6066..324de644b65 100644 --- a/src/vs/workbench/services/timer/browser/timerService.ts +++ b/src/vs/workbench/services/timer/browser/timerService.ts @@ -46,6 +46,7 @@ export interface IMemoryInfo { "timers.ellapsedExtensions" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedExtensionsReady" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedRequire" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "timers.ellapsedStorageInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedWorkspaceStorageInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedWorkspaceServiceInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedRequiredUserDataInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, @@ -215,6 +216,14 @@ export interface IStartupMetrics { */ readonly ellapsedWorkspaceStorageInit: number; + /** + * The time it took to init the storage database connection from the workbench. + * + * * Happens in the renderer-process + * * Measured with the `code/willInitStorage` and `code/didInitStorage` performance marks. + */ + readonly ellapsedStorageInit: number; + /** * The time it took to initialize the workspace and configuration service. * @@ -514,6 +523,7 @@ export abstract class AbstractTimerService implements ITimerService { ellapsedWindowLoadToRequire: this._marks.getDuration('code/willOpenNewWindow', 'code/willLoadWorkbenchMain'), ellapsedRequire: this._marks.getDuration('code/willLoadWorkbenchMain', 'code/didLoadWorkbenchMain'), ellapsedWaitForShellEnv: this._marks.getDuration('code/willWaitForShellEnv', 'code/didWaitForShellEnv'), + ellapsedStorageInit: this._marks.getDuration('code/willInitStorage', 'code/didInitStorage'), ellapsedWorkspaceStorageInit: this._marks.getDuration('code/willInitWorkspaceStorage', 'code/didInitWorkspaceStorage'), ellapsedWorkspaceServiceInit: this._marks.getDuration('code/willInitWorkspaceService', 'code/didInitWorkspaceService'), ellapsedRequiredUserDataInit: this._marks.getDuration('code/willInitRequiredUserData', 'code/didInitRequiredUserData'),