Skip to content

Commit ac847d0

Browse files
gh-66: Do not run ASYNC in STOP or PAUSE.
1 parent 99117c8 commit ac847d0

2 files changed

Lines changed: 139 additions & 8 deletions

File tree

src/interpreter.c

Lines changed: 95 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,69 @@ static int builtin_param_index(BuiltinFunction* builtin, const char* kw) {
406406
return -1;
407407
}
408408

409+
static int should_defer_async_argument_for_call(const char* func_name, int arg_index, Expr* arg_expr) {
410+
if (!func_name || !arg_expr) return 0;
411+
if (arg_index != 0) return 0;
412+
if (arg_expr->type != EXPR_ASYNC) return 0;
413+
return strcmp(func_name, "STOP") == 0 || strcmp(func_name, "PAUSE") == 0;
414+
}
415+
416+
static Value make_deferred_async_handle(Expr* async_expr, Env* env) {
417+
Value thr_val = value_thr_new();
418+
if (thr_val.type == VAL_THR && thr_val.as.thr) {
419+
thr_val.as.thr->body = async_expr->as.async.block;
420+
thr_val.as.thr->env = env;
421+
}
422+
return thr_val;
423+
}
424+
425+
static int start_deferred_async_handle(Interpreter* interp, Value thr_val, int line, int col) {
426+
if (thr_val.type != VAL_THR || !thr_val.as.thr) return 0;
427+
if (value_thr_get_finished(thr_val) || value_thr_get_started(thr_val)) return 0;
428+
429+
if (!thr_val.as.thr->body || !thr_val.as.thr->env) {
430+
if (interp && !interp->error) {
431+
interp->error = strdup("Cannot start deferred ASYNC: missing body or environment");
432+
interp->error_line = line;
433+
interp->error_col = col;
434+
}
435+
return -1;
436+
}
437+
438+
ThrStart* start = safe_malloc(sizeof(ThrStart));
439+
Interpreter* thr_interp = safe_malloc(sizeof(Interpreter));
440+
*thr_interp = (Interpreter){0};
441+
thr_interp->global_env = interp->global_env;
442+
thr_interp->loop_depth = 0;
443+
thr_interp->error = NULL;
444+
thr_interp->error_line = 0;
445+
thr_interp->error_col = 0;
446+
thr_interp->in_try_block = false;
447+
thr_interp->modules = interp->modules;
448+
thr_interp->shushed = interp->shushed;
449+
450+
start->interp = thr_interp;
451+
start->env = thr_val.as.thr->env;
452+
start->body = thr_val.as.thr->body;
453+
start->thr_val = value_copy(thr_val);
454+
455+
if (thrd_create(&thr_val.as.thr->thread, thr_worker, start) != thrd_success) {
456+
value_thr_set_finished(thr_val, 1);
457+
value_free(start->thr_val);
458+
free(thr_interp);
459+
free(start);
460+
if (interp && !interp->error) {
461+
interp->error = strdup("Failed to start deferred ASYNC");
462+
interp->error_line = line;
463+
interp->error_col = col;
464+
}
465+
return -1;
466+
}
467+
468+
value_thr_set_started(thr_val, 1);
469+
return 0;
470+
}
471+
409472
static void label_map_add(LabelMap* map, Value key, int index) {
410473
if (map->count + 1 > map->capacity) {
411474
size_t new_cap = map->capacity == 0 ? 8 : map->capacity * 2;
@@ -828,6 +891,7 @@ Value eval_expr(Interpreter* interp, Expr* expr, Env* env) {
828891
int kwc = (int)expr->as.call.kw_count;
829892
Value* args = NULL;
830893
Expr** arg_nodes = NULL;
894+
bool deferred_async_arg0 = false;
831895

832896
// For builtins, keywords are supported only if the builtin declares param names.
833897
if (kwc > 0 && (!builtin->param_names || builtin->param_count <= 0)) {
@@ -875,13 +939,19 @@ Value eval_expr(Interpreter* interp, Expr* expr, Env* env) {
875939

876940
// Evaluate positional args
877941
for (int i = 0; i < pos_argc; i++) {
878-
arg_nodes[i] = expr->as.call.args.items[i];
942+
Expr* arg_expr = expr->as.call.args.items[i];
943+
arg_nodes[i] = arg_expr;
944+
if (should_defer_async_argument_for_call(func_name, i, arg_expr)) {
945+
args[i] = make_deferred_async_handle(arg_expr, env);
946+
deferred_async_arg0 = true;
947+
continue;
948+
}
879949
if (((strcmp(func_name, "DEL") == 0 || strcmp(func_name, "EXIST") == 0 || strcmp(func_name, "IMPORT") == 0 || strcmp(func_name, "ASSIGN") == 0) && i == 0)
880950
|| ((strcmp(func_name, "IMPORT") == 0 || strcmp(func_name, "IMPORT_PATH") == 0) && i == 1)) {
881951
// leave as null placeholder
882952
continue;
883953
}
884-
args[i] = eval_expr(interp, expr->as.call.args.items[i], env);
954+
args[i] = eval_expr(interp, arg_expr, env);
885955
if (interp->error) {
886956
for (int j = 0; j <= i; j++) value_free(args[j]);
887957
free(args);
@@ -915,12 +985,18 @@ Value eval_expr(Interpreter* interp, Expr* expr, Env* env) {
915985
return value_null();
916986
}
917987
// Evaluate kw expr in caller env (left-to-right preserved)
918-
Value v = eval_expr(interp, valnode, env);
919-
if (interp->error) {
920-
for (int j = 0; j < max_slot; j++) value_free(args[j]);
921-
free(args);
922-
free(arg_nodes);
923-
return value_null();
988+
Value v;
989+
if (should_defer_async_argument_for_call(func_name, idx, valnode)) {
990+
v = make_deferred_async_handle(valnode, env);
991+
deferred_async_arg0 = true;
992+
} else {
993+
v = eval_expr(interp, valnode, env);
994+
if (interp->error) {
995+
for (int j = 0; j < max_slot; j++) value_free(args[j]);
996+
free(args);
997+
free(arg_nodes);
998+
return value_null();
999+
}
9241000
}
9251001
// assign into slot
9261002
if (idx >= max_slot) {
@@ -978,6 +1054,17 @@ Value eval_expr(Interpreter* interp, Expr* expr, Env* env) {
9781054
// Call builtin
9791055
Value result = builtin->impl(interp, args, effective_argc, arg_nodes, env, expr->line, expr->column);
9801056

1057+
// Start deferred STOP/PAUSE ASYNC argument only after the builtin call completes.
1058+
if (deferred_async_arg0 && args && max_slot > 0) {
1059+
if (start_deferred_async_handle(interp, args[0], expr->line, expr->column) != 0) {
1060+
value_free(result);
1061+
for (int i = 0; i < max_slot; i++) value_free(args[i]);
1062+
free(args);
1063+
free(arg_nodes);
1064+
return value_null();
1065+
}
1066+
}
1067+
9811068
// Clean up
9821069
if (args) {
9831070
for (int i = 0; i < max_slot; i++) value_free(args[i]);

tests/test2.pre

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,50 @@ DEL(pause_thr)
140140
DEL(waited_p)
141141
PRINT("PAUSE/RESUME/PAUSED: PASS\n")
142142

143+
! Regression test for GH-66: ASYNC argument execution must be deferred
144+
! until STOP/PAUSE call evaluation completes.
145+
PRINT("Testing deferred ASYNC in STOP/PAUSE (regression GH-66)...")
146+
147+
INT: gh66_stop_flag = 0d0
148+
THR: gh66_stop_thr = STOP(ASYNC{
149+
gh66_stop_flag = 0d1
150+
})
151+
INT: gh66_wait = 0d0
152+
WHILE(LT(gh66_wait, 0d11390)){
153+
IF(EQ(gh66_stop_flag, 0d1)){ BREAK(0d1) }
154+
gh66_wait = ADD(gh66_wait, 0d1)
155+
}
156+
ASSERT(EQ(gh66_stop_flag, 0d0))
157+
DEL(gh66_stop_thr)
158+
DEL(gh66_stop_flag)
159+
DEL(gh66_wait)
160+
161+
INT: gh66_pause_flag = 0d0
162+
INT: gh66_pause_go = 0d0
163+
THR: gh66_pause_thr = PAUSE(ASYNC{
164+
gh66_pause_flag = 0d1
165+
WHILE(EQ(gh66_pause_go, 0d0)){
166+
! busy-wait until main resumes and releases this worker
167+
}
168+
})
169+
INT: gh66_wait2 = 0d0
170+
WHILE(LT(gh66_wait2, 0d11390)){
171+
IF(EQ(gh66_pause_flag, 0d1)){ BREAK(0d1) }
172+
gh66_wait2 = ADD(gh66_wait2, 0d1)
173+
}
174+
ASSERT(EQ(gh66_pause_flag, 0d0))
175+
ASSERT(EQ(PAUSED(gh66_pause_thr), 0d1))
176+
gh66_pause_go = 0d1
177+
RESUME(gh66_pause_thr)
178+
AWAIT(gh66_pause_thr)
179+
ASSERT(EQ(gh66_pause_flag, 0d1))
180+
DEL(gh66_pause_thr)
181+
DEL(gh66_pause_flag)
182+
DEL(gh66_pause_go)
183+
DEL(gh66_wait2)
184+
185+
PRINT("Deferred ASYNC STOP/PAUSE: PASS\n")
186+
143187
PRINT("Testing maps...")
144188
! Simple map literal and lookup
145189
MAP: m = <"foo" = 0d5, "bar" = 0d10, "nested" = <"a" = 0d1, "b" = 0d2>>

0 commit comments

Comments
 (0)