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
3296 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, reply: { okChannel: string; cancelChannel: string; reason: ShutdownReason }) => {
38
this.logService.trace(`[lifecycle] onBeforeUnload (reason: ${reply.reason})`);
39
40
// trigger onBeforeShutdown events and veto collecting
41
const veto = await this.handleBeforeShutdown(reply.reason);
42
43
// veto: cancel unload
44
if (veto) {
45
this.logService.trace('[lifecycle] onBeforeUnload prevented via veto');
46
47
// Indicate as event
48
this._onShutdownVeto.fire();
49
50
ipcRenderer.send(reply.cancelChannel, windowId);
51
}
52
53
// no veto: allow unload
54
else {
55
this.logService.trace('[lifecycle] onBeforeUnload continues without veto');
56
57
this.shutdownReason = reply.reason;
58
ipcRenderer.send(reply.okChannel, windowId);
59
}
60
});
61
62
// Main side indicates that we will indeed shutdown
63
ipcRenderer.on('vscode:onWillUnload', async (event: unknown, reply: { replyChannel: string; reason: ShutdownReason }) => {
64
this.logService.trace(`[lifecycle] onWillUnload (reason: ${reply.reason})`);
65
66
// trigger onWillShutdown events and joining
67
await this.handleWillShutdown(reply.reason);
68
69
// trigger onDidShutdown event now that we know we will quit
70
this._onDidShutdown.fire();
71
72
// acknowledge to main side
73
ipcRenderer.send(reply.replyChannel, windowId);
74
});
75
}
76
77
protected async handleBeforeShutdown(reason: ShutdownReason): Promise<boolean> {
78
const logService = this.logService;
79
80
const vetos: (boolean | Promise<boolean>)[] = [];
81
const pendingVetos = new Set<string>();
82
83
let finalVeto: (() => boolean | Promise<boolean>) | undefined = undefined;
84
let finalVetoId: string | undefined = undefined;
85
86
// before-shutdown event with veto support
87
this._onBeforeShutdown.fire({
88
reason,
89
veto(value, id) {
90
vetos.push(value);
91
92
// Log any veto instantly
93
if (value === true) {
94
logService.info(`[lifecycle]: Shutdown was prevented (id: ${id})`);
95
}
96
97
// Track promise completion
98
else if (value instanceof Promise) {
99
pendingVetos.add(id);
100
value.then(veto => {
101
if (veto === true) {
102
logService.info(`[lifecycle]: Shutdown was prevented (id: ${id})`);
103
}
104
}).finally(() => pendingVetos.delete(id));
105
}
106
},
107
finalVeto(value, id) {
108
if (!finalVeto) {
109
finalVeto = value;
110
finalVetoId = id;
111
} else {
112
throw new Error(`[lifecycle]: Final veto is already defined (id: ${id})`);
113
}
114
}
115
});
116
117
const longRunningBeforeShutdownWarning = disposableTimeout(() => {
118
logService.warn(`[lifecycle] onBeforeShutdown is taking a long time, pending operations: ${Array.from(pendingVetos).join(', ')}`);
119
}, NativeLifecycleService.BEFORE_SHUTDOWN_WARNING_DELAY);
120
121
try {
122
123
// First: run list of vetos in parallel
124
let veto = await handleVetos(vetos, error => this.handleBeforeShutdownError(error, reason));
125
if (veto) {
126
return veto;
127
}
128
129
// Second: run the final veto if defined
130
if (finalVeto) {
131
try {
132
pendingVetos.add(finalVetoId as unknown as string);
133
veto = await (finalVeto as () => Promise<boolean>)();
134
if (veto) {
135
logService.info(`[lifecycle]: Shutdown was prevented by final veto (id: ${finalVetoId})`);
136
}
137
} catch (error) {
138
veto = true; // treat error as veto
139
140
this.handleBeforeShutdownError(error, reason);
141
}
142
}
143
144
return veto;
145
} finally {
146
longRunningBeforeShutdownWarning.dispose();
147
}
148
}
149
150
private handleBeforeShutdownError(error: Error, reason: ShutdownReason): void {
151
this.logService.error(`[lifecycle]: Error during before-shutdown phase (error: ${toErrorMessage(error)})`);
152
153
this._onBeforeShutdownError.fire({ reason, error });
154
}
155
156
protected async handleWillShutdown(reason: ShutdownReason): Promise<void> {
157
this._willShutdown = true;
158
159
const joiners: Promise<void>[] = [];
160
const lastJoiners: (() => Promise<void>)[] = [];
161
const pendingJoiners = new Set<IWillShutdownEventJoiner>();
162
const cts = new CancellationTokenSource();
163
this._onWillShutdown.fire({
164
reason,
165
token: cts.token,
166
joiners: () => Array.from(pendingJoiners.values()),
167
join(promiseOrPromiseFn, joiner) {
168
pendingJoiners.add(joiner);
169
170
if (joiner.order === WillShutdownJoinerOrder.Last) {
171
const promiseFn = typeof promiseOrPromiseFn === 'function' ? promiseOrPromiseFn : () => promiseOrPromiseFn;
172
lastJoiners.push(() => promiseFn().finally(() => pendingJoiners.delete(joiner)));
173
} else {
174
const promise = typeof promiseOrPromiseFn === 'function' ? promiseOrPromiseFn() : promiseOrPromiseFn;
175
promise.finally(() => pendingJoiners.delete(joiner));
176
joiners.push(promise);
177
}
178
},
179
force: () => {
180
cts.dispose(true);
181
}
182
});
183
184
const longRunningWillShutdownWarning = disposableTimeout(() => {
185
this.logService.warn(`[lifecycle] onWillShutdown is taking a long time, pending operations: ${Array.from(pendingJoiners).map(joiner => joiner.id).join(', ')}`);
186
}, NativeLifecycleService.WILL_SHUTDOWN_WARNING_DELAY);
187
188
try {
189
await raceCancellation(Promises.settled(joiners), cts.token);
190
} catch (error) {
191
this.logService.error(`[lifecycle]: Error during will-shutdown phase in default joiners (error: ${toErrorMessage(error)})`);
192
}
193
194
try {
195
await raceCancellation(Promises.settled(lastJoiners.map(lastJoiner => lastJoiner())), cts.token);
196
} catch (error) {
197
this.logService.error(`[lifecycle]: Error during will-shutdown phase in last joiners (error: ${toErrorMessage(error)})`);
198
}
199
200
longRunningWillShutdownWarning.dispose();
201
}
202
203
shutdown(): Promise<void> {
204
return this.nativeHostService.closeWindow();
205
}
206
}
207
208
registerSingleton(ILifecycleService, NativeLifecycleService, InstantiationType.Eager);
209
210