From 2223cbbd871b7c8b94577054b3dc436c8e182a33 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Tue, 2 Dec 2025 21:25:03 -0800 Subject: [PATCH] fix coroutine processing --- {tests => examples}/server/html.js | 0 {tests => examples}/server/meow.txt | 0 {tests => examples}/server/radix3.js | 0 {tests => examples}/server/server.js | 0 include/ant.h | 3 +- meson.build | 2 +- src/ant.c | 77 +++++++++++++++++++++++----- src/main.c | 16 +----- tests/simple_await_test.cjs | 5 ++ tests/simple_sync_await.cjs | 9 ++++ tests/simple_timer_await.cjs | 16 ++++++ tests/test_await_return.cjs | 7 +++ tests/test_concurrent_timing.cjs | 20 ++++++++ tests/test_cpu_usage.cjs | 8 +++ tests/test_debug_await.cjs | 14 +++++ tests/test_immediate_concurrent.cjs | 21 ++++++++ tests/test_practical_async.cjs | 29 +++++++++++ tests/test_two_awaits.cjs | 32 ++++++++++++ 18 files changed, 230 insertions(+), 29 deletions(-) rename {tests => examples}/server/html.js (100%) rename {tests => examples}/server/meow.txt (100%) rename {tests => examples}/server/radix3.js (100%) rename {tests => examples}/server/server.js (100%) create mode 100644 tests/simple_await_test.cjs create mode 100644 tests/simple_sync_await.cjs create mode 100644 tests/simple_timer_await.cjs create mode 100644 tests/test_await_return.cjs create mode 100644 tests/test_concurrent_timing.cjs create mode 100644 tests/test_cpu_usage.cjs create mode 100644 tests/test_debug_await.cjs create mode 100644 tests/test_immediate_concurrent.cjs create mode 100644 tests/test_practical_async.cjs create mode 100644 tests/test_two_awaits.cjs diff --git a/tests/server/html.js b/examples/server/html.js similarity index 100% rename from tests/server/html.js rename to examples/server/html.js diff --git a/tests/server/meow.txt b/examples/server/meow.txt similarity index 100% rename from tests/server/meow.txt rename to examples/server/meow.txt diff --git a/tests/server/radix3.js b/examples/server/radix3.js similarity index 100% rename from tests/server/radix3.js rename to examples/server/radix3.js diff --git a/tests/server/server.js b/examples/server/server.js similarity index 100% rename from tests/server/server.js rename to examples/server/server.js diff --git a/include/ant.h b/include/ant.h index 679cb05..1af877d 100644 --- a/include/ant.h +++ b/include/ant.h @@ -72,4 +72,5 @@ void js_prop_iter_end(js_prop_iter_t *iter); jsval_t js_mkpromise(struct js *js); void js_resolve_promise(struct js *js, jsval_t promise, jsval_t value); -void js_reject_promise(struct js *js, jsval_t promise, jsval_t value); \ No newline at end of file +void js_reject_promise(struct js *js, jsval_t promise, jsval_t value); +void js_run_event_loop(struct js *js); \ No newline at end of file diff --git a/meson.build b/meson.build index 4216e40..ae0f933 100644 --- a/meson.build +++ b/meson.build @@ -41,7 +41,7 @@ endif build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() version_conf = configuration_data() -version_conf.set('ANT_VERSION', '0.0.6.6') +version_conf.set('ANT_VERSION', '0.0.6.7') version_conf.set('ANT_GIT_HASH', git_hash) version_conf.set('ANT_BUILD_DATE', build_date) diff --git a/src/ant.c b/src/ant.c index bcd5e88..9bb06e9 100644 --- a/src/ant.c +++ b/src/ant.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -345,6 +346,23 @@ static bool has_pending_coroutines(void) { return pending_coroutines.head != NULL; } +void js_run_event_loop(struct js *js) { + while (has_pending_microtasks() || has_pending_timers()) { + process_microtasks(js); + + if (has_pending_timers()) { + int64_t next_timeout_ms = get_next_timer_timeout(); + + if (next_timeout_ms <= 0) { + process_timers(js); + continue; + } else { + usleep(next_timeout_ms > 1000000 ? 1000000 : next_timeout_ms * 1000); + } + } + } +} + static void free_coroutine(coroutine_t *coro) { if (coro) { if (coro->args) free(coro->args); @@ -2839,36 +2857,71 @@ static jsval_t js_unary(struct js *js) { builtin_promise_then(js, then_args, 2); js->this_val = saved_this; + uint8_t saved_flags = js->flags; + const char *saved_code = js->code; + jsoff_t saved_clen = js->clen; + jsoff_t saved_pos = js->pos; + uint8_t saved_tok = js->tok; + uint8_t saved_consumed = js->consumed; + while (!coro->is_settled) { + if (has_pending_timers()) { + int64_t next_timeout = get_next_timer_timeout(); + if (next_timeout <= 0) { + process_timers(js); + js->flags = saved_flags; + js->code = saved_code; + js->clen = saved_clen; + js->pos = saved_pos; + js->tok = saved_tok; + js->consumed = saved_consumed; + } + } + process_microtasks(js); + js->flags = saved_flags; + js->code = saved_code; + js->clen = saved_clen; + js->pos = saved_pos; + js->tok = saved_tok; + js->consumed = saved_consumed; + + int coroutines_checked = 0; + int total_coroutines = 0; - while (has_pending_coroutines()) { + coroutine_t *temp = pending_coroutines.head; + while (temp) { + total_coroutines++; + temp = temp->next; + } + + while (has_pending_coroutines() && coroutines_checked < total_coroutines) { coroutine_t *resumed = dequeue_coroutine(); + coroutines_checked++; + if (resumed == coro) { jsval_t result = resumed->result; bool is_error = resumed->is_error; free_coroutine(resumed); - if (is_error) { - return js_throw(js, result); - } + if (is_error) return js_throw(js, result); return result; } enqueue_coroutine(resumed); } - if (!has_pending_microtasks() && has_pending_timers()) { - int64_t next_timeout = get_next_timer_timeout(); - if (next_timeout <= 0) { - process_timers(js); - continue; - } - } - if (!has_pending_microtasks() && !has_pending_coroutines() && !has_pending_timers()) { free_coroutine(coro); return js_mkerr(js, "await: promise never settled"); } + + if (has_pending_timers() && !has_pending_microtasks() && !has_pending_coroutines()) { + int64_t next_timeout = get_next_timer_timeout(); + if (next_timeout > 0) { + int64_t sleep_ms = next_timeout < 1 ? 1 : (next_timeout > 1 ? 1 : next_timeout); + usleep(sleep_ms * 1000); + } + } } jsval_t result = coro->result; diff --git a/src/main.c b/src/main.c index d92ac18..c63f8b2 100644 --- a/src/main.c +++ b/src/main.c @@ -135,21 +135,7 @@ int main(int argc, char *argv[]) { int result = execute_module(js, module_file); - while (has_pending_microtasks() || has_pending_timers()) { - process_microtasks(js); - - if (has_pending_timers()) { - int64_t next_timeout_ms = get_next_timer_timeout(); - - if (next_timeout_ms <= 0) { - process_timers(js); - continue; - } else { - usleep(next_timeout_ms > 1000000 ? 1000000 : next_timeout_ms * 1000); - } - } - } - + js_run_event_loop(js); if (dump) js_dump(js); js_destroy(js); diff --git a/tests/simple_await_test.cjs b/tests/simple_await_test.cjs new file mode 100644 index 0000000..1db3ae7 --- /dev/null +++ b/tests/simple_await_test.cjs @@ -0,0 +1,5 @@ +async function test() { + await new Promise(resolve => setTimeout(resolve, 10)); + return "done"; +} +test().then(r => console.log(r)); diff --git a/tests/simple_sync_await.cjs b/tests/simple_sync_await.cjs new file mode 100644 index 0000000..75b7dae --- /dev/null +++ b/tests/simple_sync_await.cjs @@ -0,0 +1,9 @@ +// Test that works (sync promise) +async function testSync() { + console.log("Before await"); + const result = await Promise.resolve("SYNC"); + console.log("After await:", result); + return "RETURNED_" + result; +} + +testSync().then(r => console.log("Final:", r)); diff --git a/tests/simple_timer_await.cjs b/tests/simple_timer_await.cjs new file mode 100644 index 0000000..a55fccf --- /dev/null +++ b/tests/simple_timer_await.cjs @@ -0,0 +1,16 @@ +async function test() { + console.log("1: Before await"); + const x = await new Promise(resolve => setTimeout(() => { + console.log("2: Timer fired"); + resolve("VALUE"); + }, 50)); + console.log("3: After await, x =", x); + console.log("4: About to return"); + return "FINAL_" + x; +} + +console.log("A: Calling test()"); +const p = test(); +console.log("B: test() returned"); +p.then(result => console.log("C: Promise resolved:", result)); +console.log("D: Script end"); diff --git a/tests/test_await_return.cjs b/tests/test_await_return.cjs new file mode 100644 index 0000000..c0d003e --- /dev/null +++ b/tests/test_await_return.cjs @@ -0,0 +1,7 @@ +async function test() { + console.log("Before"); + await new Promise(resolve => setTimeout(resolve, 10)); + return "done"; +} + +test().then(r => console.log("Result:", r)); diff --git a/tests/test_concurrent_timing.cjs b/tests/test_concurrent_timing.cjs new file mode 100644 index 0000000..db93ab8 --- /dev/null +++ b/tests/test_concurrent_timing.cjs @@ -0,0 +1,20 @@ +console.log("START"); +const start = Date.now(); + +async function delay(ms, name) { + console.log(`${name}: Starting ${ms}ms wait at +${Date.now() - start}ms`); + await new Promise(resolve => setTimeout(() => { + console.log(`${name}: Timer fired at +${Date.now() - start}ms`); + resolve(); + }, ms)); + console.log(`${name}: Resumed at +${Date.now() - start}ms`); + return `${name}_done`; +} + +console.log("Calling func1 at +${Date.now() - start}ms"); +delay(100, "func1").then(r => console.log(`func1 complete: ${r} at +${Date.now() - start}ms`)); +console.log("Called func1, calling func2 at +${Date.now() - start}ms"); +delay(50, "func2").then(r => console.log(`func2 complete: ${r} at +${Date.now() - start}ms`)); +console.log("Called func2 at +${Date.now() - start}ms"); + +console.log("END at +${Date.now() - start}ms"); diff --git a/tests/test_cpu_usage.cjs b/tests/test_cpu_usage.cjs new file mode 100644 index 0000000..4b44b9c --- /dev/null +++ b/tests/test_cpu_usage.cjs @@ -0,0 +1,8 @@ +async function test() { + console.log("Starting wait..."); + await new Promise(resolve => setTimeout(resolve, 500)); + console.log("Done!"); + return "success"; +} + +test().then(r => console.log("Result:", r)); diff --git a/tests/test_debug_await.cjs b/tests/test_debug_await.cjs new file mode 100644 index 0000000..c883a41 --- /dev/null +++ b/tests/test_debug_await.cjs @@ -0,0 +1,14 @@ +async function test() { + console.log("1: Before await"); + const x = await new Promise(resolve => setTimeout(() => { + console.log("2: Timer callback"); + resolve("VALUE"); + }, 10)); + console.log("3: x assigned, x =", x); + console.log("4: About to return"); + return x; +} + +console.log("A: Calling test"); +test().then(r => console.log("Final:", r)); +console.log("B: After test call"); diff --git a/tests/test_immediate_concurrent.cjs b/tests/test_immediate_concurrent.cjs new file mode 100644 index 0000000..ca01919 --- /dev/null +++ b/tests/test_immediate_concurrent.cjs @@ -0,0 +1,21 @@ +// Test with IMMEDIATELY resolved promises (should work) +console.log("=== Immediate Promises Test ==="); + +async function func1() { + console.log("func1: start"); + const result = await Promise.resolve("func1_result"); + console.log("func1: after await, result:", result); + return result; +} + +async function func2() { + console.log("func2: start"); + const result = await Promise.resolve("func2_result"); + console.log("func2: after await, result:", result); + return result; +} + +func1().then(r => console.log("func1 final:", r)); +func2().then(r => console.log("func2 final:", r)); + +console.log("=== Both called ==="); diff --git a/tests/test_practical_async.cjs b/tests/test_practical_async.cjs new file mode 100644 index 0000000..3cf2829 --- /dev/null +++ b/tests/test_practical_async.cjs @@ -0,0 +1,29 @@ +// Real-world async pattern: sequential operations +async function fetchUser(id) { + console.log(`Fetching user ${id}...`); + return new Promise(resolve => setTimeout(() => { + resolve({ id, name: `User${id}` }); + }, 50)); +} + +async function fetchUserPosts(userId) { + console.log(`Fetching posts for user ${userId}...`); + return new Promise(resolve => setTimeout(() => { + resolve([`Post1 by ${userId}`, `Post2 by ${userId}`]); + }, 30)); +} + +async function getUserData(id) { + const user = await fetchUser(id); + console.log("Got user:", user.name); + + const posts = await fetchUserPosts(user.id); + console.log("Got posts:", posts.length); + + return { user, posts }; +} + +// This is the common pattern - sequential async calls within one function +getUserData(123).then(data => { + console.log("Complete!", data.user.name, "has", data.posts.length, "posts"); +}); diff --git a/tests/test_two_awaits.cjs b/tests/test_two_awaits.cjs new file mode 100644 index 0000000..dcfe4df --- /dev/null +++ b/tests/test_two_awaits.cjs @@ -0,0 +1,32 @@ +// Test what happens when TWO async functions are awaiting simultaneously +console.log("=== Multiple Awaits Test ==="); + +async function delay(ms, name) { + console.log(`${name}: Creating ${ms}ms delay`); + return new Promise(resolve => { + setTimeout(() => { + console.log(`${name}: Timer fired after ${ms}ms`); + resolve(`${name}_RESULT`); + }, ms); + }); +} + +async function func1() { + console.log("func1: Before await"); + const result = await delay(100, "func1"); + console.log("func1: After await, result:", result); + return result; +} + +async function func2() { + console.log("func2: Before await"); + const result = await delay(50, "func2"); + console.log("func2: After await, result:", result); + return result; +} + +// Call both functions - they should run concurrently +func1().then(r => console.log("func1 final:", r)); +func2().then(r => console.log("func2 final:", r)); + +console.log("=== Both functions called ==="); -- 2.51.2