flag used for actor stopping insetad of counter

This commit is contained in:
2026-02-17 17:59:12 -06:00
parent 5ee51198a7
commit b16fa75706
10 changed files with 115 additions and 109 deletions

View File

@@ -252,6 +252,7 @@ const char* cell_get_core_path(void) {
void actor_disrupt(cell_rt *crt)
{
crt->disrupt = 1;
JS_SetPauseFlag(crt->context, 2);
if (crt->state != ACTOR_RUNNING)
actor_free(crt);
}
@@ -265,7 +266,6 @@ void script_startup(cell_rt *prt)
g_runtime = JS_NewRuntime();
}
JSContext *js = JS_NewContext(g_runtime);
JS_SetInterruptHandler(js, (JSInterruptHandler *)actor_interrupt_cb, prt);
JS_SetContextOpaque(js, prt);
prt->context = js;
@@ -558,7 +558,6 @@ int cell_init(int argc, char **argv)
cli_rt->context = ctx;
JS_SetContextOpaque(ctx, cli_rt);
JS_SetInterruptHandler(ctx, (JSInterruptHandler *)actor_interrupt_cb, cli_rt);
JS_AddGCRef(ctx, &cli_rt->idx_buffer_ref);
JS_AddGCRef(ctx, &cli_rt->on_exception_ref);
@@ -709,7 +708,6 @@ check_actors:
JS_DeleteGCRef(ctx, &cli_rt->message_handle_ref);
JS_DeleteGCRef(ctx, &cli_rt->unneeded_ref);
JS_DeleteGCRef(ctx, &cli_rt->actor_sym_ref);
JS_SetInterruptHandler(ctx, NULL, NULL);
pthread_mutex_destroy(cli_rt->mutex);
free(cli_rt->mutex);
@@ -772,9 +770,9 @@ int uncaught_exception(JSContext *js, JSValue v)
JS_GetException(js);
cell_rt *crt = JS_GetContextOpaque(js);
if (crt && !JS_IsNull(crt->on_exception_ref.val)) {
/* Disable interrupt handler so actor_die can send messages
/* Disable interruption so actor_die can send messages
without being re-interrupted. */
JS_SetInterruptHandler(js, NULL, NULL);
JS_SetPauseFlag(js, 0);
JSValue err = JS_NewString(js, "interrupted");
JS_Call(js, crt->on_exception_ref.val, JS_NULL, 1, &err);
/* Clear any secondary exception from the callback. */

View File

@@ -1,5 +1,6 @@
#include <pthread.h>
#include <stdatomic.h>
/* Letter type for unified message queue */
typedef enum {
@@ -67,8 +68,7 @@ typedef struct cell_rt {
int affinity;
uint64_t turn_start_ns; // cell_ns() when turn began
uint64_t turn_deadline_ns; // turn_start_ns + fast or slow timer
int is_slow_turn; // 1 if running under slow timer
_Atomic uint32_t turn_gen; // incremented each turn start
int slow_strikes; // consecutive slow-completed turns
int vm_suspended; // 1 if VM is paused mid-turn
@@ -93,7 +93,6 @@ void actor_clock(cell_rt *actor, JSValue fn);
uint32_t actor_delay(cell_rt *actor, JSValue fn, double seconds);
JSValue actor_remove_timer(cell_rt *actor, uint32_t timer_id);
void exit_handler(void);
int actor_interrupt_cb(JSRuntime *rt, cell_rt *crt);
void actor_loop();
void actor_initialize(void);
void actor_free(cell_rt *actor);

View File

@@ -678,18 +678,6 @@ static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) {
return JS_ThrowTypeError(ctx, "type mismatch in binary operation");
}
/* Check for interrupt — returns: 0 = continue, -1 = hard kill, 1 = suspend */
int reg_vm_check_interrupt(JSContext *ctx) {
if (--ctx->interrupt_counter <= 0) {
ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT;
if (ctx->interrupt_handler) {
int r = ctx->interrupt_handler(ctx->rt, ctx->interrupt_opaque);
if (r < 0) return -1; /* hard kill */
if (r > 0) return 1; /* suspend request */
}
}
return 0;
}
#ifdef HAVE_ASAN
void __asan_on_error(void) {
@@ -1406,12 +1394,17 @@ vm_dispatch:
int offset = MACH_GET_sJ(instr);
pc = (uint32_t)((int32_t)pc + offset);
if (offset < 0) {
int irc = reg_vm_check_interrupt(ctx);
if (irc < 0) {
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
if (pf == 2) {
result = JS_ThrowInternalError(ctx, "interrupted");
goto done;
}
if (irc > 0) goto suspend;
if (pf == 1) {
if (ctx->vm_call_depth > 0)
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
else
goto suspend;
}
}
VM_BREAK();
}
@@ -1426,12 +1419,17 @@ vm_dispatch:
int offset = MACH_GET_sBx(instr);
pc = (uint32_t)((int32_t)pc + offset);
if (offset < 0) {
int irc = reg_vm_check_interrupt(ctx);
if (irc < 0) {
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
if (pf == 2) {
result = JS_ThrowInternalError(ctx, "interrupted");
goto done;
}
if (irc > 0) goto suspend;
if (pf == 1) {
if (ctx->vm_call_depth > 0)
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
else
goto suspend;
}
}
}
VM_BREAK();
@@ -1447,12 +1445,17 @@ vm_dispatch:
int offset = MACH_GET_sBx(instr);
pc = (uint32_t)((int32_t)pc + offset);
if (offset < 0) {
int irc = reg_vm_check_interrupt(ctx);
if (irc < 0) {
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
if (pf == 2) {
result = JS_ThrowInternalError(ctx, "interrupted");
goto done;
}
if (irc > 0) goto suspend;
if (pf == 1) {
if (ctx->vm_call_depth > 0)
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
else
goto suspend;
}
}
}
VM_BREAK();

View File

@@ -33,6 +33,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdatomic.h>
#include <sys/time.h>
#include <time.h>
#if defined(__APPLE__)
@@ -1063,10 +1064,6 @@ static JS_BOOL JSText_equal_ascii (const JSText *text, JSValue imm) {
/* Forward declarations for stone arena functions (defined after JSContext) */
/* must be large enough to have a negligible runtime cost and small
enough to call the interrupt callback often. */
#define JS_INTERRUPT_COUNTER_INIT 10000
/* Auto-rooted C call argv — GC updates values in-place */
typedef struct CCallRoot {
JSValue *argv; /* points to C-stack-local array */
@@ -1123,8 +1120,8 @@ struct JSContext {
uint64_t random_state;
/* when the counter reaches zero, JSRutime.interrupt_handler is called */
int interrupt_counter;
/* 0 = normal, 1 = suspend (fast timer), 2 = kill (slow timer) */
_Atomic int pause_flag;
/* if NULL, RegExp compilation is not supported */
JSValue (*compile_regexp) (JSContext *ctx, JSValue pattern, JSValue flags);
@@ -1145,9 +1142,6 @@ struct JSContext {
int vm_call_depth; /* 0 = pure bytecode, >0 = C frames on stack */
size_t heap_memory_limit; /* 0 = no limit, else max heap bytes */
JSInterruptHandler *interrupt_handler;
void *interrupt_opaque;
JSValue current_exception;
JS_BOOL disruption_reported;
@@ -1544,26 +1538,14 @@ static inline void set_value (JSContext *ctx, JSValue *pval, JSValue new_val) {
void JS_ThrowInterrupted (JSContext *ctx);
static no_inline __exception int __js_poll_interrupts (JSContext *ctx) {
ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT;
if (ctx->interrupt_handler) {
int r = ctx->interrupt_handler (ctx->rt, ctx->interrupt_opaque);
if (r < 0) {
JS_ThrowInterrupted (ctx);
return -1;
}
static inline __exception int js_poll_interrupts (JSContext *ctx) {
if (unlikely (atomic_load_explicit (&ctx->pause_flag, memory_order_relaxed) >= 2)) {
JS_ThrowInterrupted (ctx);
return -1;
}
return 0;
}
static inline __exception int js_poll_interrupts (JSContext *ctx) {
if (unlikely (--ctx->interrupt_counter <= 0)) {
return __js_poll_interrupts (ctx);
} else {
return 0;
}
}
/* === PPretext (parser pretext, system-malloc, used by cell_js.c parser) === */
typedef struct PPretext {
uint32_t *data;
@@ -1661,7 +1643,6 @@ JSValue js_key_from_string (JSContext *ctx, JSValue val);
/* mach.c exports */
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame);
JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count);
int reg_vm_check_interrupt(JSContext *ctx);
#endif /* QUICKJS_INTERNAL_H */

View File

@@ -345,10 +345,8 @@ int JS_GetVMCallDepth(JSContext *ctx);
/* Set per-context heap memory limit (0 = no limit) */
void JS_SetHeapMemoryLimit(JSContext *ctx, size_t limit);
/* return != 0 if the JS code needs to be interrupted */
typedef int JSInterruptHandler (JSRuntime *rt, void *opaque);
void JS_SetInterruptHandler (JSContext *ctx, JSInterruptHandler *cb,
void *opaque);
/* Set the pause flag on a context (0=normal, 1=suspend, 2=kill) */
void JS_SetPauseFlag(JSContext *ctx, int value);
JS_BOOL JS_IsLiveObject (JSRuntime *rt, JSValue obj);

View File

@@ -1879,9 +1879,8 @@ void JS_SetPoolSize (JSRuntime *rt, size_t initial, size_t cap) {
#define free(p) free_is_forbidden (p)
#define realloc(p, s) realloc_is_forbidden (p, s)
void JS_SetInterruptHandler (JSContext *ctx, JSInterruptHandler *cb, void *opaque) {
ctx->interrupt_handler = cb;
ctx->interrupt_opaque = opaque;
void JS_SetPauseFlag (JSContext *ctx, int value) {
atomic_store_explicit (&ctx->pause_flag, value, memory_order_relaxed);
}
int JS_GetVMCallDepth(JSContext *ctx) {
@@ -5358,8 +5357,7 @@ JSValue js_regexp_toString (JSContext *ctx, JSValue this_val, int argc, JSValue
int lre_check_timeout (void *opaque) {
JSContext *ctx = opaque;
return (ctx->interrupt_handler
&& ctx->interrupt_handler (ctx->rt, ctx->interrupt_opaque));
return atomic_load_explicit (&ctx->pause_flag, memory_order_relaxed) >= 2;
}
void *lre_realloc (void *opaque, void *ptr, size_t size) {

View File

@@ -22,7 +22,9 @@ typedef struct actor_node {
typedef enum {
TIMER_JS,
TIMER_NATIVE_REMOVE
TIMER_NATIVE_REMOVE,
TIMER_PAUSE,
TIMER_KILL
} timer_type;
typedef struct {
@@ -30,6 +32,7 @@ typedef struct {
cell_rt *actor;
uint32_t timer_id;
timer_type type;
uint32_t turn_gen; /* generation at registration time */
} timer_node;
static timer_node *timer_heap = NULL;
@@ -122,7 +125,9 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval);
void actor_turn(cell_rt *actor);
void heap_push(uint64_t when, cell_rt *actor, uint32_t timer_id, timer_type type) {
timer_node node = { .execute_at_ns = when, .actor = actor, .timer_id = timer_id, .type = type };
timer_node node = { .execute_at_ns = when, .actor = actor, .timer_id = timer_id, .type = type, .turn_gen = 0 };
if (type == TIMER_PAUSE || type == TIMER_KILL)
node.turn_gen = atomic_load_explicit(&actor->turn_gen, memory_order_relaxed);
arrput(timer_heap, node);
// Bubble up
@@ -192,8 +197,19 @@ void *timer_thread_func(void *arg) {
if (t.type == TIMER_NATIVE_REMOVE) {
actor_remove_cb(t.actor, t.timer_id, 0);
} else if (t.type == TIMER_PAUSE || t.type == TIMER_KILL) {
/* Only fire if turn_gen still matches (stale timers are ignored) */
uint32_t cur = atomic_load_explicit(&t.actor->turn_gen, memory_order_relaxed);
if (cur == t.turn_gen) {
if (t.type == TIMER_PAUSE) {
JS_SetPauseFlag(t.actor->context, 1);
} else {
t.actor->disrupt = 1;
JS_SetPauseFlag(t.actor->context, 2);
}
}
} else {
pthread_mutex_lock(t.actor->msg_mutex);
pthread_mutex_lock(t.actor->msg_mutex);
int idx = hmgeti(t.actor->timers, t.timer_id);
if (idx != -1) {
JSValue cb = t.actor->timers[idx].value;
@@ -343,7 +359,6 @@ void actor_free(cell_rt *actor)
arrfree(actor->letters);
JS_SetInterruptHandler(js, NULL, NULL);
JS_FreeContext(js);
free(actor->id);
@@ -628,36 +643,6 @@ const char *register_actor(const char *id, cell_rt *actor, int mainthread, doubl
return NULL;
}
int actor_interrupt_cb(JSRuntime *rt, cell_rt *crt)
{
if (engine.shutting_down || crt->disrupt)
return -1;
if (crt->turn_deadline_ns == 0)
return 0; /* no timer set (e.g. during startup) */
uint64_t now = cell_ns();
if (now < crt->turn_deadline_ns)
return 0;
if (crt->is_slow_turn) {
#ifdef ACTOR_TRACE
fprintf(stderr, "[ACTOR_TRACE] %s: slow timer expired, killing\n",
crt->name ? crt->name : crt->id);
#endif
crt->disrupt = 1;
return -1;
}
/* Fast timer expired — check if we can suspend */
if (JS_GetVMCallDepth(crt->context) > 0) {
/* Can't suspend with C frames on stack, switch to slow timer */
crt->is_slow_turn = 1;
crt->turn_deadline_ns = now + ACTOR_SLOW_TIMER_NS;
return 0;
}
#ifdef ACTOR_TRACE
fprintf(stderr, "[ACTOR_TRACE] %s: fast timer expired, requesting suspend\n",
crt->name ? crt->name : crt->id);
#endif
return 1;
}
const char *send_message(const char *id, void *msg)
{
@@ -699,16 +684,23 @@ void actor_turn(cell_rt *actor)
JSValue result;
if (actor->vm_suspended) {
/* RESUME path: continue suspended turn under slow timer */
/* RESUME path: continue suspended turn under kill timer only */
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
JS_SetPauseFlag(actor->context, 0);
actor->turn_start_ns = cell_ns();
actor->turn_deadline_ns = actor->turn_start_ns + ACTOR_SLOW_TIMER_NS;
actor->is_slow_turn = 1;
/* Register kill timer only for resume */
pthread_mutex_lock(&engine.lock);
heap_push(actor->turn_start_ns + ACTOR_SLOW_TIMER_NS,
actor, 0, TIMER_KILL);
pthread_cond_signal(&engine.timer_cond);
pthread_mutex_unlock(&engine.lock);
result = JS_ResumeRegisterVM(actor->context);
actor->vm_suspended = 0;
if (JS_IsSuspended(result)) {
/* Still suspended after slow timer — shouldn't happen, handler returns -1 */
/* Still suspended after kill timer — shouldn't happen, kill it */
actor->disrupt = 1;
goto ENDTURN;
}
@@ -747,9 +739,18 @@ void actor_turn(cell_rt *actor)
arrdel(actor->letters, 0);
pthread_mutex_unlock(actor->msg_mutex);
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
JS_SetPauseFlag(actor->context, 0);
actor->turn_start_ns = cell_ns();
actor->turn_deadline_ns = actor->turn_start_ns + ACTOR_FAST_TIMER_NS;
actor->is_slow_turn = 0;
/* Register both pause and kill timers */
pthread_mutex_lock(&engine.lock);
heap_push(actor->turn_start_ns + ACTOR_FAST_TIMER_NS,
actor, 0, TIMER_PAUSE);
heap_push(actor->turn_start_ns + ACTOR_SLOW_TIMER_NS + ACTOR_FAST_TIMER_NS,
actor, 0, TIMER_KILL);
pthread_cond_signal(&engine.timer_cond);
pthread_mutex_unlock(&engine.lock);
if (l.type == LETTER_BLOB) {
size_t size = blob_length(l.blob_data) / 8;
@@ -784,6 +785,8 @@ void actor_turn(cell_rt *actor)
actor->slow_strikes = 0; /* completed within fast timer */
ENDTURN:
/* Invalidate any outstanding pause/kill timers for this turn */
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
actor->state = ACTOR_IDLE;
if (actor->trace_hook)

View File

@@ -142,7 +142,6 @@ void actor_free(cell_rt *actor)
arrfree(actor->letters);
JS_SetInterruptHandler(js, NULL, NULL);
JS_FreeContext(js);
free(actor->id);
@@ -353,10 +352,6 @@ const char *register_actor(const char *id, cell_rt *actor, int mainthread, doubl
return NULL;
}
int actor_interrupt_cb(JSRuntime *rt, cell_rt *crt)
{
return shutting_down || crt->disrupt;
}
const char *send_message(const char *id, void *msg)
{