Acquire a process lock on the session data directory - #335216
Acquire a process lock on the session data directory#335216D Fnx (dnch13) wants to merge 3 commits into
Conversation
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>
|
Robo (@deepak1556) |
There was a problem hiding this comment.
🟡 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/unlinkattempts 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.
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>
There was a problem hiding this comment.
🔵 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
ILifecycleMainServicehere now constructs it beforestateService.init(). Its constructor immediately reads and clearslifecycle.quitAndRestart, butFileStorageis still empty until initialization, sowasRestartedwill 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
claimSessionDatasucceeds 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 existingcode.lockPID) 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
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:
Previous MR: #333833
Related Issue: #125993