-
Notifications
You must be signed in to change notification settings - Fork 40.2k
Gemini: Fix update conflict #296447
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Gemini: Fix update conflict #296447
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| # Analysis of Issue #62476: "Setup has detected that setup is currently running" | ||
|
|
||
| ## 1. Thorough Analysis | ||
| The issue describes a situation where users see an Inno Setup popup stating "Setup has detected that setup is currently running" while using VS Code. This popup is generated by the Inno Setup installer when it detects that another instance of the installer is already running (by checking for a specific mutex). | ||
|
|
||
| VS Code uses Inno Setup for background updates on Windows. The update process works as follows: | ||
| 1. VS Code checks for updates and downloads the update package to a shared cache directory (`%TMP%\vscode-<quality>-<target>-<arch>`). | ||
| 2. Once downloaded, `Win32UpdateService.doApplyUpdate()` spawns the Inno Setup installer in the background (`/verysilent`). | ||
| 3. The installer creates a setup mutex (`<AppMutex>setup`) to prevent multiple instances from running. | ||
| 4. The installer extracts the files, creates a ready mutex (`<AppMutex>-ready`), and waits for VS Code to close. | ||
| 5. VS Code polls for the ready mutex and transitions to the "Ready" state (showing the "Restart to Update" badge). | ||
|
|
||
| The problem occurs because `doApplyUpdate()` does not check if the setup mutex is already active before spawning the installer. If multiple instances of VS Code attempt to apply the update, or if a previous background update is still running, VS Code will spawn a second instance of the installer. The second instance detects the first one and displays the error popup. | ||
|
|
||
| ## 2. Repro Scenarios | ||
|
|
||
| ### Scenario 1: Multiple VS Code Instances | ||
| 1. The user opens a VS Code window (Instance A). | ||
| 2. Instance A checks for updates, downloads the package, and spawns the background installer. | ||
| 3. While the background installer is running, the user opens a second VS Code window (Instance B). | ||
| 4. Instance B checks for updates, sees that the package is already downloaded in the shared cache directory, and immediately calls `doApplyUpdate()`. | ||
| 5. Instance B spawns a second background installer. | ||
| 6. The second installer detects the first one (via the setup mutex) and shows the popup. | ||
|
|
||
| ### Scenario 2: Interrupted Background Update | ||
| 1. A background update is running, and the installer creates the `updating_version` file in the VS Code installation directory. | ||
| 2. The user closes VS Code before the update finishes, or the update gets stuck. | ||
| 3. When the user opens VS Code again, `Win32UpdateService.postInitialize()` detects the `updating_version` file and calls `_applySpecificUpdate()`, which spawns the installer. | ||
| 4. If the previous installer is still running (e.g., it's stuck waiting for a process to close), the new installer detects it and shows the popup. | ||
|
|
||
| ### Scenario 3: Manual Update During Background Update | ||
| 1. The user manually downloads the VS Code installer and runs it while VS Code is open. | ||
| 2. At the same time, VS Code checks for updates in the background, downloads the package, and spawns the background installer. | ||
| 3. The background installer detects the manual installer (which holds the setup mutex) and shows the popup. | ||
|
|
||
| ## 3. Implementation Plan | ||
|
|
||
| To fix this issue, we need to prevent VS Code from spawning the background installer if it is already running. We can achieve this by checking the setup mutex before spawning the process. | ||
|
|
||
| **File to modify:** `src/vs/platform/update/electron-main/updateService.win32.ts` | ||
|
|
||
| **Changes:** | ||
| 1. In `doApplyUpdate()`, before spawning the installer, use `@vscode/windows-mutex` to check if the setup mutex (`${this.productService.win32MutexName}setup`) is active. | ||
| 2. If the setup mutex is active, log a message and **skip** spawning the installer. | ||
| 3. Proceed to the polling loop. The polling loop will wait for the existing installer to create the ready mutex (`${this.productService.win32MutexName}-ready`). | ||
| 4. Update the polling loop to handle the case where we didn't spawn the installer. If the setup mutex becomes inactive and the ready mutex is not active, it means the existing installer exited prematurely (e.g., it failed or was cancelled). In this case, transition to the `Idle` state with an error message. | ||
|
|
||
| **Code Snippet:** | ||
| ```typescript | ||
| const mutex = await import('@vscode/windows-mutex'); | ||
| const setupMutexName = `${this.productService.win32MutexName}setup`; | ||
| const readyMutexName = `${this.productService.win32MutexName}-ready`; | ||
|
|
||
| if (mutex.isActive(setupMutexName)) { | ||
| this.logService.info('update#doApplyUpdate: setup is already running'); | ||
| // Skip spawning the installer | ||
| } else { | ||
| // Spawn the installer as usual | ||
| // ... | ||
| } | ||
|
|
||
| // In the polling loop: | ||
| if (!this.availableUpdate?.updateProcess && !mutex.isActive(setupMutexName)) { | ||
| this.logService.warn('update#doApplyUpdate: setup is no longer running and ready mutex is not active'); | ||
| this.setState(State.Idle(getUpdateType(), 'Update failed or was cancelled')); | ||
| return; | ||
| } | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -347,35 +347,42 @@ export class Win32UpdateService extends AbstractUpdateService implements IRelaun | |||||||||||||||||||||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||
| const readyMutexName = `${this.productService.win32MutexName}-ready`; | ||||||||||||||||||||||||||||||||||||||||||||||
| const mutex = await import('@vscode/windows-mutex'); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Track the process so we can cancel it if needed | ||||||||||||||||||||||||||||||||||||||||||||||
| this.availableUpdate.updateProcess = child; | ||||||||||||||||||||||||||||||||||||||||||||||
| const setupMutexName = `${this.productService.win32MutexName}setup`; | ||||||||||||||||||||||||||||||||||||||||||||||
| const isSetupRunning = mutex.isActive(setupMutexName); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+353
to
355
|
||||||||||||||||||||||||||||||||||||||||||||||
| const setupMutexName = `${this.productService.win32MutexName}setup`; | |
| const isSetupRunning = mutex.isActive(setupMutexName); | |
| const setupMutexName = `${this.productService.win32MutexName}setup`; | |
| const updatingMutexName = `${this.productService.win32MutexName}-updating`; | |
| const isSetupRunning = mutex.isActive(setupMutexName); | |
| const isBackgroundUpdateRunning = mutex.isActive(updatingMutexName); | |
| // If setup is running but there is no background-updating mutex, this is most | |
| // likely a manual/external installer run. In that case, do not enter the | |
| // background "Updating..." flow that polls for the "-ready" mutex because | |
| // it will never be created. Instead, bail out gracefully. | |
| if (isSetupRunning && !isBackgroundUpdateRunning) { | |
| this.logService.info('update#doApplyUpdate: external setup is already running, skipping background update'); | |
| this.availableUpdate = undefined; | |
| this.setState(State.Idle(getUpdateType())); | |
| return; | |
| } |
Copilot
AI
Feb 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
State.Idle(..., 'Update failed or was cancelled') sets state.error, which the workbench update UI surfaces as an error notification even for non-explicit/background update flows. Previously, the “setup already running” case would simply exit without raising a user-visible error.
Recommend only setting an error string here when explicit === true (or using a more specific, localized message), and otherwise just log + return to Idle without error to avoid noisy notifications during background updates.
This issue also appears on line 402 of the same file.
| this.setState(State.Idle(getUpdateType(), 'Update failed or was cancelled')); | |
| return; | |
| } | |
| if (Date.now() - pollStartTime > pollTimeoutMs) { | |
| this.logService.warn('update#doApplyUpdate: polling timed out waiting for update to be ready'); | |
| this.setState(State.Idle(getUpdateType(), 'Update did not complete within expected time')); | |
| if (explicit) { | |
| this.setState(State.Idle(getUpdateType(), 'Update failed or was cancelled')); | |
| } else { | |
| this.setState(State.Idle(getUpdateType())); | |
| } | |
| return; | |
| } | |
| if (Date.now() - pollStartTime > pollTimeoutMs) { | |
| this.logService.warn('update#doApplyUpdate: polling timed out waiting for update to be ready'); | |
| if (explicit) { | |
| this.setState(State.Idle(getUpdateType(), 'Update did not complete within expected time')); | |
| } else { | |
| this.setState(State.Idle(getUpdateType())); | |
| } |
Copilot
AI
Feb 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Swallowing unlinkSync errors here makes failures to delete the update lock file silent. If the lock file can’t be removed (e.g., permissions/AV locking), the background installer may decide not to relaunch VS Code after update, leaving the user with no obvious explanation.
Instead of fully ignoring, consider logging a warning (and potentially falling back to spawning the installer) so there’s diagnostic output when the update handoff fails.
| // ignore | |
| this.logService.warn('update#quitAndInstall(): failed to remove update lock file, falling back to spawning installer', e); | |
| spawn(this.availableUpdate.packagePath, ['/silent', '/log', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], { | |
| detached: true, | |
| stdio: ['ignore', 'ignore', 'ignore'] | |
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file looks like PR analysis/notes rather than product code or documentation consumed by the build. Keeping ad-hoc reports at repo root tends to add noise and ongoing maintenance burden.
Suggestion: remove
report2.mdfrom the PR (or move the content to the PR description / linked issue) unless there’s an established docs location and purpose for it.