flag used for actor stopping insetad of counter
This commit is contained in:
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user