Path: blob/main/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/lockFile.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as fs from 'fs/promises';6import * as path from 'path';7import * as vscode from 'vscode';8import { ILogger } from '../../../../platform/log/common/logService';9import { generateUuid } from '../../../../util/vs/base/common/uuid';10import { getCopilotCliStateDir } from '../node/cliHelpers';1112export interface LockFileInfo {13socketPath: string;14scheme: string;15headers: Record<string, string>;16pid: number;17ideName: string;18timestamp: number;19workspaceFolders: string[];20isTrusted: boolean;21}2223export class LockFileHandle {24private readonly lockFilePath: string;25private readonly serverUri: vscode.Uri;26private readonly headers: Record<string, string>;27private readonly timestamp: number;28private readonly logger: ILogger;2930constructor(lockFilePath: string, serverUri: vscode.Uri, headers: Record<string, string>, timestamp: number, logger: ILogger) {31this.lockFilePath = lockFilePath;32this.serverUri = serverUri;33this.headers = headers;34this.timestamp = timestamp;35this.logger = logger;36}3738get path(): string {39return this.lockFilePath;40}4142async update(): Promise<void> {43try {44const workspaceFolders = vscode.workspace.workspaceFolders?.map(f => f.uri.fsPath) || [];4546const lockInfo: LockFileInfo = {47socketPath: this.serverUri.path,48scheme: this.serverUri.scheme,49headers: this.headers,50pid: process.pid,51ideName: vscode.env.appName,52timestamp: this.timestamp,53workspaceFolders: workspaceFolders,54isTrusted: vscode.workspace.isTrusted,55};5657await fs.writeFile(this.lockFilePath, JSON.stringify(lockInfo, null, 2), { mode: 0o600 });58this.logger.trace(`Lock file updated: ${this.lockFilePath}`);59} catch (error) {60this.logger.debug(`Failed to update lock file: ${error instanceof Error ? error.message : String(error)}`);61}62}6364async remove(): Promise<void> {65try {66await fs.unlink(this.lockFilePath);67this.logger.debug(`Lock file removed: ${this.lockFilePath}`);68} catch (error) {69if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {70this.logger.debug(`Failed to remove lock file: ${error instanceof Error ? error.message : String(error)}`);71}72}73}74}7576export async function createLockFile(serverUri: vscode.Uri, headers: Record<string, string>, logger: ILogger): Promise<LockFileHandle> {77const copilotDir = getCopilotCliStateDir();78logger.trace(`Creating lock file in: ${copilotDir}`);7980await fs.mkdir(copilotDir, { recursive: true, mode: 0o700 });8182const uuid = generateUuid();83const lockFilePath = path.join(copilotDir, `${uuid}.lock`);8485const workspaceFolders = vscode.workspace.workspaceFolders?.map(f => f.uri.fsPath) || [];86const timestamp = Date.now();8788const lockInfo: LockFileInfo = {89socketPath: serverUri.path,90scheme: serverUri.scheme,91headers: headers,92pid: process.pid,93ideName: vscode.env.appName,94timestamp: timestamp,95workspaceFolders: workspaceFolders,96isTrusted: vscode.workspace.isTrusted,97};9899await fs.writeFile(lockFilePath, JSON.stringify(lockInfo, null, 2), { mode: 0o600 });100logger.debug(`Created lock file: ${lockFilePath}`);101102return new LockFileHandle(lockFilePath, serverUri, headers, timestamp, logger);103}104105/**106* Checks if a process with the given PID is still running.107* Note: Signal 0 is a special "null signal" that doesn't actually kill the process -108* it only checks if the process exists and we have permission to signal it.109*/110export function isProcessRunning(pid: number): boolean {111try {112process.kill(pid, 0);113return true;114} catch {115return false;116}117}118119/**120* Cleans up stale lockfiles where the associated process is no longer running.121* Returns the number of lockfiles cleaned up.122*/123export async function cleanupStaleLockFiles(logger: ILogger): Promise<number> {124const copilotDir = getCopilotCliStateDir();125126let files: string[];127try {128files = await fs.readdir(copilotDir);129} catch {130return 0;131}132133const lockFiles = files.filter(file => file.endsWith('.lock'));134135const results = await Promise.all(lockFiles.map(async (file) => {136const filePath = path.join(copilotDir, file);137try {138const content = await fs.readFile(filePath, 'utf-8');139const info = JSON.parse(content) as LockFileInfo;140141if (!isProcessRunning(info.pid)) {142await fs.unlink(filePath);143logger.debug(`Removed stale lock file for PID ${info.pid}: ${filePath}`);144return true;145}146} catch {147// Skip files that can't be read or parsed148}149return false;150}));151152return results.filter(Boolean).length;153}154155156