Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/lockFile.ts
13405 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 * as fs from 'fs/promises';
7
import * as path from 'path';
8
import * as vscode from 'vscode';
9
import { ILogger } from '../../../../platform/log/common/logService';
10
import { generateUuid } from '../../../../util/vs/base/common/uuid';
11
import { getCopilotCliStateDir } from '../node/cliHelpers';
12
13
export interface LockFileInfo {
14
socketPath: string;
15
scheme: string;
16
headers: Record<string, string>;
17
pid: number;
18
ideName: string;
19
timestamp: number;
20
workspaceFolders: string[];
21
isTrusted: boolean;
22
}
23
24
export class LockFileHandle {
25
private readonly lockFilePath: string;
26
private readonly serverUri: vscode.Uri;
27
private readonly headers: Record<string, string>;
28
private readonly timestamp: number;
29
private readonly logger: ILogger;
30
31
constructor(lockFilePath: string, serverUri: vscode.Uri, headers: Record<string, string>, timestamp: number, logger: ILogger) {
32
this.lockFilePath = lockFilePath;
33
this.serverUri = serverUri;
34
this.headers = headers;
35
this.timestamp = timestamp;
36
this.logger = logger;
37
}
38
39
get path(): string {
40
return this.lockFilePath;
41
}
42
43
async update(): Promise<void> {
44
try {
45
const workspaceFolders = vscode.workspace.workspaceFolders?.map(f => f.uri.fsPath) || [];
46
47
const lockInfo: LockFileInfo = {
48
socketPath: this.serverUri.path,
49
scheme: this.serverUri.scheme,
50
headers: this.headers,
51
pid: process.pid,
52
ideName: vscode.env.appName,
53
timestamp: this.timestamp,
54
workspaceFolders: workspaceFolders,
55
isTrusted: vscode.workspace.isTrusted,
56
};
57
58
await fs.writeFile(this.lockFilePath, JSON.stringify(lockInfo, null, 2), { mode: 0o600 });
59
this.logger.trace(`Lock file updated: ${this.lockFilePath}`);
60
} catch (error) {
61
this.logger.debug(`Failed to update lock file: ${error instanceof Error ? error.message : String(error)}`);
62
}
63
}
64
65
async remove(): Promise<void> {
66
try {
67
await fs.unlink(this.lockFilePath);
68
this.logger.debug(`Lock file removed: ${this.lockFilePath}`);
69
} catch (error) {
70
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
71
this.logger.debug(`Failed to remove lock file: ${error instanceof Error ? error.message : String(error)}`);
72
}
73
}
74
}
75
}
76
77
export async function createLockFile(serverUri: vscode.Uri, headers: Record<string, string>, logger: ILogger): Promise<LockFileHandle> {
78
const copilotDir = getCopilotCliStateDir();
79
logger.trace(`Creating lock file in: ${copilotDir}`);
80
81
await fs.mkdir(copilotDir, { recursive: true, mode: 0o700 });
82
83
const uuid = generateUuid();
84
const lockFilePath = path.join(copilotDir, `${uuid}.lock`);
85
86
const workspaceFolders = vscode.workspace.workspaceFolders?.map(f => f.uri.fsPath) || [];
87
const timestamp = Date.now();
88
89
const lockInfo: LockFileInfo = {
90
socketPath: serverUri.path,
91
scheme: serverUri.scheme,
92
headers: headers,
93
pid: process.pid,
94
ideName: vscode.env.appName,
95
timestamp: timestamp,
96
workspaceFolders: workspaceFolders,
97
isTrusted: vscode.workspace.isTrusted,
98
};
99
100
await fs.writeFile(lockFilePath, JSON.stringify(lockInfo, null, 2), { mode: 0o600 });
101
logger.debug(`Created lock file: ${lockFilePath}`);
102
103
return new LockFileHandle(lockFilePath, serverUri, headers, timestamp, logger);
104
}
105
106
/**
107
* Checks if a process with the given PID is still running.
108
* Note: Signal 0 is a special "null signal" that doesn't actually kill the process -
109
* it only checks if the process exists and we have permission to signal it.
110
*/
111
export function isProcessRunning(pid: number): boolean {
112
try {
113
process.kill(pid, 0);
114
return true;
115
} catch {
116
return false;
117
}
118
}
119
120
/**
121
* Cleans up stale lockfiles where the associated process is no longer running.
122
* Returns the number of lockfiles cleaned up.
123
*/
124
export async function cleanupStaleLockFiles(logger: ILogger): Promise<number> {
125
const copilotDir = getCopilotCliStateDir();
126
127
let files: string[];
128
try {
129
files = await fs.readdir(copilotDir);
130
} catch {
131
return 0;
132
}
133
134
const lockFiles = files.filter(file => file.endsWith('.lock'));
135
136
const results = await Promise.all(lockFiles.map(async (file) => {
137
const filePath = path.join(copilotDir, file);
138
try {
139
const content = await fs.readFile(filePath, 'utf-8');
140
const info = JSON.parse(content) as LockFileInfo;
141
142
if (!isProcessRunning(info.pid)) {
143
await fs.unlink(filePath);
144
logger.debug(`Removed stale lock file for PID ${info.pid}: ${filePath}`);
145
return true;
146
}
147
} catch {
148
// Skip files that can't be read or parsed
149
}
150
return false;
151
}));
152
153
return results.filter(Boolean).length;
154
}
155
156