current commit
260929.18 · worker: discard queued messages on termination
// Flags: --experimental-web-worker
'use strict';

const common = require('../common');
const assert = require('node:assert');

function checkSerialization(worker) {
  assert.throws(() => worker.postMessage(() => {}), { name: 'DataCloneError' });
  const buffer = new ArrayBuffer(8);
  assert.throws(() => worker.postMessage(null, [buffer, buffer]), { name: 'DataCloneError' });
  assert.strictEqual(buffer.byteLength, 8);
  worker.postMessage(null, [buffer]);
  assert.strictEqual(buffer.byteLength, 0);
}

// A failed script fetch still leaves an outside port that serializes messages.
{
  const worker = new Worker('data:text/plain,');
  worker.onerror = common.mustCall(() => checkSerialization(worker));
  checkSerialization(worker);
}

// Serialization is required even after the backing thread has exited.
{
  const worker = new Worker('data:text/javascript,close()');
  worker.onerror = common.mustNotCall('worker failed');
  process.once('worker', common.mustCall((thread) => {
    thread.once('exit', common.mustCall(() => checkSerialization(worker)));
  }));
}

// Queue messages before terminating so the test does not depend on thread speed.
{
  const source = `
    onmessage = ({ data }) => {
      for (let i = 0; i < 3; i++) postMessage(i);
      const state = new Int32Array(data);
      Atomics.store(state, 0, 1);
      Atomics.notify(state, 0);
    };
  `;
  const worker = new Worker(`data:text/javascript,${encodeURIComponent(source)}`);
  worker.onerror = common.mustNotCall('worker failed');
  worker.onmessage = common.mustNotCall('message delivered after terminate()');
  const state = new Int32Array(new SharedArrayBuffer(4));
  worker.postMessage(state.buffer);
  assert.notStrictEqual(Atomics.wait(state, 0, 0, common.platformTimeout(10000)), 'timed-out');
  worker.terminate();
  worker.terminate();
  checkSerialization(worker);
}

// terminate() can run during message dispatch, including while the backing
// thread is draining messages on exit. Finish this event but discard later ones.
for (const closeAfterPosting of [false, true]) {
  const source = `
    onmessage = ({ data }) => {
      for (let i = 0; i < 3; i++) postMessage(i);
      const state = new Int32Array(data);
      Atomics.store(state, 0, 1);
      Atomics.notify(state, 0);
      if (${closeAfterPosting}) close();
    };
  `;
  const worker = new Worker(`data:text/javascript,${encodeURIComponent(source)}`);
  worker.onerror = common.mustNotCall('worker failed');
  worker.addEventListener('message', common.mustCall(({ data }) => {
    assert.strictEqual(data, 0);
    worker.terminate();
    checkSerialization(worker);
  }));
  worker.addEventListener('message', common.mustCall(({ data }) => {
    assert.strictEqual(data, 0);
  }));
  const state = new Int32Array(new SharedArrayBuffer(4));
  worker.postMessage(state.buffer);
  assert.notStrictEqual(Atomics.wait(state, 0, 0, common.platformTimeout(10000)), 'timed-out');
}