From 008056599e272f0d5772ea1b56b4f7365d8a687f Mon Sep 17 00:00:00 2001 From: Ralph Varjabedian <5989699+ralphv@users.noreply.github.com> Date: Fri, 14 Feb 2025 21:42:29 -0500 Subject: [PATCH] add force exit --- docker-run.sh | 4 ++++ src/CreateThreadosaurus.ts | 12 ++++++++++++ test/SampleClass.ts | 6 ++++++ test/create-threadosaurus.test.ts | 9 +++++++++ 4 files changed, 31 insertions(+) create mode 100644 docker-run.sh diff --git a/docker-run.sh b/docker-run.sh new file mode 100644 index 0000000..80cf1ef --- /dev/null +++ b/docker-run.sh @@ -0,0 +1,4 @@ +#!/bin/bash +NODE_VERSION=${NODE_VERSION:-21.7.1} +# easily run node commands through docker +docker run --user $(id -u):$(id -g) --rm -it -v .:/app -w /app --env-file <(env) "node:${NODE_VERSION}-alpine" "$@" \ No newline at end of file diff --git a/src/CreateThreadosaurus.ts b/src/CreateThreadosaurus.ts index d77fe47..32635de 100644 --- a/src/CreateThreadosaurus.ts +++ b/src/CreateThreadosaurus.ts @@ -3,9 +3,12 @@ import { extname } from 'path'; import TrackedPromise from './TrackedPromise'; import { Expect } from './Expect'; +const FORCE_EXIT_MESSAGE = 'force_exit'; + export function CreateThreadosaurus( ClassRef: new (...args: unknown[]) => T, maxRunTimeMs: number = 0, + forceTerminate: boolean = false, ) { const instance = new ClassRef(); if (typeof instance.get__filename !== 'function') { @@ -44,6 +47,9 @@ export function CreateThreadosaurus( } try { weAreTerminating = true; + if (forceTerminate) { + worker.postMessage(FORCE_EXIT_MESSAGE); + } await worker.terminate(); } catch { //ignore @@ -91,6 +97,12 @@ if (!isMainThread) { // this is not intended for this class return; } + parentPort?.on('message', (message: string) => { + if (message === FORCE_EXIT_MESSAGE) { + process.exit(-1); + } + }); + let filename = input.filename; if (!extname(filename)) { diff --git a/test/SampleClass.ts b/test/SampleClass.ts index af7c6bc..7fc1454 100644 --- a/test/SampleClass.ts +++ b/test/SampleClass.ts @@ -25,6 +25,12 @@ export default class SampleClass implements Threadosaurus { return new Promise((resolve) => setTimeout(resolve, 1000)); } + infinite(): Promise { + while (true) { + /* empty */ + } + } + async exit(code: number): Promise { process.exit(code); } diff --git a/test/create-threadosaurus.test.ts b/test/create-threadosaurus.test.ts index f02b0d8..5b5932d 100644 --- a/test/create-threadosaurus.test.ts +++ b/test/create-threadosaurus.test.ts @@ -44,6 +44,15 @@ describe('CreateThreadosaurus', () => { expect(String(e)).toEqual('Error: CreateThreadosaurus execution timed out'); } }); + it('test timeout with forced terminate', async () => { + const worker = CreateThreadosaurus(SampleClass, 100, true); + try { + await worker.infinite(); + fail('this should fail'); + } catch (e) { + expect(String(e)).toEqual('Error: CreateThreadosaurus execution timed out'); + } + }); it('test missing get__filename', async () => { try { class AnyClass {}