Files
scriptc/tests/corpus/5744-atomics-ring-buffer.ts
T
Chris Tate 7b83558f52 Run Atomics on shared storage without a lock
Atomics operations on 8-, 16- and 32-bit integer arrays are sequentially
consistent atomic instructions on the element, and no longer take the
process-wide shared-memory lock. Other reads and writes of shared storage
are relaxed atomic accesses: element reads and writes access the element,
and copies, fills, conversions, searches and sorts read shared bytes into
a private copy and write back only the range they change, so concurrent
accesses to the same bytes are free of data races. Non-shared storage
keeps its plain memory accesses. Atomics.wait and Atomics.notify queue
waiters in buckets chosen by location, each with its own lock and
condition variable, so waiting threads no longer serialize other Atomics.

Atomics.store answers +0 for a value that truncates to -0, as Node does.
The shared storage runtime test also runs under ThreadSanitizer.
2026-10-16 00:06:31 -05:00

66 lines
2.3 KiB
TypeScript

// A single-producer, single-consumer ring buffer: the producer writes each
// slot with ordinary stores to Uint8, Int16 and Float64 views and publishes
// it with Atomics.store; the consumer sees the slot after Atomics.load and
// sleeps in Atomics.wait while the ring is empty or full.
import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";
const SLOTS = 8;
const ITEMS = 3000;
type Ring = { buffer: SharedArrayBuffer };
function views(buffer: SharedArrayBuffer) {
return {
head: new Int32Array(buffer, 0, 2), // [written, read]
tags: new Uint8Array(buffer, 8, SLOTS),
small: new Int16Array(buffer, 16, SLOTS),
values: new Float64Array(buffer, 32, SLOTS),
};
}
if (isMainThread) {
const buffer = new SharedArrayBuffer(32 + 8 * SLOTS);
const ring = views(buffer);
const worker = new Worker(new URL(import.meta.url), { workerData: { buffer } });
worker.on("message", (message: unknown) => console.log("consumer", JSON.stringify(message)));
worker.on("exit", (code: number) => console.log("exit", code, Atomics.load(ring.head, 0), Atomics.load(ring.head, 1)));
for (let item = 0; item < ITEMS; item++) {
for (;;) {
const read = Atomics.load(ring.head, 1);
if (item - read < SLOTS) break;
Atomics.wait(ring.head, 1, read, 50);
}
const slot = item % SLOTS;
ring.tags[slot] = item & 0xff;
ring.small[slot] = -item;
ring.values[slot] = item / 4;
Atomics.store(ring.head, 0, item + 1);
Atomics.notify(ring.head, 0);
}
} else {
const { buffer } = workerData as Ring;
const ring = views(buffer);
let tagSum = 0;
let smallSum = 0;
let valueSum = 0;
let mismatches = 0;
for (let item = 0; item < ITEMS; item++) {
for (;;) {
const written = Atomics.load(ring.head, 0);
if (written > item) break;
Atomics.wait(ring.head, 0, written, 50);
}
const slot = item % SLOTS;
const tag = ring.tags[slot]!;
const small = ring.small[slot]!;
const value = ring.values[slot]!;
if (tag !== (item & 0xff) || small !== ((-item << 16) >> 16) || value !== item / 4) mismatches++;
tagSum += tag;
smallSum += small;
valueSum += value;
Atomics.store(ring.head, 1, item + 1);
Atomics.notify(ring.head, 1);
}
parentPort!.postMessage({ tagSum, smallSum, valueSum, mismatches });
}