Skip to content

Acquire a process lock on the session data directory - #335216

Open
D Fnx (dnch13) wants to merge 3 commits into
microsoft:mainfrom
dnch13:session-data-process-lock
Open

Acquire a process lock on the session data directory#335216
D Fnx (dnch13) wants to merge 3 commits into
microsoft:mainfrom
dnch13:session-data-process-lock

Conversation

@dnch13

Copy link
Copy Markdown

Robo (@deepak1556)

The main IPC handle is scoped to the product version, so right after an update the new process fails to detect the old one via claimInstance and both processes end up using the same user data directory at the same time. Chromium does not support sharing session data between multiple running browser processes and webview service worker registration then fails with an InvalidStateError.

Add a version independent process lock for the session data:

  • createStaticIPCHandle now allows omitting the version so that the same handle is produced by all versions for a given user data directory (existing callers are unchanged)
  • the main process serves that handle (environmentMainService. sessionDataLockHandle) for its whole lifetime before any storage in the user data directory is opened (claimSessionData runs right after claimInstance and before initServices)
  • if a live lock holder exists, the process waits for it to exit (polling every second, dialog after 10s with Keep Waiting/Exit that re-appears every 60s), because concurrent use of the session data directory is unsupported and can corrupt Chromium state storage
  • stale sockets of dead processes are recovered by probing the handle and unlinking it, mirroring claimInstance; the lock is released on shutdown

Previous MR: #333833
Related Issue: #125993

The main IPC handle is scoped to the product version, so right after an
update the new process fails to detect the old one via claimInstance and
both processes end up using the same user data directory at the same
time. Chromium does not support sharing session data between multiple
running browser processes and webview service worker registration then
fails with an InvalidStateError.

Add a version independent process lock for the session data:

- createStaticIPCHandle now allows omitting the version so that the same
  handle is produced by all versions for a given user data directory
  (existing callers are unchanged)
- the main process serves that handle (environmentMainService.
  sessionDataLockHandle) for its whole lifetime before any storage in the
  user data directory is opened (claimSessionData runs right after
  claimInstance and before initServices)
- if a live lock holder exists, the process waits for it to exit (polling
  every second, dialog after 10s with Keep Waiting/Exit that re-appears
  every 60s), because concurrent use of the session data directory is
  unsupported and can corrupt Chromium state storage
- stale sockets of dead processes are recovered by probing the handle and
  unlinking it, mirroring claimInstance; the lock is released on shutdown

Signed-off-by: Danila Fominykh <d@m8sh.su>
Copilot AI balanced review requested due to automatic review settings September 9, 2026 07:44
@dnch13

Copy link
Copy Markdown
Author

Robo (@deepak1556)
If possible - handle the process of fighting copilot and get that merged ASAP

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Lock release and stale-socket races can permit overlap, abort startup, or cause an unthrottled retry loop.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds a version-independent IPC lock to prevent concurrent use of Chromium session data across VS Code versions.

Changes:

  • Adds and acquires the session-data lock before service initialization.
  • Implements polling, stale-socket recovery, and a waiting dialog.
  • Extends and tests static IPC handles without versions.
File summaries
File Description
environmentMainService.ts Exposes the session-data lock handle.
main.ts Implements lock acquisition and lifecycle handling.
ipc.net.ts Supports version-independent static handles.
ipc.net.test.ts Tests handle determinism and exclusivity.
Review details

Suppressed comments (1)

src/vs/code/electron-main/main.ts:580

  • If the stale socket cannot be unlinked (for example because of permissions), swallowing the error and immediately continuing creates an unthrottled loop of serve/connect/unlink attempts and repeated warnings. Abort with the unlink error (or otherwise enter the timed wait path) rather than retrying immediately.
					} catch (unlinkError) {
						logService.warn(`Error removing stale session data lock: ${unlinkError.toString()}`);
					}

					continue;
  • Files reviewed: 4/4 changed files
  • Comments generated: 2
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/vs/code/electron-main/main.ts Outdated
Comment thread src/vs/code/electron-main/main.ts
D Fnx (dnch13) and others added 2 commits September 9, 2026 11:21
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
A normal lock release can remove the Unix socket in between the failed
serve() attempt and the connect() probe (and on Windows the named pipe
disappears when its owning process exits). The probe then failed with
ENOENT, which escaped and aborted startup instead of continuing the
polling loop. Treat ENOENT as a released-lock race and retry
acquisition.

The same race applies to the unlink() of a stale socket, which another
process may have removed in the meantime: ignore ENOENT there quietly
instead of logging a warning.

Signed-off-by: Danila Fominykh <d@m8sh.su>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Early lifecycle initialization, first-rollout compatibility, and stale-socket races undermine correctness.

Review details

Suppressed comments (3)

Previously missed (1) — in code that hasn't changed since the last review.

src/vs/code/electron-main/main.ts:121

  • Resolving ILifecycleMainService here now constructs it before stateService.init(). Its constructor immediately reads and clears lifecycle.quitAndRestart, but FileStorage is still empty until initialization, so wasRestarted will remain false and the persisted restart marker will not be cleared. This breaks restart/update window restoration; keep lifecycle construction after state initialization and decouple the early IPC cleanup from that service.

This issue also appears in the following locations of the same file:

  • line 134
  • line 579

src/vs/code/electron-main/main.ts:139

  • This does not protect the first update that ships this change: an already-running older build never binds the newly introduced handle, so claimSessionData succeeds and the new process still opens the same session directory concurrently—the motivating scenario in the PR description. Add backward-compatible detection of a pre-lock process (for example, validate the existing code.lock PID) or use a rollout strategy where the predecessor also acquires this lock.
				// Acquire the version independent lock on the session data:
				// this guarantees that only one VS Code process, of any
				// version, is using the user data directory at the same time
				// (which can happen across updates because the handle used
				// by `claimInstance` above is version scoped)
				await this.claimSessionData(logService, environmentMainService, lifecycleMainService, productService);

src/vs/code/electron-main/main.ts:581

  • The stale-socket takeover is racy between two contenders. After both observe ECONNREFUSED, one can unlink and bind the path, then the other can execute this unlink based on its earlier probe, remove the first contender's live socket, and bind a second server; both processes then believe they own the lock. Use an atomic ownership/takeover primitive rather than unlinking solely from a prior connection result.
				if (!isWindows && error.code === 'ECONNREFUSED') {
					try {
						unlinkSync(handle);
  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants