mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-19 18:14:50 +01:00
storage - scaffold a basic global storage service on the main side
This commit is contained in:
@@ -73,6 +73,7 @@ import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/platform/remote/node/remoteAgentFileSystemChannel';
|
||||
import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { SnapUpdateService } from 'vs/platform/update/electron-main/updateService.snap';
|
||||
import { IStorageMainService, StorageMainService } from 'vs/platform/storage/electron-main/storageMainService';
|
||||
|
||||
export class CodeApplication extends Disposable {
|
||||
|
||||
@@ -425,30 +426,31 @@ export class CodeApplication extends Disposable {
|
||||
this.sharedProcessClient = this.sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main'));
|
||||
|
||||
// Services
|
||||
const appInstantiationService = this.initServices(machineId);
|
||||
return this.initServices(machineId).then(appInstantiationService => {
|
||||
|
||||
// Create driver
|
||||
if (this.environmentService.driverHandle) {
|
||||
serveDriver(this.electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService).then(server => {
|
||||
this.logService.info('Driver started at:', this.environmentService.driverHandle);
|
||||
this._register(server);
|
||||
});
|
||||
}
|
||||
// Create driver
|
||||
if (this.environmentService.driverHandle) {
|
||||
serveDriver(this.electronIpcServer, this.environmentService.driverHandle, this.environmentService, appInstantiationService).then(server => {
|
||||
this.logService.info('Driver started at:', this.environmentService.driverHandle);
|
||||
this._register(server);
|
||||
});
|
||||
}
|
||||
|
||||
// Setup Auth Handler
|
||||
const authHandler = appInstantiationService.createInstance(ProxyAuthHandler);
|
||||
this._register(authHandler);
|
||||
// Setup Auth Handler
|
||||
const authHandler = appInstantiationService.createInstance(ProxyAuthHandler);
|
||||
this._register(authHandler);
|
||||
|
||||
// Open Windows
|
||||
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor));
|
||||
// Open Windows
|
||||
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor));
|
||||
|
||||
// Post Open Windows Tasks
|
||||
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
|
||||
// Post Open Windows Tasks
|
||||
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor));
|
||||
|
||||
// Tracing: Stop tracing after windows are ready if enabled
|
||||
if (this.environmentService.args.trace) {
|
||||
this.stopTracingEventually(windows);
|
||||
}
|
||||
// Tracing: Stop tracing after windows are ready if enabled
|
||||
if (this.environmentService.args.trace) {
|
||||
this.stopTracingEventually(windows);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -502,7 +504,7 @@ export class CodeApplication extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
private initServices(machineId: string): IInstantiationService {
|
||||
private initServices(machineId: string): Thenable<IInstantiationService> {
|
||||
const services = new ServiceCollection();
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
@@ -522,6 +524,7 @@ export class CodeApplication extends Disposable {
|
||||
services.set(ILaunchService, new SyncDescriptor(LaunchService));
|
||||
services.set(IIssueService, new SyncDescriptor(IssueService, [machineId, this.userEnv]));
|
||||
services.set(IMenubarService, new SyncDescriptor(MenubarService));
|
||||
services.set(IStorageMainService, new SyncDescriptor(StorageMainService));
|
||||
|
||||
// Telemetry
|
||||
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
|
||||
@@ -536,7 +539,22 @@ export class CodeApplication extends Disposable {
|
||||
services.set(ITelemetryService, NullTelemetryService);
|
||||
}
|
||||
|
||||
return this.instantiationService.createChild(services);
|
||||
const appInstantiationService = this.instantiationService.createChild(services);
|
||||
|
||||
return appInstantiationService.invokeFunction(accessor => this.initStorageService(accessor)).then(() => appInstantiationService);
|
||||
}
|
||||
|
||||
private initStorageService(accessor: ServicesAccessor): Thenable<void> {
|
||||
const storageService = accessor.get(IStorageMainService) as StorageMainService;
|
||||
|
||||
// Ensure to close storage on shutdown
|
||||
this.lifecycleService.onWillShutdown(e => e.join(storageService.close()));
|
||||
|
||||
// Initialize storage service
|
||||
return storageService.initialize().then(void 0, error => {
|
||||
errors.onUnexpectedError(error);
|
||||
this.logService.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
private openFirstWindow(accessor: ServicesAccessor): ICodeWindow[] {
|
||||
@@ -684,8 +702,7 @@ export class CodeApplication extends Disposable {
|
||||
this.historyMainService.onRecentlyOpenedChange(() => this.historyMainService.updateWindowsJumpList());
|
||||
|
||||
// Start shared process after a while
|
||||
const sharedProcess = new RunOnceScheduler(() => getShellEnvironment().then(userEnv => this.sharedProcess.spawn(userEnv)), 3000);
|
||||
sharedProcess.schedule();
|
||||
this._register(sharedProcess);
|
||||
const sharedProcessSpawn = this._register(new RunOnceScheduler(() => getShellEnvironment().then(userEnv => this.sharedProcess.spawn(userEnv)), 3000));
|
||||
sharedProcessSpawn.schedule();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
import { handleVetos } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { always } from 'vs/base/common/async';
|
||||
|
||||
export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService');
|
||||
|
||||
@@ -28,6 +29,15 @@ export interface IWindowUnloadEvent {
|
||||
veto(value: boolean | Thenable<boolean>): void;
|
||||
}
|
||||
|
||||
export interface ShutdownEvent {
|
||||
|
||||
/**
|
||||
* Allows to join the shutdown. The promise can be a long running operation but it
|
||||
* will block the application from closing.
|
||||
*/
|
||||
join(promise: Thenable<void>): void;
|
||||
}
|
||||
|
||||
export interface ILifecycleService {
|
||||
_serviceBrand: any;
|
||||
|
||||
@@ -42,9 +52,8 @@ export interface ILifecycleService {
|
||||
quitRequested: boolean;
|
||||
|
||||
/**
|
||||
* Due to the way we handle lifecycle with eventing, the general app.on('before-quit')
|
||||
* event cannot be used because it can be called twice on shutdown. Instead the onBeforeShutdown
|
||||
* handler in this module can be used and it is only called once on a shutdown sequence.
|
||||
* An event that fires when the application is about to shutdown before any window is closed.
|
||||
* The shutdown can still be prevented by any window that vetos this event.
|
||||
*/
|
||||
onBeforeShutdown: Event<void>;
|
||||
|
||||
@@ -53,37 +62,37 @@ export interface ILifecycleService {
|
||||
* vetoed the shutdown sequence. At this point listeners are ensured that the application will
|
||||
* quit without veto.
|
||||
*/
|
||||
onWillShutdown: Event<void>;
|
||||
onWillShutdown: Event<ShutdownEvent>;
|
||||
|
||||
/**
|
||||
* We provide our own event when we close a window because the general window.on('close')
|
||||
* is called even when the window prevents the closing. We want an event that truly fires
|
||||
* before the window gets closed for real.
|
||||
* An event that fires before a window closes. This event is fired after any veto has been dealt
|
||||
* with so that listeners know for sure that the window will close without veto.
|
||||
*/
|
||||
onBeforeWindowClose: Event<ICodeWindow>;
|
||||
|
||||
/**
|
||||
* An even that can be vetoed to prevent a window from being unloaded.
|
||||
* An event that fires before a window is about to unload. Listeners can veto this event to prevent
|
||||
* the window from unloading.
|
||||
*/
|
||||
onBeforeWindowUnload: Event<IWindowUnloadEvent>;
|
||||
|
||||
/**
|
||||
* Close a window for the provided reason. Shutdown handlers are triggered.
|
||||
* Unload a window for the provided reason. All lifecycle event handlers are triggered.
|
||||
*/
|
||||
unload(window: ICodeWindow, reason: UnloadReason): Thenable<boolean /* veto */>;
|
||||
|
||||
/**
|
||||
* Restart the application with optional arguments (CLI). Shutdown handlers are triggered.
|
||||
* Restart the application with optional arguments (CLI). All lifecycle event handlers are triggered.
|
||||
*/
|
||||
relaunch(options?: { addArgs?: string[], removeArgs?: string[] }): void;
|
||||
|
||||
/**
|
||||
* Shutdown the application normally. Shutdown handlers are triggered.
|
||||
* Shutdown the application normally. All lifecycle event handlers are triggered.
|
||||
*/
|
||||
quit(fromUpdate?: boolean): Thenable<boolean /* veto */>;
|
||||
|
||||
/**
|
||||
* Forcefully shutdown the application. No shutdown handlers are triggered.
|
||||
* Forcefully shutdown the application. No livecycle event handlers are triggered.
|
||||
*/
|
||||
kill(code?: number): void;
|
||||
}
|
||||
@@ -95,11 +104,14 @@ export class LifecycleService extends Disposable implements ILifecycleService {
|
||||
private static readonly QUIT_FROM_RESTART_MARKER = 'quit.from.restart'; // use a marker to find out if the session was restarted
|
||||
|
||||
private windowToCloseRequest: { [windowId: string]: boolean } = Object.create(null);
|
||||
private pendingQuitPromise: Thenable<boolean> | null;
|
||||
private pendingQuitPromiseResolve: { (veto: boolean): void } | null;
|
||||
private oneTimeListenerTokenGenerator = 0;
|
||||
private windowCounter = 0;
|
||||
|
||||
private pendingQuitPromise: Thenable<boolean> | null;
|
||||
private pendingQuitPromiseResolve: { (veto: boolean): void } | null;
|
||||
|
||||
private pendingWillShutdownPromise: Thenable<void> | null;
|
||||
|
||||
private _quitRequested = false;
|
||||
get quitRequested(): boolean { return this._quitRequested; }
|
||||
|
||||
@@ -109,8 +121,8 @@ export class LifecycleService extends Disposable implements ILifecycleService {
|
||||
private readonly _onBeforeShutdown = this._register(new Emitter<void>());
|
||||
readonly onBeforeShutdown: Event<void> = this._onBeforeShutdown.event;
|
||||
|
||||
private readonly _onWillShutdown = this._register(new Emitter<void>());
|
||||
readonly onWillShutdown: Event<void> = this._onWillShutdown.event;
|
||||
private readonly _onWillShutdown = this._register(new Emitter<ShutdownEvent>());
|
||||
readonly onWillShutdown: Event<ShutdownEvent> = this._onWillShutdown.event;
|
||||
|
||||
private readonly _onBeforeWindowClose = this._register(new Emitter<ICodeWindow>());
|
||||
readonly onBeforeWindowClose: Event<ICodeWindow> = this._onBeforeWindowClose.event;
|
||||
@@ -141,15 +153,14 @@ export class LifecycleService extends Disposable implements ILifecycleService {
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// before-quit
|
||||
app.on('before-quit', e => {
|
||||
this.logService.trace('Lifecycle#before-quit');
|
||||
|
||||
// before-quit: an event that is fired if application quit was
|
||||
// requested but before any window was closed.
|
||||
const beforeQuitListener = () => {
|
||||
if (this._quitRequested) {
|
||||
this.logService.trace('Lifecycle#before-quit - returning because quit was already requested');
|
||||
return;
|
||||
}
|
||||
|
||||
this.logService.trace('Lifecycle#app.on(before-quit)');
|
||||
this._quitRequested = true;
|
||||
|
||||
// Emit event to indicate that we are about to shutdown
|
||||
@@ -157,25 +168,77 @@ export class LifecycleService extends Disposable implements ILifecycleService {
|
||||
this._onBeforeShutdown.fire();
|
||||
|
||||
// macOS: can run without any window open. in that case we fire
|
||||
// the onShutdown() event directly because there is no veto to be expected.
|
||||
// the onWillShutdown() event directly because there is no veto
|
||||
// to be expected.
|
||||
if (isMacintosh && this.windowCounter === 0) {
|
||||
this.logService.trace('Lifecycle#onShutdown.fire()');
|
||||
this._onWillShutdown.fire();
|
||||
this.beginOnWillShutdown();
|
||||
}
|
||||
});
|
||||
};
|
||||
app.addListener('before-quit', beforeQuitListener);
|
||||
|
||||
// window-all-closed
|
||||
app.on('window-all-closed', () => {
|
||||
this.logService.trace('Lifecycle#window-all-closed');
|
||||
// window-all-closed: an event that only fires when the last window
|
||||
// was closed. We override this event to be in charge if app.quit()
|
||||
// should be called or not.
|
||||
const windowAllClosedListener = () => {
|
||||
this.logService.trace('Lifecycle#app.on(window-all-closed)');
|
||||
|
||||
// Windows/Linux: we quit when all windows have closed
|
||||
// Mac: we only quit when quit was requested
|
||||
if (this._quitRequested || process.platform !== 'darwin') {
|
||||
if (this._quitRequested || !isMacintosh) {
|
||||
app.quit();
|
||||
}
|
||||
};
|
||||
app.addListener('window-all-closed', windowAllClosedListener);
|
||||
|
||||
// will-quit: an event that is fired after all windows have been
|
||||
// closed, but before actually quitting.
|
||||
app.once('will-quit', e => {
|
||||
this.logService.trace('Lifecycle#app.on(will-quit)');
|
||||
|
||||
// Prevent the quit until the shutdown promise was resolved
|
||||
e.preventDefault();
|
||||
|
||||
// Start shutdown sequence
|
||||
const shutdownPromise = this.beginOnWillShutdown();
|
||||
|
||||
// Wait until shutdown is signaled to be complete
|
||||
always(shutdownPromise, () => {
|
||||
|
||||
// Resolve pending quit promise now without veto
|
||||
this.resolvePendingQuitPromise(false /* no veto */);
|
||||
|
||||
// Quit again, this time do not prevent this, since our
|
||||
// will-quit listener is only installed "once". Also
|
||||
// remove any listener we have that is no longer needed
|
||||
app.removeListener('before-quit', beforeQuitListener);
|
||||
app.removeListener('window-all-closed', windowAllClosedListener);
|
||||
app.quit();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private beginOnWillShutdown(): Thenable<void> {
|
||||
if (this.pendingWillShutdownPromise) {
|
||||
return this.pendingWillShutdownPromise; // shutdown is already running
|
||||
}
|
||||
|
||||
this.logService.trace('Lifecycle#onWillShutdown.fire()');
|
||||
|
||||
const joiners: Thenable<void>[] = [];
|
||||
|
||||
this._onWillShutdown.fire({
|
||||
join(promise) {
|
||||
if (promise) {
|
||||
joiners.push(promise);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.pendingWillShutdownPromise = Promise.all(joiners).then(null, err => this.logService.error(err));
|
||||
|
||||
return this.pendingWillShutdownPromise;
|
||||
}
|
||||
|
||||
registerWindow(window: ICodeWindow): void {
|
||||
|
||||
// track window count
|
||||
@@ -183,49 +246,48 @@ export class LifecycleService extends Disposable implements ILifecycleService {
|
||||
|
||||
// Window Before Closing: Main -> Renderer
|
||||
window.win.on('close', e => {
|
||||
const windowId = window.id;
|
||||
this.logService.trace('Lifecycle#window-before-close', windowId);
|
||||
|
||||
// The window already acknowledged to be closed
|
||||
const windowId = window.id;
|
||||
if (this.windowToCloseRequest[windowId]) {
|
||||
this.logService.trace('Lifecycle#window-close', windowId);
|
||||
|
||||
delete this.windowToCloseRequest[windowId];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logService.trace(`Lifecycle#window.on('close') - window ID ${window.id}`);
|
||||
|
||||
// Otherwise prevent unload and handle it from window
|
||||
e.preventDefault();
|
||||
this.unload(window, UnloadReason.CLOSE).then(veto => {
|
||||
if (!veto) {
|
||||
this.windowToCloseRequest[windowId] = true;
|
||||
|
||||
this.logService.trace('Lifecycle#onBeforeWindowClose.fire()');
|
||||
this._onBeforeWindowClose.fire(window);
|
||||
|
||||
window.close();
|
||||
} else {
|
||||
this._quitRequested = false;
|
||||
if (veto) {
|
||||
delete this.windowToCloseRequest[windowId];
|
||||
return;
|
||||
}
|
||||
|
||||
this.windowToCloseRequest[windowId] = true;
|
||||
|
||||
// Fire onBeforeWindowClose before actually closing
|
||||
this.logService.trace(`Lifecycle#onBeforeWindowClose.fire() - window ID ${windowId}`);
|
||||
this._onBeforeWindowClose.fire(window);
|
||||
|
||||
// No veto, close window now
|
||||
window.close();
|
||||
});
|
||||
});
|
||||
|
||||
// Window After Closing
|
||||
window.win.on('closed', e => {
|
||||
const windowId = window.id;
|
||||
this.logService.trace('Lifecycle#window-closed', windowId);
|
||||
this.logService.trace(`Lifecycle#window.on('closed') - window ID ${window.id}`);
|
||||
|
||||
// update window count
|
||||
this.windowCounter--;
|
||||
|
||||
// if there are no more code windows opened, fire the onShutdown event, unless
|
||||
// if there are no more code windows opened, fire the onWillShutdown event, unless
|
||||
// we are on macOS where it is perfectly fine to close the last window and
|
||||
// the application continues running (unless quit was actually requested)
|
||||
if (this.windowCounter === 0 && (!isMacintosh || this._quitRequested)) {
|
||||
this.logService.trace('Lifecycle#onShutdown.fire()');
|
||||
this._onWillShutdown.fire();
|
||||
this.beginOnWillShutdown();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -237,44 +299,53 @@ export class LifecycleService extends Disposable implements ILifecycleService {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
this.logService.trace('Lifecycle#unload()', window.id);
|
||||
|
||||
const windowUnloadReason = this._quitRequested ? UnloadReason.QUIT : reason;
|
||||
this.logService.trace(`Lifecycle#unload() - window ID ${window.id}`);
|
||||
|
||||
// first ask the window itself if it vetos the unload
|
||||
const windowUnloadReason = this._quitRequested ? UnloadReason.QUIT : reason;
|
||||
return this.onBeforeUnloadWindowInRenderer(window, windowUnloadReason).then(veto => {
|
||||
if (veto) {
|
||||
this.logService.trace('Lifecycle#unload(): veto in renderer', window.id);
|
||||
this.logService.trace(`Lifecycle#unload() - veto in renderer (window ID ${window.id})`);
|
||||
|
||||
return this.handleVeto(veto);
|
||||
return this.handleWindowUnloadVeto(veto);
|
||||
}
|
||||
|
||||
// then check for vetos in the main side
|
||||
return this.onBeforeUnloadWindowInMain(window, windowUnloadReason).then(veto => {
|
||||
if (veto) {
|
||||
this.logService.trace('Lifecycle#unload(): veto in main', window.id);
|
||||
this.logService.trace(`Lifecycle#unload() - veto in main (window ID ${window.id})`);
|
||||
|
||||
return this.handleVeto(veto);
|
||||
} else {
|
||||
this.logService.trace('Lifecycle#unload(): unload continues without veto', window.id);
|
||||
return this.handleWindowUnloadVeto(veto);
|
||||
}
|
||||
|
||||
this.logService.trace(`Lifecycle#unload() - no veto (window ID ${window.id})`);
|
||||
|
||||
// finally if there are no vetos, unload the renderer
|
||||
return this.onWillUnloadWindowInRenderer(window, windowUnloadReason).then(() => false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private handleVeto(veto: boolean): boolean {
|
||||
private handleWindowUnloadVeto(veto: boolean): boolean {
|
||||
if (!veto) {
|
||||
return false; // no veto
|
||||
}
|
||||
|
||||
// Any cancellation also cancels a pending quit if present
|
||||
if (veto && this.pendingQuitPromiseResolve) {
|
||||
this.pendingQuitPromiseResolve(true /* veto */);
|
||||
// a veto resolves any pending quit with veto
|
||||
this.resolvePendingQuitPromise(true /* veto */);
|
||||
|
||||
// a veto resets the pending quit request flag
|
||||
this._quitRequested = false;
|
||||
|
||||
return true; // veto
|
||||
}
|
||||
|
||||
private resolvePendingQuitPromise(veto: boolean): void {
|
||||
if (this.pendingQuitPromiseResolve) {
|
||||
this.pendingQuitPromiseResolve(veto);
|
||||
this.pendingQuitPromiseResolve = null;
|
||||
this.pendingQuitPromise = null;
|
||||
}
|
||||
|
||||
return veto;
|
||||
}
|
||||
|
||||
private onBeforeUnloadWindowInRenderer(window: ICodeWindow, reason: UnloadReason): Thenable<boolean /* veto */> {
|
||||
@@ -325,38 +396,28 @@ export class LifecycleService extends Disposable implements ILifecycleService {
|
||||
* by the user or not.
|
||||
*/
|
||||
quit(fromUpdate?: boolean): Thenable<boolean /* veto */> {
|
||||
this.logService.trace('Lifecycle#quit()');
|
||||
|
||||
if (!this.pendingQuitPromise) {
|
||||
this.pendingQuitPromise = new Promise(resolve => {
|
||||
|
||||
// Store as field to access it from a window cancellation
|
||||
this.pendingQuitPromiseResolve = resolve;
|
||||
|
||||
// The will-quit event is fired when all windows have closed without veto
|
||||
app.once('will-quit', () => {
|
||||
this.logService.trace('Lifecycle#will-quit');
|
||||
|
||||
if (this.pendingQuitPromiseResolve) {
|
||||
if (fromUpdate) {
|
||||
this.stateService.setItem(LifecycleService.QUIT_FROM_RESTART_MARKER, true);
|
||||
}
|
||||
|
||||
this.pendingQuitPromiseResolve(false /* no veto */);
|
||||
this.pendingQuitPromiseResolve = null;
|
||||
this.pendingQuitPromise = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Calling app.quit() will trigger the close handlers of each opened window
|
||||
// and only if no window vetoed the shutdown, we will get the will-quit event
|
||||
this.logService.trace('Lifecycle#quit() - calling app.quit()');
|
||||
app.quit();
|
||||
});
|
||||
} else {
|
||||
this.logService.trace('Lifecycle#quit() - a pending quit was found');
|
||||
if (this.pendingQuitPromise) {
|
||||
return this.pendingQuitPromise;
|
||||
}
|
||||
|
||||
this.logService.trace(`Lifecycle#quit() - from update: ${fromUpdate}`);
|
||||
|
||||
// Remember the reason for quit was to restart
|
||||
if (fromUpdate) {
|
||||
this.stateService.setItem(LifecycleService.QUIT_FROM_RESTART_MARKER, true);
|
||||
}
|
||||
|
||||
this.pendingQuitPromise = new Promise(resolve => {
|
||||
|
||||
// Store as field to access it from a window cancellation
|
||||
this.pendingQuitPromiseResolve = resolve;
|
||||
|
||||
// Calling app.quit() will trigger the close handlers of each opened window
|
||||
// and only if no window vetoed the shutdown, we will get the will-quit event
|
||||
this.logService.trace('Lifecycle#quit() - calling app.quit()');
|
||||
app.quit();
|
||||
});
|
||||
|
||||
return this.pendingQuitPromise;
|
||||
}
|
||||
|
||||
@@ -400,6 +461,7 @@ export class LifecycleService extends Disposable implements ILifecycleService {
|
||||
}
|
||||
|
||||
// relaunch after we are sure there is no veto
|
||||
this.logService.trace('Lifecycle#relaunch() - calling app.relaunch()');
|
||||
app.relaunch({ args });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
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 { IStorage, Storage, IStorageLoggingOptions, NullStorage } from 'vs/base/node/storage';
|
||||
import { join } from 'path';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { mark } from 'vs/base/common/performance';
|
||||
|
||||
export const IStorageMainService = createDecorator<IStorageMainService>('storageMainService');
|
||||
|
||||
export interface IStorageMainService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
/**
|
||||
* Emitted whenever data is updated or deleted.
|
||||
*/
|
||||
readonly onDidChangeStorage: Event<IStorageChangeEvent>;
|
||||
|
||||
/**
|
||||
* Emitted when the storage is about to persist. This is the right time
|
||||
* to persist data to ensure it is stored before the application shuts
|
||||
* down.
|
||||
*/
|
||||
readonly onWillSaveState: Event<void>;
|
||||
|
||||
/**
|
||||
* Retrieve an element stored with the given key from storage. Use
|
||||
* the provided defaultValue if the element is null or undefined.
|
||||
*/
|
||||
get(key: string, fallbackValue: string): string;
|
||||
|
||||
/**
|
||||
* Retrieve an element stored with the given key from storage. Use
|
||||
* the provided defaultValue if the element is null or undefined. The element
|
||||
* will be converted to a boolean.
|
||||
*/
|
||||
getBoolean(key: string, fallbackValue: boolean): boolean;
|
||||
|
||||
/**
|
||||
* Retrieve an element stored with the given key from storage. Use
|
||||
* the provided defaultValue if the element is null or undefined. The element
|
||||
* will be converted to a number using parseInt with a base of 10.
|
||||
*/
|
||||
getInteger(key: string, fallbackValue: number): number;
|
||||
|
||||
/**
|
||||
* Store a string value under the given key to storage. The value will
|
||||
* be converted to a string.
|
||||
*/
|
||||
store(key: string, value: any): void;
|
||||
|
||||
/**
|
||||
* Delete an element stored under the provided key from storage.
|
||||
*/
|
||||
remove(key: string): void;
|
||||
}
|
||||
|
||||
export interface IStorageChangeEvent {
|
||||
key: string;
|
||||
}
|
||||
|
||||
export class StorageMainService extends Disposable implements IStorageMainService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private static STORAGE_NAME = 'temp.vscdb';
|
||||
|
||||
private _onDidChangeStorage: Emitter<IStorageChangeEvent> = this._register(new Emitter<IStorageChangeEvent>());
|
||||
get onDidChangeStorage(): Event<IStorageChangeEvent> { return this._onDidChangeStorage.event; }
|
||||
|
||||
private _onWillSaveState: Emitter<void> = this._register(new Emitter<void>());
|
||||
get onWillSaveState(): Event<void> { return this._onWillSaveState.event; }
|
||||
|
||||
private storage: IStorage;
|
||||
|
||||
constructor(
|
||||
@ILogService private logService: ILogService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService
|
||||
) {
|
||||
super();
|
||||
|
||||
const useInMemoryStorage = !!environmentService.extensionTestsPath; // no storage during extension tests!
|
||||
|
||||
this.storage = new NullStorage() || new Storage({
|
||||
path: useInMemoryStorage ? Storage.IN_MEMORY_PATH : join(environmentService.globalStorageHome, StorageMainService.STORAGE_NAME),
|
||||
logging: this.createLogginOptions()
|
||||
});
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private createLogginOptions(): IStorageLoggingOptions {
|
||||
const loggedStorageErrors = new Set<string>();
|
||||
|
||||
return {
|
||||
logTrace: (this.logService.getLevel() === LogLevel.Trace) ? msg => this.logService.trace(msg) : void 0,
|
||||
logError: error => {
|
||||
this.logService.error(error);
|
||||
|
||||
const errorStr = `${error}`;
|
||||
if (!loggedStorageErrors.has(errorStr)) {
|
||||
loggedStorageErrors.add(errorStr);
|
||||
|
||||
/* __GDPR__
|
||||
"sqliteMainStorageError" : {
|
||||
"storageError": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('sqliteMainStorageError', {
|
||||
'storageError': errorStr
|
||||
});
|
||||
}
|
||||
}
|
||||
} as IStorageLoggingOptions;
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.storage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key })));
|
||||
}
|
||||
|
||||
initialize(): Thenable<void> {
|
||||
mark('main:willInitGlobalStorage');
|
||||
return this.storage.init().then(() => {
|
||||
mark('main:didInitGlobalStorage');
|
||||
}, error => {
|
||||
mark('main:didInitGlobalStorage');
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
get(key: string, fallbackValue: string): string {
|
||||
return this.storage.get(key, fallbackValue);
|
||||
}
|
||||
|
||||
getBoolean(key: string, fallbackValue: boolean): boolean {
|
||||
return this.storage.getBoolean(key, fallbackValue);
|
||||
}
|
||||
|
||||
getInteger(key: string, fallbackValue: number): number {
|
||||
return this.storage.getInteger(key, fallbackValue);
|
||||
}
|
||||
|
||||
store(key: string, value: any): Thenable<void> {
|
||||
return this.storage.set(key, value);
|
||||
}
|
||||
|
||||
remove(key: string): Thenable<void> {
|
||||
return this.storage.delete(key);
|
||||
}
|
||||
|
||||
close(): Thenable<void> {
|
||||
this.logService.trace('StorageMainService#close() - begin');
|
||||
|
||||
// Signal as event so that clients can still store data
|
||||
this._onWillSaveState.fire();
|
||||
|
||||
// Do it
|
||||
return this.storage.close().then(() => this.logService.trace('StorageMainService#close() - finished'));
|
||||
}
|
||||
}
|
||||
@@ -147,10 +147,7 @@ export class StorageService extends Disposable implements IStorageService {
|
||||
}
|
||||
|
||||
return migrationPromise.then(() => {
|
||||
mark('willInitGlobalStorage');
|
||||
return this.globalStorage.init().then(() => {
|
||||
mark('didInitGlobalStorage');
|
||||
});
|
||||
return this.globalStorage.init();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -415,7 +412,7 @@ export class StorageService extends Disposable implements IStorageService {
|
||||
workspaceItemsParsed.set(key, safeParse(value));
|
||||
});
|
||||
|
||||
console.group(`Storage: Global (integrity: ${result[2]}, load: ${getDuration('willInitGlobalStorage', 'didInitGlobalStorage')}, path: ${this.globalStorageWorkspacePath})`);
|
||||
console.group(`Storage: Global (integrity: ${result[2]}, load: ${getDuration('main:willInitGlobalStorage', 'main:didInitGlobalStorage')}, path: ${this.globalStorageWorkspacePath})`);
|
||||
let globalValues: { key: string, value: string }[] = [];
|
||||
globalItems.forEach((value, key) => {
|
||||
globalValues.push({ key, value });
|
||||
|
||||
@@ -292,6 +292,7 @@ export class WorkbenchShell extends Disposable {
|
||||
const workbenchReadyDuration = perf.getDuration(initialStartup ? 'main:started' : 'main:loadWindow', 'didStartWorkbench');
|
||||
const workspaceStorageRequireDuration = perf.getDuration('willRequireSQLite', 'didRequireSQLite');
|
||||
const workspaceStorageSchemaDuration = perf.getDuration('willSetupSQLiteSchema', 'didSetupSQLiteSchema');
|
||||
const globalStorageInitDuration = perf.getDuration('main:willInitGlobalStorage', 'main:didInitGlobalStorage');
|
||||
const workspaceStorageInitDuration = perf.getDuration('willInitWorkspaceStorage', 'didInitWorkspaceStorage');
|
||||
const workspaceStorageFileExistsDuration = perf.getDuration('willCheckWorkspaceStorageExists', 'didCheckWorkspaceStorageExists');
|
||||
const workspaceStorageMigrationDuration = perf.getDuration('willMigrateWorkspaceStorageKeys', 'didMigrateWorkspaceStorageKeys');
|
||||
@@ -314,6 +315,7 @@ export class WorkbenchShell extends Disposable {
|
||||
"workspaceMigrationTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
"workspaceRequireTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
"workspaceSchemaTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
"globalReadTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
"workspaceReadTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
"localStorageTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
"workbenchRequireTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
@@ -329,6 +331,7 @@ export class WorkbenchShell extends Disposable {
|
||||
'workspaceMigrationTime': workspaceStorageMigrationDuration,
|
||||
'workspaceRequireTime': workspaceStorageRequireDuration,
|
||||
'workspaceSchemaTime': workspaceStorageSchemaDuration,
|
||||
'globalReadTime': globalStorageInitDuration,
|
||||
'workspaceReadTime': workspaceStorageInitDuration,
|
||||
'localStorageTime': localStorageDuration,
|
||||
'workbenchRequireTime': workbenchLoadDuration,
|
||||
@@ -356,6 +359,7 @@ export class WorkbenchShell extends Disposable {
|
||||
"workspaceMigrationTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
"workspaceRequireTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
"workspaceSchemaTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
"globalReadTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
"workspaceReadTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
"localStorageTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
"workbenchRequireTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
@@ -370,6 +374,7 @@ export class WorkbenchShell extends Disposable {
|
||||
'workspaceMigrationTime': workspaceStorageMigrationDuration,
|
||||
'workspaceRequireTime': workspaceStorageRequireDuration,
|
||||
'workspaceSchemaTime': workspaceStorageSchemaDuration,
|
||||
'globalReadTime': globalStorageInitDuration,
|
||||
'workspaceReadTime': workspaceStorageInitDuration,
|
||||
'localStorageTime': localStorageDuration,
|
||||
'workbenchRequireTime': workbenchLoadDuration,
|
||||
|
||||
@@ -26,9 +26,11 @@ class Info {
|
||||
|
||||
static getTimerInfo(metrics: IStartupMetrics, nodeModuleLoadTime?: number): { [name: string]: Info } {
|
||||
const table: { [name: string]: Info } = Object.create(null);
|
||||
table['start => app.isReady'] = new Info(metrics.timers.ellapsedAppReady, '[main]', metrics.initialStartup);
|
||||
table['nls:start => nls:end'] = new Info(metrics.timers.ellapsedNlsGeneration, '[main]', metrics.initialStartup);
|
||||
table['app.isReady => window.loadUrl()'] = new Info(metrics.timers.ellapsedWindowLoad, '[main]', metrics.initialStartup);
|
||||
table['start => app.isReady'] = new Info(metrics.timers.ellapsedAppReady, '[main]', `initial startup: ${metrics.initialStartup}`);
|
||||
table['nls:start => nls:end'] = new Info(metrics.timers.ellapsedNlsGeneration, '[main]', `initial startup: ${metrics.initialStartup}`);
|
||||
table['app.isReady => window.loadUrl()'] = new Info(metrics.timers.ellapsedWindowLoad, '[main]', `initial startup: ${metrics.initialStartup}`);
|
||||
|
||||
table['init global storage'] = new Info(metrics.timers.ellapsedGlobalStorageInit, '[main]', `initial startup: ${metrics.initialStartup}`);
|
||||
|
||||
table['window.loadUrl() => begin to require(workbench.main.js)'] = new Info(metrics.timers.ellapsedWindowLoadToRequire, '[main->renderer]', StartupKindToString(metrics.windowKind));
|
||||
table['require(workbench.main.js)'] = new Info(metrics.timers.ellapsedRequire, '[renderer]', `cached data: ${(metrics.didUseCachedData ? 'YES' : 'NO')}${nodeModuleLoadTime ? `, node_modules took ${nodeModuleLoadTime}ms` : ''}`);
|
||||
|
||||
@@ -53,6 +53,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.ellapsedGlobalStorageInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"timers.ellapsedWorkspaceStorageRequire" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"timers.ellapsedWorkspaceStorageInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
"timers.ellapsedViewletRestore" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
|
||||
@@ -194,6 +195,15 @@ export interface IStartupMetrics {
|
||||
*/
|
||||
ellapsedWindowLoadToRequire: number;
|
||||
|
||||
/**
|
||||
* The time it took to require the global storage DB, connect to it
|
||||
* and load the initial set of values.
|
||||
*
|
||||
* * Happens in the main-process
|
||||
* * Measured with the `main:willInitGlobalStorage` and `main:didInitGlobalStorage` performance marks.
|
||||
*/
|
||||
ellapsedGlobalStorageInit: number;
|
||||
|
||||
/**
|
||||
* The time it took to require the workspace storage DB.
|
||||
*
|
||||
@@ -388,6 +398,7 @@ class TimerService implements ITimerService {
|
||||
ellapsedWindowLoad: initialStartup ? perf.getDuration('main:appReady', 'main:loadWindow') : undefined,
|
||||
ellapsedWindowLoadToRequire: perf.getDuration('main:loadWindow', 'willLoadWorkbenchMain'),
|
||||
ellapsedRequire: perf.getDuration('willLoadWorkbenchMain', 'didLoadWorkbenchMain'),
|
||||
ellapsedGlobalStorageInit: perf.getDuration('main:willInitGlobalStorage', 'main:didInitGlobalStorage'),
|
||||
ellapsedWorkspaceStorageRequire: perf.getDuration('willRequireSQLite', 'didRequireSQLite'),
|
||||
ellapsedWorkspaceStorageInit: perf.getDuration('willInitWorkspaceStorage', 'didInitWorkspaceStorage'),
|
||||
ellapsedExtensions: perf.getDuration('willLoadExtensions', 'didLoadExtensions'),
|
||||
|
||||
Reference in New Issue
Block a user