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

sqlite - implement a way to join shutdown phase with a long running operation

上级 5893222e
......@@ -11,20 +11,44 @@ import { isThenable } from 'vs/base/common/async';
export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService');
/**
* An event that is send out when the window is about to close. Clients have a chance to veto the closing by either calling veto
* with a boolean "true" directly or with a promise that resolves to a boolean. Returning a promise is useful
* in cases of long running operations on shutdown.
* An event that is send out when the window is about to close. Clients have a chance to veto
* the closing by either calling veto with a boolean "true" directly or with a promise that
* resolves to a boolean. Returning a promise is useful in cases of long running operations
* on shutdown.
*
* Note: It is absolutely important to avoid long running promises on this call. Please try hard to return
* a boolean directly. Returning a promise has quite an impact on the shutdown sequence!
* Note: It is absolutely important to avoid long running promises on this call. Please try hard
* to return a boolean directly. Returning a promise has quite an impact on the shutdown sequence!
*/
export interface ShutdownEvent {
export interface WillShutdownEvent {
/**
* Allows to veto the shutdown. The veto can be a long running operation.
* Allows to veto the shutdown. The veto can be a long running operation but it
* will block the application from closing.
*/
veto(value: boolean | Thenable<boolean>): void;
/**
* The reason why Code will be shutting down.
*/
reason: ShutdownReason;
}
/**
* An event that is send out when the window closes. Clients have a chance to join the closing
* by providing a promise from the join method. Returning a promise is useful in cases of long
* running operations on shutdown.
*
* Note: It is absolutely important to avoid long running promises on this call. Please try hard
* to return a boolean directly. Returning a promise has quite an impact on the shutdown sequence!
*/
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;
/**
* The reason why Code is shutting down.
*/
......@@ -51,6 +75,7 @@ export const enum StartupKind {
ReloadedWindow = 3,
ReopenedWindow = 4,
}
export function StartupKindToString(startupKind: StartupKind): string {
switch (startupKind) {
case StartupKind.NewWindow: return 'NewWindow';
......@@ -65,6 +90,7 @@ export const enum LifecyclePhase {
Running = 3,
Eventually = 4
}
export function LifecyclePhaseToString(phase: LifecyclePhase) {
switch (phase) {
case LifecyclePhase.Starting: return 'Starting';
......@@ -92,17 +118,11 @@ export interface ILifecycleService {
*/
readonly phase: LifecyclePhase;
/**
* Returns a promise that resolves when a certain lifecycle phase
* has started.
*/
when(phase: LifecyclePhase): Thenable<void>;
/**
* Fired before shutdown happens. Allows listeners to veto against the
* shutdown.
*/
readonly onWillShutdown: Event<ShutdownEvent>;
readonly onWillShutdown: Event<WillShutdownEvent>;
/**
* Fired when no client is preventing the shutdown from happening. Can be used to dispose heavy resources
......@@ -110,7 +130,13 @@ export interface ILifecycleService {
*
* The event carries a shutdown reason that indicates how the shutdown was triggered.
*/
readonly onShutdown: Event<ShutdownReason>;
readonly onShutdown: Event<ShutdownEvent>;
/**
* Returns a promise that resolves when a certain lifecycle phase
* has started.
*/
when(phase: LifecyclePhase): Thenable<void>;
}
export const NullLifecycleService: ILifecycleService = {
......
......@@ -5,7 +5,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { ILifecycleService, ShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, handleVetos, LifecyclePhaseToString } from 'vs/platform/lifecycle/common/lifecycle';
import { ILifecycleService, WillShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, handleVetos, LifecyclePhaseToString, ShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ipcRenderer as ipc } from 'electron';
import { Event, Emitter } from 'vs/base/common/event';
......@@ -14,59 +14,78 @@ import { mark } from 'vs/base/common/performance';
import { Barrier } from 'vs/base/common/async';
import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { Disposable } from 'vs/base/common/lifecycle';
import { onUnexpectedError } from 'vs/base/common/errors';
export class LifecycleService implements ILifecycleService {
export class LifecycleService extends Disposable implements ILifecycleService {
private static readonly _lastShutdownReasonKey = 'lifecyle.lastShutdownReason';
private static readonly LAST_SHUTDOWN_REASON_KEY = 'lifecyle.lastShutdownReason';
_serviceBrand: any;
private readonly _onWillShutdown = new Emitter<ShutdownEvent>();
private readonly _onShutdown = new Emitter<ShutdownReason>();
private readonly _onWillShutdown = this._register(new Emitter<WillShutdownEvent>());
get onWillShutdown(): Event<WillShutdownEvent> { return this._onWillShutdown.event; }
private readonly _onShutdown = this._register(new Emitter<ShutdownEvent>());
get onShutdown(): Event<ShutdownEvent> { return this._onShutdown.event; }
private readonly _startupKind: StartupKind;
get startupKind(): StartupKind { return this._startupKind; }
private _phase: LifecyclePhase = LifecyclePhase.Starting;
get phase(): LifecyclePhase { return this._phase; }
private _phaseWhen = new Map<LifecyclePhase, Barrier>();
constructor(
@INotificationService private readonly _notificationService: INotificationService,
@IWindowService private readonly _windowService: IWindowService,
@IStorageService private readonly _storageService: IStorageService,
@ILogService private readonly _logService: ILogService
@INotificationService private notificationService: INotificationService,
@IWindowService private windowService: IWindowService,
@IStorageService private storageService: IStorageService,
@ILogService private logService: ILogService
) {
const lastShutdownReason = this._storageService.getInteger(LifecycleService._lastShutdownReasonKey, StorageScope.WORKSPACE);
this._storageService.remove(LifecycleService._lastShutdownReasonKey, StorageScope.WORKSPACE);
super();
this._startupKind = this.resolveStartupKind();
this.registerListeners();
}
private resolveStartupKind(): StartupKind {
const lastShutdownReason = this.storageService.getInteger(LifecycleService.LAST_SHUTDOWN_REASON_KEY, StorageScope.WORKSPACE);
this.storageService.remove(LifecycleService.LAST_SHUTDOWN_REASON_KEY, StorageScope.WORKSPACE);
let startupKind: StartupKind;
if (lastShutdownReason === ShutdownReason.RELOAD) {
this._startupKind = StartupKind.ReloadedWindow;
startupKind = StartupKind.ReloadedWindow;
} else if (lastShutdownReason === ShutdownReason.LOAD) {
this._startupKind = StartupKind.ReopenedWindow;
startupKind = StartupKind.ReopenedWindow;
} else {
this._startupKind = StartupKind.NewWindow;
startupKind = StartupKind.NewWindow;
}
this._logService.trace(`lifecycle: starting up (startup kind: ${this._startupKind})`);
this.logService.trace(`lifecycle: starting up (startup kind: ${this._startupKind})`);
this._registerListeners();
return startupKind;
}
private _registerListeners(): void {
const windowId = this._windowService.getCurrentWindowId();
private registerListeners(): void {
const windowId = this.windowService.getCurrentWindowId();
// Main side indicates that window is about to unload, check for vetos
ipc.on('vscode:onBeforeUnload', (event, reply: { okChannel: string, cancelChannel: string, reason: ShutdownReason }) => {
this._logService.trace(`lifecycle: onBeforeUnload (reason: ${reply.reason})`);
this.logService.trace(`lifecycle: onBeforeUnload (reason: ${reply.reason})`);
// store shutdown reason to retrieve next startup
this._storageService.store(LifecycleService._lastShutdownReasonKey, JSON.stringify(reply.reason), StorageScope.WORKSPACE);
this.storageService.store(LifecycleService.LAST_SHUTDOWN_REASON_KEY, JSON.stringify(reply.reason), StorageScope.WORKSPACE);
// trigger onWillShutdown events and veto collecting
this.onBeforeUnload(reply.reason).then(veto => {
this.handleWillShutdown(reply.reason).then(veto => {
if (veto) {
this._logService.trace('lifecycle: onBeforeUnload prevented via veto');
this._storageService.remove(LifecycleService._lastShutdownReasonKey, StorageScope.WORKSPACE);
this.logService.trace('lifecycle: onBeforeUnload prevented via veto');
this.storageService.remove(LifecycleService.LAST_SHUTDOWN_REASON_KEY, StorageScope.WORKSPACE);
ipc.send(reply.cancelChannel, windowId);
} else {
this._logService.trace('lifecycle: onBeforeUnload continues without veto');
this.logService.trace('lifecycle: onBeforeUnload continues without veto');
ipc.send(reply.okChannel, windowId);
}
});
......@@ -74,14 +93,16 @@ export class LifecycleService implements ILifecycleService {
// Main side indicates that we will indeed shutdown
ipc.on('vscode:onWillUnload', (event, reply: { replyChannel: string, reason: ShutdownReason }) => {
this._logService.trace(`lifecycle: onWillUnload (reason: ${reply.reason})`);
this.logService.trace(`lifecycle: onWillUnload (reason: ${reply.reason})`);
this._onShutdown.fire(reply.reason);
ipc.send(reply.replyChannel, windowId);
// trigger onShutdown events and joining
return this.handleShutdown(reply.reason).then(() => {
ipc.send(reply.replyChannel, windowId);
});
});
}
private onBeforeUnload(reason: ShutdownReason): TPromise<boolean> {
private handleWillShutdown(reason: ShutdownReason): TPromise<boolean> {
const vetos: (boolean | Thenable<boolean>)[] = [];
this._onWillShutdown.fire({
......@@ -91,11 +112,28 @@ export class LifecycleService implements ILifecycleService {
reason
});
return handleVetos(vetos, err => this._notificationService.error(toErrorMessage(err)));
return handleVetos(vetos, err => {
this.notificationService.error(toErrorMessage(err));
onUnexpectedError(err);
});
}
get phase(): LifecyclePhase {
return this._phase;
private handleShutdown(reason: ShutdownReason): Thenable<void> {
const joiners: Thenable<void>[] = [];
this._onShutdown.fire({
join(promise) {
if (promise) {
joiners.push(promise);
}
},
reason
});
return TPromise.join(joiners).then(() => void 0, err => {
this.notificationService.error(toErrorMessage(err));
onUnexpectedError(err);
});
}
set phase(value: LifecyclePhase) {
......@@ -107,7 +145,7 @@ export class LifecycleService implements ILifecycleService {
return;
}
this._logService.trace(`lifecycle: phase changed (value: ${value})`);
this.logService.trace(`lifecycle: phase changed (value: ${value})`);
this._phase = value;
mark(`LifecyclePhase/${LifecyclePhaseToString(value)}`);
......@@ -131,16 +169,4 @@ export class LifecycleService implements ILifecycleService {
return barrier.wait();
}
get startupKind(): StartupKind {
return this._startupKind;
}
get onWillShutdown(): Event<ShutdownEvent> {
return this._onWillShutdown.event;
}
get onShutdown(): Event<ShutdownReason> {
return this._onShutdown.event;
}
}
......@@ -13,6 +13,7 @@ import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { ReadyState } from 'vs/platform/windows/common/windows';
import { handleVetos } from 'vs/platform/lifecycle/common/lifecycle';
import { isMacintosh, isWindows } from 'vs/base/common/platform';
import { Disposable } from 'vs/base/common/lifecycle';
export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService');
......@@ -80,41 +81,39 @@ export interface ILifecycleService {
kill(code?: number): void;
}
export class LifecycleService implements ILifecycleService {
export class LifecycleService extends Disposable implements ILifecycleService {
_serviceBrand: any;
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 };
private quitRequested: boolean;
private windowToCloseRequest: { [windowId: string]: boolean } = Object.create(null);
private quitRequested = false;
private pendingQuitPromise: TPromise<boolean>;
private pendingQuitPromiseComplete: TValueCallback<boolean>;
private oneTimeListenerTokenGenerator: number;
private _wasRestarted: boolean;
private windowCounter: number;
private oneTimeListenerTokenGenerator = 0;
private windowCounter = 0;
private _onBeforeShutdown = new Emitter<void>();
onBeforeShutdown: Event<void> = this._onBeforeShutdown.event;
private _wasRestarted: boolean = false;
get wasRestarted(): boolean { return this._wasRestarted; }
private _onShutdown = new Emitter<void>();
onShutdown: Event<void> = this._onShutdown.event;
private readonly _onBeforeShutdown = this._register(new Emitter<void>());
readonly onBeforeShutdown: Event<void> = this._onBeforeShutdown.event;
private _onBeforeWindowClose = new Emitter<ICodeWindow>();
onBeforeWindowClose: Event<ICodeWindow> = this._onBeforeWindowClose.event;
private readonly _onShutdown = this._register(new Emitter<void>());
readonly onShutdown: Event<void> = this._onShutdown.event;
private _onBeforeWindowUnload = new Emitter<IWindowUnloadEvent>();
onBeforeWindowUnload: Event<IWindowUnloadEvent> = this._onBeforeWindowUnload.event;
private readonly _onBeforeWindowClose = this._register(new Emitter<ICodeWindow>());
readonly onBeforeWindowClose: Event<ICodeWindow> = this._onBeforeWindowClose.event;
private readonly _onBeforeWindowUnload = this._register(new Emitter<IWindowUnloadEvent>());
readonly onBeforeWindowUnload: Event<IWindowUnloadEvent> = this._onBeforeWindowUnload.event;
constructor(
@ILogService private logService: ILogService,
@IStateService private stateService: IStateService
) {
this.windowToCloseRequest = Object.create(null);
this.quitRequested = false;
this.oneTimeListenerTokenGenerator = 0;
this._wasRestarted = false;
this.windowCounter = 0;
super();
this.handleRestarted();
}
......@@ -127,10 +126,6 @@ export class LifecycleService implements ILifecycleService {
}
}
get wasRestarted(): boolean {
return this._wasRestarted;
}
get isQuitRequested(): boolean {
return !!this.quitRequested;
}
......
......@@ -10,6 +10,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { IWorkspaceStorageChangeEvent, INextWorkspaceStorageService, StorageScope } from 'vs/platform/storage2/common/nextWorkspaceStorageService';
import { NextStorageServiceImpl } from 'vs/platform/storage2/node/nextStorageServiceImpl';
import { INextStorageService } from 'vs/platform/storage2/common/nextStorageService';
import { ILifecycleService, ShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
export class NextWorkspaceStorageServiceImpl extends Disposable implements INextWorkspaceStorageService {
_serviceBrand: any;
......@@ -23,7 +24,8 @@ export class NextWorkspaceStorageServiceImpl extends Disposable implements INext
constructor(
workspaceDBPath: string,
@ILogService logService: ILogService,
@IEnvironmentService environmentService: IEnvironmentService
@IEnvironmentService environmentService: IEnvironmentService,
@ILifecycleService private lifecycleService: ILifecycleService
) {
super();
......@@ -36,12 +38,18 @@ export class NextWorkspaceStorageServiceImpl extends Disposable implements INext
private registerListeners(): void {
this._register(this.globalStorage.onDidChangeStorage(keys => this.handleDidChangeStorage(keys, StorageScope.GLOBAL)));
this._register(this.workspaceStorage.onDidChangeStorage(keys => this.handleDidChangeStorage(keys, StorageScope.WORKSPACE)));
this._register(this.lifecycleService.onShutdown(event => this.onShutdown(event)));
}
private handleDidChangeStorage(keys: Set<string>, scope: StorageScope): void {
this._onDidChangeStorage.fire({ keys, scope });
}
private onShutdown(event: ShutdownEvent): void {
event.join(this.close());
}
init(): Promise<void> {
return Promise.all([this.globalStorage.init(), this.workspaceStorage.init()]).then(() => void 0);
}
......
......@@ -380,7 +380,7 @@ export class WorkbenchShell extends Disposable {
serviceCollection.set(IDialogService, instantiationService.createInstance(DialogService));
const lifecycleService = instantiationService.createInstance(LifecycleService);
this._register(lifecycleService.onShutdown(reason => this.dispose(reason)));
this._register(lifecycleService.onShutdown(event => this.dispose(event.reason)));
serviceCollection.set(ILifecycleService, lifecycleService);
this.lifecycleService = lifecycleService;
......
......@@ -9,7 +9,7 @@ import * as objects from 'vs/base/common/objects';
import { TPromise } from 'vs/base/common/winjs.base';
import { isWindows } from 'vs/base/common/platform';
import { findFreePort } from 'vs/base/node/ports';
import { ILifecycleService, ShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
import { ILifecycleService, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
......@@ -536,7 +536,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
}
}
private _onWillShutdown(event: ShutdownEvent): void {
private _onWillShutdown(event: WillShutdownEvent): void {
// If the extension development host was started without debugger attached we need
// to communicate this back to the main side to terminate the debug session
......
......@@ -7,7 +7,7 @@ import * as sinon from 'sinon';
import * as platform from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { ILifecycleService, ShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
import { ILifecycleService, WillShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestWindowsService, TestContextService, TestFileService } from 'vs/workbench/test/workbenchTestServices';
import { toResource } from 'vs/base/test/common/utils';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
......@@ -37,7 +37,7 @@ class ServiceAccessor {
}
}
class ShutdownEventImpl implements ShutdownEvent {
class ShutdownEventImpl implements WillShutdownEvent {
public value: boolean | TPromise<boolean>;
public reason = ShutdownReason.CLOSE;
......
......@@ -26,7 +26,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { IEditorOptions, IResourceInput } from 'vs/platform/editor/common/editor';
import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IWorkspaceContextService, IWorkspace as IWorkbenchWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, Workspace } from 'vs/platform/workspace/common/workspace';
import { ILifecycleService, ShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { ILifecycleService, WillShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, ShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService';
import { FileOperationEvent, IFileService, IResolveContentOptions, FileOperationError, IFileStat, IResolveFileResult, FileChangesEvent, IResolveFileOptions, IContent, IUpdateContentOptions, IStreamContent, ICreateFileOptions, ITextSnapshot, IResourceEncodings } from 'vs/platform/files/common/files';
......@@ -1152,26 +1152,29 @@ export class TestLifecycleService implements ILifecycleService {
public phase: LifecyclePhase;
public startupKind: StartupKind;
private _onWillShutdown = new Emitter<ShutdownEvent>();
private _onShutdown = new Emitter<ShutdownReason>();
private _onWillShutdown = new Emitter<WillShutdownEvent>();
private _onShutdown = new Emitter<ShutdownEvent>();
when(): TPromise<void> {
return TPromise.as(void 0);
}
public fireShutdown(reason = ShutdownReason.QUIT): void {
this._onShutdown.fire(reason);
this._onShutdown.fire({
join: () => { },
reason
});
}
public fireWillShutdown(event: ShutdownEvent): void {
public fireWillShutdown(event: WillShutdownEvent): void {
this._onWillShutdown.fire(event);
}
public get onWillShutdown(): Event<ShutdownEvent> {
public get onWillShutdown(): Event<WillShutdownEvent> {
return this._onWillShutdown.event;
}
public get onShutdown(): Event<ShutdownReason> {
public get onShutdown(): Event<ShutdownEvent> {
return this._onShutdown.event;
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册