MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

events,console: fix once listener cleanup and trace sink routing

+74 -11
+5 -1
examples/spec/console.js
··· 74 74 instance.error('problem'); 75 75 instance.warn('warning'); 76 76 instance.assert(false, 'boom'); 77 + instance.trace('trace'); 77 78 78 79 testDeep('Console instance stdout routing', stdoutSink.chunks, [ 79 80 'hello world\n', ··· 88 89 testDeep('Console instance stderr routing', stderrSink.chunks, [ 89 90 'problem\n', 90 91 'warning\n', 91 - 'Assertion failed: boom\n' 92 + 'Assertion failed: boom\n', 93 + 'Trace: trace\n' 92 94 ]); 95 + 96 + test('Console instance trace writes stack to stderr sink', stderrSink.chunks.some(chunk => chunk.includes('at ')), true); 93 97 94 98 summary();
+23
examples/spec/events.js
··· 215 215 eeNodeOnce.emit('ready', 'ok', 7); 216 216 testDeep('events.once resolves emitted args', await eeNodeOncePromise, ['ok', 7]); 217 217 218 + const eeRaw = new EventEmitter(); 219 + function rawOnce() {} 220 + function rawOn() {} 221 + eeRaw.once('raw', rawOnce); 222 + eeRaw.on('raw', rawOn); 223 + const rawListeners = eeRaw.rawListeners('raw'); 224 + test('rawListeners returns wrapper for once listeners', rawListeners[0] !== rawOnce, true); 225 + test('rawListeners wrapper exposes original listener', rawListeners[0].listener, rawOnce); 226 + test('rawListeners keeps non-once listeners unchanged', rawListeners[1], rawOn); 227 + 218 228 const etNodeOnce = new EventTarget(); 219 229 const etNodeOncePromise = nodeOnce(etNodeOnce, 'ping'); 220 230 etNodeOnce.dispatchEvent(new Event('ping')); ··· 290 300 disposedAbort.dispose(); 291 301 disposedAbortController.abort(); 292 302 test('events.addAbortListener dispose removes listener', disposedAbortValue, 0); 303 + 304 + let onceAbortCalls = 0; 305 + const onceAbortController = new AbortController(); 306 + const onceAbortListener = () => { 307 + onceAbortCalls++; 308 + }; 309 + addAbortListener(onceAbortController.signal, onceAbortListener); 310 + const onceAbortEmitter = new EventEmitter(); 311 + const onceAbortPromise = nodeOnce(onceAbortEmitter, 'done', { signal: onceAbortController.signal }); 312 + onceAbortEmitter.emit('done', 'ok'); 313 + await onceAbortPromise; 314 + onceAbortController.abort(); 315 + test('events.once removes abort listener after resolve', onceAbortCalls, 1); 293 316 294 317 test('events.getMaxListeners default', getMaxListeners(eeNodeOnce), 10); 295 318 test('events.setMaxListeners no-op return', setMaxListeners(20, eeNodeOnce), undefined);
+34 -6
src/modules/events.c
··· 48 48 49 49 typedef struct { 50 50 ant_value_t callback; 51 + ant_value_t raw_callback; 51 52 ant_value_t signal; 52 53 bool once; 53 54 bool capture; ··· 225 226 return EVENTS_DEFAULT_MAX_LISTENERS; 226 227 } 227 228 228 - static ant_value_t eventemitter_get_listeners_array(ant_t *js, ant_value_t target, ant_value_t key) { 229 + static ant_value_t js_eventemitter_once_wrapper(ant_t *js, ant_value_t *args, int nargs) { 230 + ant_value_t listener = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 231 + if (!is_callable(listener)) return js_mkundef(); 232 + return sv_vm_call(js->vm, js, listener, js->this_val, args, nargs, NULL, false); 233 + } 234 + 235 + static ant_value_t eventemitter_get_listeners_array(ant_t *js, ant_value_t target, ant_value_t key, bool raw) { 229 236 ant_value_t result = js_mkarr(js); 230 237 EventType *evt = NULL; 231 238 ··· 236 243 for (unsigned int i = 0; i < utarray_len(evt->listeners); i++) { 237 244 EventListenerEntry *entry = (EventListenerEntry *)utarray_eltptr(evt->listeners, i); 238 245 if (!entry) continue; 239 - js_arr_push(js, result, entry->callback); 246 + js_arr_push(js, result, raw && entry->once && is_callable(entry->raw_callback) 247 + ? entry->raw_callback 248 + : entry->callback 249 + ); 240 250 } 241 251 242 252 return result; ··· 535 545 if (e->callback == cb && e->capture == capture) return js_mkundef(); 536 546 } 537 547 538 - EventListenerEntry entry = { cb, signal, once, capture }; 548 + EventListenerEntry entry = { cb, js_mkundef(), signal, once, capture }; 539 549 utarray_push_back(evt->listeners, &entry); 540 550 return js_mkundef(); 541 551 } ··· 677 687 if (!evt) return false; 678 688 679 689 entry.callback = listener; 690 + entry.raw_callback = js_mkundef(); 680 691 entry.signal = js_mkundef(); 681 692 entry.once = once; 682 693 entry.capture = false; 683 694 695 + if (once) { 696 + entry.raw_callback = js_heavy_mkfun(js, js_eventemitter_once_wrapper, listener); 697 + if (is_callable(entry.raw_callback)) js_set(js, entry.raw_callback, "listener", listener); 698 + } 699 + 684 700 if (!prepend || utarray_len(evt->listeners) == 0) utarray_push_back(evt->listeners, &entry); 685 701 else { 686 702 utarray_push_back(evt->listeners, &entry); ··· 735 751 736 752 for (unsigned int i = 0; i < utarray_len(evt->listeners); i++) { 737 753 EventListenerEntry *entry = (EventListenerEntry *)utarray_eltptr(evt->listeners, i); 738 - if (entry->callback == listener) { 754 + if (entry->callback == listener || entry->raw_callback == listener) { 739 755 utarray_erase(evt->listeners, i, 1); 740 756 return true; 741 757 }} ··· 959 975 if (nargs < 1) return js_mkarr(js); 960 976 ant_value_t key = evt_key_from_arg(args[0]); 961 977 if (!key) return js_mkarr(js); 962 - return eventemitter_get_listeners_array(js, js_getthis(js), key); 978 + return eventemitter_get_listeners_array(js, js_getthis(js), key, true); 963 979 } 964 980 965 981 static ant_value_t js_eventemitter_eventNames(ant_t *js, ant_value_t *args, int nargs) { ··· 987 1003 if (vtype(settled) == T_BOOL && settled == js_true) return js_mkundef(); 988 1004 js_set_slot(state, SLOT_SETTLED, js_true); 989 1005 1006 + ant_value_t signal = js_get(js, state, "signal"); 1007 + ant_value_t abort_listener = js_get(js, state, "abortListener"); 1008 + if (abort_signal_is_signal(signal) && is_callable(abort_listener)) 1009 + abort_signal_remove_listener(js, signal, abort_listener); 1010 + 990 1011 ant_value_t values = js_mkarr(js); 991 1012 for (int i = 0; i < nargs; i++) js_arr_push(js, values, args[i]); 992 1013 js_resolve_promise(js, promise, values); ··· 1006 1027 js_set_slot(state, SLOT_SETTLED, js_true); 1007 1028 1008 1029 ant_value_t signal = js_get(js, state, "signal"); 1030 + ant_value_t abort_listener = js_get(js, state, "abortListener"); 1031 + if (abort_signal_is_signal(signal) && is_callable(abort_listener)) 1032 + abort_signal_remove_listener(js, signal, abort_listener); 1033 + 1009 1034 ant_value_t reason = abort_signal_get_reason(signal); 1010 1035 if (vtype(reason) == T_UNDEF) reason = js_mkerr(js, "The operation was aborted"); 1011 1036 js_reject_promise(js, promise, reason); ··· 1088 1113 js_events_once_reject_aborted(js, promise, signal); 1089 1114 return promise; 1090 1115 } 1116 + ant_value_t abort_listener = js_heavy_mkfun(js, js_events_once_abort_listener, state); 1091 1117 js_set(js, state, "signal", signal); 1092 - abort_signal_add_listener(js, signal, js_heavy_mkfun(js, js_events_once_abort_listener, state)); 1118 + js_set(js, state, "abortListener", abort_listener); 1119 + abort_signal_add_listener(js, signal, abort_listener); 1093 1120 } 1094 1121 1095 1122 return js_events_once_attach(js, promise, target, key, listener, signal); ··· 1283 1310 for (unsigned int i = 0; i < utarray_len(evt->listeners); i++) { 1284 1311 EventListenerEntry *e = (EventListenerEntry *)utarray_eltptr(evt->listeners, i); 1285 1312 mark(js, e->callback); 1313 + if (vtype(e->raw_callback) != T_UNDEF) mark(js, e->raw_callback); 1286 1314 if (vtype(e->signal) != T_UNDEF) mark(js, e->signal); 1287 1315 } 1288 1316 }}
+6 -2
src/modules/io.c
··· 628 628 static ant_value_t js_console_trace(ant_t *js, ant_value_t *args, int nargs) { 629 629 ant_value_t this_obj = console_get_effective_this(js, js_getthis(js)); 630 630 console_emit_current(js, true, "Trace:", args, nargs); 631 - if (console_get_target_stream(js, this_obj, true) == js_mkundef()) js_print_stack_trace_vm(js, stderr); 632 - else js_print_stack_trace_vm(js, stderr); 631 + ant_value_t stack = js_capture_raw_stack(js); 632 + if (vtype(stack) == T_STR) { 633 + size_t stack_len = 0; 634 + const char *stack_str = js_getstr(js, stack, &stack_len); 635 + console_write_string(js, this_obj, true, stack_str, stack_len); 636 + } else js_print_stack_trace_vm(js, stderr); 633 637 return js_mkundef(); 634 638 } 635 639
+1 -1
src/modules/process.c
··· 1863 1863 1864 1864 js_set(js, process_obj, "pid", js_mknum((double)getpid())); 1865 1865 js_set(js, process_obj, "ppid", js_mknum((double)getppid())); 1866 - 1866 + 1867 1867 ant_value_t versions_obj = js_mkobj(js); 1868 1868 js_set(js, versions_obj, "ant", js_mkstr(js, ANT_VERSION, strlen(ANT_VERSION))); 1869 1869 char uv_ver[32];
+5 -1
tests/test_eventemitter_max_listeners.cjs
··· 22 22 ee.prependOnceListener('x', b); 23 23 24 24 assert.equal(ee.listenerCount('x'), 2); 25 - assert.deepEqual(ee.rawListeners('x'), [b, a]); 25 + const raw = ee.rawListeners('x'); 26 + assert.equal(raw.length, 2); 27 + assert.notEqual(raw[0], b); 28 + assert.equal(raw[0].listener, b); 29 + assert.equal(raw[1], a); 26 30 27 31 const pt = new PassThrough(); 28 32 assert.equal(typeof pt.setMaxListeners, 'function');