fix(runtime): share uptime anchor across threads

This commit is contained in:
Chris Tate
2026-08-12 09:15:54 -05:00
parent cce924aed4
commit 4b099ea2fd
5 changed files with 55 additions and 10 deletions
+30 -3
View File
@@ -1103,8 +1103,27 @@ void scr_process_exit(double code) {
* attribute monotonic stamp — the binary's own start, which is what
* "the current Node.js process" means for a compiled program). */
#ifdef _WIN32
#if defined(SCR_LIB) && defined(SCR_THREAD_INSTANCES)
/* The uptime anchor belongs to the host PROCESS, not to a library
* instance. InitOnce keeps the lazy Windows spelling race-free when
* several thread instances ask for the clock concurrently. */
static INIT_ONCE scr_uptime_once = INIT_ONCE_STATIC_INIT;
static double scr_uptime_t0_ms;
static BOOL CALLBACK scr_uptime_anchor_once(PINIT_ONCE once, PVOID param,
PVOID *ctx) {
(void)once;
(void)param;
(void)ctx;
scr_uptime_t0_ms = (double)GetTickCount64();
return TRUE;
}
static void scr_uptime_anchor_init(void) {
InitOnceExecuteOnce(&scr_uptime_once, scr_uptime_anchor_once, NULL, NULL);
}
#else
static SCR_TL double scr_uptime_t0_ms;
static void scr_uptime_anchor_init(void) { scr_uptime_t0_ms = (double)GetTickCount64(); }
#endif
static double scr_uptime_now_ms(void) { return (double)GetTickCount64(); }
#else
#include <sys/resource.h>
@@ -1112,7 +1131,11 @@ static double scr_uptime_now_ms(void) { return (double)GetTickCount64(); }
#ifdef __APPLE__
#include <mach/mach.h>
#endif
static SCR_TL double scr_uptime_t0_ms;
/* A load-time, process-wide anchor: the constructor runs on only the
* initial thread, while thread-instanced archives are entered from worker
* threads whose TLS slots would otherwise stay zero. It is immutable once
* main starts, so sharing it adds no cross-instance mutable state. */
static double scr_uptime_t0_ms;
static double scr_uptime_now_ms(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
@@ -1124,7 +1147,9 @@ __attribute__((constructor)) static void scr_uptime_anchor_init(void) {
#endif
double scr_process_uptime(void) {
#ifdef _WIN32
#if defined(_WIN32) && defined(SCR_LIB) && defined(SCR_THREAD_INSTANCES)
scr_uptime_anchor_init();
#elif defined(_WIN32)
if (scr_uptime_t0_ms == 0) scr_uptime_anchor_init();
#endif
return (scr_uptime_now_ms() - scr_uptime_t0_ms) / 1000.0;
@@ -1134,7 +1159,9 @@ double scr_process_uptime(void) {
* start (Node's timeOrigin anchor), fractional — the same monotonic
* clock and anchor as uptime, in Node's performance.now units. */
double scr_perf_now(void) {
#ifdef _WIN32
#if defined(_WIN32) && defined(SCR_LIB) && defined(SCR_THREAD_INSTANCES)
scr_uptime_anchor_init();
#elif defined(_WIN32)
if (scr_uptime_t0_ms == 0) scr_uptime_anchor_init();
#endif
return scr_uptime_now_ms() - scr_uptime_t0_ms;
+5 -5
View File
@@ -446,11 +446,11 @@ function buildThreadProbe(
return bin;
}
const THREADED_EXPECTED = `t0: bump x100 -> 101, calls_seen 100, sums_ok=1, trap fell through 0
const THREADED_EXPECTED = `t0: bump x100 -> 101, calls_seen 100, sums_ok=1, clocks_ok=1, trap fell through 0
t0 sink: calls=1 ctx_ok=1 fields=3 code=[SC4014] symbol=[mt_boom] addr_nonzero=1
t1: bump x150 -> 151, calls_seen 150, sums_ok=1, post_ok=1
t2: bump x200 -> 201, calls_seen 200, sums_ok=1, post_ok=1
t3: bump x250 -> 251, calls_seen 250, sums_ok=1, post_ok=1
t1: bump x150 -> 151, calls_seen 150, sums_ok=1, clocks_ok=1, post_ok=1
t2: bump x200 -> 201, calls_seen 200, sums_ok=1, clocks_ok=1, post_ok=1
t3: bump x250 -> 251, calls_seen 250, sums_ok=1, clocks_ok=1, post_ok=1
survivor sinks: 0 0 0
`;
@@ -510,7 +510,7 @@ localizationTest("M7: thread-instanced and runtime-localized archives compose in
: [];
expect([...defined].sort()).toEqual(
[
"mt_boom", "mt_bump", "mt_calls_seen", "mt_collect", "mt_init", "mt_set_panic_sink", "mt_sum_to",
"mt_boom", "mt_bump", "mt_calls_seen", "mt_collect", "mt_init", "mt_perf_now", "mt_set_panic_sink", "mt_sum_to", "mt_uptime",
...toolchainDefinitions,
].sort(),
);
@@ -4,6 +4,8 @@
// (array index OOB — the runtime's own range trap) that must reach only the
// calling thread's sink. No top-level output: four instances init
// concurrently in the probe.
import { performance } from "node:perf_hooks";
let calls = 0;
export function bump(x: number): number {
@@ -27,3 +29,11 @@ export function boom(i: number): number {
const xs = [1, 2, 3];
return xs[i]!;
}
export function uptime(): number {
return process.uptime();
}
export function perfNow(): number {
return performance.now();
}
+7 -1
View File
@@ -23,6 +23,8 @@ extern double mt_bump(double x);
extern double mt_calls_seen(void);
extern double mt_sum_to(double n);
extern double mt_boom(double i);
extern double mt_uptime(void);
extern double mt_perf_now(void);
#define NTHREADS 4
@@ -80,6 +82,7 @@ typedef struct {
double last_bump;
double calls_seen;
int sums_ok;
int clocks_ok;
int trap_fell_through; /* thread 0 only */
int post_ok; /* threads 1..3 only */
} Worker;
@@ -121,6 +124,9 @@ static void *worker(void *arg) {
Worker *w = arg;
mt_set_panic_sink(sink, &w->sink);
mt_init(); /* concurrent across all four threads: each init is instance-local */
double uptime = mt_uptime();
double perf_now = mt_perf_now();
w->clocks_ok = uptime >= 0 && uptime < 60 && perf_now >= 0 && perf_now < 60000;
w->sums_ok = 1;
double last = 0;
for (int i = 0; i < w->iters; i++) {
@@ -163,7 +169,7 @@ int main(void) {
for (int i = 0; i < NTHREADS; i++) pthread_join(threads[i], NULL);
for (int i = 0; i < NTHREADS; i++) {
Worker *w = &workers[i];
printf("t%d: bump x%d -> %.0f, calls_seen %.0f, sums_ok=%d", i, w->iters, w->last_bump, w->calls_seen, w->sums_ok);
printf("t%d: bump x%d -> %.0f, calls_seen %.0f, sums_ok=%d, clocks_ok=%d", i, w->iters, w->last_bump, w->calls_seen, w->sums_ok, w->clocks_ok);
if (i == 0) {
printf(", trap fell through %d\n", w->trap_fell_through);
printf("t0 sink: calls=%d ctx_ok=%d fields=%d code=[%s] symbol=[%s] addr_nonzero=%d\n",
@@ -16,6 +16,8 @@
{ "export": "bump", "symbol": "mt_bump", "params": ["f64"], "returns": "f64" },
{ "export": "callsSeen", "symbol": "mt_calls_seen", "params": [], "returns": "f64" },
{ "export": "sumTo", "symbol": "mt_sum_to", "params": ["f64"], "returns": "f64" },
{ "export": "boom", "symbol": "mt_boom", "params": ["f64"], "returns": "f64" }
{ "export": "boom", "symbol": "mt_boom", "params": ["f64"], "returns": "f64" },
{ "export": "uptime", "symbol": "mt_uptime", "params": [], "returns": "f64" },
{ "export": "perfNow", "symbol": "mt_perf_now", "params": [], "returns": "f64" }
]
}