From 2c75fac1a5bc92977dd22423e5f30d9eb611cb01 Mon Sep 17 00:00:00 2001 From: Sergei Zharinov <zharinov@users.noreply.github.com> Date: Sat, 29 Apr 2023 19:19:35 +0300 Subject: [PATCH] fix(rubygems): Copy strings via buffers instead of slice hack (#21876) --- .../rubygems/versions-datasource.ts | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/lib/modules/datasource/rubygems/versions-datasource.ts b/lib/modules/datasource/rubygems/versions-datasource.ts index e398bd0a5c..5122bd759a 100644 --- a/lib/modules/datasource/rubygems/versions-datasource.ts +++ b/lib/modules/datasource/rubygems/versions-datasource.ts @@ -45,12 +45,22 @@ const Lines = z } } return { packageName, deletedVersions, addedVersions }; - }) + }), + { + onError: ({ error: err, input }) => { + logger.debug( + { err, input }, + 'Rubygems: failed to parse some version lines' + ); + }, + } ) ); type Lines = z.infer<typeof Lines>; export class VersionsDatasource extends Datasource { + private isInitialFetch = true; + constructor(override readonly id: string) { super(id); } @@ -96,10 +106,22 @@ export class VersionsDatasource extends Datasource { } /** - * https://bugs.chromium.org/p/v8/issues/detail?id=2869 + * 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 static copystr(x: string): string { - return (' ' + x).slice(1); + 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( @@ -107,7 +129,7 @@ export class VersionsDatasource extends Datasource { lines: Lines ): void { for (const line of lines) { - const packageName = VersionsDatasource.copystr(line.packageName); + const packageName = this.copystr(line.packageName); let versions = packageReleases.get(packageName) ?? []; const { deletedVersions, addedVersions } = line; @@ -120,7 +142,7 @@ export class VersionsDatasource extends Datasource { const existingVersions = new Set(versions); for (const addedVersion of addedVersions) { if (!existingVersions.has(addedVersion)) { - const version = VersionsDatasource.copystr(addedVersion); + const version = this.copystr(addedVersion); versions.push(version); } } @@ -169,6 +191,7 @@ export class VersionsDatasource extends Datasource { const lines = Lines.parse(newLines); this.updatePackageReleases(regCache.packageReleases, lines); + this.isInitialFetch = false; } private updateRubyGemsVersionsPromise: Promise<void> | null = null; -- GitLab