Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts
5256 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { handleVetos } from '../../../../platform/lifecycle/common/lifecycle.js';
7
import { ShutdownReason, ILifecycleService, IWillShutdownEventJoiner, WillShutdownJoinerOrder } from '../common/lifecycle.js';
8
import { IStorageService } from '../../../../platform/storage/common/storage.js';
9
import { ipcRenderer } from '../../../../base/parts/sandbox/electron-browser/globals.js';
10
import { ILogService } from '../../../../platform/log/common/log.js';
11
import { AbstractLifecycleService } from '../common/lifecycleService.js';
12
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
13
import { INativeHostService } from '../../../../platform/native/common/native.js';
14
import { Promises, disposableTimeout, raceCancellation } from '../../../../base/common/async.js';
15
import { toErrorMessage } from '../../../../base/common/errorMessage.js';
16
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
17
18
export class NativeLifecycleService extends AbstractLifecycleService {
19
20
private static readonly BEFORE_SHUTDOWN_WARNING_DELAY = 5000;
21
private static readonly WILL_SHUTDOWN_WARNING_DELAY = 800;
22
23
constructor(
24
@INativeHostService private readonly nativeHostService: INativeHostService,
25
@IStorageService storageService: IStorageService,
26
@ILogService logService: ILogService
27
) {
28
super(logService, storageService);
29
30
this.registerListeners();
31
}
32
33
private registerListeners(): void {
34
const windowId = this.nativeHostService.windowId;
35
36
// Main side indicates that window is about to unload, check for vetos
37
ipcRenderer.on('vscode:onBeforeUnload', async (event: unknown, ...args: unknown[]) => {
38
const reply = args[0] as { okChannel: string; cancelChannel: string; reason: ShutdownReason };
39
this.logService.trace(`[lifecycle] onBeforeUnload (reason: ${reply.reason})`);
40
41
// trigger onBeforeShutdown events and veto collecting
42
const veto = await this.handleBeforeShutdown(reply.reason);
43
44
// veto: cancel unload
45
if (veto) {
46
this.logService.trace('[lifecycle] onBeforeUnload prevented via veto');
47
48
// Indicate as event
49
this._onShutdownVeto.fire();
50
51
ipcRenderer.send(reply.cancelChannel, windowId);
52
}
53
54
// no veto: allow unload
55
else {
56
this.logService.trace('[lifecycle] onBeforeUnload continues without veto');
57
58
this.shutdownReason = reply.reason;
59
ipcRenderer.send(reply.okChannel, windowId);
60
}
61
});
62
63
// Main side indicates that we will indeed shutdown
64
ipcRenderer.on('vscode:onWillUnload', async (event: unknown, ...args: unknown[]) => {
65
const reply = args[0] as { replyChannel: string; reason: ShutdownReason };
66
this.logService.trace(`[lifecycle] onWillUnload (reason: ${reply.reason})`);
67
68
// trigger onWillShutdown events and joining
69
await this.handleWillShutdown(reply.reason);
70
71
// trigger onDidShutdown event now that we know we will quit
72
this._onDidShutdown.fire();
73
74
// acknowledge to main side
75
ipcRenderer.send(reply.replyChannel, windowId);
76
});
77
}
78
79
protected async handleBeforeShutdown(reason: ShutdownReason): Promise<boolean> {
80
const logService = this.logService;
81
82
const vetos: (boolean | Promise<boolean>)[] = [];
83
const pendingVetos = new Set<string>();
84
85
let finalVeto: (() => boolean | Promise<boolean>) | undefined = undefined;
86
let finalVetoId: string | undefined = undefined;
87
88
// before-shutdown event with veto support
89
this._onBeforeShutdown.fire({
90
reason,
91
veto(value, id) {
92
vetos.push(value);
93
94
// Log any veto instantly
95
if (value === true) {
96
logService.info(`[lifecycle]: Shutdown was prevented (id: ${id})`);
97
}
98
99
// Track promise completion
100
else if (value instanceof Promise) {
101
pendingVetos.add(id);
102
value.then(veto => {
103
if (veto === true) {
104
logService.info(`[lifecycle]: Shutdown was prevented (id: ${id})`);
105
}
106
}).finally(() => pendingVetos.delete(id));
107
}
108
},
109
finalVeto(value, id) {
110
if (!finalVeto) {
111
finalVeto = value;
112
finalVetoId = id;
113
} else {
114
throw new Error(`[lifecycle]: Final veto is already defined (id: ${id})`);
115
}
116
}
117
});
118
119
const longRunningBeforeShutdownWarning = disposableTimeout(() => {
120
logService.warn(`[lifecycle] onBeforeShutdown is taking a long time, pending operations: ${Array.from(pendingVetos).join(', ')}`);
121
}, NativeLifecycleService.BEFORE_SHUTDOWN_WARNING_DELAY);
122
123
try {
124
125
// First: run list of vetos in parallel
126
let veto = await handleVetos(vetos, error => this.handleBeforeShutdownError(error, reason));
127
if (veto) {
128
return veto;
129
}
130
131
// Second: run the final veto if defined
132
if (finalVeto) {
133
try {
134
pendingVetos.add(finalVetoId as unknown as string);
135
veto = await (finalVeto as () => Promise<boolean>)();
136
if (veto) {
137
logService.info(`[lifecycle]: Shutdown was prevented by final veto (id: ${finalVetoId})`);
138
}
139
} catch (error) {
140
veto = true; // treat error as veto
141
142
this.handleBeforeShutdownError(error, reason);
143
}
144
}
145
146
return veto;
147
} finally {
148
longRunningBeforeShutdownWarning.dispose();
149
}
150
}
151
152
private handleBeforeShutdownError(error: Error, reason: ShutdownReason): void {
153
this.logService.error(`[lifecycle]: Error during before-shutdown phase (error: ${toErrorMessage(error)})`);
154
155
this._onBeforeShutdownError.fire({ reason, error });
156
}
157
158
protected async handleWillShutdown(reason: ShutdownReason): Promise<void> {
159
this._willShutdown = true;
160
161
const joiners: Promise<void>[] = [];
162
const lastJoiners: (() => Promise<void>)[] = [];
163
const pendingJoiners = new Set<IWillShutdownEventJoiner>();
164
const cts = new CancellationTokenSource();
165
this._onWillShutdown.fire({
166
reason,
167
token: cts.token,
168
joiners: () => Array.from(pendingJoiners.values()),
169
join(promiseOrPromiseFn, joiner) {
170
pendingJoiners.add(joiner);
171
172
if (joiner.order === WillShutdownJoinerOrder.Last) {
173
const promiseFn = typeof promiseOrPromiseFn === 'function' ? promiseOrPromiseFn : () => promiseOrPromiseFn;
174
lastJoiners.push(() => promiseFn().finally(() => pendingJoiners.delete(joiner)));
175
} else {
176
const promise = typeof promiseOrPromiseFn === 'function' ? promiseOrPromiseFn() : promiseOrPromiseFn;
177
promise.finally(() => pendingJoiners.delete(joiner));
178
joiners.push(promise);
179
}
180
},
181
force: () => {
182
cts.dispose(true);
183
}
184
});
185
186
const longRunningWillShutdownWarning = disposableTimeout(() => {
187
this.logService.warn(`[lifecycle] onWillShutdown is taking a long time, pending operations: ${Array.from(pendingJoiners).map(joiner => joiner.id).join(', ')}`);
188
}, NativeLifecycleService.WILL_SHUTDOWN_WARNING_DELAY);
189
190
try {
191
await raceCancellation(Promises.settled(joiners), cts.token);
192
} catch (error) {
193
this.logService.error(`[lifecycle]: Error during will-shutdown phase in default joiners (error: ${toErrorMessage(error)})`);
194
}
195
196
try {
197
await raceCancellation(Promises.settled(lastJoiners.map(lastJoiner => lastJoiner())), cts.token);
198
} catch (error) {
199
this.logService.error(`[lifecycle]: Error during will-shutdown phase in last joiners (error: ${toErrorMessage(error)})`);
200
}
201
202
longRunningWillShutdownWarning.dispose();
203
}
204
205
shutdown(): Promise<void> {
206
return this.nativeHostService.closeWindow();
207
}
208
}
209
210
registerSingleton(ILifecycleService, NativeLifecycleService, InstantiationType.Eager);
211
212