From 1fe6f30e64e178fce4cb295efedc6730a24680bb Mon Sep 17 00:00:00 2001 From: 4gray Date: Mon, 27 Jul 2026 00:33:17 +0200 Subject: [PATCH] chore(perf): prepare database post-GC capture --- .../database-worker-post-gc-selection.spec.ts | 145 ++++++++++++++++++ .../database-worker-post-gc-selection.ts | 70 +++++++++ .../m3u-refresh-cancellation.benchmark.ts | 1 + .../performance-build-config.spec.ts | 13 ++ 4 files changed, 229 insertions(+) create mode 100644 apps/electron-backend-e2e/src/performance/database-worker-post-gc-selection.spec.ts create mode 100644 apps/electron-backend-e2e/src/performance/database-worker-post-gc-selection.ts diff --git a/apps/electron-backend-e2e/src/performance/database-worker-post-gc-selection.spec.ts b/apps/electron-backend-e2e/src/performance/database-worker-post-gc-selection.spec.ts new file mode 100644 index 000000000..89559182f --- /dev/null +++ b/apps/electron-backend-e2e/src/performance/database-worker-post-gc-selection.spec.ts @@ -0,0 +1,145 @@ +/* eslint-disable playwright/expect-expect -- These are Node assertion-based performance contract tests. */ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +type WorkerKind = 'database.worker' | 'playlist-refresh.worker'; + +interface TestWorkerRecord { + readonly captureGeneration: number | null; + readonly finalized: boolean; + readonly kind: WorkerKind; + readonly ordinal: number; + readonly pendingCount: number; + readonly sampleTimer: object | null; +} + +type SelectionReason = + | 'database-worker-missing' + | 'database-worker-not-idle' + | 'multiple-database-workers'; + +interface Selection { + readonly selected: T | null; + readonly unavailableReason: SelectionReason | null; +} + +type Selector = ( + records: readonly T[], + activeGeneration: number +) => Selection; + +interface SelectionModule { + createDatabaseWorkerPostGcSelectionApi?: () => { + readonly select: Selector; + }; +} + +const selectionModulePromise = import( + new URL('./database-worker-post-gc-selection.ts', import.meta.url).href +) + .then((module) => module as SelectionModule) + .catch(() => null); + +function workerRecord( + ordinal: number, + overrides: Partial = {} +): TestWorkerRecord { + return { + captureGeneration: 7, + finalized: true, + kind: 'database.worker', + ordinal, + pendingCount: 0, + sampleTimer: null, + ...overrides, + }; +} + +async function restoreSerializableSelector(): Promise { + const module = await selectionModulePromise; + assert.ok(module, 'database worker post-GC selector module must exist'); + const factory = module.createDatabaseWorkerPostGcSelectionApi; + assert.equal(typeof factory, 'function'); + const source = factory.toString(); + assert.doesNotMatch(source, /__name/); + + const restoredFactory = Function( + `"use strict"; return (${source});` + )() as () => { + readonly select: Selector; + }; + return restoredFactory().select; +} + +test('selects the only current-generation database worker independently of sampling and finalization state', async () => { + const select = await restoreSerializableSelector(); + const currentDatabase = workerRecord(4, { + finalized: true, + sampleTimer: null, + }); + const previousDatabase = workerRecord(1, { + captureGeneration: 6, + finalized: false, + sampleTimer: {}, + }); + const currentPlaylist = workerRecord(2, { + finalized: false, + kind: 'playlist-refresh.worker', + sampleTimer: {}, + }); + + const selection = select( + [previousDatabase, currentPlaylist, currentDatabase], + 7 + ); + + assert.equal(selection.selected, currentDatabase); + assert.deepEqual(selection, { + selected: currentDatabase, + unavailableReason: null, + }); +}); + +test('reports a missing database worker after ignoring playlists and previous generations', async () => { + const select = await restoreSerializableSelector(); + + assert.deepEqual( + select( + [ + workerRecord(1, { captureGeneration: 6 }), + workerRecord(2, { kind: 'playlist-refresh.worker' }), + ], + 7 + ), + { + selected: null, + unavailableReason: 'database-worker-missing', + } + ); +}); + +test('fails closed for multiple current-generation database workers instead of taking the first', async () => { + const select = await restoreSerializableSelector(); + const first = workerRecord(1, { + finalized: false, + sampleTimer: {}, + }); + const second = workerRecord(2, { + finalized: true, + sampleTimer: null, + }); + + assert.deepEqual(select([first, second], 7), { + selected: null, + unavailableReason: 'multiple-database-workers', + }); +}); + +test('fails closed while the only current-generation database worker is not idle', async () => { + const select = await restoreSerializableSelector(); + + assert.deepEqual(select([workerRecord(3, { pendingCount: 2 })], 7), { + selected: null, + unavailableReason: 'database-worker-not-idle', + }); +}); diff --git a/apps/electron-backend-e2e/src/performance/database-worker-post-gc-selection.ts b/apps/electron-backend-e2e/src/performance/database-worker-post-gc-selection.ts new file mode 100644 index 000000000..149e4bada --- /dev/null +++ b/apps/electron-backend-e2e/src/performance/database-worker-post-gc-selection.ts @@ -0,0 +1,70 @@ +export type DatabaseWorkerPostGcUnavailableReason = + | 'database-worker-missing' + | 'database-worker-not-idle' + | 'multiple-database-workers'; + +export interface DatabaseWorkerPostGcSelectionRecord { + readonly captureGeneration: number | null; + readonly kind: string; + readonly ordinal: number; + readonly pendingCount: number; +} + +export interface DatabaseWorkerPostGcSelection { + readonly selected: T | null; + readonly unavailableReason: DatabaseWorkerPostGcUnavailableReason | null; +} + +export interface DatabaseWorkerPostGcSelectionApi { + select( + records: readonly T[], + activeGeneration: number + ): DatabaseWorkerPostGcSelection; +} + +export function createDatabaseWorkerPostGcSelectionApi(): DatabaseWorkerPostGcSelectionApi { + const helpers = { + select( + records: readonly T[], + activeGeneration: number + ): DatabaseWorkerPostGcSelection { + const currentDatabaseWorkers: T[] = []; + for (const record of records) { + if ( + record.captureGeneration === activeGeneration && + record.kind === 'database.worker' + ) { + currentDatabaseWorkers.push(record); + } + } + + if (currentDatabaseWorkers.length === 0) { + return Object.freeze({ + selected: null, + unavailableReason: 'database-worker-missing', + }); + } + if (currentDatabaseWorkers.length > 1) { + return Object.freeze({ + selected: null, + unavailableReason: 'multiple-database-workers', + }); + } + + const selected = currentDatabaseWorkers[0] as T; + if (selected.pendingCount > 0) { + return Object.freeze({ + selected: null, + unavailableReason: 'database-worker-not-idle', + }); + } + + return Object.freeze({ + selected, + unavailableReason: null, + }); + }, + }; + + return Object.freeze({ select: helpers.select }); +} diff --git a/apps/electron-backend-e2e/src/performance/m3u-refresh-cancellation.benchmark.ts b/apps/electron-backend-e2e/src/performance/m3u-refresh-cancellation.benchmark.ts index d78ad9f73..cf0858f9b 100644 --- a/apps/electron-backend-e2e/src/performance/m3u-refresh-cancellation.benchmark.ts +++ b/apps/electron-backend-e2e/src/performance/m3u-refresh-cancellation.benchmark.ts @@ -163,6 +163,7 @@ async function runIteration( server.serveSeedFixture(); app = await launchElectronApp(dataDirectory, { args: [ + '--js-flags=--expose-gc', '--remote-debugging-address=127.0.0.1', `--remote-debugging-port=${RENDERER_CDP_PORT}`, `--user-data-dir=${join(dataDirectory, 'user-data')}`, diff --git a/apps/electron-backend-e2e/src/performance/performance-build-config.spec.ts b/apps/electron-backend-e2e/src/performance/performance-build-config.spec.ts index c854a5568..07a20c4d6 100644 --- a/apps/electron-backend-e2e/src/performance/performance-build-config.spec.ts +++ b/apps/electron-backend-e2e/src/performance/performance-build-config.spec.ts @@ -257,3 +257,16 @@ test('the cancellation benchmark enables preload performance capture', () => { assert.match(source, /IPTVNATOR_PERF_CAPTURE:\s*'1'/); }); + +test('the cancellation benchmark exposes GC to Electron worker isolates', () => { + const source = readFileSync( + join( + workspaceRoot, + 'apps/electron-backend-e2e/src/performance/m3u-refresh-cancellation.benchmark.ts' + ), + 'utf8' + ); + + assert.match(source, /'--js-flags=--expose-gc'/); + assert.doesNotMatch(source, /execArgv/); +});