From a6827772a5f210ca5b760586e0b685ef0054cfbb Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Sat, 6 Apr 2024 14:25:57 -0700 Subject: [PATCH] fix(esmLoader): unref MessagePort in the main thread (#30271) This prevents the process from not exiting forever due to an open port. --- .../playwright/src/transform/portTransport.ts | 16 ++++++ tests/playwright-test/clear-cache.spec.ts | 51 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 tests/playwright-test/clear-cache.spec.ts diff --git a/packages/playwright/src/transform/portTransport.ts b/packages/playwright/src/transform/portTransport.ts index a99bf76c6c..f99e39f6ce 100644 --- a/packages/playwright/src/transform/portTransport.ts +++ b/packages/playwright/src/transform/portTransport.ts @@ -33,17 +33,33 @@ export class PortTransport { if (ackId) { const callback = this._callbacks.get(ackId); this._callbacks.delete(ackId); + this._resetRef(); callback?.(result); return; } }); + // Make sure to unref **after** adding a 'message' event listener. + // https://nodejs.org/api/worker_threads.html#portref + this._resetRef(); } async send(method: string, params: any) { return await new Promise(f => { const id = ++this._lastId; this._callbacks.set(id, f); + this._resetRef(); this._port.postMessage({ id, method, params }); }); } + + private _resetRef() { + if (this._callbacks.size) { + // When we are waiting for a response, ref the port to prevent this process from exiting. + (this._port as any).ref(); + } else { + // When we are not waiting for a response, unref the port to prevent this process + // from hanging forever. + (this._port as any).unref(); + } + } } diff --git a/tests/playwright-test/clear-cache.spec.ts b/tests/playwright-test/clear-cache.spec.ts new file mode 100644 index 0000000000..3c43ea08e9 --- /dev/null +++ b/tests/playwright-test/clear-cache.spec.ts @@ -0,0 +1,51 @@ +/** + * Copyright Microsoft Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { test, expect } from './playwright-test-fixtures'; +import path from 'path'; + +export const ctReactCliEntrypoint = path.join(__dirname, '../../packages/playwright-ct-react/cli.js'); + +test('should clear cache with type:module', async ({ runCLICommand }) => { + const result = await runCLICommand({ + 'playwright.config.ts': ` + import { defineConfig } from '@playwright/test'; + export default defineConfig({}); + `, + 'pacakge.json': ` + { "type": "module" } + `, + 'a.spec.ts': ` + import { test } from '@playwright/test'; + test('example', () => {}); + `, + }, 'clear-cache'); + expect(result.exitCode).toBe(0); +}); + +test('should clear cache for ct', async ({ runCLICommand }) => { + const result = await runCLICommand({ + 'playwright.config.ts': ` + import { defineConfig } from '@playwright/test'; + export default defineConfig({}); + `, + 'a.spec.ts': ` + import { test } from '@playwright/test'; + test('example', () => {}); + `, + }, 'clear-cache', [], ctReactCliEntrypoint); + expect(result.exitCode).toBe(0); +});