Skip to content
Open
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 59 additions & 32 deletions src/vs/platform/update/electron-main/updateService.win32.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,56 +344,76 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
const cachePath = await this.cachePath;
const sessionEndFlagPath = path.join(cachePath, 'session-ending.flag');
const cancelFilePath = path.join(cachePath, `cancel.flag`);
await this.unlink(cancelFilePath);

const progressFilePath = path.join(cachePath, `update-progress`);
await this.unlink(progressFilePath);

this.availableUpdate.updateFilePath = path.join(cachePath, `CodeSetup-${this.productService.quality}-${update.version}.flag`);
this.availableUpdate.cancelFilePath = cancelFilePath;

await pfs.Promises.writeFile(this.availableUpdate.updateFilePath, 'flag');
const child = spawn(this.availableUpdate.packagePath,
[
'/verysilent',
'/log',
`/update="${this.availableUpdate.updateFilePath}"`,
`/progress="${progressFilePath}"`,
`/sessionend="${sessionEndFlagPath}"`,
`/cancel="${cancelFilePath}"`,
'/nocloseapplications',
'/mergetasks=runcode,!desktopicon,!quicklaunchicon'
],
{
detached: true,
stdio: ['ignore', 'ignore', 'ignore'],
windowsVerbatimArguments: true,
env: { ...process.env, __COMPAT_LAYER: 'RunAsInvoker' }
}
);
const readyMutexName = `${this.productService.win32MutexName}-ready`;
const updatingMutexName = `${this.productService.win32MutexName}-updating`;
const setupMutexName = `${this.productService.win32MutexName}setup`;
const mutex = await import('@vscode/windows-mutex');
const isInstallerActive = () => mutex.isActive(updatingMutexName) || mutex.isActive(setupMutexName);

// Track the process so we can cancel it if needed
this.availableUpdate.updateProcess = child;
// Skip the spawn if another Inno Setup is already running for this product (background update or a manual installer);
// otherwise Inno's "Setup is already running" modal pops up. The `-ready` mutex poll below still advances our state when it finishes.
if (isInstallerActive()) {
this.logService.info('update#doApplyUpdate: another instance is already running setup, waiting for it to finish');
} else {
Comment thread
dmitrivMS marked this conversation as resolved.
await this.unlink(cancelFilePath);
await this.unlink(progressFilePath);
await pfs.Promises.writeFile(this.availableUpdate.updateFilePath, 'flag');

const child = spawn(this.availableUpdate.packagePath,
[
'/verysilent',
'/log',
`/update="${this.availableUpdate.updateFilePath}"`,
`/progress="${progressFilePath}"`,
`/sessionend="${sessionEndFlagPath}"`,
`/cancel="${cancelFilePath}"`,
'/nocloseapplications',
'/mergetasks=runcode,!desktopicon,!quicklaunchicon'
],
{
detached: true,
stdio: ['ignore', 'ignore', 'ignore'],
windowsVerbatimArguments: true,
env: { ...process.env, __COMPAT_LAYER: 'RunAsInvoker' }
}
);

child.once('exit', () => {
this.availableUpdate = undefined;
this.setState(State.Idle(getUpdateType()));
});
// Track the process so we can cancel it if needed
this.availableUpdate.updateProcess = child;

const readyMutexName = `${this.productService.win32MutexName}-ready`;
const mutex = await import('@vscode/windows-mutex');
child.once('exit', () => {
this.availableUpdate = undefined;
this.setState(State.Idle(getUpdateType()));
});
}

this.updateCancellationTokenSource?.dispose(true);
const cts = this.updateCancellationTokenSource = new CancellationTokenSource();
const token = cts.token;

const poll = async () => {
let seenRunning = false;
while (this.state.type === StateType.Updating && !token.isCancellationRequested) {
if (mutex.isActive(readyMutexName)) {
this.setState(State.Ready(update, explicit, this._overwrite));
return;
}

// Inno gone without `-ready` => install cancelled/failed; drop to Idle.
if (isInstallerActive()) {
seenRunning = true;
} else if (seenRunning) {
if (!this.availableUpdate?.updateProcess) {
this.availableUpdate = undefined;
this.setState(State.Idle(getUpdateType()));
Comment thread
dmitrivMS marked this conversation as resolved.
}
return;
}

try {
const progressContent = await readFile(progressFilePath, 'utf8');
if (!token.isCancellationRequested) {
Expand Down Expand Up @@ -442,7 +462,14 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun
this.logService.trace('update#cancelPendingUpdate: cancelling pending update');
const { updateProcess, updateFilePath, cancelFilePath } = this.availableUpdate;

if (updateProcess && updateProcess.exitCode === null) {
// If we didn't spawn the installer ourselves (another instance is running setup),
// don't touch its in-use files or try to kill its process.
if (!updateProcess) {
Comment thread
dmitrivMS marked this conversation as resolved.
Outdated
this.availableUpdate = undefined;
return;
Comment thread
dmitrivMS marked this conversation as resolved.
Outdated
}

if (updateProcess.exitCode === null) {
// Remove all listeners to prevent the exit handler from changing state
updateProcess.removeAllListeners();
const exitPromise = new Promise<boolean>(resolve => updateProcess.once('exit', () => resolve(true)));
Expand Down
Loading