diff --git a/.changeset/shiny-kids-check.md b/.changeset/shiny-kids-check.md new file mode 100644 index 000000000..451ef2345 --- /dev/null +++ b/.changeset/shiny-kids-check.md @@ -0,0 +1,5 @@ +--- +"synckit": patch +--- + +fix: unexpected null pointer exception due to race condition diff --git a/package.json b/package.json index 313af0144..bfad963ae 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,7 @@ "detail": true, "ignoreAsAssertion": true, "ignoreFiles": [ - "**/*.d.ts" + "lib" ], "ignoreNonNullAssertion": true, "showRelativePath": true, diff --git a/src/index.ts b/src/index.ts index 9fc11e8aa..e01190c17 100644 --- a/src/index.ts +++ b/src/index.ts @@ -542,9 +542,22 @@ function startWorkerThread>( throw new Error('Internal error: Atomics.wait() failed: ' + status) } - const { id, ...message } = ( - receiveMessageOnPort(mainPort) as { message: WorkerToMainMessage } - ).message + const result = receiveMessageOnPort(mainPort) as + | { message: WorkerToMainMessage } + | undefined + + const msg = result?.message + + if (msg?.id == null || msg.id < expectedId) { + const waitingTime = Date.now() - start + return receiveMessageWithId( + port, + expectedId, + waitingTimeout ? waitingTimeout - waitingTime : undefined, + ) + } + + const { id, ...message } = msg if (id < expectedId) { const waitingTime = Date.now() - start diff --git a/test/reliability.spec.ts b/test/reliability.spec.ts new file mode 100644 index 000000000..74cb12e76 --- /dev/null +++ b/test/reliability.spec.ts @@ -0,0 +1,22 @@ +import path from 'node:path' + +import { _dirname, testIf } from './helpers.js' + +const times = 1e6 + +const test = testIf(!!process.env.CI) + +test(`Reliability (${times} runs)`, async () => { + const { createSyncFn } = await import('synckit') + const identity = createSyncFn(path.resolve(_dirname, `./worker-identity.js`)) + + for (let index = 0; index < times; index++) { + try { + // eslint-disable-next-line jest/no-standalone-expect + expect(identity(index)).toBe(index) + } catch (error) { + console.error(`Failed on ${index + 1}/${times} run.`) + throw error + } + } +}) diff --git a/test/worker-identity.js b/test/worker-identity.js new file mode 100644 index 000000000..4aa88ba98 --- /dev/null +++ b/test/worker-identity.js @@ -0,0 +1,3 @@ +import { runAsWorker } from 'synckit' + +runAsWorker(value => value)