diff --git a/lib/modules/datasource/rubygems/versions-datasource.ts b/lib/modules/datasource/rubygems/versions-datasource.ts index 1e56a5d89958e8..a6d5de0bda0cf3 100644 --- a/lib/modules/datasource/rubygems/versions-datasource.ts +++ b/lib/modules/datasource/rubygems/versions-datasource.ts @@ -6,6 +6,7 @@ import { getElapsedMinutes } from '../../../util/date'; import { HttpError } from '../../../util/http'; import { newlineRegex } from '../../../util/regex'; import { LooseArray } from '../../../util/schema-utils'; +import { copystr } from '../../../util/string'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; @@ -51,8 +52,6 @@ const Lines = z type Lines = z.infer; export class VersionsDatasource extends Datasource { - private isInitialFetch = true; - constructor(override readonly id: string) { super(id); } @@ -97,31 +96,12 @@ export class VersionsDatasource extends Datasource { return { releases }; } - /** - * Since each `/versions` reponse exceed 10MB, - * there is potential for a memory leak if we construct slices - * of the response body and cache them long-term: - * - * https://bugs.chromium.org/p/v8/issues/detail?id=2869 - * - * This method meant to be called for `version` and `packageName` - * before storing them in the cache. - */ - private copystr(x: string): string { - const len = Buffer.byteLength(x, 'utf8'); - const buf = this.isInitialFetch - ? Buffer.allocUnsafe(len) // allocate from pre-allocated buffer - : Buffer.allocUnsafeSlow(len); // allocate standalone buffer - buf.write(x, 'utf8'); - return buf.toString('utf8'); - } - private updatePackageReleases( packageReleases: PackageReleases, lines: Lines ): void { for (const line of lines) { - const packageName = this.copystr(line.packageName); + const packageName = copystr(line.packageName); let versions = packageReleases.get(packageName) ?? []; const { deletedVersions, addedVersions } = line; @@ -134,7 +114,7 @@ export class VersionsDatasource extends Datasource { const existingVersions = new Set(versions); for (const addedVersion of addedVersions) { if (!existingVersions.has(addedVersion)) { - const version = this.copystr(addedVersion); + const version = copystr(addedVersion); versions.push(version); } } @@ -183,7 +163,6 @@ export class VersionsDatasource extends Datasource { const lines = Lines.parse(newLines); this.updatePackageReleases(regCache.packageReleases, lines); - this.isInitialFetch = false; } private updateRubyGemsVersionsPromise: Promise | null = null; diff --git a/lib/util/string.ts b/lib/util/string.ts index 7a4d7d87608fb4..d62a1be1a242c0 100644 --- a/lib/util/string.ts +++ b/lib/util/string.ts @@ -67,3 +67,18 @@ export function titleCase(input: string): string { return words.join(' '); } + +/** + * Sometimes we extract small strings from a multi-megabyte files. + * If we then save them in the in-memory cache, V8 may not free + * the initial buffer, which can lead to memory leaks: + * + * https://bugs.chromium.org/p/v8/issues/detail?id=2869 + * + */ +export function copystr(x: string): string { + const len = Buffer.byteLength(x, 'utf8'); + const buf = Buffer.allocUnsafeSlow(len); + buf.write(x, 'utf8'); + return buf.toString('utf8'); +}