Skip to content

Commit b3962b0

Browse files
leifericfclaude
andcommitted
agents: add C-API await_for timeout-fires test
The previous test_c_api_agents only exercised the trivial await_for path (zero in-flight returns 1 immediately). Add a real timeout-fires probe: register a C-side test-sleep prim, queue an action that sleeps 250ms, then call mino_await_for with a 50ms deadline. The first call must return 0 (timeout); a subsequent mino_await drains the still-running action. Validates both halves of the perimeter -- the timed wait actually waits, and the cv broadcast on action completion wakes the next await. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6b0432e commit b3962b0

1 file changed

Lines changed: 63 additions & 7 deletions

File tree

tests/embed_stm_test.c

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@
1818
#include <stdio.h>
1919
#include <string.h>
2020
#include <stdlib.h>
21+
#if defined(_WIN32) && defined(_MSC_VER)
22+
# include <windows.h>
23+
# define test_sleep_ms(ms) Sleep((DWORD)(ms))
24+
#else
25+
# include <time.h>
26+
static void test_sleep_ms(long long ms)
27+
{
28+
struct timespec ts;
29+
ts.tv_sec = (time_t)(ms / 1000);
30+
ts.tv_nsec = (long)((ms % 1000) * 1000000L);
31+
nanosleep(&ts, NULL);
32+
}
33+
#endif
2134

2235
static int failures = 0;
2336

@@ -547,6 +560,23 @@ static void test_shutdown_agents_self_call_throws(void)
547560
mino_state_free(S);
548561
}
549562

563+
/* Sleep for N milliseconds inside an action, so an embed-side test
564+
* can block an agent's in-flight long enough to make await_for
565+
* actually time out. Registered in env as `test-sleep`. */
566+
static mino_val_t *prim_test_sleep(mino_state_t *S, mino_val_t *args,
567+
mino_env_t *env)
568+
{
569+
long long ms = 0;
570+
(void)env;
571+
if (args != NULL && args->type == MINO_CONS
572+
&& args->as.cons.car != NULL
573+
&& args->as.cons.car->type == MINO_INT) {
574+
ms = args->as.cons.car->as.i;
575+
}
576+
test_sleep_ms(ms);
577+
return mino_nil(S);
578+
}
579+
550580
/* Drive every public C-API agent entry from C. Validates that the
551581
* mino_send / mino_send_off / mino_await / mino_await_for /
552582
* mino_agent_error / mino_restart_agent perimeter is wired up the
@@ -592,13 +622,8 @@ static void test_c_api_agents(void)
592622
"agent should have value 101 after one inc send-off");
593623
}
594624

595-
/* mino_await_for timeout: queue an action that sleeps longer
596-
* than the timeout. Build a fn that calls (Thread/sleep 200);
597-
* mino doesn't expose that directly, but we can simulate via a
598-
* busy spin -- actually simpler: just check the no-op timeout
599-
* shape with an agent that has zero in-flight, where it
600-
* immediately succeeds. The blocking-timeout case is exercised
601-
* by the Clojure-level await-for tests. */
625+
/* mino_await_for trivial path: zero in-flight means it returns 1
626+
* immediately without blocking. */
602627
{
603628
mino_val_t *a = mino_agent(S, mino_int(S, 0));
604629
mino_val_t *agents[2];
@@ -607,6 +632,37 @@ static void test_c_api_agents(void)
607632
"mino_await_for should return 1 when nothing is queued");
608633
}
609634

635+
/* mino_await_for fires the timeout: register a C-side test-sleep
636+
* primitive, queue an action that sleeps 250ms, then call
637+
* mino_await_for with a 50ms deadline. The first call must time
638+
* out (return 0). A subsequent mino_await with no deadline must
639+
* drain. Asserts both sides of the perimeter -- the timed wait
640+
* actually waits, and the cv broadcast on action completion
641+
* wakes the next await. */
642+
{
643+
mino_val_t *a = mino_agent(S, mino_int(S, 0));
644+
mino_val_t *agents[2];
645+
mino_val_t *slow_fn;
646+
mino_register_fn(S, env, "test-sleep", prim_test_sleep);
647+
slow_fn = mino_eval_string(S,
648+
"(fn [v] (test-sleep 250) (inc v))", env);
649+
REQUIRE(slow_fn != NULL, "slow_fn should compile");
650+
REQUIRE(mino_send(S, a, slow_fn, NULL) != NULL,
651+
"mino_send of slow action should enqueue");
652+
agents[0] = a; agents[1] = NULL;
653+
REQUIRE(mino_await_for(S, 50, agents) == 0,
654+
"mino_await_for should return 0 when the deadline "
655+
"fires before the action completes");
656+
/* Action is still running on the worker; await with no
657+
* deadline drains it. */
658+
REQUIRE(mino_await(S, agents) != NULL,
659+
"mino_await must drain the action started above");
660+
REQUIRE(a->as.agent.val != NULL && a->as.agent.val->type == MINO_INT,
661+
"agent value should be int after the slow action runs");
662+
REQUIRE(a->as.agent.val->as.i == 1,
663+
"agent value should be 1 after one slow inc action");
664+
}
665+
610666
/* mino_agent_error / mino_restart_agent: install a validator
611667
* that rejects negatives, drive the agent into the failed
612668
* state, then restart with a clean value. */

0 commit comments

Comments
 (0)