diff --git a/meson.build b/meson.build index 5d01c05..5a7def3 100644 --- a/meson.build +++ b/meson.build @@ -79,7 +79,7 @@ endif build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() version_conf = configuration_data() -version_conf.set('ANT_VERSION', '0.2.2.26') +version_conf.set('ANT_VERSION', '0.2.2.27') version_conf.set('ANT_GIT_HASH', git_hash) version_conf.set('ANT_BUILD_DATE', build_date) diff --git a/src/modules/timer.c b/src/modules/timer.c index 403fec7..0f97bc3 100644 --- a/src/modules/timer.c +++ b/src/modules/timer.c @@ -235,39 +235,47 @@ int has_pending_immediates(void) { return 0; } -void process_timers(struct js *js) { - if (timer_state.timers == NULL) return; +static void remove_timer(timer_entry_t *target) { + timer_entry_t **ptr = &timer_state.timers; +scan: + if (!*ptr) return; + if (*ptr == target) { *ptr = target->next; free(target); return; } + ptr = &(*ptr)->next; + goto scan; +} + +void process_timers(struct js *js) { uint64_t current_time = get_current_time_ms(); - timer_entry_t **entry_ptr = &timer_state.timers; + timer_entry_t **ptr = &timer_state.timers; + timer_entry_t *entry; + +scan: + if (!*ptr) return; + entry = *ptr; - while (*entry_ptr != NULL) { - timer_entry_t *entry = *entry_ptr; - - if (!entry->active) { - *entry_ptr = entry->next; - free(entry); - continue; - } - - if (entry->active && current_time >= entry->target_time_ms) { - jsval_t args[0]; - js_call(js, entry->callback, args, 0); - - process_microtasks(js); - - if (entry->is_interval) { - entry->target_time_ms = get_current_time_ms() + entry->interval_ms; - entry_ptr = &entry->next; - } else { - *entry_ptr = entry->next; - free(entry); - } - continue; - } - - entry_ptr = &entry->next; + if (!entry->active) { + *ptr = entry->next; + free(entry); + goto scan; } + + if (current_time < entry->target_time_ms) { + ptr = &entry->next; + goto scan; + } + + jsval_t args[0]; + js_call(js, entry->callback, args, 0); + process_microtasks(js); + + if (entry->is_interval && entry->active) { + entry->target_time_ms = get_current_time_ms() + entry->interval_ms; + } else remove_timer(entry); + + current_time = get_current_time_ms(); + ptr = &timer_state.timers; + goto scan; } int has_pending_timers(void) { diff --git a/tests/test_timer_mutation.cjs b/tests/test_timer_mutation.cjs new file mode 100644 index 0000000..fa13930 --- /dev/null +++ b/tests/test_timer_mutation.cjs @@ -0,0 +1,36 @@ +// Test timer list mutation during callback execution +// This tests the case where a setTimeout callback adds new timers + +let results = []; +let expected = ['timer1', 'timer2', 'timer3', 'done']; + +// Timer 1 fires and adds timer 2 at head of list +setTimeout(() => { + results.push('timer1'); + + // Add a new timer with 0ms delay - inserts at head of timer list + setTimeout(() => { + results.push('timer2'); + + // Add another timer from within timer2 + setTimeout(() => { + results.push('timer3'); + }, 0); + }, 0); +}, 10); + +// Final check after all timers should have fired +setTimeout(() => { + results.push('done'); + + const passed = JSON.stringify(results) === JSON.stringify(expected); + console.log('Results:', JSON.stringify(results)); + console.log('Expected:', JSON.stringify(expected)); + console.log('Test:', passed ? 'PASSED' : 'FAILED'); + + if (!passed) { + process.exit(1); + } +}, 100); + +console.log('Timer mutation test started...');