mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-22 01:29:04 +01:00
Add garbage collection for unused content-addressed askpass directories (#289723)
* Initial plan * Add garbage collection for old content-addressed askpass directories - Implement updateDirectoryMtime to update folder mtime when used - Add garbageCollectOldDirectories to remove folders older than 7 days - Update ensureAskpassScripts to call GC on every activation - Add comprehensive test coverage for GC functionality Co-authored-by: joaomoreno <22350+joaomoreno@users.noreply.github.com> * Remove GC from fast path to keep it fast Only run garbage collection when creating new directories, not when reusing existing ones. Old folders only accumulate when creating new content-addressed directories. Co-authored-by: joaomoreno <22350+joaomoreno@users.noreply.github.com> * Hoist askpassBaseDir variable to avoid duplication Declare askpassBaseDir once at the top of the function and reuse it when constructing askpassDir and when calling garbageCollectOldDirectories. Co-authored-by: joaomoreno <22350+joaomoreno@users.noreply.github.com> * Fix test failures and address bot review comments - Export ensureAskpassScripts for testing to avoid dependency on isWindowsUserOrSystemSetup check - Remove redundant success log after directory removal - Update tests to call ensureAskpassScripts directly instead of getAskpassPaths - Remove Windows-only restrictions from tests to make them cross-platform - Remove setTimeout workarounds - tests now properly await async operations Co-authored-by: joaomoreno <22350+joaomoreno@users.noreply.github.com> * formatting --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: joaomoreno <22350+joaomoreno@users.noreply.github.com> Co-authored-by: João Moreno <joaomoreno@users.noreply.github.com>
This commit is contained in:
@@ -128,6 +128,74 @@ async function copyFileSecure(
|
||||
await setWindowsPermissions(dest, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the modification time of a directory to mark it as recently used.
|
||||
*/
|
||||
async function updateDirectoryMtime(dirPath: string, logger: LogOutputChannel): Promise<void> {
|
||||
try {
|
||||
const now = new Date();
|
||||
await fs.promises.utimes(dirPath, now, now);
|
||||
logger.trace(`[askpassManager] Updated mtime for ${dirPath}`);
|
||||
} catch (err) {
|
||||
logger.warn(`[askpassManager] Failed to update mtime for ${dirPath}: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Garbage collects old content-addressed askpass directories that haven't been used in 7 days.
|
||||
* This prevents accumulation of old versions when VS Code updates.
|
||||
*/
|
||||
async function garbageCollectOldDirectories(
|
||||
askpassBaseDir: string,
|
||||
currentHash: string,
|
||||
logger: LogOutputChannel
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Check if the askpass base directory exists
|
||||
try {
|
||||
await fs.promises.access(askpassBaseDir);
|
||||
} catch {
|
||||
// Directory doesn't exist, nothing to clean
|
||||
return;
|
||||
}
|
||||
|
||||
const entries = await fs.promises.readdir(askpassBaseDir);
|
||||
const sevenDaysAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);
|
||||
|
||||
for (const entry of entries) {
|
||||
// Skip the current content-addressed directory
|
||||
if (entry === currentHash) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const entryPath = path.join(askpassBaseDir, entry);
|
||||
|
||||
try {
|
||||
const stat = await fs.promises.stat(entryPath);
|
||||
|
||||
// Only process directories
|
||||
if (!stat.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the directory hasn't been used in 7 days
|
||||
if (stat.mtime.getTime() < sevenDaysAgo) {
|
||||
logger.info(`[askpassManager] Removing old askpass directory: ${entryPath} (last used: ${stat.mtime.toISOString()})`);
|
||||
|
||||
// Remove the directory and all its contents
|
||||
await fs.promises.rm(entryPath, { recursive: true, force: true });
|
||||
} else {
|
||||
logger.trace(`[askpassManager] Keeping askpass directory: ${entryPath} (last used: ${stat.mtime.toISOString()})`);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn(`[askpassManager] Failed to process/remove directory ${entryPath}: ${err}`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn(`[askpassManager] Failed to garbage collect old directories: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
export interface AskpassPaths {
|
||||
readonly askpass: string;
|
||||
readonly askpassMain: string;
|
||||
@@ -144,7 +212,7 @@ export interface AskpassPaths {
|
||||
* @param storageDir The user-controlled storage directory (context.storageUri.fsPath)
|
||||
* @param logger Logger for diagnostic output
|
||||
*/
|
||||
async function ensureAskpassScripts(
|
||||
export async function ensureAskpassScripts(
|
||||
sourceDir: string,
|
||||
storageDir: string,
|
||||
logger: LogOutputChannel
|
||||
@@ -162,7 +230,8 @@ async function ensureAskpassScripts(
|
||||
logger.trace(`[askpassManager] Content hash: ${contentHash}`);
|
||||
|
||||
// Create content-addressed directory
|
||||
const askpassDir = path.join(storageDir, 'askpass', contentHash);
|
||||
const askpassBaseDir = path.join(storageDir, 'askpass');
|
||||
const askpassDir = path.join(askpassBaseDir, contentHash);
|
||||
|
||||
const destPaths: AskpassPaths = {
|
||||
askpass: path.join(askpassDir, 'askpass.sh'),
|
||||
@@ -177,6 +246,10 @@ async function ensureAskpassScripts(
|
||||
const stat = await fs.promises.stat(destPaths.askpass);
|
||||
if (stat.isFile()) {
|
||||
logger.trace(`[askpassManager] Using existing content-addressed askpass at ${askpassDir}`);
|
||||
|
||||
// Update mtime to mark this directory as recently used
|
||||
await updateDirectoryMtime(askpassDir, logger);
|
||||
|
||||
return destPaths;
|
||||
}
|
||||
} catch {
|
||||
@@ -200,6 +273,12 @@ async function ensureAskpassScripts(
|
||||
|
||||
logger.info(`[askpassManager] Successfully created content-addressed askpass scripts`);
|
||||
|
||||
// Update mtime to mark this directory as recently used
|
||||
await updateDirectoryMtime(askpassDir, logger);
|
||||
|
||||
// Garbage collect old directories
|
||||
await garbageCollectOldDirectories(askpassBaseDir, contentHash, logger);
|
||||
|
||||
return destPaths;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user