Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce and use global storage service in main process #139632

Merged
merged 6 commits into from Dec 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/vs/code/electron-main/app.ts
Expand Up @@ -67,7 +67,7 @@ import { SharedProcess } from 'vs/platform/sharedProcess/electron-main/sharedPro
import { ISignService } from 'vs/platform/sign/common/sign';
import { IStateMainService } from 'vs/platform/state/electron-main/state';
import { StorageDatabaseChannel } from 'vs/platform/storage/electron-main/storageIpc';
import { IStorageMainService, StorageMainService } from 'vs/platform/storage/electron-main/storageMainService';
import { GlobalStorageMainService, IGlobalStorageMainService, IStorageMainService, StorageMainService } from 'vs/platform/storage/electron-main/storageMainService';
import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties';
import { ITelemetryService, machineIdKey, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
import { TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
Expand Down Expand Up @@ -520,6 +520,7 @@ export class CodeApplication extends Disposable {

// Storage
services.set(IStorageMainService, new SyncDescriptor(StorageMainService));
services.set(IGlobalStorageMainService, new SyncDescriptor(GlobalStorageMainService));

// External terminal
if (isWindows) {
Expand Down
Expand Up @@ -25,7 +25,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { asJson, asText, IRequestService, isSuccess } from 'vs/platform/request/common/request';
import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry';
import { getTelemetryLevel, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';

Expand Down Expand Up @@ -1062,10 +1062,7 @@ export class ExtensionGalleryServiceWithNoStorageService extends AbstractExtensi
}
}

export async function resolveMarketplaceHeaders(version: string, productService: IProductService, environmentService: IEnvironmentService, configurationService: IConfigurationService, fileService: IFileService, storageService: {
get: (key: string, scope: StorageScope) => string | undefined,
store: (key: string, value: string, scope: StorageScope, target: StorageTarget) => void
} | undefined): Promise<{ [key: string]: string; }> {
export async function resolveMarketplaceHeaders(version: string, productService: IProductService, environmentService: IEnvironmentService, configurationService: IConfigurationService, fileService: IFileService, storageService: IStorageService | undefined): Promise<{ [key: string]: string; }> {
const headers: IHeaders = {
'X-Market-Client-Id': `VSCode ${version}`,
'User-Agent': `VSCode ${version} (${productService.nameShort})`
Expand Down
7 changes: 2 additions & 5 deletions src/vs/platform/serviceMachineId/common/serviceMachineId.ts
Expand Up @@ -7,12 +7,9 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { generateUuid, isUUID } from 'vs/base/common/uuid';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService } from 'vs/platform/files/common/files';
import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';

export async function getServiceMachineId(environmentService: IEnvironmentService, fileService: IFileService, storageService: {
get: (key: string, scope: StorageScope, fallbackValue?: string | undefined) => string | undefined,
store: (key: string, value: string, scope: StorageScope, target: StorageTarget) => void
} | undefined): Promise<string> {
export async function getServiceMachineId(environmentService: IEnvironmentService, fileService: IFileService, storageService: IStorageService | undefined): Promise<string> {
let uuid: string | null = storageService ? storageService.get('storage.serviceMachineId', StorageScope.GLOBAL) || null : null;
if (uuid) {
return uuid;
Expand Down
2 changes: 1 addition & 1 deletion src/vs/platform/storage/common/storage.ts
Expand Up @@ -245,7 +245,7 @@ export abstract class AbstractStorageService extends Disposable implements IStor
private readonly flushWhenIdleScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), this.options.flushInterval));
private readonly runFlushWhenIdle = this._register(new MutableDisposable());

constructor(private options: IStorageServiceOptions = { flushInterval: AbstractStorageService.DEFAULT_FLUSH_INTERVAL }) {
constructor(private readonly options: IStorageServiceOptions = { flushInterval: AbstractStorageService.DEFAULT_FLUSH_INTERVAL }) {
super();
}

Expand Down
23 changes: 15 additions & 8 deletions src/vs/platform/storage/electron-main/storageMain.ts
Expand Up @@ -52,6 +52,12 @@ export interface IStorageMain extends IDisposable {
*/
readonly whenInit: Promise<void>;

/**
* Provides access to the `IStorage` implementation which will be
* in-memory for as long as the storage has not been initialized.
*/
readonly storage: IStorage;

/**
* Required call to ensure the service can be used.
*/
Expand Down Expand Up @@ -93,7 +99,8 @@ abstract class BaseStorageMain extends Disposable implements IStorageMain {
private readonly _onDidCloseStorage = this._register(new Emitter<void>());
readonly onDidCloseStorage = this._onDidCloseStorage.event;

private storage: IStorage = new Storage(new InMemoryStorageDatabase()); // storage is in-memory until initialized
private _storage: IStorage = new Storage(new InMemoryStorageDatabase()); // storage is in-memory until initialized
get storage(): IStorage { return this._storage; }

private initializePromise: Promise<void> | undefined = undefined;

Expand All @@ -117,8 +124,8 @@ abstract class BaseStorageMain extends Disposable implements IStorageMain {
// Replace our in-memory storage with the real
// once as soon as possible without awaiting
// the init call.
this.storage.dispose();
this.storage = storage;
this._storage.dispose();
this._storage = storage;

// Re-emit storage changes via event
this._register(storage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key })));
Expand Down Expand Up @@ -157,20 +164,20 @@ abstract class BaseStorageMain extends Disposable implements IStorageMain {

protected abstract doCreate(): Promise<IStorage>;

get items(): Map<string, string> { return this.storage.items; }
get items(): Map<string, string> { return this._storage.items; }

get(key: string, fallbackValue: string): string;
get(key: string, fallbackValue?: string): string | undefined;
get(key: string, fallbackValue?: string): string | undefined {
return this.storage.get(key, fallbackValue);
return this._storage.get(key, fallbackValue);
}

set(key: string, value: string | boolean | number | undefined | null): Promise<void> {
return this.storage.set(key, value);
return this._storage.set(key, value);
}

delete(key: string): Promise<void> {
return this.storage.delete(key);
return this._storage.delete(key);
}

async close(): Promise<void> {
Expand All @@ -184,7 +191,7 @@ abstract class BaseStorageMain extends Disposable implements IStorageMain {
}

// Propagate to storage lib
await this.storage.close();
await this._storage.close();

// Signal as event
this._onDidCloseStorage.fire();
Expand Down
102 changes: 101 additions & 1 deletion src/vs/platform/storage/electron-main/storageMainService.ts
Expand Up @@ -5,12 +5,17 @@

import { once } from 'vs/base/common/functional';
import { Disposable } from 'vs/base/common/lifecycle';
import { IStorage } from 'vs/base/parts/storage/common/storage';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { ILogService } from 'vs/platform/log/common/log';
import { AbstractStorageService, IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { GlobalStorageMain, IStorageMain, IStorageMainOptions, WorkspaceStorageMain } from 'vs/platform/storage/electron-main/storageMain';
import { IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';

//#region Storage Main Service (intent: make global and workspace storage accessible to windows from main process)

export const IStorageMainService = createDecorator<IStorageMainService>('storageMainService');

Expand All @@ -20,11 +25,17 @@ export interface IStorageMainService {

/**
* Provides access to the global storage shared across all windows.
*
* Note: DO NOT use this for reading/writing from the main process!
* Rather use `IGlobalStorageMainService` for that purpose.
*/
readonly globalStorage: IStorageMain;

/**
* Provides access to the workspace storage specific to a single window.
*
* Note: DO NOT use this for reading/writing from the main process!
* This is currently not supported.
*/
workspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier): IStorageMain;
}
Expand Down Expand Up @@ -131,3 +142,92 @@ export class StorageMainService extends Disposable implements IStorageMainServic

//#endregion
}

//#endregion


//#region Global Main Storage Service (intent: use global storage from main process)

export const IGlobalStorageMainService = createDecorator<IStorageMainService>('globalStorageMainService');

/**
* A specialized `IStorageService` interface that only allows
* access to the `StorageScope.GLOBAL` scope.
*/
export interface IGlobalStorageMainService extends IStorageService {

/**
* Important: unlike other storage services in the renderer, the
* main process does not await the storage to be ready, rather
* storage is being initialized while a window opens to reduce
* pressure on startup.
*
* As such, any client wanting to access global storage from the
* main process needs to wait for `whenReady`, otherwise there is
* a chance that the service operates on an in-memory store that
* is not backed by any persistent DB.
*/
readonly whenReady: Promise<void>;

get(key: string, scope: StorageScope.GLOBAL, fallbackValue: string): string;
get(key: string, scope: StorageScope.GLOBAL, fallbackValue?: string): string | undefined;

getBoolean(key: string, scope: StorageScope.GLOBAL, fallbackValue: boolean): boolean;
getBoolean(key: string, scope: StorageScope.GLOBAL, fallbackValue?: boolean): boolean | undefined;

getNumber(key: string, scope: StorageScope.GLOBAL, fallbackValue: number): number;
getNumber(key: string, scope: StorageScope.GLOBAL, fallbackValue?: number): number | undefined;

store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope.GLOBAL, target: StorageTarget): void;

remove(key: string, scope: StorageScope.GLOBAL): void;

keys(scope: StorageScope.GLOBAL, target: StorageTarget): string[];

migrate(toWorkspace: IWorkspaceInitializationPayload): never;

isNew(scope: StorageScope.GLOBAL): boolean;
}

export class GlobalStorageMainService extends AbstractStorageService implements IGlobalStorageMainService {

declare readonly _serviceBrand: undefined;

readonly whenReady = this.storageMainService.globalStorage.whenInit;

constructor(
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
@IStorageMainService private readonly storageMainService: IStorageMainService
) {
super();
}

protected doInitialize(): Promise<void> {

// global storage is being initialized as part
// of the first window opening, so we do not
// trigger it here but can join it
return this.storageMainService.globalStorage.whenInit;
}

protected getStorage(scope: StorageScope): IStorage | undefined {
switch (scope) {
case StorageScope.GLOBAL:
return this.storageMainService.globalStorage.storage;
case StorageScope.WORKSPACE:
return undefined; // unsupported from main process
}
}

protected getLogDetails(scope: StorageScope): string | undefined {
return scope === StorageScope.GLOBAL ? this.environmentMainService.globalStorageHome.fsPath : undefined;
}

protected override shouldFlushWhenIdle(): boolean {
return false; // not needed here, will be triggered from any window that is opened
}

migrate(): never {
throw new Error('Migrating storage is unsupported from main process');
}
}
9 changes: 3 additions & 6 deletions src/vs/platform/windows/electron-main/window.ts
Expand Up @@ -29,7 +29,7 @@ import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifec
import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol';
import { IStorageMainService } from 'vs/platform/storage/electron-main/storageMainService';
import { IGlobalStorageMainService } from 'vs/platform/storage/electron-main/storageMainService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
Expand Down Expand Up @@ -149,7 +149,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
@ILogService private readonly logService: ILogService,
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
@IFileService private readonly fileService: IFileService,
@IStorageMainService private readonly storageMainService: IStorageMainService,
@IGlobalStorageMainService private readonly globalStorageMainService: IGlobalStorageMainService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IThemeMainService private readonly themeMainService: IThemeMainService,
@IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService,
Expand Down Expand Up @@ -544,10 +544,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private marketplaceHeadersPromise: Promise<object> | undefined;
private getMarketplaceHeaders(): Promise<object> {
if (!this.marketplaceHeadersPromise) {
this.marketplaceHeadersPromise = resolveMarketplaceHeaders(this.productService.version, this.productService, this.environmentMainService, this.configurationService, this.fileService, {
get: key => this.storageMainService.globalStorage.get(key),
store: (key, value) => this.storageMainService.globalStorage.set(key, value)
});
this.marketplaceHeadersPromise = resolveMarketplaceHeaders(this.productService.version, this.productService, this.environmentMainService, this.configurationService, this.fileService, this.globalStorageMainService);
}
return this.marketplaceHeadersPromise;
}
Expand Down