提交 093239c5 编写于 作者: B Benjamin Pasero

some lifecycle 💄

上级 9c900345
......@@ -9,7 +9,7 @@ import { WindowsManager } from 'vs/code/electron-main/windows';
import { IWindowsService, OpenContext, ActiveWindowManager } from 'vs/platform/windows/common/windows';
import { WindowsChannel } from 'vs/platform/windows/node/windowsIpc';
import { WindowsService } from 'vs/platform/windows/electron-main/windowsService';
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { ILifecycleService, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { getShellEnvironment } from 'vs/code/node/shellEnv';
import { IUpdateService } from 'vs/platform/update/common/update';
import { UpdateChannel } from 'vs/platform/update/node/updateIpc';
......@@ -36,7 +36,7 @@ import { getDelayedChannel, StaticRouter } from 'vs/base/parts/ipc/node/ipc';
import product from 'vs/platform/node/product';
import pkg from 'vs/platform/node/package';
import { ProxyAuthHandler } from 'vs/code/electron-main/auth';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
import { TPromise } from 'vs/base/common/winjs.base';
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
......@@ -74,11 +74,10 @@ import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/platform/remote/node/remoteA
import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { SnapUpdateService } from 'vs/platform/update/electron-main/updateService.snap';
export class CodeApplication {
export class CodeApplication extends Disposable {
private static readonly MACHINE_ID_KEY = 'telemetry.machineId';
private toDispose: IDisposable[];
private windowsMainService: IWindowsMainService;
private electronIpcServer: ElectronIPCServer;
......@@ -98,7 +97,10 @@ export class CodeApplication {
@IHistoryMainService private historyMainService: IHistoryMainService,
@ILabelService private labelService: ILabelService
) {
this.toDispose = [mainIpcServer, configurationService];
super();
this._register(mainIpcServer);
this._register(configurationService);
this.registerListeners();
}
......@@ -113,11 +115,8 @@ export class CodeApplication {
// Contextmenu via IPC support
registerContextMenuListener();
app.on('will-quit', () => {
this.logService.trace('App#will-quit: disposing resources');
this.dispose();
});
// Dispose on shutdown
this.lifecycleService.onWillShutdown(() => this.dispose());
app.on('accessibility-support-changed', (event: Event, accessibilitySupportEnabled: boolean) => {
if (this.windowsMainService) {
......@@ -428,64 +427,63 @@ export class CodeApplication {
// Services
const appInstantiationService = this.initServices(machineId);
let promise: TPromise<any> = TPromise.as(null);
// 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.toDispose.push(server);
this._register(server);
});
}
return promise.then(() => {
// Setup Auth Handler
const authHandler = appInstantiationService.createInstance(ProxyAuthHandler);
this.toDispose.push(authHandler);
// Open Windows
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(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.logService.info(`Tracing: waiting for windows to get ready...`);
let recordingStopped = false;
const stopRecording = (timeout) => {
if (recordingStopped) {
return;
}
recordingStopped = true; // only once
contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => {
if (!timeout) {
this.windowsMainService.showMessageBox({
type: 'info',
message: localize('trace.message', "Successfully created trace."),
detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path),
buttons: [localize('trace.ok', "Ok")]
}, this.windowsMainService.getLastActiveWindow());
} else {
this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`);
}
});
};
// Wait up to 30s before creating the trace anyways
const timeoutHandle = setTimeout(() => stopRecording(true), 30000);
// Wait for all windows to get ready and stop tracing then
TPromise.join(windows.map(window => window.ready())).then(() => {
clearTimeout(timeoutHandle);
stopRecording(false);
});
// Setup Auth Handler
const authHandler = appInstantiationService.createInstance(ProxyAuthHandler);
this._register(authHandler);
// Open Windows
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(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);
}
});
}
private stopTracingEventually(windows: ICodeWindow[]): void {
this.logService.info(`Tracing: waiting for windows to get ready...`);
let recordingStopped = false;
const stopRecording = (timeout) => {
if (recordingStopped) {
return;
}
recordingStopped = true; // only once
contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => {
if (!timeout) {
this.windowsMainService.showMessageBox({
type: 'info',
message: localize('trace.message', "Successfully created trace."),
detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path),
buttons: [localize('trace.ok', "Ok")]
}, this.windowsMainService.getLastActiveWindow());
} else {
this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`);
}
});
};
// Wait up to 30s before creating the trace anyways
const timeoutHandle = setTimeout(() => stopRecording(true), 30000);
// Wait for all windows to get ready and stop tracing then
TPromise.join(windows.map(window => window.ready())).then(() => {
clearTimeout(timeoutHandle);
stopRecording(false);
});
}
......@@ -525,7 +523,7 @@ export class CodeApplication {
services.set(IIssueService, new SyncDescriptor(IssueService, [machineId, this.userEnv]));
services.set(IMenubarService, new SyncDescriptor(MenubarService));
// Telemtry
// Telemetry
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
const channel = getDelayedChannel(this.sharedProcessClient.then(c => c.getChannel('telemetryAppender')));
const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService));
......@@ -581,7 +579,7 @@ export class CodeApplication {
this.sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel));
// Lifecycle
this.lifecycleService.ready();
(this.lifecycleService as LifecycleService).ready();
// Propagate to clients
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); // TODO@Joao: unfold this
......@@ -619,7 +617,7 @@ export class CodeApplication {
// Watch Electron URLs and forward them to the UrlService
const urls = args['open-url'] ? args._urls : [];
const urlListener = new ElectronURLListener(urls, urlService, this.windowsMainService);
this.toDispose.push(urlListener);
this._register(urlListener);
this.windowsMainService.ready(this.userEnv);
......@@ -651,7 +649,7 @@ export class CodeApplication {
try {
const Mutex = (require.__$__nodeRequire('windows-mutex') as any).Mutex;
windowsMutex = new Mutex(product.win32MutexName);
this.toDispose.push({ dispose: () => windowsMutex.release() });
this._register(toDisposable(() => windowsMutex.release()));
} catch (e) {
if (!this.environmentService.isBuilt) {
windowsMainService.showMessageBox({
......@@ -688,10 +686,6 @@ export class CodeApplication {
// Start shared process after a while
const sharedProcess = new RunOnceScheduler(() => this.sharedProcess.spawn(), 3000);
sharedProcess.schedule();
this.toDispose.push(sharedProcess);
}
private dispose(): void {
this.toDispose = dispose(this.toDispose);
this._register(sharedProcess);
}
}
......@@ -54,17 +54,13 @@ function createServices(args: ParsedArgs, bufferLogService: BufferLogService): I
const services = new ServiceCollection();
const environmentService = new EnvironmentService(args, process.execPath);
const consoleLogService = new ConsoleLogMainService(getLogLevel(environmentService));
const logService = new MultiplexLogService([consoleLogService, bufferLogService]);
const labelService = new LabelService(environmentService, undefined, undefined);
const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
process.once('exit', () => logService.dispose());
// Eventually cleanup
setTimeout(() => cleanupOlderLogs(environmentService).then(null, err => console.error(err)), 10000);
services.set(IEnvironmentService, environmentService);
services.set(ILabelService, labelService);
services.set(ILabelService, new LabelService(environmentService, void 0, void 0));
services.set(ILogService, logService);
services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService));
......@@ -294,36 +290,44 @@ function quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void
lifecycleService.kill(exitCode);
}
function patchEnvironment(environmentService: IEnvironmentService): typeof process.env {
const instanceEnvironment: typeof process.env = {
VSCODE_IPC_HOOK: environmentService.mainIPCHandle,
VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG'],
VSCODE_LOGS: process.env['VSCODE_LOGS']
};
if (process.env['VSCODE_PORTABLE']) {
instanceEnvironment['VSCODE_PORTABLE'] = process.env['VSCODE_PORTABLE'];
}
assign(process.env, instanceEnvironment);
return instanceEnvironment;
}
function startup(args: ParsedArgs): void {
// We need to buffer the spdlog logs until we are sure
// we are the only instance running, otherwise we'll have concurrent
// log file access on Windows
// https://github.com/Microsoft/vscode/issues/41218
// log file access on Windows (https://github.com/Microsoft/vscode/issues/41218)
const bufferLogService = new BufferLogService();
const instantiationService = createServices(args, bufferLogService);
const instantiationService = createServices(args, bufferLogService);
instantiationService.invokeFunction(accessor => {
// Patch `process.env` with the instance's environment
const environmentService = accessor.get(IEnvironmentService);
const instanceEnv: typeof process.env = {
VSCODE_IPC_HOOK: environmentService.mainIPCHandle,
VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG'],
VSCODE_LOGS: process.env['VSCODE_LOGS']
};
if (process.env['VSCODE_PORTABLE']) {
instanceEnv['VSCODE_PORTABLE'] = process.env['VSCODE_PORTABLE'];
}
assign(process.env, instanceEnv);
// Patch `process.env` with the instance's environment
const instanceEnvironment = patchEnvironment(environmentService);
// Startup
return instantiationService.invokeFunction(a => createPaths(a.get(IEnvironmentService)))
return instantiationService
.invokeFunction(a => createPaths(a.get(IEnvironmentService)))
.then(() => instantiationService.invokeFunction(setupIPC))
.then(mainIpcServer => {
bufferLogService.logger = createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath);
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnv).startup();
return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();
});
}).then(null, err => instantiationService.invokeFunction(quit, err));
}
......
......@@ -70,7 +70,7 @@ export class SharedProcess implements ISharedProcess {
const disposables: IDisposable[] = [];
this.lifecycleService.onShutdown(() => {
this.lifecycleService.onWillShutdown(() => {
dispose(disposables);
// Shut the shared process down when we are quitting
......
......@@ -15,7 +15,7 @@ import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
import { hasArgs, asArray } from 'vs/platform/environment/node/argv';
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, app } from 'electron';
import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/node/paths';
import { ILifecycleService, UnloadReason, IWindowUnloadEvent } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { ILifecycleService, UnloadReason, IWindowUnloadEvent, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions } from 'vs/platform/windows/common/windows';
......@@ -1285,7 +1285,7 @@ export class WindowsManager implements IWindowsMainService {
window.win.on('closed', () => this.onWindowClosed(window));
// Lifecycle
this.lifecycleService.registerWindow(window);
(this.lifecycleService as LifecycleService).registerWindow(window);
}
// Existing window
......
......@@ -53,7 +53,7 @@ export interface ILifecycleService {
* vetoed the shutdown sequence. At this point listeners are ensured that the application will
* quit without veto.
*/
onShutdown: Event<void>;
onWillShutdown: Event<void>;
/**
* We provide our own event when we close a window because the general window.on('close')
......@@ -86,9 +86,6 @@ export interface ILifecycleService {
* Forcefully shutdown the application. No shutdown handlers are triggered.
*/
kill(code?: number): void;
ready(): void;
registerWindow(window: ICodeWindow): void;
}
export class LifecycleService extends Disposable implements ILifecycleService {
......@@ -112,8 +109,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 _onShutdown = this._register(new Emitter<void>());
readonly onShutdown: Event<void> = this._onShutdown.event;
private readonly _onWillShutdown = this._register(new Emitter<void>());
readonly onWillShutdown: Event<void> = this._onWillShutdown.event;
private readonly _onBeforeWindowClose = this._register(new Emitter<ICodeWindow>());
readonly onBeforeWindowClose: Event<ICodeWindow> = this._onBeforeWindowClose.event;
......@@ -163,7 +160,7 @@ export class LifecycleService extends Disposable implements ILifecycleService {
// the onShutdown() event directly because there is no veto to be expected.
if (isMacintosh && this.windowCounter === 0) {
this.logService.trace('Lifecycle#onShutdown.fire()');
this._onShutdown.fire();
this._onWillShutdown.fire();
}
});
......@@ -228,7 +225,7 @@ export class LifecycleService extends Disposable implements ILifecycleService {
// the application continues running (unless quit was actually requested)
if (this.windowCounter === 0 && (!isMacintosh || this._quitRequested)) {
this.logService.trace('Lifecycle#onShutdown.fire()');
this._onShutdown.fire();
this._onWillShutdown.fire();
}
});
}
......@@ -363,12 +360,6 @@ export class LifecycleService extends Disposable implements ILifecycleService {
return this.pendingQuitPromise;
}
kill(code?: number): void {
this.logService.trace('Lifecycle#kill()');
app.exit(code);
}
relaunch(options?: { addArgs?: string[], removeArgs?: string[] }): void {
this.logService.trace('Lifecycle#relaunch()');
......@@ -386,9 +377,11 @@ export class LifecycleService extends Disposable implements ILifecycleService {
}
}
let vetoed = false;
let quitVetoed = false;
app.once('quit', () => {
if (!vetoed) {
if (!quitVetoed) {
// Remember the reason for quit was to restart
this.stateService.setItem(LifecycleService.QUIT_FROM_RESTART_MARKER, true);
// Windows: we are about to restart and as such we need to restore the original
......@@ -406,12 +399,19 @@ export class LifecycleService extends Disposable implements ILifecycleService {
this.logService.error(err);
}
// relaunch after we are sure there is no veto
app.relaunch({ args });
}
});
this.quit().then(veto => {
vetoed = veto;
});
// app.relaunch() does not quit automatically, so we quit first,
// check for vetoes and then relaunch from the app.on('quit') event
this.quit().then(veto => quitVetoed = veto);
}
kill(code?: number): void {
this.logService.trace('Lifecycle#kill()');
app.exit(code);
}
}
......@@ -22,6 +22,7 @@ import { IMenubarData, IMenubarKeybinding, MenubarMenuItem, isMenubarMenuItemSep
import { URI } from 'vs/base/common/uri';
import { ILabelService } from 'vs/platform/label/common/label';
import { IStateService } from 'vs/platform/state/common/state';
import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
const telemetryFrom = 'menu';
......@@ -35,7 +36,7 @@ export class Menubar {
private static readonly MAX_MENU_RECENT_ENTRIES = 10;
private static readonly lastKnownMenubarStorageKey = 'lastKnownMenubarData';
private isQuitting: boolean;
private willShutdown: boolean;
private appMenuInstalled: boolean;
private closedLastWindow: boolean;
......@@ -61,7 +62,8 @@ export class Menubar {
@ITelemetryService private telemetryService: ITelemetryService,
@IHistoryMainService private historyMainService: IHistoryMainService,
@ILabelService private labelService: ILabelService,
@IStateService private stateService: IStateService
@IStateService private stateService: IStateService,
@ILifecycleService private lifecycleService: ILifecycleService
) {
this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0);
......@@ -151,9 +153,7 @@ export class Menubar {
private registerListeners(): void {
// Keep flag when app quits
app.on('will-quit', () => {
this.isQuitting = true;
});
this.lifecycleService.onWillShutdown(() => this.willShutdown = true);
// // Listen to some events from window service to update menu
this.historyMainService.onRecentlyOpenedChange(() => this.scheduleUpdateMenu());
......@@ -212,9 +212,9 @@ export class Menubar {
// See also https://github.com/electron/electron/issues/846
//
// Run delayed to prevent updating menu while it is open
if (!this.isQuitting) {
if (!this.willShutdown) {
setTimeout(() => {
if (!this.isQuitting) {
if (!this.willShutdown) {
this.install();
}
}, 10 /* delay this because there is an issue with updating a menu when it is open */);
......
......@@ -132,6 +132,11 @@ export class StorageService extends Disposable implements IStorageService {
mark('willInitWorkspaceStorage');
return this.createWorkspaceStorage(workspaceStoragePath, result.wasCreated ? StorageHint.STORAGE_DOES_NOT_EXIST : void 0).init().then(() => {
mark('didInitWorkspaceStorage');
}, error => {
mark('didInitWorkspaceStorage');
return Promise.reject(error);
}).then(() => {
// Migrate storage if this is the first start and we are not using in-memory
let migrationPromise: Thenable<void>;
......
......@@ -153,7 +153,7 @@ export class SnapUpdateService extends AbstractUpdateService2 {
const onDebouncedCurrentChange = debounceEvent(onCurrentChange, (_, e) => e, 2000);
const listener = onDebouncedCurrentChange(this.checkForUpdates, this);
lifecycleService.onShutdown(() => {
lifecycleService.onWillShutdown(() => {
listener.dispose();
watcher.close();
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册