-
Notifications
You must be signed in to change notification settings - Fork 214
Clean up old distributions #3763
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
6c794d1
Clean up old distributions
koesie10 1843467
Fix typo
koesie10 ec5fe52
Shuffle directories when cleaning distributions
koesie10 408df85
Do not log errors for ENOENT
koesie10 afdef00
Merge remote-tracking branch 'origin/main' into koesie10/cleanup-dist…
koesie10 a488877
Update CHANGELOG
koesie10 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
extensions/ql-vscode/src/codeql-cli/distribution/cleaner.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| import type { ExtensionContext } from "vscode"; | ||
| import { getDirectoryNamesInsidePath } from "../../common/files"; | ||
| import { sleep } from "../../common/time"; | ||
| import type { BaseLogger } from "../../common/logging"; | ||
| import { join } from "path"; | ||
| import { getErrorMessage } from "../../common/helpers-pure"; | ||
| import { pathExists, remove } from "fs-extra"; | ||
|
|
||
| interface ExtensionManagedDistributionManager { | ||
| folderIndex: number; | ||
| distributionFolderPrefix: string; | ||
| } | ||
|
|
||
| interface DistributionDirectory { | ||
| directoryName: string; | ||
| folderIndex: number; | ||
| } | ||
|
|
||
| /** | ||
| * This class is responsible for cleaning up old distributions that are no longer needed. In normal operation, this | ||
| * should not be necessary as the old distribution is deleted when the distribution is updated. However, in some cases | ||
| * the extension may leave behind old distribution which can result in a significant amount of space (> 100 GB) being | ||
| * taking up by unused distributions. | ||
| */ | ||
| export class ExtensionManagedDistributionCleaner { | ||
| constructor( | ||
| private readonly extensionContext: ExtensionContext, | ||
| private readonly logger: BaseLogger, | ||
| private readonly manager: ExtensionManagedDistributionManager, | ||
| ) {} | ||
|
|
||
| public start() { | ||
| // Intentionally starting this without waiting for it | ||
| void this.cleanup().catch((e: unknown) => { | ||
| void this.logger.log( | ||
| `Failed to clean up old versions of the CLI: ${getErrorMessage(e)}`, | ||
| ); | ||
| }); | ||
| } | ||
|
|
||
| public async cleanup() { | ||
| if (!(await pathExists(this.extensionContext.globalStorageUri.fsPath))) { | ||
| return; | ||
| } | ||
|
|
||
| const currentFolderIndex = this.manager.folderIndex; | ||
|
|
||
| const distributionDirectoryRegex = new RegExp( | ||
| `^${this.manager.distributionFolderPrefix}(\\d+)$`, | ||
| ); | ||
|
|
||
| const existingDirectories = await getDirectoryNamesInsidePath( | ||
| this.extensionContext.globalStorageUri.fsPath, | ||
| ); | ||
| const distributionDirectories = existingDirectories | ||
| .map((dir): DistributionDirectory | null => { | ||
| const match = dir.match(distributionDirectoryRegex); | ||
| if (!match) { | ||
| // When the folderIndex is 0, the distributionFolderPrefix is used as the directory name | ||
| if (dir === this.manager.distributionFolderPrefix) { | ||
| return { | ||
| directoryName: dir, | ||
| folderIndex: 0, | ||
| }; | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| return { | ||
| directoryName: dir, | ||
| folderIndex: parseInt(match[1]), | ||
| }; | ||
| }) | ||
| .filter((dir) => dir !== null); | ||
|
|
||
| // Clean up all directories that are older than the current one | ||
| const cleanableDirectories = distributionDirectories.filter( | ||
| (dir) => dir.folderIndex < currentFolderIndex, | ||
| ); | ||
|
|
||
| if (cleanableDirectories.length === 0) { | ||
| return; | ||
| } | ||
|
|
||
| void this.logger.log( | ||
| `Cleaning up ${cleanableDirectories.length} old versions of the CLI.`, | ||
| ); | ||
|
|
||
| for (const cleanableDirectory of cleanableDirectories) { | ||
| // Wait 60 seconds between each cleanup to avoid overloading the system (even though the remove call should be async) | ||
| await sleep(10_000); | ||
|
|
||
| const path = join( | ||
| this.extensionContext.globalStorageUri.fsPath, | ||
| cleanableDirectory.directoryName, | ||
| ); | ||
|
|
||
| // Delete this directory | ||
| try { | ||
| await remove(path); | ||
| } catch (e) { | ||
| void this.logger.log( | ||
| `Tried to clean up an old version of the CLI at ${path} but encountered an error: ${getErrorMessage(e)}.`, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| void this.logger.log( | ||
| `Cleaned up ${cleanableDirectories.length} old versions of the CLI.`, | ||
| ); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
146 changes: 146 additions & 0 deletions
146
extensions/ql-vscode/test/vscode-tests/no-workspace/codeql-cli/distribution/cleaner.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| import { ExtensionManagedDistributionCleaner } from "../../../../../src/codeql-cli/distribution/cleaner"; | ||
| import { mockedObject } from "../../../../mocked-object"; | ||
| import type { ExtensionContext } from "vscode"; | ||
| import { Uri } from "vscode"; | ||
| import { createMockLogger } from "../../../../__mocks__/loggerMock"; | ||
| import type { DirectoryResult } from "tmp-promise"; | ||
| import { dir } from "tmp-promise"; | ||
| import { outputFile, pathExists } from "fs-extra"; | ||
| import { join } from "path"; | ||
| import { codeQlLauncherName } from "../../../../../src/common/distribution"; | ||
| import { getDirectoryNamesInsidePath } from "../../../../../src/common/files"; | ||
|
|
||
| describe("ExtensionManagedDistributionCleaner", () => { | ||
| let globalStorageDirectory: DirectoryResult; | ||
|
|
||
| let manager: ExtensionManagedDistributionCleaner; | ||
|
|
||
| beforeEach(async () => { | ||
| globalStorageDirectory = await dir({ | ||
| unsafeCleanup: true, | ||
| }); | ||
|
|
||
| manager = new ExtensionManagedDistributionCleaner( | ||
| mockedObject<ExtensionContext>({ | ||
| globalStorageUri: Uri.file(globalStorageDirectory.path), | ||
| }), | ||
| createMockLogger(), | ||
| { | ||
| folderIndex: 768, | ||
| distributionFolderPrefix: "distribution", | ||
| }, | ||
| ); | ||
|
|
||
| // Mock setTimeout to call the callback immediately | ||
| jest.spyOn(global, "setTimeout").mockImplementation((callback) => { | ||
| callback(); | ||
| return 0 as unknown as ReturnType<typeof setTimeout>; | ||
| }); | ||
| }); | ||
|
|
||
| afterEach(async () => { | ||
| await globalStorageDirectory.cleanup(); | ||
| }); | ||
|
|
||
| it("does nothing when no distributions exist", async () => { | ||
| await manager.cleanup(); | ||
| }); | ||
|
|
||
| it("does nothing when only the current distribution exists", async () => { | ||
| await outputFile( | ||
| join( | ||
| globalStorageDirectory.path, | ||
| "distribution768", | ||
| "codeql", | ||
| "bin", | ||
| codeQlLauncherName(), | ||
| ), | ||
| "launcher!", | ||
| ); | ||
|
|
||
| await manager.cleanup(); | ||
|
|
||
| expect( | ||
| await pathExists( | ||
| join( | ||
| globalStorageDirectory.path, | ||
| "distribution768", | ||
| "codeql", | ||
| "bin", | ||
| codeQlLauncherName(), | ||
| ), | ||
| ), | ||
| ).toBe(true); | ||
| }); | ||
|
|
||
| it("removes old distributions", async () => { | ||
| await outputFile( | ||
| join( | ||
| globalStorageDirectory.path, | ||
| "distribution", | ||
| "codeql", | ||
| "bin", | ||
| codeQlLauncherName(), | ||
| ), | ||
| "launcher!", | ||
| ); | ||
| await outputFile( | ||
| join( | ||
| globalStorageDirectory.path, | ||
| "distribution12", | ||
| "codeql", | ||
| "bin", | ||
| codeQlLauncherName(), | ||
| ), | ||
| "launcher!", | ||
| ); | ||
| await outputFile( | ||
| join( | ||
| globalStorageDirectory.path, | ||
| "distribution244", | ||
| "codeql", | ||
| "bin", | ||
| codeQlLauncherName(), | ||
| ), | ||
| "launcher!", | ||
| ); | ||
| await outputFile( | ||
| join( | ||
| globalStorageDirectory.path, | ||
| "distribution637", | ||
| "codeql", | ||
| "bin", | ||
| codeQlLauncherName(), | ||
| ), | ||
| "launcher!", | ||
| ); | ||
| await outputFile( | ||
| join( | ||
| globalStorageDirectory.path, | ||
| "distribution768", | ||
| "codeql", | ||
| "bin", | ||
| codeQlLauncherName(), | ||
| ), | ||
| "launcher!", | ||
| ); | ||
| await outputFile( | ||
| join( | ||
| globalStorageDirectory.path, | ||
| "distribution890", | ||
| "codeql", | ||
| "bin", | ||
| codeQlLauncherName(), | ||
| ), | ||
| "launcher!", | ||
| ); | ||
|
|
||
| const promise = manager.cleanup(); | ||
|
|
||
| await promise; | ||
|
|
||
| expect( | ||
| (await getDirectoryNamesInsidePath(globalStorageDirectory.path)).sort(), | ||
| ).toEqual(["distribution768", "distribution890"]); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.