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.

stabilize async coroutines and node stream compatibility

- fix async generator await/yield, overlap, and reentrant next semantics
- harden coroutine teardown, active async tracking, and queued microtask rooting
- fix JIT lexical-this handling for closure-heavy callback paths
- improve Node compatibility for fs/promises FileHandle, util.promisify, and EventEmitter listeners()
- fix stream lifecycle regressions around flowing startup, Transform callbacks, and tty ReadStream state ownership

+2804 -232
+2
include/ant.h
··· 97 97 ant_value_t js_mksym_for(ant_t *, const char *key); 98 98 ant_value_t js_symbol_to_string(ant_t *js, ant_value_t sym); 99 99 ant_value_t js_get_sym(ant_t *, ant_value_t obj, ant_value_t sym); 100 + ant_value_t js_get_symbol(ant_t *, ant_value_t obj, const char *key); 100 101 ant_value_t js_get_sym_with_receiver(ant_t *, ant_value_t obj, ant_value_t sym, ant_value_t receiver); 101 102 102 103 ant_value_t js_mkobj(ant_t *); ··· 116 117 117 118 void js_set(ant_t *, ant_value_t, const char *, ant_value_t); 118 119 void js_set_sym(ant_t *, ant_value_t obj, ant_value_t sym, ant_value_t val); 120 + void js_set_symbol(ant_t *, ant_value_t obj, const char *key, ant_value_t val); 119 121 void js_saveval(ant_t *js, ant_offset_t off, ant_value_t v); 120 122 void js_merge_obj(ant_t *, ant_value_t dst, ant_value_t src); 121 123 void js_arr_push(ant_t *, ant_value_t arr, ant_value_t val);
+3
include/modules/generator.h
··· 5 5 #include "types.h" 6 6 7 7 void init_generator_module(void); 8 + void generator_mark_for_gc(ant_t *js, ant_value_t gen); 9 + 10 + bool generator_resume_pending_request(ant_t *js, coroutine_t *coro, ant_value_t result); 8 11 coroutine_t *generator_get_coro_for_gc(ant_value_t gen); 9 12 10 13 #endif
+24
include/modules/stream.h
··· 3 3 4 4 #include "types.h" 5 5 6 + typedef void (*stream_finalize_fn)( 7 + ant_t *js, 8 + ant_value_t stream_obj, 9 + void *state 10 + ); 11 + 12 + typedef struct { 13 + bool writing; 14 + bool pending_final; 15 + bool final_started; 16 + void *attached_state; 17 + stream_finalize_fn attached_state_finalize; 18 + } stream_private_state_t; 19 + 6 20 void stream_init_constructors(ant_t *js); 7 21 8 22 ant_value_t stream_library(ant_t *js); ··· 17 31 ant_value_t stream_construct_readable(ant_t *js, ant_value_t base_proto, ant_value_t options); 18 32 ant_value_t stream_construct_writable(ant_t *js, ant_value_t base_proto, ant_value_t options); 19 33 ant_value_t stream_readable_push(ant_t *js, ant_value_t stream_obj, ant_value_t chunk, ant_value_t encoding); 34 + ant_value_t stream_readable_maybe_read(ant_t *js, ant_value_t stream_obj); 35 + ant_value_t stream_readable_flush(ant_t *js, ant_value_t stream_obj); 36 + ant_value_t stream_readable_push_value(ant_t *js, ant_value_t stream_obj, ant_value_t chunk, ant_value_t encoding); 37 + ant_value_t stream_readable_continue_flowing(ant_t *js, ant_value_t *args, int nargs); 38 + ant_value_t stream_readable_begin_flowing(ant_t *js, ant_value_t stream_obj); 39 + ant_value_t stream_writable_begin_end(ant_t *js, ant_value_t stream_obj, ant_value_t callback); 20 40 21 41 void stream_init_readable_object(ant_t *js, ant_value_t obj, ant_value_t options); 22 42 void stream_init_writable_object(ant_t *js, ant_value_t obj, ant_value_t options); 43 + 44 + void *stream_get_attached_state(ant_value_t stream_obj); 45 + void stream_clear_attached_state(ant_value_t stream_obj); 46 + void stream_set_attached_state(ant_value_t stream_obj, void *state, stream_finalize_fn finalize); 23 47 24 48 #endif
+3
include/modules/timer.h
··· 14 14 void queue_microtask(ant_t *js, ant_value_t callback); 15 15 void queue_microtask_with_args(ant_t *js, ant_value_t callback, ant_value_t *args, int nargs); 16 16 17 + void queue_next_tick(ant_t *js, ant_value_t callback); 18 + void queue_next_tick_with_args(ant_t *js, ant_value_t callback, ant_value_t *args, int nargs); 19 + 17 20 bool js_maybe_drain_microtasks(ant_t *js); 18 21 bool js_maybe_drain_microtasks_after_async_settle(ant_t *js); 19 22
+2
include/sugar.h
··· 68 68 bool mco_started; 69 69 bool is_ready; 70 70 bool did_suspend; 71 + bool free_pending; 71 72 } coroutine_t; 72 73 73 74 typedef struct { ··· 91 92 void enqueue_coroutine(coroutine_t *coro); 92 93 void remove_coroutine(coroutine_t *coro); 93 94 void free_coroutine(coroutine_t *coro); 95 + void reap_retired_coroutines(void); 94 96 95 97 ant_value_t start_async_in_coroutine(ant_t *js, const char *code, size_t code_len, ant_value_t closure_scope, ant_value_t *args, int nargs); 96 98 ant_value_t resume_coroutine_wrapper(ant_t *js, ant_value_t *args, int nargs);
+25 -4
src/ant.c
··· 10819 10819 if (!h) continue; 10820 10820 10821 10821 if (h->await_coro) { 10822 - settle_and_resume_coroutine(js, h->await_coro, val, state != 1); 10822 + coroutine_t *await_coro = h->await_coro; 10823 + h->await_coro = NULL; 10824 + settle_and_resume_coroutine(js, await_coro, val, state != 1); 10823 10825 continue; 10824 10826 } 10825 10827 ··· 10906 10908 10907 10909 if (src_pd->state == 0) gc_root_pending_promise(js_obj_ptr(js_as_obj(val))); 10908 10910 else queue_promise_trigger(js, val); 10909 - 10910 10911 GC_ROOT_RESTORE(js, root_mark); 10911 10912 10912 10913 return; ··· 10953 10954 void js_reject_promise(ant_t *js, ant_value_t p, ant_value_t val) { 10954 10955 if (vtype(val) == T_ERR) { 10955 10956 if (vdata(val) != 0) val = mkval(T_OBJ, vdata(val)); 10956 - else if (js->thrown_exists && is_object_type(js->thrown_value)) val = js->thrown_value; 10957 + else if (js->thrown_exists) val = js->thrown_value; 10957 10958 else val = js_make_error_silent(js, JS_ERR_INTERNAL, "unknown error"); 10958 10959 } 10959 10960 ··· 11038 11039 } 11039 11040 11040 11041 ant_value_t exec_args[] = { res_fn, rej_fn }; 11041 - sv_vm_call(js->vm, js, executor, js_mkundef(), exec_args, 2, NULL, false); 11042 + ant_value_t exec_result = sv_vm_call(js->vm, js, executor, js_mkundef(), exec_args, 2, NULL, false); 11043 + 11044 + if (is_err(exec_result) || js->thrown_exists) { 11045 + ant_value_t reject_val = js->thrown_exists ? js->thrown_value : exec_result; 11046 + js->thrown_exists = false; 11047 + js->thrown_value = js_mkundef(); 11048 + js->thrown_stack = js_mkundef(); 11049 + js_reject_promise(js, p, reject_val); 11050 + } 11042 11051 11043 11052 GC_ROOT_RESTORE(js, root_mark); 11044 11053 return p; ··· 13776 13785 } else mkprop(js, obj, sym, val, 0); 13777 13786 } 13778 13787 13788 + void js_set_symbol(ant_t *js, ant_value_t obj, const char *key, ant_value_t val) { 13789 + ant_value_t sym = js_mksym_for(js, key); 13790 + if (is_err(sym)) return; 13791 + js_set_sym(js, obj, sym, val); 13792 + } 13793 + 13779 13794 ant_value_t js_get_sym_with_receiver(ant_t *js, ant_value_t obj, ant_value_t sym, ant_value_t receiver) { 13780 13795 if (vtype(sym) != T_SYMBOL) return js_mkundef(); 13781 13796 ant_offset_t sym_off = (ant_offset_t)vdata(sym); ··· 13825 13840 13826 13841 ant_value_t js_get_sym(ant_t *js, ant_value_t obj, ant_value_t sym) { 13827 13842 return js_get_sym_with_receiver(js, obj, sym, obj); 13843 + } 13844 + 13845 + ant_value_t js_get_symbol(ant_t *js, ant_value_t obj, const char *key) { 13846 + ant_value_t sym = js_mksym_for(js, key); 13847 + if (is_err(sym)) return sym; 13848 + return js_get_sym(js, obj, sym); 13828 13849 } 13829 13850 13830 13851 static bool js_try_get(ant_t *js, ant_value_t obj, const char *key, ant_value_t *out) {
+1
src/gc/objects.c
··· 235 235 if (obj->type_tag == T_GENERATOR) { 236 236 coroutine_t *coro = generator_get_coro_for_gc(js_obj_from_ptr(obj)); 237 237 if (coro) gc_mark_coroutine(js, coro); 238 + generator_mark_for_gc(js, js_obj_from_ptr(obj)); 238 239 } 239 240 240 241 if (obj->type_tag == T_MAP) {
+81 -2
src/modules/child_process.c
··· 1256 1256 return js_mkundef(); 1257 1257 } 1258 1258 1259 + static ant_value_t exec_file_promisify_callback(ant_t *js, ant_value_t *args, int nargs) { 1260 + ant_value_t state = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 1261 + if (!is_object_type(state)) return js_mkundef(); 1262 + 1263 + ant_value_t settled = js_get_slot(state, SLOT_SETTLED); 1264 + if (vtype(settled) == T_BOOL && settled == js_true) return js_mkundef(); 1265 + js_set_slot(state, SLOT_SETTLED, js_true); 1266 + 1267 + ant_value_t promise = js_get_slot(state, SLOT_DATA); 1268 + if (vtype(promise) != T_PROMISE) return js_mkundef(); 1269 + 1270 + ant_value_t stdout_val = nargs > 1 ? args[1] : js_mkstr(js, "", 0); 1271 + ant_value_t stderr_val = nargs > 2 ? args[2] : js_mkstr(js, "", 0); 1272 + 1273 + if (nargs > 0 && !is_null(args[0]) && !is_undefined(args[0])) { 1274 + if (is_object_type(args[0])) { 1275 + js_set(js, args[0], "stdout", stdout_val); 1276 + js_set(js, args[0], "stderr", stderr_val); 1277 + } 1278 + js_reject_promise(js, promise, args[0]); 1279 + return js_mkundef(); 1280 + } 1281 + 1282 + ant_value_t result = js_mkobj(js); 1283 + js_set(js, result, "stdout", stdout_val); 1284 + js_set(js, result, "stderr", stderr_val); 1285 + js_resolve_promise(js, promise, result); 1286 + 1287 + return js_mkundef(); 1288 + } 1289 + 1290 + static ant_value_t exec_file_promisified_call(ant_t *js, ant_value_t *args, int nargs) { 1291 + ant_value_t original = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 1292 + if (!is_callable(original)) return js_mkerr(js, "execFile promisify target is not callable"); 1293 + 1294 + ant_value_t promise = js_mkpromise(js); 1295 + ant_value_t state = js_mkobj(js); 1296 + 1297 + js_set_slot(state, SLOT_DATA, promise); 1298 + js_set_slot(state, SLOT_SETTLED, js_false); 1299 + 1300 + ant_value_t callback = js_heavy_mkfun(js, exec_file_promisify_callback, state); 1301 + ant_value_t *call_args = malloc((size_t)(nargs + 1) * sizeof(ant_value_t)); 1302 + 1303 + if (!call_args) { 1304 + js_reject_promise(js, promise, js_mkerr(js, "Out of memory")); 1305 + return promise; 1306 + } 1307 + 1308 + for (int i = 0; i < nargs; i++) call_args[i] = args[i]; 1309 + call_args[nargs] = callback; 1310 + 1311 + ant_value_t call_result = sv_vm_call( 1312 + js->vm, js, original, js_getthis(js), 1313 + call_args, nargs + 1, NULL, false 1314 + ); free(call_args); 1315 + 1316 + ant_value_t settled = js_get_slot(state, SLOT_SETTLED); 1317 + bool is_settled = (vtype(settled) == T_BOOL && settled == js_true); 1318 + 1319 + if (!is_settled && (is_err(call_result) || js->thrown_exists)) { 1320 + ant_value_t ex = js->thrown_exists ? js->thrown_value : call_result; 1321 + js->thrown_exists = false; 1322 + js->thrown_value = js_mkundef(); 1323 + js->thrown_stack = js_mkundef(); 1324 + js_set_slot(state, SLOT_SETTLED, js_true); 1325 + js_reject_promise(js, promise, ex); 1326 + } 1327 + 1328 + return promise; 1329 + } 1330 + 1259 1331 static ant_value_t builtin_execFile(ant_t *js, ant_value_t *args, int nargs) { 1260 1332 ant_value_t argv = js_mkundef(); 1261 1333 ant_value_t options = js_mkundef(); ··· 1739 1811 1740 1812 ant_value_t child_process_library(ant_t *js) { 1741 1813 ant_value_t lib = js_mkobj(js); 1814 + ant_value_t exec_fn = js_mkfun(builtin_exec); 1815 + ant_value_t exec_file_fn = js_heavy_mkfun(js, builtin_execFile, js_mkundef()); 1742 1816 child_process_init_constructor(js); 1743 1817 1818 + js_set_symbol(js, exec_file_fn, 1819 + "nodejs.util.promisify.custom", 1820 + js_heavy_mkfun(js, exec_file_promisified_call, exec_file_fn) 1821 + ); 1822 + 1744 1823 js_set(js, lib, "ChildProcess", g_child_process_ctor); 1745 1824 js_set(js, lib, "spawn", js_mkfun(builtin_spawn)); 1746 - js_set(js, lib, "exec", js_mkfun(builtin_exec)); 1747 - js_set(js, lib, "execFile", js_mkfun(builtin_execFile)); 1825 + js_set(js, lib, "exec", exec_fn); 1826 + js_set(js, lib, "execFile", exec_file_fn); 1748 1827 js_set(js, lib, "execSync", js_mkfun(builtin_execSync)); 1749 1828 js_set(js, lib, "execFileSync", js_mkfun(builtin_execFileSync)); 1750 1829 js_set(js, lib, "spawnSync", js_mkfun(builtin_spawnSync));
+38 -9
src/modules/events.c
··· 226 226 return EVENTS_DEFAULT_MAX_LISTENERS; 227 227 } 228 228 229 + static ant_value_t eventemitter_call_listener( 230 + ant_t *js, 231 + ant_value_t listener, 232 + ant_value_t this_val, 233 + ant_value_t *args, 234 + int nargs 235 + ) { 236 + if (!is_callable(listener)) return js_mkundef(); 237 + if (sv_check_c_stack_overflow(js)) 238 + return js_mkerr_typed(js, JS_ERR_RANGE | JS_ERR_NO_STACK, "Maximum call stack size exceeded"); 239 + 240 + sv_call_plan_t plan; 241 + ant_value_t err = sv_prepare_call( 242 + js->vm, js, listener, this_val, args, nargs, 243 + NULL, SV_CALL_MODE_NORMAL, &plan 244 + ); 245 + 246 + if (is_err(err)) return err; 247 + return sv_execute_call_plan(js->vm, js, &plan, NULL); 248 + } 249 + 229 250 static ant_value_t js_eventemitter_once_wrapper(ant_t *js, ant_value_t *args, int nargs) { 230 251 ant_value_t listener = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 231 252 if (!is_callable(listener)) return js_mkundef(); 232 - return sv_vm_call(js->vm, js, listener, js->this_val, args, nargs, NULL, false); 253 + return eventemitter_call_listener(js, listener, js->this_val, args, nargs); 233 254 } 234 255 235 256 static ant_value_t eventemitter_get_listeners_array(ant_t *js, ant_value_t target, ant_value_t key, bool raw) { ··· 590 611 591 612 for (unsigned int i = 0; i < n;) { 592 613 EventListenerEntry *entry = (EventListenerEntry *)utarray_eltptr(evt->listeners, i); 593 - 614 + 594 615 if (vtype(entry->signal) != T_UNDEF && abort_signal_is_aborted(entry->signal)) { 595 616 utarray_erase(evt->listeners, i, 1); 596 617 n--; continue; 597 618 } 598 - 599 - ant_value_t cb = entry->callback; 600 - bool once = entry->once; 619 + 620 + ant_value_t cb = entry->callback; 621 + bool once = entry->once; 601 622 if (once) { utarray_erase(evt->listeners, i, 1); n--; } else i++; 602 623 603 624 uint8_t t = vtype(cb); 604 625 if (t != T_FUNC && t != T_CFUNC) continue; 605 - 606 - sv_vm_call(js->vm, js, cb, js_mkundef(), call_args, 1, NULL, false); 626 + 627 + eventemitter_call_listener(js, cb, js_mkundef(), call_args, 1); 607 628 if (data && data->stop_immediate) break; 608 629 } 609 630 ··· 809 830 if (vtype(entry->signal) != T_UNDEF && abort_signal_is_aborted(entry->signal)) continue; 810 831 if (vtype(cb) != T_FUNC && vtype(cb) != T_CFUNC) continue; 811 832 812 - ant_value_t result = sv_vm_call(js->vm, js, cb, target, args, nargs, NULL, false); 833 + ant_value_t result = eventemitter_call_listener(js, cb, target, args, nargs); 813 834 if (vtype(result) == T_ERR) { 814 835 if (vtype(key) == T_STR) fprintf(stderr, "Error in event listener for '%s': %s\n", js_str(js, key), js_str(js, result)); 815 836 else fprintf(stderr, "Error in event listener: %s\n", js_str(js, result)); ··· 978 999 return eventemitter_get_listeners_array(js, js_getthis(js), key, true); 979 1000 } 980 1001 1002 + static ant_value_t js_eventemitter_listeners(ant_t *js, ant_value_t *args, int nargs) { 1003 + if (nargs < 1) return js_mkarr(js); 1004 + ant_value_t key = evt_key_from_arg(args[0]); 1005 + if (!key) return js_mkarr(js); 1006 + return eventemitter_get_listeners_array(js, js_getthis(js), key, false); 1007 + } 1008 + 981 1009 static ant_value_t js_eventemitter_eventNames(ant_t *js, ant_value_t *args, int nargs) { 982 1010 ant_value_t this_obj = js_getthis(js); 983 1011 ant_value_t result = js_mkarr(js); ··· 1146 1174 if (already_aborted) { 1147 1175 ant_value_t event = js_mkobj(js); 1148 1176 js_set(js, event, "type", js_mkstr(js, "abort", 5)); 1149 - sv_vm_call(js->vm, js, args[1], args[0], &event, 1, NULL, false); 1177 + eventemitter_call_listener(js, args[1], args[0], &event, 1); 1150 1178 } else abort_signal_add_listener(js, args[0], args[1]); 1151 1179 1152 1180 ant_value_t state = js_mkobj(js); ··· 1219 1247 js_set(js, eventemitter_proto, "listenerCount", js_mkfun(js_eventemitter_listenerCount)); 1220 1248 js_set(js, eventemitter_proto, "setMaxListeners", js_mkfun(js_eventemitter_setMaxListeners)); 1221 1249 js_set(js, eventemitter_proto, "getMaxListeners", js_mkfun(js_eventemitter_getMaxListeners)); 1250 + js_set(js, eventemitter_proto, "listeners", js_mkfun(js_eventemitter_listeners)); 1222 1251 js_set(js, eventemitter_proto, "rawListeners", js_mkfun(js_eventemitter_rawListeners)); 1223 1252 js_set(js, eventemitter_proto, "eventNames", js_mkfun(js_eventemitter_eventNames)); 1224 1253 js_set_sym(js, eventemitter_proto, get_toStringTag_sym(), js_mkstr(js, "EventEmitter", 12));
+315 -4
src/modules/fs.c
··· 141 141 static ant_value_t g_dirent_proto = 0; 142 142 static ant_value_t g_fswatcher_proto = 0; 143 143 static ant_value_t g_fswatcher_ctor = 0; 144 + static ant_value_t g_filehandle_proto = 0; 144 145 static ant_value_t g_readstream_proto = 0; 145 146 static ant_value_t g_readstream_ctor = 0; 146 147 static ant_value_t g_writestream_proto = 0; ··· 149 150 static fs_watcher_t *active_watchers = NULL; 150 151 static UT_array *pending_requests = NULL; 151 152 152 - enum { FS_WATCHER_NATIVE_TAG = 0x46535754u }; // FSWT 153 + enum { 154 + FS_WATCHER_NATIVE_TAG = 0x46535754u, // FSWT 155 + FS_FILEHANDLE_NATIVE_TAG = 0x46534648u // FSFH 156 + }; 153 157 154 158 static fs_watcher_t *fs_watcher_data(ant_value_t value) { 155 159 if (!js_check_native_tag(value, FS_WATCHER_NATIVE_TAG)) return NULL; ··· 1329 1333 free(req); 1330 1334 } 1331 1335 1336 + static ant_value_t fs_rejected_promise(ant_t *js, ant_value_t err) { 1337 + ant_value_t promise = js_mkpromise(js); 1338 + js_reject_promise(js, promise, err); 1339 + return promise; 1340 + } 1341 + 1342 + static ant_value_t fs_resolved_promise(ant_t *js, ant_value_t value) { 1343 + ant_value_t promise = js_mkpromise(js); 1344 + js_resolve_promise(js, promise, value); 1345 + return promise; 1346 + } 1347 + 1348 + static ant_value_t fs_filehandle_require_this(ant_t *js) { 1349 + ant_value_t this_obj = js_getthis(js); 1350 + if (!is_object_type(this_obj) || !js_check_native_tag(this_obj, FS_FILEHANDLE_NATIVE_TAG)) 1351 + return js_mkerr_typed(js, JS_ERR_TYPE, "FileHandle method called on incompatible receiver"); 1352 + return this_obj; 1353 + } 1354 + 1355 + static ant_value_t fs_filehandle_fd_value(ant_t *js, ant_value_t handle_obj) { 1356 + ant_value_t fd_val = js_get_slot(handle_obj, SLOT_DATA); 1357 + if (vtype(fd_val) != T_NUM) fd_val = js_get(js, handle_obj, "fd"); 1358 + return fd_val; 1359 + } 1360 + 1361 + static ant_value_t fs_filehandle_get_fd(ant_t *js, ant_value_t handle_obj) { 1362 + ant_value_t fd_val = fs_filehandle_fd_value(js, handle_obj); 1363 + if (vtype(fd_val) != T_NUM || js_getnum(fd_val) < 0) 1364 + return js_mkerr_typed(js, JS_ERR_TYPE, "FileHandle is closed"); 1365 + return fd_val; 1366 + } 1367 + 1368 + static void fs_filehandle_mark_closed(ant_t *js, ant_value_t handle_obj) { 1369 + js_set_slot(handle_obj, SLOT_DATA, js_mkundef()); 1370 + js_set(js, handle_obj, "fd", js_mknum(-1)); 1371 + } 1372 + 1373 + static ant_value_t builtin_fs_filehandle_close(ant_t *js, ant_value_t *args, int nargs) { 1374 + ant_value_t handle_obj = fs_filehandle_require_this(js); 1375 + if (is_err(handle_obj)) return fs_rejected_promise(js, handle_obj); 1376 + 1377 + ant_value_t fd_val = fs_filehandle_fd_value(js, handle_obj); 1378 + if (vtype(fd_val) != T_NUM || js_getnum(fd_val) < 0) 1379 + return fs_resolved_promise(js, js_mkundef()); 1380 + 1381 + uv_fs_t req; 1382 + int result = uv_fs_close(uv_default_loop(), &req, (uv_file)(int)js_getnum(fd_val), NULL); 1383 + uv_fs_req_cleanup(&req); 1384 + if (result < 0) return fs_rejected_promise(js, fs_mk_uv_error(js, result, "close", NULL, NULL)); 1385 + 1386 + fs_filehandle_mark_closed(js, handle_obj); 1387 + return fs_resolved_promise(js, js_mkundef()); 1388 + } 1389 + 1390 + static ant_value_t builtin_fs_filehandle_stat(ant_t *js, ant_value_t *args, int nargs) { 1391 + ant_value_t handle_obj = fs_filehandle_require_this(js); 1392 + if (is_err(handle_obj)) return fs_rejected_promise(js, handle_obj); 1393 + 1394 + ant_value_t fd_val = fs_filehandle_get_fd(js, handle_obj); 1395 + if (is_err(fd_val)) return fs_rejected_promise(js, fd_val); 1396 + 1397 + uv_fs_t req; 1398 + int result = uv_fs_fstat(NULL, &req, (uv_file)(int)js_getnum(fd_val), NULL); 1399 + if (result < 0) { 1400 + ant_value_t err = fs_mk_uv_error(js, result, "fstat", NULL, NULL); 1401 + uv_fs_req_cleanup(&req); 1402 + return fs_rejected_promise(js, err); 1403 + } 1404 + 1405 + ant_value_t stat_obj = fs_stats_object_from_uv(js, &req.statbuf); 1406 + uv_fs_req_cleanup(&req); 1407 + return fs_resolved_promise(js, stat_obj); 1408 + } 1409 + 1410 + static ant_value_t builtin_fs_filehandle_sync(ant_t *js, ant_value_t *args, int nargs) { 1411 + ant_value_t handle_obj = fs_filehandle_require_this(js); 1412 + if (is_err(handle_obj)) return fs_rejected_promise(js, handle_obj); 1413 + 1414 + ant_value_t fd_val = fs_filehandle_get_fd(js, handle_obj); 1415 + if (is_err(fd_val)) return fs_rejected_promise(js, fd_val); 1416 + 1417 + uv_fs_t req; 1418 + int result = uv_fs_fsync(uv_default_loop(), &req, (uv_file)(int)js_getnum(fd_val), NULL); 1419 + uv_fs_req_cleanup(&req); 1420 + if (result < 0) return fs_rejected_promise(js, fs_mk_uv_error(js, result, "fsync", NULL, NULL)); 1421 + 1422 + return fs_resolved_promise(js, js_mkundef()); 1423 + } 1424 + 1425 + static ant_value_t builtin_fs_filehandle_read(ant_t *js, ant_value_t *args, int nargs) { 1426 + ant_value_t handle_obj = fs_filehandle_require_this(js); 1427 + if (is_err(handle_obj)) return fs_rejected_promise(js, handle_obj); 1428 + 1429 + ant_value_t fd_val = fs_filehandle_get_fd(js, handle_obj); 1430 + if (is_err(fd_val)) return fs_rejected_promise(js, fd_val); 1431 + if (nargs < 1) return fs_rejected_promise(js, js_mkerr(js, "FileHandle.read() requires a buffer argument")); 1432 + 1433 + TypedArrayData *ta_data = buffer_get_typedarray_data(args[0]); 1434 + if (!ta_data || !ta_data->buffer || !ta_data->buffer->data) 1435 + return fs_rejected_promise(js, js_mkerr(js, "FileHandle.read() buffer must be a Buffer or TypedArray")); 1436 + 1437 + size_t buf_len = ta_data->byte_length; 1438 + size_t offset = 0; 1439 + size_t length = buf_len; 1440 + int64_t position = -1; 1441 + 1442 + if (nargs >= 2 && vtype(args[1]) == T_NUM) offset = (size_t)js_getnum(args[1]); 1443 + if (nargs >= 3 && vtype(args[2]) == T_NUM) length = (size_t)js_getnum(args[2]); 1444 + if (nargs >= 4 && vtype(args[3]) == T_NUM) position = (int64_t)js_getnum(args[3]); 1445 + 1446 + if (offset > buf_len) return fs_rejected_promise(js, js_mkerr(js, "offset is out of bounds")); 1447 + if (offset + length > buf_len) return fs_rejected_promise(js, js_mkerr(js, "length extends beyond buffer")); 1448 + 1449 + uint8_t *buf_data = ta_data->buffer->data + ta_data->byte_offset; 1450 + uv_buf_t buf = uv_buf_init((char *)(buf_data + offset), (unsigned int)length); 1451 + 1452 + uv_fs_t req; 1453 + int result = uv_fs_read(uv_default_loop(), &req, (uv_file)(int)js_getnum(fd_val), &buf, 1, position, NULL); 1454 + uv_fs_req_cleanup(&req); 1455 + if (result < 0) return fs_rejected_promise(js, fs_mk_uv_error(js, result, "read", NULL, NULL)); 1456 + 1457 + ant_value_t out = js_mkobj(js); 1458 + js_set(js, out, "bytesRead", js_mknum((double)result)); 1459 + js_set(js, out, "buffer", args[0]); 1460 + 1461 + return fs_resolved_promise(js, out); 1462 + } 1463 + 1464 + static ant_value_t builtin_fs_filehandle_write(ant_t *js, ant_value_t *args, int nargs) { 1465 + ant_value_t handle_obj = fs_filehandle_require_this(js); 1466 + if (is_err(handle_obj)) return fs_rejected_promise(js, handle_obj); 1467 + 1468 + ant_value_t fd_val = fs_filehandle_get_fd(js, handle_obj); 1469 + if (is_err(fd_val)) return fs_rejected_promise(js, fd_val); 1470 + if (nargs < 1) return fs_rejected_promise(js, js_mkerr(js, "FileHandle.write() requires a data argument")); 1471 + 1472 + const char *write_data = NULL; 1473 + size_t write_len = 0; 1474 + int64_t position = -1; 1475 + 1476 + if (vtype(args[0]) == T_STR) { 1477 + write_data = js_getstr(js, args[0], &write_len); 1478 + if (!write_data) return fs_rejected_promise(js, js_mkerr(js, "Failed to get string")); 1479 + if (nargs >= 2 && vtype(args[1]) == T_NUM) position = (int64_t)js_getnum(args[1]); 1480 + } else { 1481 + TypedArrayData *ta_data = buffer_get_typedarray_data(args[0]); 1482 + if (!ta_data || !ta_data->buffer || !ta_data->buffer->data) 1483 + return fs_rejected_promise(js, js_mkerr(js, "FileHandle.write() data must be a Buffer, TypedArray, DataView, or string")); 1484 + 1485 + uint8_t *buf_data = ta_data->buffer->data + ta_data->byte_offset; 1486 + size_t buf_len = ta_data->byte_length; 1487 + size_t offset = 0; 1488 + size_t length = buf_len; 1489 + 1490 + if (nargs >= 2) { 1491 + if (vtype(args[1]) == T_OBJ) { 1492 + ant_value_t off_val = js_get(js, args[1], "offset"); 1493 + ant_value_t len_val = js_get(js, args[1], "length"); 1494 + ant_value_t pos_val = js_get(js, args[1], "position"); 1495 + if (vtype(off_val) == T_NUM) offset = (size_t)js_getnum(off_val); 1496 + if (vtype(len_val) == T_NUM) length = (size_t)js_getnum(len_val); 1497 + else length = buf_len - offset; 1498 + if (vtype(pos_val) == T_NUM) position = (int64_t)js_getnum(pos_val); 1499 + } else if (vtype(args[1]) == T_NUM) { 1500 + offset = (size_t)js_getnum(args[1]); 1501 + length = buf_len - offset; 1502 + if (nargs >= 3 && vtype(args[2]) == T_NUM) length = (size_t)js_getnum(args[2]); 1503 + if (nargs >= 4 && vtype(args[3]) == T_NUM) position = (int64_t)js_getnum(args[3]); 1504 + }} 1505 + 1506 + if (offset > buf_len) return fs_rejected_promise(js, js_mkerr(js, "offset is out of bounds")); 1507 + if (offset + length > buf_len) return fs_rejected_promise(js, js_mkerr(js, "length extends beyond buffer")); 1508 + write_data = (const char *)(buf_data + offset); 1509 + write_len = length; 1510 + } 1511 + 1512 + uv_buf_t buf = uv_buf_init((char *)write_data, (unsigned int)write_len); 1513 + uv_fs_t req; 1514 + int result = uv_fs_write(uv_default_loop(), &req, (uv_file)(int)js_getnum(fd_val), &buf, 1, position, NULL); 1515 + uv_fs_req_cleanup(&req); 1516 + if (result < 0) return fs_rejected_promise(js, fs_mk_uv_error(js, result, "write", NULL, NULL)); 1517 + 1518 + ant_value_t out = js_mkobj(js); 1519 + js_set(js, out, "bytesWritten", js_mknum((double)result)); 1520 + js_set(js, out, "buffer", args[0]); 1521 + 1522 + return fs_resolved_promise(js, out); 1523 + } 1524 + 1525 + static ant_value_t builtin_fs_filehandle_writeFile(ant_t *js, ant_value_t *args, int nargs) { 1526 + ant_value_t handle_obj = fs_filehandle_require_this(js); 1527 + if (is_err(handle_obj)) return fs_rejected_promise(js, handle_obj); 1528 + 1529 + ant_value_t fd_val = fs_filehandle_get_fd(js, handle_obj); 1530 + if (is_err(fd_val)) return fs_rejected_promise(js, fd_val); 1531 + if (nargs < 1) return fs_rejected_promise(js, js_mkerr(js, "FileHandle.writeFile() requires data")); 1532 + 1533 + const char *data = NULL; 1534 + size_t len = 0; 1535 + 1536 + if (vtype(args[0]) == T_STR) { 1537 + data = js_getstr(js, args[0], &len); 1538 + if (!data) return fs_rejected_promise(js, js_mkerr(js, "Failed to get string")); 1539 + } else { 1540 + TypedArrayData *ta_data = buffer_get_typedarray_data(args[0]); 1541 + if (!ta_data || !ta_data->buffer || !ta_data->buffer->data) 1542 + return fs_rejected_promise(js, js_mkerr(js, "FileHandle.writeFile() data must be a Buffer, TypedArray, DataView, or string")); 1543 + data = (const char *)(ta_data->buffer->data + ta_data->byte_offset); 1544 + len = ta_data->byte_length; 1545 + } 1546 + 1547 + size_t written = 0; 1548 + while (written < len) { 1549 + uv_buf_t buf = uv_buf_init((char *)(data + written), (unsigned int)(len - written)); 1550 + uv_fs_t req; 1551 + int result = uv_fs_write(uv_default_loop(), &req, (uv_file)(int)js_getnum(fd_val), &buf, 1, -1, NULL); 1552 + uv_fs_req_cleanup(&req); 1553 + if (result < 0) return fs_rejected_promise(js, fs_mk_uv_error(js, result, "write", NULL, NULL)); 1554 + if (result == 0) return fs_rejected_promise(js, js_mkerr(js, "short write")); 1555 + written += (size_t)result; 1556 + } 1557 + 1558 + return fs_resolved_promise(js, js_mkundef()); 1559 + } 1560 + 1561 + static void fs_init_filehandle_proto(ant_t *js) { 1562 + if (is_object_type(g_filehandle_proto)) return; 1563 + g_filehandle_proto = js_mkobj(js); 1564 + js_set_native_tag(g_filehandle_proto, FS_FILEHANDLE_NATIVE_TAG); 1565 + js_set(js, g_filehandle_proto, "close", js_mkfun(builtin_fs_filehandle_close)); 1566 + js_set(js, g_filehandle_proto, "stat", js_mkfun(builtin_fs_filehandle_stat)); 1567 + js_set(js, g_filehandle_proto, "sync", js_mkfun(builtin_fs_filehandle_sync)); 1568 + js_set(js, g_filehandle_proto, "read", js_mkfun(builtin_fs_filehandle_read)); 1569 + js_set(js, g_filehandle_proto, "write", js_mkfun(builtin_fs_filehandle_write)); 1570 + js_set(js, g_filehandle_proto, "writeFile", js_mkfun(builtin_fs_filehandle_writeFile)); 1571 + js_set_sym(js, g_filehandle_proto, get_toStringTag_sym(), js_mkstr(js, "FileHandle", 10)); 1572 + } 1573 + 1574 + static ant_value_t fs_make_filehandle(ant_t *js, int fd) { 1575 + fs_init_filehandle_proto(js); 1576 + ant_value_t handle = js_mkobj(js); 1577 + 1578 + js_set_native_tag(handle, FS_FILEHANDLE_NATIVE_TAG); 1579 + js_set_proto_init(handle, g_filehandle_proto); 1580 + js_set_slot(handle, SLOT_DATA, js_mknum((double)fd)); 1581 + js_set(js, handle, "fd", js_mknum((double)fd)); 1582 + 1583 + return handle; 1584 + } 1585 + 1332 1586 static void remove_pending_request(fs_request_t *req) { 1333 1587 if (!req || !pending_requests) return; 1334 1588 unsigned int len = utarray_len(pending_requests); ··· 1367 1621 } else reject_value = js_mkerr(req->js, "%s", err_msg); 1368 1622 1369 1623 if (is_err(reject_value)) { 1370 - reject_value = req->js->thrown_value; 1371 - if (vtype(reject_value) == T_UNDEF) 1624 + reject_value = req->js->thrown_exists ? req->js->thrown_value : js_mkundef(); 1625 + if (!req->js->thrown_exists) 1372 1626 reject_value = js_mkstr(req->js, err_msg, strlen(err_msg)); 1373 1627 req->js->thrown_exists = false; 1374 1628 req->js->thrown_value = js_mkundef(); ··· 3892 4146 free_fs_request(req); 3893 4147 } 3894 4148 4149 + static void on_open_filehandle_complete(uv_fs_t *uv_req) { 4150 + fs_request_t *req = (fs_request_t *)uv_req->data; 4151 + 4152 + if (uv_req->result < 0) { 4153 + fs_request_fail(req, (int)uv_req->result); 4154 + req->completed = 1; 4155 + complete_request(req); 4156 + return; 4157 + } 4158 + 4159 + req->completed = 1; 4160 + js_resolve_promise( 4161 + req->js, req->promise, 4162 + fs_make_filehandle(req->js, (int)uv_req->result) 4163 + ); 4164 + 4165 + remove_pending_request(req); 4166 + free_fs_request(req); 4167 + } 4168 + 3895 4169 static ant_value_t builtin_fs_open_fd(ant_t *js, ant_value_t *args, int nargs) { 3896 4170 if (nargs < 1) return js_mkerr(js, "open() requires a path argument"); 3897 4171 if (vtype(args[0]) != T_STR) return js_mkerr(js, "open() path must be a string"); ··· 3924 4198 return req->promise; 3925 4199 } 3926 4200 4201 + static ant_value_t builtin_fs_open_filehandle(ant_t *js, ant_value_t *args, int nargs) { 4202 + if (nargs < 1) return js_mkerr(js, "open() requires a path argument"); 4203 + if (vtype(args[0]) != T_STR) return js_mkerr(js, "open() path must be a string"); 4204 + 4205 + size_t path_len; 4206 + char *path = js_getstr(js, args[0], &path_len); 4207 + if (!path) return js_mkerr(js, "Failed to get path string"); 4208 + 4209 + int flags = (nargs >= 2) ? parse_open_flags(js, args[1]) : O_RDONLY; 4210 + int mode = (nargs >= 3 && vtype(args[2]) == T_NUM) ? (int)js_getnum(args[2]) : 0666; 4211 + 4212 + fs_request_t *req = calloc(1, sizeof(fs_request_t)); 4213 + if (!req) return js_mkerr(js, "Out of memory"); 4214 + 4215 + req->js = js; 4216 + req->op_type = FS_OP_OPEN; 4217 + req->promise = js_mkpromise(js); 4218 + req->path = strndup(path, path_len); 4219 + req->uv_req.data = req; 4220 + 4221 + utarray_push_back(pending_requests, &req); 4222 + int result = uv_fs_open( 4223 + uv_default_loop(), &req->uv_req, req->path, 4224 + flags, mode, on_open_filehandle_complete 4225 + ); 4226 + 4227 + if (result < 0) { 4228 + fs_request_fail(req, result); 4229 + req->completed = 1; 4230 + complete_request(req); 4231 + } 4232 + 4233 + return req->promise; 4234 + } 4235 + 3927 4236 static void on_close_fd_complete(uv_fs_t *uv_req) { 3928 4237 fs_request_t *req = (fs_request_t *)uv_req->data; 3929 4238 ··· 3973 4282 fs_watch_options_t opts; 3974 4283 fs_watcher_t *watcher = NULL; 3975 4284 uv_handle_t *handle = NULL; 4285 + 3976 4286 const char *path = NULL; 3977 4287 size_t path_len = 0; 3978 4288 int rc = 0; ··· 4287 4597 js_set(js, lib, "cp", js_mkfun(builtin_fs_cp)); 4288 4598 js_set(js, lib, "copyFile", js_mkfun(builtin_fs_copyFile)); 4289 4599 js_set(js, lib, "readFile", js_mkfun(builtin_fs_readFile)); 4290 - js_set(js, lib, "open", js_mkfun(builtin_fs_open_fd)); 4600 + js_set(js, lib, "open", js_mkfun(builtin_fs_open_filehandle)); 4291 4601 js_set(js, lib, "close", js_mkfun(builtin_fs_close_fd)); 4292 4602 js_set(js, lib, "writeFile", js_mkfun(builtin_fs_writeFile)); 4293 4603 js_set(js, lib, "write", js_mkfun(builtin_fs_write_fd)); ··· 4475 4785 4476 4786 if (g_fswatcher_proto) mark(js, g_fswatcher_proto); 4477 4787 if (g_fswatcher_ctor) mark(js, g_fswatcher_ctor); 4788 + if (g_filehandle_proto) mark(js, g_filehandle_proto); 4478 4789 if (g_readstream_proto) mark(js, g_readstream_proto); 4479 4790 if (g_readstream_ctor) mark(js, g_readstream_ctor); 4480 4791 if (g_writestream_proto) mark(js, g_writestream_proto);
+288 -22
src/modules/generator.c
··· 8 8 #include "runtime.h" 9 9 #include "sugar.h" 10 10 11 + #include "gc/roots.h" 11 12 #include "silver/engine.h" 12 13 #include "modules/generator.h" 13 14 #include "modules/symbol.h" 15 + 16 + enum { GENERATOR_NATIVE_TAG = 0x47454e52u }; // GENR 14 17 15 18 typedef enum { 16 19 GEN_SUSPENDED_START = 0, ··· 19 22 GEN_COMPLETED = 3, 20 23 } generator_state_t; 21 24 22 - enum { GENERATOR_NATIVE_TAG = 0x47454e52u }; // GENR 25 + typedef struct generator_request { 26 + sv_resume_kind_t kind; 27 + ant_value_t value; 28 + ant_value_t promise; 29 + struct generator_request *next; 30 + } generator_request_t; 23 31 24 32 typedef struct generator_data { 25 33 coroutine_t *coro; 26 34 generator_state_t state; 35 + generator_request_t *queue_head; 36 + generator_request_t *queue_tail; 27 37 bool is_async; 28 38 } generator_data_t; 39 + 40 + static ant_value_t generator_resume_kind( 41 + ant_t *js, ant_value_t gen, 42 + ant_value_t resume_value, sv_resume_kind_t resume_kind 43 + ); 29 44 30 45 static ant_value_t generator_result(ant_t *js, bool done, ant_value_t value) { 31 46 ant_value_t result = js_mkobj(js); ··· 68 83 return promise; 69 84 } 70 85 86 + static generator_request_t *generator_dequeue_request(generator_data_t *data) { 87 + if (!data || !data->queue_head) return NULL; 88 + generator_request_t *req = data->queue_head; 89 + data->queue_head = req->next; 90 + if (!data->queue_head) data->queue_tail = NULL; 91 + req->next = NULL; 92 + return req; 93 + } 94 + 95 + static void generator_free_queue(generator_data_t *data) { 96 + if (!data) return; 97 + generator_request_t *req = data->queue_head; 98 + 99 + while (req) { 100 + generator_request_t *next = req->next; 101 + free(req); 102 + req = next; 103 + } 104 + 105 + data->queue_head = NULL; 106 + data->queue_tail = NULL; 107 + } 108 + 109 + static ant_value_t generator_enqueue_request( 110 + ant_t *js, ant_value_t gen, sv_resume_kind_t kind, ant_value_t value 111 + ) { 112 + generator_data_t *data = generator_data(gen); 113 + if (!data || !data->is_async) return js_mkerr_typed(js, JS_ERR_TYPE, "Generator is already executing"); 114 + 115 + ant_value_t promise = js_mkpromise(js); 116 + if (is_err(promise)) return promise; 117 + 118 + generator_request_t *req = (generator_request_t *)calloc(1, sizeof(*req)); 119 + if (!req) return js_mkerr(js, "out of memory for generator request"); 120 + 121 + *req = (generator_request_t){ 122 + .kind = kind, 123 + .value = value, 124 + .promise = promise, 125 + .next = NULL, 126 + }; 127 + 128 + if (data->queue_tail) data->queue_tail->next = req; 129 + else data->queue_head = req; 130 + data->queue_tail = req; 131 + 132 + return promise; 133 + } 134 + 135 + static void generator_settle_request_promise(ant_t *js, ant_value_t promise, ant_value_t result) { 136 + if (is_err(result)) { 137 + ant_value_t reject_value = js->thrown_exists ? js->thrown_value : result; 138 + js->thrown_exists = false; 139 + js->thrown_value = js_mkundef(); 140 + js_reject_promise(js, promise, reject_value); 141 + } else js_resolve_promise(js, promise, result); 142 + } 143 + 144 + static void generator_process_queue(ant_t *js, ant_value_t gen) { 145 + generator_data_t *data = generator_data(gen); 146 + if (!data || !data->is_async) return; 147 + 148 + GC_ROOT_SAVE(root_mark, js); 149 + GC_ROOT_PIN(js, gen); 150 + 151 + while (data->state != GEN_EXECUTING && data->queue_head) { 152 + generator_request_t *req = generator_dequeue_request(data); 153 + if (!req) break; 154 + 155 + GC_ROOT_PIN(js, req->value); 156 + GC_ROOT_PIN(js, req->promise); 157 + 158 + coroutine_t *coro = data->coro; 159 + if (coro) coro->async_promise = req->promise; 160 + 161 + ant_value_t result = generator_resume_kind(js, gen, req->value, req->kind); 162 + GC_ROOT_PIN(js, result); 163 + 164 + if (vtype(result) != T_PROMISE || result != req->promise) { 165 + generator_settle_request_promise(js, req->promise, result); 166 + js_maybe_drain_microtasks_after_async_settle(js); 167 + } 168 + 169 + free(req); 170 + data = generator_data(gen); 171 + if (!data) break; 172 + } 173 + 174 + GC_ROOT_RESTORE(js, root_mark); 175 + } 176 + 71 177 static coroutine_t *generator_coro(ant_value_t gen) { 72 178 generator_data_t *data = generator_data(gen); 73 179 return data ? data->coro : NULL; 74 180 } 75 181 182 + static void generator_clear_coro(ant_value_t gen, coroutine_t *coro) { 183 + generator_data_t *data = generator_data(gen); 184 + if (data && data->coro == coro) data->coro = NULL; 185 + if (coro) free_coroutine(coro); 186 + } 187 + 76 188 coroutine_t *generator_get_coro_for_gc(ant_value_t gen) { 77 189 return generator_coro(gen); 78 190 } 79 191 80 - static void generator_clear_coro(ant_value_t gen, coroutine_t *coro) { 192 + void generator_mark_for_gc(ant_t *js, ant_value_t gen) { 81 193 generator_data_t *data = generator_data(gen); 82 - if (data && data->coro == coro) data->coro = NULL; 83 - if (coro) free_coroutine(coro); 194 + if (!data) return; 195 + for (generator_request_t *req = data->queue_head; req; req = req->next) { 196 + gc_mark_value(js, req->value); 197 + gc_mark_value(js, req->promise); 198 + } 199 + } 200 + 201 + static ant_value_t generator_find_owner_in_list(ant_object_t *head, coroutine_t *coro) { 202 + for (ant_object_t *obj = head; obj; obj = obj->next) { 203 + ant_value_t candidate = js_obj_from_ptr(obj); 204 + generator_data_t *data = generator_data(candidate); 205 + if (data && data->coro == coro) return candidate; 206 + } 207 + return js_mkundef(); 208 + } 209 + 210 + static ant_value_t generator_find_owner(ant_t *js, coroutine_t *coro) { 211 + ant_value_t gen = generator_find_owner_in_list(js->objects, coro); 212 + if (vtype(gen) != T_UNDEF) return gen; 213 + gen = generator_find_owner_in_list(js->objects_old, coro); 214 + if (vtype(gen) != T_UNDEF) return gen; 215 + return generator_find_owner_in_list(js->permanent_objects, coro); 216 + } 217 + 218 + bool generator_resume_pending_request(ant_t *js, coroutine_t *coro, ant_value_t result) { 219 + if (!coro || coro->type != CORO_GENERATOR || vtype(coro->async_promise) != T_PROMISE) return false; 220 + 221 + ant_value_t gen = generator_find_owner(js, coro); 222 + generator_data_t *data = generator_data(gen); 223 + if (!data || !data->is_async) return false; 224 + 225 + GC_ROOT_SAVE(root_mark, js); 226 + GC_ROOT_PIN(js, gen); 227 + GC_ROOT_PIN(js, result); 228 + 229 + ant_value_t pending = coro->async_promise; 230 + GC_ROOT_PIN(js, pending); 231 + 232 + if (is_err(result)) { 233 + ant_value_t reject_value = js->thrown_exists ? js->thrown_value : result; 234 + js->thrown_exists = false; 235 + js->thrown_value = js_mkundef(); 236 + 237 + GC_ROOT_PIN(js, reject_value); 238 + coro->async_promise = js_mkundef(); 239 + generator_set_state(gen, GEN_COMPLETED); 240 + generator_clear_coro(gen, coro); 241 + 242 + js_reject_promise(js, pending, reject_value); 243 + js_maybe_drain_microtasks_after_async_settle(js); 244 + generator_process_queue(js, gen); 245 + GC_ROOT_RESTORE(js, root_mark); 246 + 247 + return true; 248 + } 249 + 250 + if (coro->sv_vm && coro->sv_vm->suspended) { 251 + if (vtype(coro->awaited_promise) != T_UNDEF) { 252 + generator_set_state(gen, GEN_EXECUTING); 253 + GC_ROOT_RESTORE(js, root_mark); 254 + return true; 255 + } 256 + 257 + ant_value_t out = generator_result(js, false, result); 258 + GC_ROOT_PIN(js, out); 259 + coro->async_promise = js_mkundef(); 260 + generator_set_state(gen, GEN_SUSPENDED_YIELD); 261 + 262 + js_resolve_promise(js, pending, out); 263 + js_maybe_drain_microtasks_after_async_settle(js); 264 + generator_process_queue(js, gen); 265 + GC_ROOT_RESTORE(js, root_mark); 266 + 267 + return true; 268 + } 269 + 270 + ant_value_t out = generator_result(js, true, result); 271 + GC_ROOT_PIN(js, out); 272 + coro->async_promise = js_mkundef(); 273 + 274 + generator_set_state(gen, GEN_COMPLETED); 275 + generator_clear_coro(gen, coro); 276 + 277 + js_resolve_promise(js, pending, out); 278 + js_maybe_drain_microtasks_after_async_settle(js); 279 + generator_process_queue(js, gen); 280 + GC_ROOT_RESTORE(js, root_mark); 281 + 282 + return true; 84 283 } 85 284 86 285 static void generator_finalize(ant_t *js, ant_object_t *obj) { ··· 89 288 if (!data) return; 90 289 91 290 if (data->coro) { 92 - free_coroutine(data->coro); 291 + coroutine_t *coro = data->coro; 292 + bool linked_active = false; 293 + bool linked_pending = 294 + coro->prev || coro->next || 295 + pending_coroutines.head == coro || 296 + pending_coroutines.tail == coro; 297 + 298 + for (coroutine_t *it = js->active_async_coro; it; it = it->active_parent) if (it == coro) { 299 + linked_active = true; 300 + break; 301 + } 302 + 303 + bool awaiting = vtype(coro->awaited_promise) != T_UNDEF; 304 + if (!linked_pending && !linked_active && !awaiting) free_coroutine(coro); 93 305 data->coro = NULL; 94 306 } 95 307 96 308 js_set_native_ptr(gen, NULL); 97 309 js_set_native_tag(gen, 0); 310 + 311 + generator_free_queue(data); 98 312 free(data); 99 313 } 100 314 101 315 static ant_value_t generator_resume_kind( 102 316 ant_t *js, ant_value_t gen, ant_value_t resume_value, sv_resume_kind_t resume_kind 103 317 ) { 318 + GC_ROOT_SAVE(root_mark, js); 319 + GC_ROOT_PIN(js, gen); 320 + GC_ROOT_PIN(js, resume_value); 104 321 coroutine_t *coro = generator_coro(gen); 322 + 105 323 if (!coro || !coro->sv_vm) { 106 324 generator_set_state(gen, GEN_COMPLETED); 107 - if (resume_kind == SV_RESUME_THROW) return js_throw(js, resume_value); 108 - return generator_result(js, true, resume_kind == SV_RESUME_RETURN ? resume_value : js_mkundef()); 325 + if (resume_kind == SV_RESUME_THROW) { 326 + GC_ROOT_RESTORE(js, root_mark); 327 + return js_throw(js, resume_value); 328 + } 329 + 330 + ant_value_t out = generator_result( 331 + js, true, resume_kind == SV_RESUME_RETURN 332 + ? resume_value : js_mkundef() 333 + ); 334 + 335 + GC_ROOT_RESTORE(js, root_mark); 336 + return out; 109 337 } 110 338 111 339 generator_state_t state = generator_state(gen); 112 - if (state == GEN_EXECUTING) return js_mkerr_typed( 113 - js, JS_ERR_TYPE, "Generator is already executing" 114 - ); 340 + if (state == GEN_EXECUTING) { 341 + ant_value_t queued = generator_enqueue_request(js, gen, resume_kind, resume_value); 342 + GC_ROOT_RESTORE(js, root_mark); 343 + return queued; 344 + } 115 345 116 346 if (state == GEN_COMPLETED) { 117 - if (resume_kind == SV_RESUME_THROW) return js_throw(js, resume_value); 118 - return generator_result(js, true, resume_kind == SV_RESUME_RETURN ? resume_value : js_mkundef()); 347 + if (resume_kind == SV_RESUME_THROW) { 348 + GC_ROOT_RESTORE(js, root_mark); 349 + return js_throw(js, resume_value); 350 + } 351 + 352 + ant_value_t out = generator_result( 353 + js, true, resume_kind == SV_RESUME_RETURN 354 + ? resume_value : js_mkundef() 355 + ); 356 + 357 + GC_ROOT_RESTORE(js, root_mark); 358 + return out; 119 359 } 120 360 121 361 if (state == GEN_SUSPENDED_START && resume_kind == SV_RESUME_THROW) { 122 362 generator_set_state(gen, GEN_COMPLETED); 123 363 generator_clear_coro(gen, coro); 364 + GC_ROOT_RESTORE(js, root_mark); 124 365 return js_throw(js, resume_value); 125 366 } 126 367 127 368 if (state == GEN_SUSPENDED_START && resume_kind == SV_RESUME_RETURN) { 128 369 generator_set_state(gen, GEN_COMPLETED); 129 370 generator_clear_coro(gen, coro); 130 - return generator_result(js, true, resume_value); 371 + ant_value_t out = generator_result(js, true, resume_value); 372 + GC_ROOT_RESTORE(js, root_mark); 373 + return out; 131 374 } 132 375 133 376 generator_set_state(gen, GEN_EXECUTING); ··· 151 394 coro->sv_vm->suspended_resume_pending = true; 152 395 result = sv_resume_suspended(coro->sv_vm); 153 396 } 154 - 397 + 398 + GC_ROOT_PIN(js, result); 155 399 js->active_async_coro = saved_active; 156 400 coro->active_parent = NULL; 157 401 158 402 if (is_err(result)) { 159 403 generator_set_state(gen, GEN_COMPLETED); 160 404 generator_clear_coro(gen, coro); 405 + GC_ROOT_RESTORE(js, root_mark); 161 406 return result; 162 407 } 163 408 164 409 if (coro->sv_vm && coro->sv_vm->suspended) { 410 + if (generator_is_async(gen) && vtype(coro->awaited_promise) != T_UNDEF) { 411 + generator_set_state(gen, GEN_EXECUTING); 412 + if (vtype(coro->async_promise) != T_PROMISE) { 413 + coro->async_promise = js_mkpromise(js); 414 + GC_ROOT_PIN(js, coro->async_promise); 415 + } 416 + 417 + ant_value_t out = coro->async_promise; 418 + GC_ROOT_RESTORE(js, root_mark); 419 + return out; 420 + } 421 + 165 422 generator_set_state(gen, GEN_SUSPENDED_YIELD); 166 - return generator_result(js, false, result); 423 + ant_value_t out = generator_result(js, false, result); 424 + GC_ROOT_RESTORE(js, root_mark); 425 + 426 + return out; 167 427 } 168 428 169 429 generator_set_state(gen, GEN_COMPLETED); 170 430 generator_clear_coro(gen, coro); 171 431 172 - return generator_result(js, true, result); 432 + ant_value_t out = generator_result(js, true, result); 433 + GC_ROOT_RESTORE(js, root_mark); 434 + 435 + return out; 173 436 } 174 437 175 438 static ant_value_t generator_resume(ant_t *js, ant_value_t gen, ant_value_t resume_value) { ··· 180 443 ant_value_t gen = js->this_val; 181 444 if (vtype(gen) != T_GENERATOR) 182 445 return js_mkerr_typed(js, JS_ERR_TYPE, "Generator.prototype.next called on incompatible receiver"); 183 - 446 + 184 447 ant_value_t resume_value = nargs > 0 ? args[0] : js_mkundef(); 185 448 ant_value_t result = generator_resume(js, gen, resume_value); 186 449 450 + if (generator_is_async(gen) && vtype(result) == T_PROMISE) return result; 187 451 return generator_is_async(gen) ? generator_async_wrap_result(js, result) : result; 188 452 } 189 453 ··· 193 457 return js_mkerr_typed(js, JS_ERR_TYPE, "Generator.prototype.return called on incompatible receiver"); 194 458 195 459 generator_state_t state = generator_state(gen); 196 - if (state == GEN_EXECUTING) 460 + if (state == GEN_EXECUTING && !generator_is_async(gen)) 197 461 return js_mkerr_typed(js, JS_ERR_TYPE, "Generator is already executing"); 198 - 462 + 199 463 ant_value_t value = nargs > 0 ? args[0] : js_mkundef(); 200 464 ant_value_t result = generator_resume_kind(js, gen, value, SV_RESUME_RETURN); 201 465 466 + if (generator_is_async(gen) && vtype(result) == T_PROMISE) return result; 202 467 return generator_is_async(gen) ? generator_async_wrap_result(js, result) : result; 203 468 } 204 469 ··· 206 471 ant_value_t gen = js->this_val; 207 472 if (vtype(gen) != T_GENERATOR) 208 473 return js_mkerr_typed(js, JS_ERR_TYPE, "Generator.prototype.throw called on incompatible receiver"); 209 - 474 + 210 475 generator_state_t state = generator_state(gen); 211 - if (state == GEN_EXECUTING) 476 + if (state == GEN_EXECUTING && !generator_is_async(gen)) 212 477 return js_mkerr_typed(js, JS_ERR_TYPE, "Generator is already executing"); 213 - 478 + 214 479 ant_value_t value = nargs > 0 ? args[0] : js_mkundef(); 215 480 ant_value_t result = generator_resume_kind(js, gen, value, SV_RESUME_THROW); 216 481 482 + if (generator_is_async(gen) && vtype(result) == T_PROMISE) return result; 217 483 return generator_is_async(gen) ? generator_async_wrap_result(js, result) : result; 218 484 } 219 485
+17 -20
src/modules/process.c
··· 31 31 #include "errors.h" 32 32 #include "output.h" 33 33 #include "utils.h" 34 + #include "tty_ctrl.h" 34 35 #include "internal.h" 35 36 #include "descriptors.h" 36 37 #include "runtime.h" ··· 783 784 return this_obj; 784 785 } 785 786 786 - static ant_value_t js_stdout_write(ant_t *js, ant_value_t *args, int nargs) { 787 + static ant_value_t process_write_stream(ant_t *js, ant_value_t *args, int nargs, FILE *stream, int fd) { 787 788 if (nargs < 1) return js_false; 789 + 788 790 size_t len = 0; 789 791 char *data = js_getstr(js, args[0], &len); 790 - 791 792 ant_output_stream_t *out = NULL; 793 + 792 794 if (!data) return js_false; 793 - 794 - out = ant_output_stream(stdout); 795 + if (uv_guess_handle(fd) == UV_TTY) { 796 + return js_bool(tty_ctrl_write_fd(fd, data, len)); 797 + } 798 + 799 + out = ant_output_stream(stream); 795 800 ant_output_stream_begin(out); 796 - 801 + 797 802 if (!ant_output_stream_append(out, data, len)) return js_false; 798 803 return ant_output_stream_flush(out) ? js_true : js_false; 804 + } 805 + 806 + static ant_value_t js_stdout_write(ant_t *js, ant_value_t *args, int nargs) { 807 + return process_write_stream(js, args, nargs, stdout, STDOUT_FILENO); 799 808 } 800 809 801 810 static ant_value_t js_stdout_on(ant_t *js, ant_value_t *args, int nargs) { ··· 886 895 } 887 896 888 897 static ant_value_t js_stdout_columns_getter(ant_t *js, ant_value_t *args, int nargs) { 889 - (void)args; (void)nargs; 890 898 int rows = 0, cols = 0; 891 899 get_tty_size(STDOUT_FILENO, &rows, &cols); 892 900 return js_mknum(cols); 893 901 } 894 902 895 903 static ant_value_t js_stderr_write(ant_t *js, ant_value_t *args, int nargs) { 896 - if (nargs < 1) return js_false; 897 - size_t len = 0; 898 - char *data = js_getstr(js, args[0], &len); 899 - 900 - ant_output_stream_t *out = NULL; 901 - if (!data) return js_false; 902 - 903 - out = ant_output_stream(stderr); 904 - ant_output_stream_begin(out); 905 - 906 - if (!ant_output_stream_append(out, data, len)) return js_false; 907 - return ant_output_stream_flush(out) ? js_true : js_false; 904 + return process_write_stream(js, args, nargs, stderr, STDERR_FILENO); 908 905 } 909 906 910 907 static ant_value_t js_stderr_on(ant_t *js, ant_value_t *args, int nargs) { ··· 1762 1759 if (vtype(cb) != T_FUNC && vtype(cb) != T_CFUNC) 1763 1760 return js_mkerr_typed(js, JS_ERR_TYPE, "process.nextTick callback is not a function"); 1764 1761 1765 - if (nargs <= 1) queue_microtask(js, cb); 1766 - else queue_microtask_with_args(js, cb, args + 1, nargs - 1); 1762 + if (nargs <= 1) queue_next_tick(js, cb); 1763 + else queue_next_tick_with_args(js, cb, args + 1, nargs - 1); 1767 1764 1768 1765 return js_mkundef(); 1769 1766 }
+231 -79
src/modules/stream.c
··· 37 37 static double g_default_high_water_mark = 16384.0; 38 38 static double g_default_object_high_water_mark = 16.0; 39 39 40 - static ant_value_t stream_readable_maybe_read(ant_t *js, ant_value_t stream_obj); 41 - static ant_value_t stream_readable_flush(ant_t *js, ant_value_t stream_obj); 42 - static ant_value_t stream_readable_push_value(ant_t *js, ant_value_t stream_obj, ant_value_t chunk, ant_value_t encoding); 43 - static ant_value_t stream_readable_continue_flowing(ant_t *js, ant_value_t *args, int nargs); 44 - 45 40 static ant_value_t stream_noop(ant_t *js, ant_value_t *args, int nargs) { 46 41 return js_mkundef(); 47 42 } 48 43 49 44 static bool stream_is_instance(ant_value_t value) { 50 45 return is_object_type(value) && js_check_native_tag(value, STREAM_NATIVE_TAG); 46 + } 47 + 48 + static inline void stream_set_end_callback(ant_t *js, ant_value_t stream_obj, ant_value_t callback) { 49 + js_set_slot_wb(js, stream_obj, SLOT_AUX, callback); 50 + } 51 + 52 + static stream_private_state_t *stream_private_state(ant_value_t stream_obj) { 53 + if (!stream_is_instance(stream_obj)) return NULL; 54 + return (stream_private_state_t *)js_get_native_ptr(stream_obj); 51 55 } 52 56 53 57 static ant_value_t stream_require_this(ant_t *js, ant_value_t value, const char *label) { ··· 74 78 return js_mkerr(js, "event must be a string or Symbol"); 75 79 } 76 80 81 + void *stream_get_attached_state(ant_value_t stream_obj) { 82 + stream_private_state_t *priv = stream_private_state(stream_obj); 83 + return priv ? priv->attached_state : NULL; 84 + } 85 + 86 + void stream_set_attached_state( 87 + ant_value_t stream_obj, 88 + void *state, 89 + stream_finalize_fn finalize 90 + ) { 91 + stream_private_state_t *priv = stream_private_state(stream_obj); 92 + if (!priv) return; 93 + priv->attached_state = state; 94 + priv->attached_state_finalize = finalize; 95 + } 96 + 97 + void stream_clear_attached_state(ant_value_t stream_obj) { 98 + stream_private_state_t *priv = stream_private_state(stream_obj); 99 + if (!priv) return; 100 + priv->attached_state = NULL; 101 + priv->attached_state_finalize = NULL; 102 + } 103 + 104 + static void stream_finalize(ant_t *js, ant_object_t *obj) { 105 + ant_value_t stream_obj = js_obj_from_ptr(obj); 106 + stream_private_state_t *priv = stream_private_state(stream_obj); 107 + if (!priv) return; 108 + js_set_native_ptr(stream_obj, NULL); 109 + if (priv->attached_state && priv->attached_state_finalize) 110 + priv->attached_state_finalize(js, stream_obj, priv->attached_state); 111 + free(priv); 112 + } 113 + 77 114 static ant_value_t stream_call( 78 115 ant_t *js, 79 116 ant_value_t fn, ··· 83 120 bool is_ctor 84 121 ) { 85 122 if (!is_callable(fn)) return js_mkundef(); 86 - return sv_vm_call(js->vm, js, fn, this_val, args, nargs, NULL, is_ctor); 123 + if (sv_check_c_stack_overflow(js)) 124 + return js_mkerr_typed(js, JS_ERR_RANGE | JS_ERR_NO_STACK, "Maximum call stack size exceeded"); 125 + 126 + sv_call_mode_t mode = is_ctor ? SV_CALL_MODE_CONSTRUCT : SV_CALL_MODE_NORMAL; 127 + sv_call_plan_t plan; 128 + ant_value_t err = sv_prepare_call(js->vm, js, fn, this_val, args, nargs, NULL, mode, &plan); 129 + if (is_err(err)) return err; 130 + 131 + return sv_execute_call_plan(js->vm, js, &plan, NULL); 87 132 } 88 133 89 134 static ant_value_t stream_call_prop( ··· 276 321 277 322 static ant_value_t stream_make_base_object(ant_t *js, ant_value_t proto) { 278 323 ant_value_t obj = js_mkobj(js); 324 + stream_private_state_t *priv = calloc(1, sizeof(*priv)); 325 + 279 326 if (is_object_type(proto)) js_set_proto_init(obj, proto); 280 327 js_set_native_tag(obj, STREAM_NATIVE_TAG); 328 + 329 + if (priv) js_set_native_ptr(obj, priv); 330 + js_set_slot(obj, SLOT_AUX, js_mkundef()); 331 + js_set_finalizer(obj, stream_finalize); 332 + 281 333 return obj; 282 334 } 283 335 ··· 596 648 done_state = js_mkobj(js); 597 649 js_set(js, done_state, "stream", stream_obj); 598 650 done = stream_make_once(js, js_heavy_mkfun(js, stream_destroy_done, done_state), js_mkundef()); 599 - destroy_fn = js_get(js, stream_obj, "_destroy"); 651 + destroy_fn = js_getprop_fallback(js, stream_obj, "_destroy"); 600 652 601 653 if (is_callable(destroy_fn)) { 602 654 destroy_args[0] = is_undefined(error) ? js_mknull() : error; ··· 616 668 617 669 static ant_value_t stream_readable_start_flowing(ant_t *js, ant_value_t *args, int nargs) { 618 670 ant_value_t stream_obj = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 671 + return stream_readable_begin_flowing(js, stream_obj); 672 + } 673 + 674 + ant_value_t stream_readable_continue_flowing(ant_t *js, ant_value_t *args, int nargs) { 675 + ant_value_t stream_obj = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 619 676 ant_value_t state = stream_readable_state(js, stream_obj); 620 677 621 678 if (!is_object_type(state)) return js_mkundef(); 679 + js_set(js, state, "flowingReadScheduled", js_false); 680 + 622 681 if (!js_truthy(js, js_get(js, state, "flowing"))) return js_mkundef(); 682 + if (js_truthy(js, js_get(js, stream_obj, "destroyed"))) return js_mkundef(); 683 + if (js_truthy(js, js_get(js, state, "ended"))) return js_mkundef(); 623 684 624 - { 625 - ant_value_t saved_this = js->this_val; 626 - js->this_val = stream_obj; 627 - js_stream_resume(js, NULL, 0); 628 - js->this_val = saved_this; 629 - } 630 685 stream_readable_maybe_read(js, stream_obj); 631 686 stream_readable_flush(js, stream_obj); 687 + 632 688 return js_mkundef(); 633 689 } 634 690 635 - static ant_value_t stream_readable_continue_flowing(ant_t *js, ant_value_t *args, int nargs) { 636 - ant_value_t stream_obj = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 691 + ant_value_t stream_readable_begin_flowing(ant_t *js, ant_value_t stream_obj) { 637 692 ant_value_t state = stream_readable_state(js, stream_obj); 638 693 639 694 if (!is_object_type(state)) return js_mkundef(); 640 - js_set(js, state, "flowingReadScheduled", js_false); 641 - 642 695 if (!js_truthy(js, js_get(js, state, "flowing"))) return js_mkundef(); 643 - if (js_truthy(js, js_get(js, stream_obj, "destroyed"))) return js_mkundef(); 644 - if (js_truthy(js, js_get(js, state, "ended"))) return js_mkundef(); 696 + 697 + { 698 + ant_value_t saved_this = js->this_val; 699 + js->this_val = stream_obj; 700 + js_stream_resume(js, NULL, 0); 701 + js->this_val = saved_this; 702 + } 645 703 646 704 stream_readable_maybe_read(js, stream_obj); 647 705 stream_readable_flush(js, stream_obj); ··· 649 707 return js_mkundef(); 650 708 } 651 709 652 - static ant_value_t stream_readable_flush(ant_t *js, ant_value_t stream_obj) { 710 + ant_value_t stream_readable_flush(ant_t *js, ant_value_t stream_obj) { 653 711 ant_value_t state = stream_readable_state(js, stream_obj); 654 712 bool emitted_data = false; 655 713 ··· 675 733 return js_mkundef(); 676 734 } 677 735 678 - static ant_value_t stream_readable_maybe_read(ant_t *js, ant_value_t stream_obj) { 736 + ant_value_t stream_readable_maybe_read(ant_t *js, ant_value_t stream_obj) { 679 737 ant_value_t state = stream_readable_state(js, stream_obj); 680 738 ant_value_t read_fn = 0; 681 739 ant_value_t args[1]; ··· 686 744 if (js_truthy(js, js_get(js, state, "ended"))) return js_mkundef(); 687 745 if (stream_readable_buffer_len(js, stream_obj) > 0) return js_mkundef(); 688 746 689 - read_fn = js_get(js, stream_obj, "_read"); 747 + read_fn = js_getprop_fallback(js, stream_obj, "_read"); 690 748 js_set(js, state, "reading", js_true); 691 749 args[0] = js_get(js, state, "highWaterMark"); 750 + 692 751 if (is_callable(read_fn)) stream_call(js, read_fn, stream_obj, args, 1, false); 693 752 js_set(js, state, "reading", js_false); 753 + 694 754 return js_mkundef(); 695 755 } 696 756 697 - static ant_value_t stream_readable_push_value( 757 + ant_value_t stream_readable_push_value( 698 758 ant_t *js, 699 759 ant_value_t stream_obj, 700 760 ant_value_t chunk, ··· 713 773 } 714 774 715 775 normalized = stream_normalize_chunk( 716 - js, 717 - chunk, 776 + js, chunk, 718 777 js_truthy(js, js_get(js, state, "objectMode")), 719 778 is_undefined(encoding) ? js_mkstr(js, "utf8", 4) : encoding 720 779 ); ··· 722 781 723 782 stream_buffer_push(js, stream_obj, normalized); 724 783 if (js_truthy(js, js_get(js, state, "flowing"))) stream_readable_flush(js, stream_obj); 784 + 725 785 return js_bool(js_truthy(js, js_get(js, state, "flowing"))); 726 786 } 727 787 ··· 747 807 748 808 chunk = stream_buffer_shift(js, stream_obj); 749 809 if (js_truthy(js, js_get(js, state, "flowing"))) stream_readable_flush(js, stream_obj); 810 + 750 811 return chunk; 751 812 } 752 813 ··· 758 819 if (is_err(stream_obj)) return stream_obj; 759 820 if (nargs < 2) return js_mkerr(js, "on requires 2 arguments (event, listener)"); 760 821 key = stream_event_key(js, args[0]); 822 + 761 823 if (is_err(key)) return key; 762 824 if (!eventemitter_add_listener_val(js, stream_obj, key, args[1], false)) 763 825 return js_mkerr(js, "listener must be a function"); 764 - 826 + 765 827 if (stream_key_is_cstr(js, key, "data")) { 766 828 state = stream_readable_state(js, stream_obj); 767 829 if (is_object_type(state)) js_set(js, state, "flowing", js_true); ··· 778 840 779 841 state = stream_readable_state(js, stream_obj); 780 842 if (is_object_type(state)) js_set(js, state, "flowing", js_true); 843 + 781 844 js_stream_resume(js, NULL, 0); 782 845 stream_readable_maybe_read(js, stream_obj); 783 846 stream_readable_flush(js, stream_obj); 847 + 784 848 return stream_obj; 785 849 } 786 850 ··· 810 874 ant_value_t state_obj = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 811 875 ant_value_t stream_obj = js_get(js, state_obj, "stream"); 812 876 ant_value_t callback = js_get(js, state_obj, "callback"); 877 + 878 + stream_private_state_t *priv = stream_private_state(stream_obj); 813 879 ant_value_t err = nargs > 0 ? args[0] : js_mkundef(); 880 + 881 + if (priv) priv->writing = false; 814 882 815 883 if (!is_undefined(err) && !is_null(err)) { 816 884 ant_value_t destroy_args[1] = { err }; 817 885 js_set(js, state_obj, "done", js_true); 818 - { 886 + if (priv) priv->pending_final = false; { 819 887 ant_value_t saved_this = js->this_val; 820 888 js->this_val = stream_obj; 821 889 js_stream_destroy(js, destroy_args, 1); 822 890 js->this_val = saved_this; 823 891 } 892 + 824 893 if (is_callable(callback)) stream_call_callback(js, callback, &err, 1); 825 894 return js_mkundef(); 826 895 } 827 896 828 897 if (is_callable(callback)) stream_call_callback(js, callback, NULL, 0); 829 898 stream_emit_named(js, stream_obj, "drain"); 899 + 900 + if (priv && priv->pending_final && !priv->final_started) { 901 + ant_value_t end_callback = js_get_slot(stream_obj, SLOT_AUX); 902 + priv->pending_final = false; 903 + stream_set_end_callback(js, stream_obj, js_mkundef()); 904 + return stream_writable_begin_end(js, stream_obj, end_callback); 905 + } 906 + 830 907 return js_mkundef(); 831 908 } 832 909 833 - static ant_value_t js_writable_write(ant_t *js, ant_value_t *args, int nargs) { 834 - ant_value_t stream_obj = stream_require_this(js, js_getthis(js), "Writable"); 910 + static ant_value_t stream_writable_write_impl( 911 + ant_t *js, 912 + ant_value_t stream_obj, 913 + ant_value_t chunk, 914 + ant_value_t encoding, 915 + ant_value_t callback, 916 + bool allow_after_end 917 + ) { 835 918 ant_value_t state = 0; 836 - ant_value_t callback = js_mkundef(); 837 - ant_value_t encoding = js_mkstr(js, "utf8", 4); 838 919 ant_value_t normalized = 0; 839 920 ant_value_t write_fn = 0; 840 921 ant_value_t done_state = 0; 841 922 ant_value_t done = 0; 842 923 ant_value_t write_args[3]; 843 924 844 - if (is_err(stream_obj)) return stream_obj; 845 925 state = stream_writable_state(js, stream_obj); 846 926 if (!is_object_type(state)) return js_false; 847 927 848 - if (nargs > 1 && is_callable(args[1])) callback = args[1]; 849 - else if (nargs > 1 && !is_undefined(args[1])) encoding = args[1]; 850 - if (nargs > 2 && is_callable(args[2])) callback = args[2]; 851 - 852 - if (js_truthy(js, js_get(js, stream_obj, "writableEnded")) || js_truthy(js, js_get(js, stream_obj, "destroyed"))) { 928 + if ( 929 + (!allow_after_end && js_truthy(js, js_get(js, stream_obj, "writableEnded"))) || 930 + js_truthy(js, js_get(js, stream_obj, "destroyed")) 931 + ) { 853 932 ant_value_t err = js_mkerr(js, "write after end"); 854 933 if (is_callable(callback)) stream_call_callback(js, callback, &err, 1); 855 934 else stream_emit_error(js, stream_obj, err); ··· 857 936 } 858 937 859 938 normalized = stream_normalize_chunk( 860 - js, 861 - nargs > 0 ? args[0] : js_mkundef(), 939 + js, chunk, 862 940 js_truthy(js, js_get(js, state, "objectMode")), 863 941 encoding 864 942 ); 943 + 865 944 if (is_err(normalized)) return normalized; 866 - 867 945 done_state = js_mkobj(js); 946 + 868 947 js_set(js, done_state, "stream", stream_obj); 869 948 js_set(js, done_state, "callback", callback); 870 949 js_set(js, done_state, "done", js_false); 950 + 871 951 done = stream_make_once(js, js_heavy_mkfun(js, stream_writable_write_done, done_state), js_mkundef()); 872 - write_fn = js_get(js, stream_obj, "_write"); 952 + write_fn = js_getprop_fallback(js, stream_obj, "_write"); 953 + stream_private_state_t *priv = stream_private_state(stream_obj); 954 + if (priv) priv->writing = true; 873 955 874 956 write_args[0] = normalized; 875 957 write_args[1] = encoding; 876 958 write_args[2] = done; 959 + 877 960 if (is_callable(write_fn)) { 878 - ant_value_t result = stream_call(js, write_fn, stream_obj, write_args, 3, false); 879 - if (is_err(result)) { 880 - ant_value_t err_args[1] = { result }; 881 - stream_call_callback(js, done, err_args, 1); 882 - return js_false; 883 - } 884 - } 961 + ant_value_t result = stream_call(js, write_fn, stream_obj, write_args, 3, false); 962 + if (is_err(result)) { 963 + ant_value_t err_args[1] = { result }; 964 + stream_call_callback(js, done, err_args, 1); 965 + return js_false; 966 + }} 885 967 886 968 return js_bool(!js_truthy(js, js_get(js, stream_obj, "destroyed"))); 887 969 } 888 970 971 + static ant_value_t js_writable_write(ant_t *js, ant_value_t *args, int nargs) { 972 + ant_value_t stream_obj = stream_require_this(js, js_getthis(js), "Writable"); 973 + ant_value_t callback = js_mkundef(); 974 + ant_value_t encoding = js_mkstr(js, "utf8", 4); 975 + 976 + if (is_err(stream_obj)) return stream_obj; 977 + 978 + if (nargs > 1 && is_callable(args[1])) callback = args[1]; 979 + else if (nargs > 1 && !is_undefined(args[1])) encoding = args[1]; 980 + if (nargs > 2 && is_callable(args[2])) callback = args[2]; 981 + 982 + return stream_writable_write_impl( 983 + js, stream_obj, 984 + nargs > 0 ? args[0] : js_mkundef(), 985 + encoding, callback, false 986 + ); 987 + } 988 + 889 989 static ant_value_t stream_writable_end_done(ant_t *js, ant_value_t *args, int nargs) { 890 990 ant_value_t state_obj = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 891 991 ant_value_t stream_obj = js_get(js, state_obj, "stream"); ··· 905 1005 js_set(js, stream_obj, "writableFinished", js_true); 906 1006 js_set(js, stream_writable_state(js, stream_obj), "finished", js_true); 907 1007 stream_emit_named(js, stream_obj, "finish"); 1008 + 908 1009 if (is_callable(callback)) stream_call_callback(js, callback, NULL, 0); 909 1010 if (!js_truthy(js, js_get(js, stream_obj, "readable"))) stream_emit_named(js, stream_obj, "close"); 1011 + 910 1012 return js_mkundef(); 911 1013 } 912 1014 1015 + ant_value_t stream_writable_begin_end(ant_t *js, ant_value_t stream_obj, ant_value_t callback) { 1016 + ant_value_t final_fn = 0; 1017 + ant_value_t final_args[1]; 1018 + ant_value_t done_state = 0; 1019 + ant_value_t done = 0; 1020 + stream_private_state_t *priv = stream_private_state(stream_obj); 1021 + 1022 + done_state = js_mkobj(js); 1023 + js_set(js, done_state, "stream", stream_obj); 1024 + js_set(js, done_state, "callback", callback); 1025 + 1026 + done = stream_make_once(js, js_heavy_mkfun(js, stream_writable_end_done, done_state), js_mkundef()); 1027 + if (priv) { 1028 + priv->final_started = true; 1029 + priv->pending_final = false; 1030 + } 1031 + 1032 + stream_set_end_callback(js, stream_obj, js_mkundef()); 1033 + final_fn = js_getprop_fallback(js, stream_obj, "_final"); 1034 + final_args[0] = done; 1035 + 1036 + if (is_callable(final_fn)) stream_call(js, final_fn, stream_obj, final_args, 1, false); 1037 + else stream_call_callback(js, done, NULL, 0); 1038 + 1039 + return stream_obj; 1040 + } 1041 + 1042 + static ant_value_t stream_writable_end_after_write(ant_t *js, ant_value_t *args, int nargs) { 1043 + ant_value_t state_obj = js_get_slot(js_getcurrentfunc(js), SLOT_DATA); 1044 + ant_value_t stream_obj = js_get(js, state_obj, "stream"); 1045 + ant_value_t callback = js_get(js, state_obj, "callback"); 1046 + stream_private_state_t *priv = stream_private_state(stream_obj); 1047 + ant_value_t err = nargs > 0 ? args[0] : js_mkundef(); 1048 + 1049 + if (!is_undefined(err) && !is_null(err)) { 1050 + ant_value_t destroy_args[1] = { err }; 1051 + ant_value_t saved_this = js->this_val; 1052 + js->this_val = stream_obj; 1053 + js_stream_destroy(js, destroy_args, 1); 1054 + js->this_val = saved_this; 1055 + if (is_callable(callback)) stream_call_callback(js, callback, &err, 1); 1056 + return js_mkundef(); 1057 + } 1058 + 1059 + if (priv) priv->pending_final = false; 1060 + stream_set_end_callback(js, stream_obj, js_mkundef()); 1061 + 1062 + return stream_writable_begin_end(js, stream_obj, callback); 1063 + } 1064 + 913 1065 static ant_value_t js_writable_end(ant_t *js, ant_value_t *args, int nargs) { 914 1066 ant_value_t stream_obj = stream_require_this(js, js_getthis(js), "Writable"); 915 1067 ant_value_t callback = js_mkundef(); 916 1068 ant_value_t chunk = js_mkundef(); 917 1069 ant_value_t encoding = js_mkundef(); 918 - ant_value_t final_fn = 0; 919 - ant_value_t final_args[1]; 920 - ant_value_t done_state = 0; 921 - ant_value_t done = 0; 1070 + ant_value_t after_write_state = 0; 1071 + ant_value_t after_write = 0; 1072 + stream_private_state_t *priv = NULL; 922 1073 923 1074 if (is_err(stream_obj)) return stream_obj; 924 - 925 1075 if (nargs > 0 && is_callable(args[0])) callback = args[0]; 926 1076 else { 927 1077 if (nargs > 0) chunk = args[0]; ··· 930 1080 if (nargs > 2 && is_callable(args[2])) callback = args[2]; 931 1081 } 932 1082 933 - if (!is_undefined(chunk) && !is_null(chunk)) { 934 - ant_value_t write_args[3]; 935 - ant_value_t saved_this = js->this_val; 936 - write_args[0] = chunk; 937 - write_args[1] = encoding; 938 - write_args[2] = js_mkundef(); 939 - js->this_val = stream_obj; 940 - js_writable_write(js, write_args, 3); 941 - js->this_val = saved_this; 942 - } 943 - 944 1083 if (js_truthy(js, js_get(js, stream_obj, "writableEnded"))) { 945 1084 if (is_callable(callback)) stream_call_callback(js, callback, NULL, 0); 946 1085 return stream_obj; ··· 948 1087 949 1088 js_set(js, stream_obj, "writableEnded", js_true); 950 1089 js_set(js, stream_writable_state(js, stream_obj), "ended", js_true); 1090 + priv = stream_private_state(stream_obj); 951 1091 952 - done_state = js_mkobj(js); 953 - js_set(js, done_state, "stream", stream_obj); 954 - js_set(js, done_state, "callback", callback); 955 - done = stream_make_once(js, js_heavy_mkfun(js, stream_writable_end_done, done_state), js_mkundef()); 1092 + if (!is_undefined(chunk) && !is_null(chunk)) { 1093 + after_write_state = js_mkobj(js); 1094 + js_set(js, after_write_state, "stream", stream_obj); 1095 + js_set(js, after_write_state, "callback", callback); 1096 + 1097 + after_write = stream_make_once( 1098 + js, js_heavy_mkfun(js, stream_writable_end_after_write, after_write_state), 1099 + js_mkundef() 1100 + ); 1101 + 1102 + stream_writable_write_impl(js, stream_obj, chunk, encoding, after_write, true); 1103 + return stream_obj; 1104 + } 956 1105 957 - final_fn = js_get(js, stream_obj, "_final"); 958 - final_args[0] = done; 959 - if (is_callable(final_fn)) stream_call(js, final_fn, stream_obj, final_args, 1, false); 960 - else stream_call_callback(js, done, NULL, 0); 1106 + if (priv && priv->writing && !priv->final_started) { 1107 + priv->pending_final = true; 1108 + stream_set_end_callback(js, stream_obj, callback); 1109 + return stream_obj; 1110 + } 961 1111 962 - return stream_obj; 1112 + return stream_writable_begin_end(js, stream_obj, callback); 963 1113 } 964 1114 965 1115 static ant_value_t js_transform__transform(ant_t *js, ant_value_t *args, int nargs) { ··· 977 1127 ant_value_t outer_callback = js_get(js, state_obj, "callback"); 978 1128 979 1129 if (nargs > 0 && !is_null(args[0]) && !is_undefined(args[0])) { 980 - stream_call_callback(js, outer_callback, &args[0], 1); 1130 + if (is_callable(outer_callback)) stream_call(js, outer_callback, stream_obj, &args[0], 1, false); 981 1131 return js_mkundef(); 982 1132 } 983 1133 984 1134 if (nargs > 1 && !is_null(args[1]) && !is_undefined(args[1])) 985 1135 stream_readable_push_value(js, stream_obj, args[1], js_mkundef()); 986 1136 987 - stream_call_callback(js, outer_callback, NULL, 0); 1137 + if (is_callable(outer_callback)) stream_call(js, outer_callback, stream_obj, NULL, 0, false); 988 1138 return js_mkundef(); 989 1139 } 990 1140 ··· 996 1146 ant_value_t call_args[3]; 997 1147 998 1148 if (is_err(stream_obj)) return stream_obj; 999 - transform_fn = js_get(js, stream_obj, "_transform"); 1149 + transform_fn = js_getprop_fallback(js, stream_obj, "_transform"); 1000 1150 1001 1151 cb_state = js_mkobj(js); 1002 1152 js_set(js, cb_state, "stream", stream_obj); ··· 1016 1166 ant_value_t callback = js_get(js, state_obj, "callback"); 1017 1167 1018 1168 if (nargs > 0 && !is_null(args[0]) && !is_undefined(args[0])) { 1019 - stream_call_callback(js, callback, &args[0], 1); 1169 + if (is_callable(callback)) stream_call(js, callback, stream_obj, &args[0], 1, false); 1020 1170 return js_mkundef(); 1021 1171 } 1022 1172 1023 1173 if (nargs > 1 && !is_null(args[1]) && !is_undefined(args[1])) 1024 1174 stream_readable_push_value(js, stream_obj, args[1], js_mkundef()); 1025 1175 stream_readable_push_value(js, stream_obj, js_mknull(), js_mkundef()); 1026 - stream_call_callback(js, callback, NULL, 0); 1176 + if (is_callable(callback)) stream_call(js, callback, stream_obj, NULL, 0, false); 1177 + 1027 1178 return js_mkundef(); 1028 1179 } 1029 1180 ··· 1035 1186 ant_value_t call_args[1]; 1036 1187 1037 1188 if (is_err(stream_obj)) return stream_obj; 1038 - flush_fn = js_get(js, stream_obj, "_flush"); 1189 + flush_fn = js_getprop_fallback(js, stream_obj, "_flush"); 1190 + 1039 1191 if (!is_callable(flush_fn)) { 1040 1192 stream_readable_push_value(js, stream_obj, js_mknull(), js_mkundef()); 1041 1193 stream_call_callback(js, nargs > 0 ? args[0] : js_mkundef(), NULL, 0);
+105 -20
src/modules/timer.c
··· 48 48 ant_t *js; 49 49 timer_entry_t *timers; 50 50 51 + microtask_entry_t *next_ticks; 52 + microtask_entry_t *next_ticks_tail; 53 + microtask_entry_t *next_ticks_processing; 51 54 microtask_entry_t *microtasks; 52 55 microtask_entry_t *microtasks_tail; 53 56 microtask_entry_t *microtasks_processing; ··· 57 60 int next_timer_id; 58 61 int next_immediate_id; 59 62 int active_timer_count; 60 - } timer_state = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1, 1, 0}; 63 + } timer_state = { 64 + .js = NULL, 65 + .timers = NULL, 66 + .next_ticks = NULL, 67 + .next_ticks_tail = NULL, 68 + .next_ticks_processing = NULL, 69 + .microtasks = NULL, 70 + .microtasks_tail = NULL, 71 + .microtasks_processing = NULL, 72 + .immediates = NULL, 73 + .immediates_tail = NULL, 74 + .next_timer_id = 1, 75 + .next_immediate_id = 1, 76 + .active_timer_count = 0, 77 + }; 61 78 62 79 static void add_timer_entry(timer_entry_t *entry) { 63 80 entry->next = timer_state.timers; ··· 515 532 return js_timers_promises_setImmediate(js, args, nargs); 516 533 } 517 534 518 - static void queue_microtask_entry(microtask_entry_t *entry) { 519 - if (timer_state.microtasks_tail == NULL) { 520 - timer_state.microtasks = entry; 521 - timer_state.microtasks_tail = entry; 522 - } else { 523 - timer_state.microtasks_tail->next = entry; 524 - timer_state.microtasks_tail = entry; 525 - }} 535 + static void queue_microtask_entry( 536 + microtask_entry_t **head, 537 + microtask_entry_t **tail, 538 + microtask_entry_t *entry 539 + ) { 540 + if (*tail == NULL) goto empty; 541 + 542 + (*tail)->next = entry; 543 + *tail = entry; 544 + return; 545 + 546 + empty: 547 + *head = entry; 548 + *tail = entry; 549 + } 526 550 527 551 void queue_microtask(ant_t *js, ant_value_t callback) { 528 552 microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t)); ··· 533 557 entry->next = NULL; 534 558 entry->argc = 0; 535 559 536 - queue_microtask_entry(entry); 560 + queue_microtask_entry(&timer_state.microtasks, &timer_state.microtasks_tail, entry); 537 561 } 538 562 539 563 void queue_microtask_with_args(ant_t *js, ant_value_t callback, ant_value_t *args, int nargs) { ··· 548 572 entry->argc = (uint8_t)nargs; 549 573 550 574 for (int i = 0; i < nargs; i++) entry->argv[i] = args[i]; 551 - queue_microtask_entry(entry); 575 + queue_microtask_entry(&timer_state.microtasks, &timer_state.microtasks_tail, entry); 576 + } 577 + 578 + void queue_next_tick(ant_t *js, ant_value_t callback) { 579 + microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t)); 580 + if (entry == NULL) return; 581 + 582 + entry->callback = callback; 583 + entry->promise = js_mkundef(); 584 + entry->next = NULL; 585 + entry->argc = 0; 586 + 587 + queue_microtask_entry(&timer_state.next_ticks, &timer_state.next_ticks_tail, entry); 588 + } 589 + 590 + void queue_next_tick_with_args(ant_t *js, ant_value_t callback, ant_value_t *args, int nargs) { 591 + if (nargs <= 0) { queue_next_tick(js, callback); return; } 592 + 593 + microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t) + (size_t)nargs * sizeof(ant_value_t)); 594 + if (entry == NULL) return; 595 + 596 + entry->callback = callback; 597 + entry->promise = js_mkundef(); 598 + entry->next = NULL; 599 + entry->argc = (uint8_t)nargs; 600 + 601 + for (int i = 0; i < nargs; i++) entry->argv[i] = args[i]; 602 + queue_microtask_entry(&timer_state.next_ticks, &timer_state.next_ticks_tail, entry); 552 603 } 553 604 554 605 void queue_promise_trigger(ant_t *js, ant_value_t promise) { ··· 564 615 entry->promise = promise; 565 616 entry->next = NULL; 566 617 567 - if (timer_state.microtasks_tail == NULL) { 568 - timer_state.microtasks = entry; 569 - timer_state.microtasks_tail = entry; 570 - } else { 571 - timer_state.microtasks_tail->next = entry; 572 - timer_state.microtasks_tail = entry; 573 - } 618 + queue_microtask_entry(&timer_state.microtasks, &timer_state.microtasks_tail, entry); 574 619 } 575 620 576 621 static inline void process_microtask_entry(ant_t *js, microtask_entry_t *entry) { ··· 592 637 ant_value_t callback = entry->callback; 593 638 GC_ROOT_PIN(js, callback); 594 639 640 + for (uint8_t i = 0; i < entry->argc; i++) GC_ROOT_PIN(js, entry->argv[i]); 595 641 sv_vm_call(js->vm, js, callback, js_mkundef(), entry->argv, entry->argc, NULL, false); 596 642 GC_ROOT_RESTORE(js, root_mark); 597 643 } ··· 602 648 timer_state.microtasks = NULL; 603 649 timer_state.microtasks_tail = NULL; 604 650 timer_state.microtasks_processing = batch; 651 + 652 + return batch; 653 + } 654 + 655 + static inline microtask_entry_t *take_next_tick_batch(void) { 656 + microtask_entry_t *batch = timer_state.next_ticks; 657 + 658 + timer_state.next_ticks = NULL; 659 + timer_state.next_ticks_tail = NULL; 660 + timer_state.next_ticks_processing = batch; 661 + 605 662 return batch; 606 663 } 607 664 ··· 614 671 free(entry); 615 672 }} 616 673 674 + static inline void process_next_tick_batch(ant_t *js, microtask_entry_t *batch) { 675 + while (batch != NULL) { 676 + microtask_entry_t *entry = batch; 677 + batch = entry->next; 678 + timer_state.next_ticks_processing = batch; 679 + process_microtask_entry(js, entry); 680 + free(entry); 681 + }} 682 + 617 683 static void process_microtasks_internal(ant_t *js, bool check_unhandled_rejections) { 618 684 microtask_entry_t *batch = NULL; 619 685 620 686 if (!js || js->microtasks_draining) return; 621 687 js->microtasks_draining = true; 622 688 689 + while (timer_state.next_ticks != NULL || timer_state.microtasks != NULL) { 690 + while ((batch = timer_state.next_ticks) != NULL) { 691 + batch = take_next_tick_batch(); 692 + process_next_tick_batch(js, batch); 693 + } 623 694 while ((batch = timer_state.microtasks) != NULL) { 624 695 batch = take_microtask_batch(); 625 696 process_microtask_batch(js, batch); 626 - } 697 + }} 627 698 699 + timer_state.next_ticks_processing = NULL; 628 700 timer_state.microtasks_processing = NULL; 629 701 if (check_unhandled_rejections) js_check_unhandled_rejections(js); 630 702 js->microtasks_draining = false; 703 + reap_retired_coroutines(); 631 704 } 632 705 633 706 void process_microtasks(ant_t *js) { ··· 684 757 } 685 758 686 759 int has_pending_microtasks(void) { 687 - return timer_state.microtasks != NULL ? 1 : 0; 760 + return (timer_state.next_ticks != NULL || timer_state.microtasks != NULL) ? 1 : 0; 688 761 } 689 762 690 763 static void timers_define_common(ant_t *js, ant_value_t obj) { ··· 736 809 for (microtask_entry_t *m = timer_state.microtasks; m; m = m->next) { 737 810 mark(js, m->callback); 738 811 mark(js, m->promise); 812 + for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]); 739 813 } 740 814 for (microtask_entry_t *m = timer_state.microtasks_processing; m; m = m->next) { 741 815 mark(js, m->callback); 742 816 mark(js, m->promise); 817 + for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]); 818 + } 819 + for (microtask_entry_t *m = timer_state.next_ticks; m; m = m->next) { 820 + mark(js, m->callback); 821 + mark(js, m->promise); 822 + for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]); 823 + } 824 + for (microtask_entry_t *m = timer_state.next_ticks_processing; m; m = m->next) { 825 + mark(js, m->callback); 826 + mark(js, m->promise); 827 + for (uint8_t i = 0; i < m->argc; i++) mark(js, m->argv[i]); 743 828 } 744 829 for (immediate_entry_t *i = timer_state.immediates; i; i = i->next) { 745 830 mark(js, i->callback);
+206 -37
src/modules/tty.c
··· 38 38 #include "silver/engine.h" 39 39 40 40 #include "modules/stream.h" 41 + #include "modules/buffer.h" 42 + #include "modules/events.h" 41 43 #include "modules/symbol.h" 42 44 #include "modules/tty.h" 43 45 ··· 46 48 static ant_value_t g_tty_writestream_proto = 0; 47 49 static ant_value_t g_tty_writestream_ctor = 0; 48 50 51 + typedef struct tty_read_stream_state { 52 + ant_t *js; 53 + ant_value_t stream_obj; 54 + uv_tty_t tty; 55 + 56 + int fd; 57 + bool initialized; 58 + bool reading; 59 + bool closing; 60 + } tty_read_stream_state_t; 61 + 62 + static void invoke_callback_if_needed(ant_t *js, ant_value_t cb, ant_value_t arg) { 63 + if (!is_callable(cb)) return; 64 + ant_value_t cb_args[1] = { arg }; 65 + sv_vm_call(js->vm, js, cb, js_mkundef(), cb_args, 1, NULL, false); 66 + } 67 + 49 68 static bool parse_fd(ant_value_t value, int *fd_out) { 50 69 int fd = 0; 51 70 if (!tty_ctrl_parse_int_value(value, &fd)) return false; ··· 69 88 return fd; 70 89 } 71 90 91 + static tty_read_stream_state_t *tty_read_stream_state_from_obj(ant_value_t stream_obj) { 92 + return (tty_read_stream_state_t *)stream_get_attached_state(stream_obj); 93 + } 94 + 95 + static ant_value_t make_stream_error(ant_t *js, const char *op, int fd, int uv_code) { 96 + if (uv_code != 0) return js_mkerr_typed( 97 + js, JS_ERR_GENERIC, 98 + "tty stream %s failed for fd %d: %s", 99 + op, fd, uv_strerror(uv_code) 100 + ); 101 + 102 + return js_mkerr_typed(js, JS_ERR_GENERIC, "tty stream %s failed for fd %d", op, fd); 103 + } 104 + 105 + static void tty_read_stream_emit_error(tty_read_stream_state_t *state, const char *op, int uv_code) { 106 + ant_value_t err = 0; 107 + if (!state || !state->js || !is_object_type(state->stream_obj)) return; 108 + err = make_stream_error(state->js, op, state->fd, uv_code); 109 + eventemitter_emit_args(state->js, state->stream_obj, "error", &err, 1); 110 + } 111 + 112 + static void tty_read_stream_push_chunk(tty_read_stream_state_t *state, const char *data, size_t len) { 113 + ArrayBufferData *ab = NULL; 114 + ant_value_t chunk = 0; 115 + 116 + if (!state || !state->js || !data || len == 0) return; 117 + ab = create_array_buffer_data(len); 118 + if (ab) memcpy(ab->data, data, len); 119 + 120 + chunk = ab 121 + ? create_typed_array(state->js, TYPED_ARRAY_UINT8, ab, 0, len, "Buffer") 122 + : js_mkstr(state->js, data, len); 123 + (void)stream_readable_push(state->js, state->stream_obj, chunk, js_mkundef()); 124 + } 125 + 126 + static void tty_read_stream_alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { 127 + buf->base = malloc(suggested_size); 128 + #ifdef _WIN32 129 + buf->len = (ULONG)suggested_size; 130 + #else 131 + buf->len = suggested_size; 132 + #endif 133 + } 134 + 135 + static void tty_read_stream_stop(tty_read_stream_state_t *state) { 136 + if (!state || !state->initialized || !state->reading) return; 137 + uv_read_stop((uv_stream_t *)&state->tty); 138 + state->reading = false; 139 + } 140 + 141 + static void tty_read_stream_close_cb(uv_handle_t *handle) { 142 + tty_read_stream_state_t *state = (tty_read_stream_state_t *)handle->data; 143 + free(state); 144 + } 145 + 146 + static void tty_read_stream_finalize(ant_t *js, ant_value_t stream_obj, void *data) { 147 + tty_read_stream_state_t *state = (tty_read_stream_state_t *)data; 148 + 149 + if (!state) return; 150 + tty_read_stream_stop(state); 151 + 152 + if (state->initialized && !state->closing) { 153 + state->closing = true; 154 + uv_close((uv_handle_t *)&state->tty, tty_read_stream_close_cb); 155 + return; 156 + } 157 + 158 + if (!state->initialized) free(state); 159 + } 160 + 161 + static void tty_read_stream_on_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { 162 + tty_read_stream_state_t *state = (tty_read_stream_state_t *)stream->data; 163 + 164 + if (!state || !state->js) goto cleanup; 165 + 166 + if (nread > 0) { 167 + tty_read_stream_push_chunk(state, buf->base, (size_t)nread); 168 + goto cleanup; 169 + } 170 + 171 + if (nread == UV_EOF) { 172 + tty_read_stream_stop(state); 173 + (void)stream_readable_push(state->js, state->stream_obj, js_mknull(), js_mkundef()); 174 + goto cleanup; 175 + } 176 + 177 + if (nread < 0) { 178 + tty_read_stream_stop(state); 179 + tty_read_stream_emit_error(state, "read", (int)nread); 180 + } 181 + 182 + cleanup: 183 + if (buf->base) free(buf->base); 184 + } 185 + 186 + static ant_value_t tty_readstream__read(ant_t *js, ant_value_t *args, int nargs) { 187 + ant_value_t stream_obj = js_getthis(js); 188 + tty_read_stream_state_t *state = tty_read_stream_state_from_obj(stream_obj); 189 + int rc = 0; 190 + 191 + if (!state) return js_mkundef(); 192 + if (state->closing || js_truthy(js, js_get(js, stream_obj, "destroyed"))) return js_mkundef(); 193 + 194 + if (!state->initialized) { 195 + rc = uv_tty_init(uv_default_loop(), &state->tty, state->fd, 0); 196 + if (rc != 0) { 197 + tty_read_stream_emit_error(state, "open", rc); 198 + return js_mkundef(); 199 + } 200 + #ifndef _WIN32 201 + uv_tty_set_mode(&state->tty, tty_is_raw_mode(state->fd) ? UV_TTY_MODE_RAW : UV_TTY_MODE_NORMAL); 202 + #endif 203 + state->tty.data = state; 204 + state->initialized = true; 205 + } 206 + 207 + if (state->reading) return js_mkundef(); 208 + rc = uv_read_start((uv_stream_t *)&state->tty, tty_read_stream_alloc_buffer, tty_read_stream_on_read); 209 + 210 + if (rc != 0) { 211 + tty_read_stream_emit_error(state, "read", rc); 212 + return js_mkundef(); 213 + } 214 + 215 + state->reading = true; 216 + return js_mkundef(); 217 + } 218 + 219 + static ant_value_t tty_readstream__destroy(ant_t *js, ant_value_t *args, int nargs) { 220 + ant_value_t stream_obj = js_getthis(js); 221 + tty_read_stream_state_t *state = tty_read_stream_state_from_obj(stream_obj); 222 + ant_value_t cb = nargs > 1 ? args[1] : js_mkundef(); 223 + 224 + if (!state) { 225 + invoke_callback_if_needed(js, cb, js_mknull()); 226 + return js_mkundef(); 227 + } 228 + 229 + tty_read_stream_stop(state); 230 + stream_clear_attached_state(stream_obj); 231 + 232 + if (state->initialized && !state->closing) { 233 + state->closing = true; 234 + uv_close((uv_handle_t *)&state->tty, tty_read_stream_close_cb); 235 + } else if (!state->initialized) free(state); 236 + 237 + invoke_callback_if_needed(js, cb, js_mknull()); 238 + return js_mkundef(); 239 + } 240 + 72 241 static void get_tty_size(int fd, int *rows, int *cols) { 73 242 int out_rows = 24; 74 243 int out_cols = 80; ··· 229 398 } 230 399 } 231 400 232 - static ant_value_t make_stream_error(ant_t *js, const char *op, int fd) { 233 - return js_mkerr_typed(js, JS_ERR_GENERIC, "tty stream %s failed for fd %d", op, fd); 234 - } 235 - 236 - static void invoke_callback_if_needed(ant_t *js, ant_value_t cb, ant_value_t arg) { 237 - if (!is_callable(cb)) return; 238 - ant_value_t cb_args[1] = { arg }; 239 - sv_vm_call(js->vm, js, cb, js_mkundef(), cb_args, 1, NULL, false); 240 - } 241 - 242 401 static ant_value_t maybe_callback_or_throw( 243 - ant_t *js, 244 - ant_value_t this_obj, 245 - ant_value_t cb, 246 - bool ok, 247 - const char *op, 248 - int fd 402 + ant_t *js, ant_value_t this_obj, 403 + ant_value_t cb, bool ok, 404 + const char *op, int fd 249 405 ) { 250 406 if (is_callable(cb)) { 251 - if (ok) { 252 - invoke_callback_if_needed(js, cb, js_mknull()); 253 - } else { 254 - ant_value_t err = make_stream_error(js, op, fd); 407 + if (ok) invoke_callback_if_needed(js, cb, js_mknull()); 408 + else { 409 + ant_value_t err = make_stream_error(js, op, fd, 0); 255 410 invoke_callback_if_needed(js, cb, err); 256 411 } 257 412 return this_obj; 258 413 } 259 - if (!ok) return make_stream_error(js, op, fd); 414 + if (!ok) return make_stream_error(js, op, fd, 0); 260 415 return this_obj; 261 416 } 262 417 ··· 368 523 } 369 524 370 525 static ant_value_t tty_stream_write(ant_t *js, ant_value_t *args, int nargs) { 371 - ant_value_t this_obj = js_getthis(js); 372 526 if (nargs < 1) return js_false; 373 527 374 528 size_t len = 0; ··· 383 537 384 538 if (is_callable(cb)) { 385 539 if (ok) invoke_callback_if_needed(js, cb, js_mknull()); 386 - else invoke_callback_if_needed(js, cb, make_stream_error(js, "write", fd)); 540 + else invoke_callback_if_needed(js, cb, make_stream_error(js, "write", fd, 0)); 387 541 } 388 542 389 543 if (!ok) return js_false; 390 - (void)this_obj; 391 544 return js_true; 392 545 } 393 546 394 547 static ant_value_t tty_write_stream_rows_getter(ant_t *js, ant_value_t *args, int nargs) { 395 - (void)args; (void)nargs; 396 - 397 548 int fd = stream_fd_from_this(js, ANT_STDOUT_FD); 398 549 if (!is_tty_fd(fd)) return js_mkundef(); 399 550 ··· 404 555 } 405 556 406 557 static ant_value_t tty_write_stream_columns_getter(ant_t *js, ant_value_t *args, int nargs) { 407 - (void)args; (void)nargs; 408 - 409 558 int fd = stream_fd_from_this(js, ANT_STDOUT_FD); 410 559 if (!is_tty_fd(fd)) return js_mkundef(); 411 560 ··· 416 565 } 417 566 418 567 static ant_value_t tty_write_stream_get_window_size(ant_t *js, ant_value_t *args, int nargs) { 419 - (void)args; (void)nargs; 420 - 421 568 int fd = stream_fd_from_this(js, ANT_STDOUT_FD); 422 569 int rows = 0; 423 570 int cols = 0; ··· 442 589 443 590 int fd = stream_fd_from_this(js, ANT_STDOUT_FD); 444 591 size_t seq_len = 0; 592 + 445 593 const char *seq = tty_ctrl_clear_line_seq(dir, &seq_len); 446 594 bool ok = tty_ctrl_write_fd(fd, seq, seq_len); 595 + 447 596 return maybe_callback_or_throw(js, this_obj, cb, ok, "clearLine", fd); 448 597 } 449 598 ··· 455 604 456 605 int fd = stream_fd_from_this(js, ANT_STDOUT_FD); 457 606 size_t seq_len = 0; 607 + 458 608 const char *seq = tty_ctrl_clear_screen_down_seq(&seq_len); 459 609 bool ok = tty_ctrl_write_fd(fd, seq, seq_len); 610 + 460 611 return maybe_callback_or_throw(js, this_obj, cb, ok, "clearScreenDown", fd); 461 612 } 462 613 463 614 static ant_value_t tty_write_stream_cursor_to(ant_t *js, ant_value_t *args, int nargs) { 464 615 ant_value_t this_obj = js_getthis(js); 465 616 int x = 0; 617 + 466 618 if (nargs < 1 || !tty_ctrl_parse_int_value(args[0], &x)) { 467 619 return js_mkerr_typed(js, JS_ERR_TYPE, "cursorTo(x[, y][, callback]) requires numeric x"); 468 620 } ··· 575 727 576 728 static ant_value_t tty_read_stream_set_raw_mode(ant_t *js, ant_value_t *args, int nargs) { 577 729 ant_value_t this_obj = js_getthis(js); 730 + tty_read_stream_state_t *state = tty_read_stream_state_from_obj(this_obj); 578 731 if (!is_special_object(this_obj)) { 579 732 return js_mkerr_typed(js, JS_ERR_TYPE, "setRawMode() requires a ReadStream receiver"); 580 733 } ··· 584 737 if (!tty_set_raw_mode(fd, enable)) { 585 738 return js_mkerr_typed(js, JS_ERR_GENERIC, "Failed to set raw mode for fd %d", fd); 586 739 } 740 + #ifndef _WIN32 741 + if (state && state->initialized && !state->closing) { 742 + uv_tty_set_mode(&state->tty, enable ? UV_TTY_MODE_RAW : UV_TTY_MODE_NORMAL); 743 + } 744 + #endif 587 745 js_set(js, this_obj, "isRaw", js_bool(enable)); 588 746 return this_obj; 589 747 } 590 748 591 749 static ant_value_t tty_read_stream_constructor(ant_t *js, ant_value_t *args, int nargs) { 750 + tty_read_stream_state_t *state = NULL; 592 751 if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "ReadStream(fd) requires a file descriptor"); 593 752 594 753 int fd = 0; ··· 600 759 } 601 760 602 761 if (fd == ANT_STDIN_FD) { 603 - ant_value_t stdin_obj = get_process_stream(js, "stdin"); 604 - if (is_special_object(stdin_obj)) { 605 - ensure_stream_common_props(js, stdin_obj, fd); 606 - if (vtype(js_get(js, stdin_obj, "isRaw")) == T_UNDEF) js_set(js, stdin_obj, "isRaw", js_false); 607 - return stdin_obj; 608 - } 609 - } 762 + ant_value_t stdin_obj = get_process_stream(js, "stdin"); 763 + if (is_special_object(stdin_obj)) { 764 + ensure_stream_common_props(js, stdin_obj, fd); 765 + if (vtype(js_get(js, stdin_obj, "isRaw")) == T_UNDEF) js_set(js, stdin_obj, "isRaw", js_false); 766 + return stdin_obj; 767 + }} 610 768 611 769 ant_value_t obj = stream_construct_readable(js, g_tty_readstream_proto, js_mkundef()); 612 770 if (is_err(obj)) return obj; 613 771 772 + state = calloc(1, sizeof(*state)); 773 + if (!state) return js_mkerr(js, "Out of memory"); 774 + state->js = js; 775 + state->stream_obj = obj; 776 + state->fd = fd; 777 + 614 778 ensure_stream_common_props(js, obj, fd); 779 + stream_set_attached_state(obj, state, tty_read_stream_finalize); 780 + 615 781 js_set(js, obj, "isRaw", js_false); 782 + js_set(js, obj, "_read", js_mkfun(tty_readstream__read)); 783 + js_set(js, obj, "_destroy", js_mkfun(tty_readstream__destroy)); 784 + 616 785 return obj; 617 786 } 618 787
+9 -2
src/modules/util.c
··· 1269 1269 if (nargs < 1 || !is_callable(args[0])) { 1270 1270 return js_mkerr(js, "promisify(fn) requires a function"); 1271 1271 } 1272 + 1273 + ant_value_t custom = js_get_symbol(js, args[0], "nodejs.util.promisify.custom"); 1274 + if (is_callable(custom)) return custom; 1275 + 1272 1276 return js_heavy_mkfun(js, util_promisified_call, args[0]); 1273 1277 } 1274 1278 ··· 1324 1328 return js_bool(js_deep_equal(js, args[0], args[1], true)); 1325 1329 } 1326 1330 1327 - ant_value_t util_library(ant_t *js) { 1331 + ant_value_t util_library(ant_t *js) { 1328 1332 ant_value_t lib = js_mkobj(js); 1329 1333 ant_value_t types = util_get_types_object(js); 1334 + 1335 + ant_value_t promisify = js_heavy_mkfun(js, util_promisify, js_mkundef()); 1336 + js_set(js, promisify, "custom", js_mksym_for(js, "nodejs.util.promisify.custom")); 1330 1337 1331 1338 js_set(js, lib, "format", js_mkfun(util_format)); 1332 1339 js_set(js, lib, "formatWithOptions", js_mkfun(util_format_with_options)); ··· 1337 1344 js_set(js, lib, "isDeepStrictEqual", js_mkfun(util_is_deep_strict_equal)); 1338 1345 js_set(js, lib, "parseArgs", js_mkfun(util_parse_args)); 1339 1346 js_set(js, lib, "parseEnv", js_mkfun(util_parse_env)); 1340 - js_set(js, lib, "promisify", js_mkfun(util_promisify)); 1347 + js_set(js, lib, "promisify", promisify); 1341 1348 js_set(js, lib, "callbackify", js_mkfun(util_callbackify)); 1342 1349 js_set(js, lib, "aborted", js_mkfun(util_aborted)); 1343 1350 js_set(js, lib, "stripVTControlCharacters", js_mkfun(util_strip_vt_control_characters));
+2
src/reactor.c
··· 72 72 drain: 73 73 while (event_loop_alive()) { 74 74 js_poll_events(js); 75 + reap_retired_coroutines(); 75 76 work_flags_t work = get_pending_work(); 76 77 77 78 if (work & WORK_BLOCKING) uv_run(uv_default_loop(), UV_RUN_NOWAIT); ··· 81 82 } 82 83 83 84 js_poll_events(js); 85 + reap_retired_coroutines(); 84 86 ant_value_t code = js_mknum(0); 85 87 emit_process_event("beforeExit", &code, 1); 86 88
+19 -18
src/silver/ops/async.h
··· 68 68 ant_value_t promise = ctx->coro->async_promise; 69 69 70 70 if (is_err(result)) { 71 - ant_value_t reject_value = js->thrown_value; 72 - if (vtype(reject_value) == T_UNDEF) reject_value = result; 71 + ant_value_t reject_value = js->thrown_exists ? js->thrown_value : result; 73 72 js->thrown_exists = false; 74 73 js->thrown_value = js_mkundef(); 75 74 js_reject_promise(js, promise, reject_value); ··· 98 97 ant_value_t promise = ctx->coro->async_promise; 99 98 100 99 if (is_err(result)) { 101 - ant_value_t reject_value = js->thrown_value; 102 - if (vtype(reject_value) == T_UNDEF) reject_value = result; 100 + ant_value_t reject_value = js->thrown_exists ? js->thrown_value : result; 103 101 js->thrown_exists = false; 104 102 js->thrown_value = js_mkundef(); 105 103 js_reject_promise(js, promise, reject_value); ··· 180 178 } 181 179 182 180 if (is_err(result)) { 183 - ant_value_t reject_value = js->thrown_value; 184 - if (vtype(reject_value) == T_UNDEF) reject_value = result; 181 + ant_value_t reject_value = js->thrown_exists ? js->thrown_value : result; 185 182 js->thrown_exists = false; 186 183 js->thrown_value = js_mkundef(); 184 + js->active_async_coro = coro->active_parent; 185 + coro->active_parent = NULL; 187 186 js_reject_promise(js, promise, reject_value); 188 - } else js_resolve_promise(js, promise, result); 189 - 190 - js->active_async_coro = coro->active_parent; 191 - coro->active_parent = NULL; 187 + } else { 188 + js->active_async_coro = coro->active_parent; 189 + coro->active_parent = NULL; 190 + js_resolve_promise(js, promise, result); 191 + } 192 192 193 193 free_coroutine(coro); 194 194 GC_ROOT_RESTORE(js, root_mark); ··· 306 306 ); 307 307 308 308 if (is_err(result)) { 309 - ant_value_t reject_value = js->thrown_value; 310 - if (vtype(reject_value) == T_UNDEF) reject_value = result; 309 + ant_value_t reject_value = js->thrown_exists ? js->thrown_value : result; 311 310 js->thrown_exists = false; 312 311 js->thrown_value = js_mkundef(); 313 312 js_reject_promise(js, promise, reject_value); ··· 385 384 } 386 385 387 386 if (is_err(result)) { 388 - ant_value_t reject_value = js->thrown_value; 389 - if (vtype(reject_value) == T_UNDEF) reject_value = result; 387 + ant_value_t reject_value = js->thrown_exists ? js->thrown_value : result; 390 388 js->thrown_exists = false; 391 389 js->thrown_value = js_mkundef(); 390 + js->active_async_coro = coro->active_parent; 391 + coro->active_parent = NULL; 392 392 js_reject_promise(js, promise, reject_value); 393 - } else js_resolve_promise(js, promise, result); 394 - 395 - js->active_async_coro = coro->active_parent; 396 - coro->active_parent = NULL; 393 + } else { 394 + js->active_async_coro = coro->active_parent; 395 + coro->active_parent = NULL; 396 + js_resolve_promise(js, promise, result); 397 + } 397 398 398 399 free_coroutine(coro); 399 400 GC_ROOT_RESTORE(js, root_mark);
+73 -1
src/silver/swarm.c
··· 515 515 MIR_new_uint_op(ctx, NANBOX_DATA_MASK))); 516 516 } 517 517 518 + static void mir_emit_resolve_call_this(MIR_context_t ctx, MIR_item_t fn, 519 + MIR_reg_t dst, MIR_reg_t r_closure, 520 + MIR_reg_t fallback_this, 521 + MIR_reg_t r_flags, MIR_reg_t r_bound) { 522 + MIR_label_t not_arrow = MIR_new_label(ctx); 523 + MIR_label_t done = MIR_new_label(ctx); 524 + 525 + MIR_append_insn(ctx, fn, 526 + MIR_new_insn(ctx, MIR_MOV, 527 + MIR_new_reg_op(ctx, r_flags), 528 + MIR_new_mem_op(ctx, MIR_T_U32, 529 + (MIR_disp_t)offsetof(sv_closure_t, call_flags), 530 + r_closure, 0, 1))); 531 + MIR_append_insn(ctx, fn, 532 + MIR_new_insn(ctx, MIR_AND, 533 + MIR_new_reg_op(ctx, r_flags), 534 + MIR_new_reg_op(ctx, r_flags), 535 + MIR_new_uint_op(ctx, SV_CALL_IS_ARROW))); 536 + MIR_append_insn(ctx, fn, 537 + MIR_new_insn(ctx, MIR_BEQ, 538 + MIR_new_label_op(ctx, not_arrow), 539 + MIR_new_reg_op(ctx, r_flags), 540 + MIR_new_uint_op(ctx, 0))); 541 + MIR_append_insn(ctx, fn, 542 + MIR_new_insn(ctx, MIR_MOV, 543 + MIR_new_reg_op(ctx, dst), 544 + MIR_new_mem_op(ctx, MIR_JSVAL, 545 + (MIR_disp_t)offsetof(sv_closure_t, bound_this), 546 + r_closure, 0, 1))); 547 + MIR_append_insn(ctx, fn, 548 + MIR_new_insn(ctx, MIR_JMP, MIR_new_label_op(ctx, done))); 549 + 550 + MIR_append_insn(ctx, fn, not_arrow); 551 + MIR_append_insn(ctx, fn, 552 + MIR_new_insn(ctx, MIR_MOV, 553 + MIR_new_reg_op(ctx, dst), 554 + MIR_new_reg_op(ctx, fallback_this))); 555 + MIR_append_insn(ctx, fn, 556 + MIR_new_insn(ctx, MIR_MOV, 557 + MIR_new_reg_op(ctx, r_bound), 558 + MIR_new_mem_op(ctx, MIR_JSVAL, 559 + (MIR_disp_t)offsetof(sv_closure_t, bound_this), 560 + r_closure, 0, 1))); 561 + MIR_append_insn(ctx, fn, 562 + MIR_new_insn(ctx, MIR_BEQ, 563 + MIR_new_label_op(ctx, done), 564 + MIR_new_reg_op(ctx, r_bound), 565 + MIR_new_uint_op(ctx, mkval(T_UNDEF, 0)))); 566 + MIR_append_insn(ctx, fn, 567 + MIR_new_insn(ctx, MIR_MOV, 568 + MIR_new_reg_op(ctx, dst), 569 + MIR_new_reg_op(ctx, r_bound))); 570 + MIR_append_insn(ctx, fn, done); 571 + } 572 + 518 573 static void mir_emit_value_to_objptr_or_jmp( 519 574 MIR_context_t ctx, MIR_item_t fn, 520 575 MIR_reg_t v, MIR_reg_t out_ptr, ··· 4526 4581 MIR_label_t inl_join = MIR_new_label(ctx); 4527 4582 4528 4583 MIR_reg_t r_inl_cl = 0; 4584 + char inl_this_rn[32], inl_flags_rn[32], inl_bound_rn[32]; 4585 + snprintf(inl_this_rn, sizeof(inl_this_rn), "inl%d_this", cn); 4586 + snprintf(inl_flags_rn, sizeof(inl_flags_rn), "inl%d_flags", cn); 4587 + snprintf(inl_bound_rn, sizeof(inl_bound_rn), "inl%d_bound", cn); 4588 + MIR_reg_t r_inl_this = MIR_new_func_reg(ctx, jit_func->u.func, 4589 + MIR_JSVAL, inl_this_rn); 4590 + MIR_reg_t r_inl_flags = MIR_new_func_reg(ctx, jit_func->u.func, 4591 + MIR_T_I64, inl_flags_rn); 4592 + MIR_reg_t r_inl_bound = MIR_new_func_reg(ctx, jit_func->u.func, 4593 + MIR_JSVAL, inl_bound_rn); 4529 4594 { 4530 4595 char cl_rn[32]; snprintf(cl_rn, sizeof(cl_rn), "inl%d_cl", cn); 4531 4596 r_inl_cl = MIR_new_func_reg(ctx, jit_func->u.func, MIR_T_I64, cl_rn); ··· 4567 4632 MIR_new_uint_op(ctx, (uint64_t)(uintptr_t)inline_callee))); 4568 4633 } 4569 4634 4635 + mir_emit_resolve_call_this(ctx, jit_func, r_inl_this, r_inl_cl, 4636 + r_this, r_inl_flags, r_inl_bound); 4637 + 4570 4638 bool inlined = jit_emit_inline_body( 4571 4639 ctx, jit_func, inline_callee, 4572 4640 inl_arg_regs, (int)call_argc, 4573 4641 r_call_res, inl_slow, inl_join, 4574 4642 r_bool, &r_d_slot, cn, 4575 - r_inl_cl, r_this, 4643 + r_inl_cl, r_inl_this, 4576 4644 r_vm, r_js, 4577 4645 helper2_proto, imp_seq, imp_sne, imp_eq, imp_ne, 4578 4646 gf_proto, imp_get_field, ··· 4729 4797 MIR_new_mem_op(ctx, MIR_T_P, 4730 4798 (MIR_disp_t)offsetof(sv_closure_t, func), 4731 4799 r_callee_cl, 0, 1))); 4800 + mir_emit_resolve_call_this(ctx, jit_func, r_call_this, r_callee_cl, 4801 + r_call_this, r_bool, r_tmp2); 4732 4802 4733 4803 MIR_append_insn(ctx, jit_func, 4734 4804 MIR_new_insn(ctx, MIR_BEQ, ··· 7054 7124 MIR_new_mem_op(ctx, MIR_T_P, 7055 7125 (MIR_disp_t)offsetof(sv_closure_t, func), 7056 7126 r_callee_cl, 0, 1))); 7127 + mir_emit_resolve_call_this(ctx, jit_func, r_call_this, r_callee_cl, 7128 + r_call_this, r_bool, r_tmp2); 7057 7129 7058 7130 MIR_append_insn(ctx, jit_func, 7059 7131 MIR_new_insn(ctx, MIR_BEQ,
+89 -13
src/sugar.c
··· 1 1 #include "internal.h" 2 2 #include "sugar.h" 3 + 4 + #include "modules/generator.h" 3 5 #include "modules/timer.h" 4 6 #include "silver/engine.h" 5 - #include "silver/vm.h" 6 7 7 8 #define MCO_USE_VMEM_ALLOCATOR 8 9 #define MCO_ZERO_MEMORY ··· 12 13 13 14 uint32_t coros_this_tick = 0; 14 15 coroutine_queue_t pending_coroutines = {NULL, NULL}; 16 + 15 17 static bool coro_stack_size_initialized = false; 18 + static coroutine_t *retired_coroutines = NULL; 16 19 17 20 bool has_pending_coroutines(void) { 18 21 return pending_coroutines.head != NULL; ··· 53 56 coro->next = NULL; 54 57 } 55 58 59 + static void clear_await_coro_from_promise_state(ant_promise_state_t *pd, coroutine_t *coro) { 60 + if (!pd || !coro || pd->handler_count == 0) return; 61 + 62 + if (pd->handler_count == 1) { 63 + if (pd->inline_handler.await_coro == coro) pd->inline_handler.await_coro = NULL; 64 + return; 65 + } 66 + 67 + if (!pd->handlers) return; 68 + promise_handler_t *h = NULL; 69 + 70 + while ((h = (promise_handler_t *)utarray_next(pd->handlers, h))) 71 + if (h->await_coro == coro) h->await_coro = NULL; 72 + } 73 + 74 + static void clear_await_coro_from_object_list(ant_object_t *head, coroutine_t *coro) { 75 + for (ant_object_t *obj = head; obj; obj = obj->next) 76 + if (obj->promise_state) clear_await_coro_from_promise_state(obj->promise_state, coro); 77 + } 78 + 79 + static inline bool coroutine_is_queued(coroutine_t *coro) { 80 + return coro && ( 81 + coro->prev || coro->next || 82 + pending_coroutines.head == coro || 83 + pending_coroutines.tail == coro 84 + ); 85 + } 86 + 87 + static void retire_coroutine_storage(coroutine_t *coro) { 88 + if (!coro) return; 89 + coro->next = retired_coroutines; 90 + coro->prev = NULL; 91 + retired_coroutines = coro; 92 + } 93 + 94 + void reap_retired_coroutines(void) { 95 + coroutine_t *coro = retired_coroutines; 96 + retired_coroutines = NULL; 97 + while (coro) { 98 + coroutine_t *next = coro->next; 99 + CORO_FREE(coro); 100 + coro = next; 101 + } 102 + } 103 + 56 104 void free_coroutine(coroutine_t *coro) { 57 - if (!coro) return; 105 + if (!coro || coro->free_pending) return; 106 + coro->free_pending = true; 107 + 108 + ant_t *js = coro->js; 109 + if (js) { 110 + clear_await_coro_from_object_list(js->objects, coro); 111 + clear_await_coro_from_object_list(js->objects_old, coro); 112 + clear_await_coro_from_object_list(js->permanent_objects, coro); 113 + 114 + if ( 115 + coro->prev || coro->next || 116 + pending_coroutines.head == coro || 117 + pending_coroutines.tail == coro 118 + ) remove_coroutine(coro); 119 + 120 + if (js->active_async_coro == coro) js->active_async_coro = coro->active_parent; 121 + coro->active_parent = NULL; 122 + } 58 123 59 124 if (coro->mco) { 60 125 void *ctx = mco_get_user_data(coro->mco); ··· 63 128 coro->mco = NULL; 64 129 } 65 130 66 - if (coro->args) CORO_FREE(coro->args); 67 - if (coro->sv_vm) { sv_vm_destroy(coro->sv_vm); coro->sv_vm = NULL; } 131 + if (coro->args) { 132 + CORO_FREE(coro->args); 133 + coro->args = NULL; 134 + } 135 + 136 + if (coro->sv_vm) { 137 + sv_vm_destroy(coro->sv_vm); 138 + coro->sv_vm = NULL; 139 + } 68 140 69 - CORO_FREE(coro); 141 + coro->js = NULL; 142 + coro->active_parent = NULL; 143 + retire_coroutine_storage(coro); 70 144 } 71 145 72 146 static size_t calculate_coro_stack_size(void) { ··· 109 183 ant_value_t result = sv_resume_suspended(coro->sv_vm); 110 184 111 185 coro->is_settled = false; 112 - coro->awaited_promise = js_mkundef(); 113 - 114 186 if (coro->sv_vm->suspended) { 115 187 js->active_async_coro = coro->active_parent; 116 188 coro->active_parent = NULL; 189 + if (generator_resume_pending_request(js, coro, result)) return; 117 190 return; 118 - } remove_coroutine(coro); 191 + } 192 + 193 + js->active_async_coro = coro->active_parent; 194 + coro->active_parent = NULL; 195 + 196 + if (generator_resume_pending_request(js, coro, result)) return; 197 + if (coroutine_is_queued(coro)) remove_coroutine(coro); 119 198 120 199 if (is_err(result)) { 121 - ant_value_t reject_value = js->thrown_value; 122 - if (vtype(reject_value) == T_UNDEF) reject_value = result; 200 + ant_value_t reject_value = js->thrown_exists ? js->thrown_value : result; 123 201 js->thrown_exists = false; 124 202 js->thrown_value = js_mkundef(); 125 203 js_reject_promise(js, coro->async_promise, reject_value); 126 204 } else js_resolve_promise(js, coro->async_promise, result); 127 205 128 206 js_maybe_drain_microtasks_after_async_settle(js); 129 - js->active_async_coro = coro->active_parent; 130 - 131 - coro->active_parent = NULL; 132 207 free_coroutine(coro); 133 208 134 209 return; ··· 180 255 if (!coro) return; 181 256 ant_value_t args[1] = { value }; 182 257 settle_coroutine(coro, args, 1, is_error); 258 + coro->awaited_promise = js_mkundef(); 183 259 resume_coroutine_if_suspended(js, coro); 184 260 }
+12
src/types/modules/child_process.d.ts
··· 36 36 cwd?: string; 37 37 } 38 38 39 + interface ExecFileResult { 40 + stdout: string; 41 + stderr: string; 42 + } 43 + 39 44 interface SpawnSyncOptions { 40 45 input?: string; 41 46 } ··· 46 51 47 52 function spawn(command: string, args?: string[], options?: SpawnOptions): ChildProcess & Promise<SpawnResult>; 48 53 function exec(command: string, options?: ExecOptions): Promise<SpawnResult>; 54 + function execFile(file: string, args?: string[], options?: ExecOptions): ChildProcess; 55 + function execFile( 56 + file: string, 57 + args: string[] | undefined, 58 + options: ExecOptions | undefined, 59 + callback: (err: Error | null, stdout: string, stderr: string) => void 60 + ): ChildProcess; 49 61 function execSync(command: string): string; 50 62 function spawnSync(command: string, args?: string[], options?: SpawnSyncOptions): SpawnResult; 51 63 function fork(modulePath: string, options?: ForkOptions): ChildProcess & Promise<SpawnResult>;
+1
src/types/modules/events.d.ts
··· 16 16 emit(event: string, ...args: unknown[]): boolean; 17 17 removeAllListeners(event?: string): this; 18 18 listenerCount(event: string): number; 19 + listeners(event: string): Array<(...args: unknown[]) => void>; 19 20 eventNames(): string[]; 20 21 } 21 22
+21 -1
src/types/modules/fs.d.ts
··· 199 199 } 200 200 201 201 declare module 'fs/promises' { 202 + interface FileHandle { 203 + readonly fd: number; 204 + close(): Promise<void>; 205 + stat(): Promise<Stats>; 206 + sync(): Promise<void>; 207 + read( 208 + buffer: ArrayBufferView, 209 + offset?: number, 210 + length?: number, 211 + position?: number | null 212 + ): Promise<{ bytesRead: number; buffer: ArrayBufferView }>; 213 + write( 214 + data: string | ArrayBufferView, 215 + offsetOrOptions?: number | { offset?: number; length?: number; position?: number | null }, 216 + length?: number, 217 + position?: number | null 218 + ): Promise<{ bytesWritten: number; buffer: string | ArrayBufferView }>; 219 + writeFile(data: string | ArrayBufferView): Promise<void>; 220 + } 221 + 202 222 interface Stats { 203 223 size: number; 204 224 mode: number; ··· 227 247 228 248 function readFile(path: string, encoding: Encoding): Promise<string>; 229 249 function readFile(path: string): Promise<Uint8Array>; 230 - function open(path: string, flags?: string, mode?: number): Promise<number>; 250 + function open(path: string, flags?: string, mode?: number): Promise<FileHandle>; 231 251 function close(fd: number): Promise<void>; 232 252 function writeFile(path: string, data: string | ArrayBufferView): Promise<void>; 233 253 function write(fd: number, data: string | ArrayBufferView, offset?: number, length?: number, position?: number | null): Promise<number>;
+3
src/types/modules/util.d.ts
··· 67 67 }): { values: Record<string, unknown>; positionals: string[] }; 68 68 function parseEnv(content: string): Record<string, string>; 69 69 function promisify(fn: (...args: unknown[]) => unknown): (...args: unknown[]) => Promise<unknown>; 70 + namespace promisify { 71 + const custom: symbol; 72 + } 70 73 function callbackify(fn: (...args: unknown[]) => Promise<unknown>): (...args: unknown[]) => void; 71 74 function aborted(signal: AbortSignal, resource: object): Promise<void>; 72 75 function stripVTControlCharacters(str: string): string;
+2
tests/fixtures/execfile_promisify_child.cjs
··· 1 + process.stdout.write('OUT'); 2 + process.stderr.write('ERR');
+31
tests/fixtures/tty_readstream_da_timeout_child.cjs
··· 1 + const { openSync } = require('fs'); 2 + const tty = require('tty'); 3 + 4 + const fd = openSync('/dev/tty', 'r'); 5 + const stream = new tty.ReadStream(fd); 6 + stream.setRawMode(true); 7 + 8 + let settled = false; 9 + 10 + stream.on('data', (data) => { 11 + if (settled) { 12 + return; 13 + } 14 + settled = true; 15 + try { stream.setRawMode(false); } catch {} 16 + console.log('DATA', JSON.stringify(Buffer.from(data).toString('latin1'))); 17 + process.exit(0); 18 + }); 19 + 20 + process.stdout.write('READY\n'); 21 + process.stdout.write('\x1b[c'); 22 + 23 + setTimeout(() => { 24 + if (settled) { 25 + return; 26 + } 27 + settled = true; 28 + try { stream.setRawMode(false); } catch {} 29 + console.log('TIMEOUT'); 30 + process.exit(2); 31 + }, 100);
+20
tests/fixtures/tty_readstream_dev_tty_child.cjs
··· 1 + const fs = require('fs'); 2 + const tty = require('tty'); 3 + 4 + const fd = fs.openSync('/dev/tty', 'r'); 5 + const stream = new tty.ReadStream(fd); 6 + 7 + console.log('READY'); 8 + 9 + const timeout = setTimeout(() => { 10 + console.log('TIMEOUT'); 11 + process.exit(3); 12 + }, 2000); 13 + 14 + stream.on('data', chunk => { 15 + clearTimeout(timeout); 16 + console.log('DATA', JSON.stringify(Buffer.from(chunk).toString('utf8'))); 17 + process.exit(0); 18 + }); 19 + 20 + stream.resume();
+18
tests/fixtures/tty_readstream_dev_tty_on_data_flowing_child.cjs
··· 1 + const fs = require('fs'); 2 + const tty = require('tty'); 3 + 4 + const fd = fs.openSync('/dev/tty', 'r'); 5 + const stream = new tty.ReadStream(fd); 6 + 7 + console.log('READY'); 8 + 9 + const timeout = setTimeout(() => { 10 + console.log('TIMEOUT'); 11 + process.exit(3); 12 + }, 2000); 13 + 14 + stream.on('data', chunk => { 15 + clearTimeout(timeout); 16 + console.log('DATA', JSON.stringify(Buffer.from(chunk).toString('utf8'))); 17 + process.exit(0); 18 + });
+142
tests/repro_amp_startup_probe.cjs
··· 1 + const { openSync } = require('fs'); 2 + const tty = require('tty'); 3 + 4 + const QUERY_KITTY = '\x1b[?u'; 5 + const QUERY_DA1 = '\x1b[c'; 6 + const ENABLE_FOCUS = '\x1b[?1004h'; 7 + const ENABLE_INBAND_RESIZE = '\x1b[?2048h'; 8 + const ENABLE_BRACKETED_PASTE = '\x1b[?2004h'; 9 + const ENABLE_KITTY_KEYBOARD = '\x1b[>7u'; 10 + 11 + const startedAt = Date.now(); 12 + 13 + function nowMs() { 14 + return Date.now() - startedAt; 15 + } 16 + 17 + function writeLog(record) { 18 + process.stderr.write(`${JSON.stringify({ t: nowMs(), ...record })}\n`); 19 + } 20 + 21 + function toHex(data) { 22 + return Buffer.from(data).toString('hex'); 23 + } 24 + 25 + function cleanup(stream) { 26 + if (!stream) { 27 + return; 28 + } 29 + try { 30 + stream.setRawMode(false); 31 + } catch {} 32 + try { 33 + stream.removeAllListeners('data'); 34 + } catch {} 35 + try { 36 + stream.destroy(); 37 + } catch {} 38 + } 39 + 40 + function parseDeviceAttributes(data) { 41 + const text = Buffer.from(data).toString('latin1'); 42 + const match = text.match(/\x1b\[\?(\d+)((?:;\d+)*)c/); 43 + if (!match) { 44 + return null; 45 + } 46 + 47 + const primary = Number(match[1]); 48 + const secondary = match[2] 49 + ? match[2].split(';').filter(Boolean).map((value) => Number(value)) 50 + : []; 51 + 52 + return { primary, secondary }; 53 + } 54 + 55 + async function main() { 56 + const fd = openSync('/dev/tty', 'r'); 57 + const stream = new tty.ReadStream(fd); 58 + stream.on('error', (error) => { 59 + writeLog({ type: 'stream-error', message: error && error.message ? error.message : String(error) }); 60 + process.exitCode = 1; 61 + }); 62 + stream.setRawMode(true); 63 + 64 + let probe = null; 65 + 66 + function settle(reason) { 67 + if (!probe) { 68 + return; 69 + } 70 + 71 + const current = probe; 72 + probe = null; 73 + clearTimeout(current.timeout); 74 + writeLog({ 75 + type: 'probe-finish', 76 + stage: current.stage, 77 + reason, 78 + elapsed: Date.now() - current.startedAt, 79 + kittyQueryResponse: current.kittyQueryResponse, 80 + deviceAttributes: current.deviceAttributes, 81 + }); 82 + current.resolve({ 83 + reason, 84 + kittyQueryResponse: current.kittyQueryResponse, 85 + deviceAttributes: current.deviceAttributes, 86 + }); 87 + } 88 + 89 + stream.on('data', (data) => { 90 + writeLog({ type: 'read', bytes: toHex(data) }); 91 + 92 + if (!probe) { 93 + return; 94 + } 95 + 96 + const da = parseDeviceAttributes(data); 97 + if (da) { 98 + probe.deviceAttributes = da; 99 + writeLog({ type: 'device-attributes', stage: probe.stage, deviceAttributes: da }); 100 + if (probe.kittyQueryResponse !== null) { 101 + settle('complete'); 102 + } 103 + } 104 + }); 105 + 106 + async function runProbe(stage) { 107 + if (probe) { 108 + settle('preempt'); 109 + } 110 + 111 + return new Promise((resolve) => { 112 + probe = { 113 + stage, 114 + startedAt: Date.now(), 115 + kittyQueryResponse: null, 116 + deviceAttributes: null, 117 + resolve, 118 + timeout: setTimeout(() => settle('timeout'), 200), 119 + }; 120 + 121 + writeLog({ type: 'probe-start', stage }); 122 + process.stdout.write(QUERY_KITTY); 123 + process.stdout.write(QUERY_DA1); 124 + }); 125 + } 126 + 127 + try { 128 + await runProbe('before_enable'); 129 + process.stdout.write(ENABLE_FOCUS); 130 + process.stdout.write(ENABLE_INBAND_RESIZE); 131 + process.stdout.write(ENABLE_BRACKETED_PASTE); 132 + process.stdout.write(ENABLE_KITTY_KEYBOARD); 133 + await runProbe('after_enable'); 134 + } finally { 135 + cleanup(stream); 136 + } 137 + } 138 + 139 + main().catch((error) => { 140 + writeLog({ type: 'fatal', message: error && error.message ? error.message : String(error) }); 141 + process.exitCode = 1; 142 + });
+50
tests/repro_async_generator_await_yield.cjs
··· 1 + function assert(cond, msg) { 2 + if (!cond) throw new Error(msg); 3 + } 4 + 5 + async function* delayedSingle() { 6 + await Promise.resolve(); 7 + yield 42; 8 + } 9 + 10 + async function* delayedDouble() { 11 + await Promise.resolve(); 12 + yield 1; 13 + await Promise.resolve(); 14 + yield 2; 15 + } 16 + 17 + async function main() { 18 + { 19 + const it = delayedSingle(); 20 + const first = await it.next(); 21 + console.log("single:first", JSON.stringify(first)); 22 + assert(first.done === false, "single first result should not be done"); 23 + assert(first.value === 42, "single first result should yield 42"); 24 + } 25 + 26 + { 27 + const it = delayedDouble(); 28 + const first = await it.next(); 29 + const second = await it.next(); 30 + const third = await it.next(); 31 + console.log( 32 + "double", 33 + JSON.stringify(first), 34 + JSON.stringify(second), 35 + JSON.stringify(third), 36 + ); 37 + assert(first.done === false, "double first result should not be done"); 38 + assert(first.value === 1, "double first result should yield 1"); 39 + assert(second.done === false, "double second result should not be done"); 40 + assert(second.value === 2, "double second result should yield 2"); 41 + assert(third.done === true, "double third result should be done"); 42 + } 43 + 44 + console.log("async generator await+yield repro passed"); 45 + } 46 + 47 + main().catch((err) => { 48 + console.error(err && err.stack ? err.stack : String(err)); 49 + throw err; 50 + });
+43
tests/repro_async_generator_crash_stress.cjs
··· 1 + async function* delayedPair(tag) { 2 + await new Promise((resolve) => setTimeout(resolve, 0)); 3 + yield tag + ":one"; 4 + await new Promise((resolve) => setTimeout(resolve, 0)); 5 + yield tag + ":two"; 6 + } 7 + 8 + async function stressOverlap(iteration) { 9 + const it = delayedPair("overlap:" + iteration); 10 + const p1 = it.next(); 11 + const p2 = it.next(); 12 + const p3 = it.next(); 13 + return Promise.allSettled([p1, p2, p3]); 14 + } 15 + 16 + async function stressReturn(iteration) { 17 + const it = delayedPair("return:" + iteration); 18 + const p1 = it.next(); 19 + const p2 = it.return("done:" + iteration); 20 + return Promise.allSettled([p1, p2]); 21 + } 22 + 23 + async function stressThrow(iteration) { 24 + const it = delayedPair("throw:" + iteration); 25 + const p1 = it.next(); 26 + const p2 = it.throw(new Error("boom:" + iteration)); 27 + return Promise.allSettled([p1, p2]); 28 + } 29 + 30 + async function main() { 31 + for (let i = 0; i < 512; i++) { 32 + if ((i & 31) === 0) console.log("iter", i); 33 + await stressOverlap(i); 34 + await stressReturn(i); 35 + await stressThrow(i); 36 + } 37 + console.log("async generator crash stress completed"); 38 + } 39 + 40 + main().catch((err) => { 41 + console.error(err && err.stack ? err.stack : String(err)); 42 + throw err; 43 + });
+46
tests/repro_async_generator_overlap.cjs
··· 1 + function assert(cond, msg) { 2 + if (!cond) throw new Error(msg); 3 + } 4 + 5 + async function* delayedPair() { 6 + await new Promise((resolve) => setTimeout(resolve, 0)); 7 + yield 1; 8 + await new Promise((resolve) => setTimeout(resolve, 0)); 9 + yield 2; 10 + } 11 + 12 + async function runOnce(iteration) { 13 + const it = delayedPair(); 14 + 15 + const p1 = it.next(); 16 + const p2 = it.next(); 17 + const p3 = it.next(); 18 + 19 + const r1 = await p1; 20 + const r2 = await p2; 21 + const r3 = await p3; 22 + 23 + console.log( 24 + "iter", 25 + iteration, 26 + JSON.stringify(r1), 27 + JSON.stringify(r2), 28 + JSON.stringify(r3), 29 + ); 30 + 31 + assert(r1.done === false, "first next should not be done"); 32 + assert(r1.value === 1, "first next should yield 1"); 33 + assert(r2.done === false, "second next should not be done"); 34 + assert(r2.value === 2, "second next should yield 2"); 35 + assert(r3.done === true, "third next should complete the iterator"); 36 + } 37 + 38 + async function main() { 39 + for (let i = 0; i < 64; i++) await runOnce(i); 40 + console.log("async generator overlap repro passed"); 41 + } 42 + 43 + main().catch((err) => { 44 + console.error(err && err.stack ? err.stack : String(err)); 45 + throw err; 46 + });
+42
tests/repro_async_generator_reentrant_then.cjs
··· 1 + function assert(cond, msg) { 2 + if (!cond) throw new Error(msg); 3 + } 4 + 5 + async function* delayedPair(tag) { 6 + await new Promise((resolve) => setTimeout(resolve, 0)); 7 + yield tag + ":one"; 8 + await new Promise((resolve) => setTimeout(resolve, 0)); 9 + yield tag + ":two"; 10 + } 11 + 12 + function runChain(iteration) { 13 + const it = delayedPair("chain:" + iteration); 14 + 15 + return it.next().then((first) => { 16 + console.log("first", iteration, JSON.stringify(first)); 17 + assert(first.done === false, "first result should not be done"); 18 + assert(first.value === "chain:" + iteration + ":one", "first result should yield first value"); 19 + return it.next().then((second) => { 20 + console.log("second", iteration, JSON.stringify(second)); 21 + assert(second.done === false, "second result should not be done"); 22 + assert(second.value === "chain:" + iteration + ":two", "second result should yield second value"); 23 + return it.next().then((third) => { 24 + console.log("third", iteration, JSON.stringify(third)); 25 + assert(third.done === true, "third result should finish the iterator"); 26 + }); 27 + }); 28 + }); 29 + } 30 + 31 + async function main() { 32 + for (let i = 0; i < 128; i++) { 33 + if ((i & 31) === 0) console.log("iter", i); 34 + await runChain(i); 35 + } 36 + console.log("async generator reentrant then repro completed"); 37 + } 38 + 39 + main().catch((err) => { 40 + console.error(err && err.stack ? err.stack : String(err)); 41 + throw err; 42 + });
+46
tests/test_async_generator_gc_liveness.cjs
··· 1 + function assert(cond, msg) { 2 + if (!cond) throw new Error(msg); 3 + } 4 + 5 + function churnGarbage(rounds, width) { 6 + const ring = new Array(8); 7 + for (let r = 0; r < rounds; r++) { 8 + const batch = new Array(width); 9 + for (let i = 0; i < width; i++) { 10 + batch[i] = { 11 + i, 12 + label: "item-" + i, 13 + arr: [i, i + 1, i + 2, i + 3], 14 + }; 15 + } 16 + ring[r & 7] = batch; 17 + } 18 + return ring.length; 19 + } 20 + 21 + async function* delayedCounter() { 22 + await new Promise((resolve) => setTimeout(resolve, 0)); 23 + yield 42; 24 + } 25 + 26 + async function runOnce() { 27 + let iter = delayedCounter(); 28 + const nextPromise = iter.next(); 29 + iter = null; 30 + 31 + churnGarbage(128, 256); 32 + 33 + const result = await nextPromise; 34 + assert(result && result.value === 42, "expected yielded value"); 35 + assert(result.done === false, "expected unfinished iterator result"); 36 + } 37 + 38 + async function main() { 39 + for (let i = 0; i < 64; i++) await runOnce(); 40 + console.log("async generator survives GC after iterator becomes unreachable"); 41 + } 42 + 43 + main().catch((err) => { 44 + console.error(err && err.stack ? err.stack : String(err)); 45 + throw err; 46 + });
+38
tests/test_child_process_execfile_git_options.cjs
··· 1 + const { promisify } = require('node:util'); 2 + const { execFile } = require('node:child_process'); 3 + const path = require('node:path'); 4 + 5 + async function main() { 6 + const execFileAsync = promisify(execFile); 7 + const cwd = path.join(__dirname, '..'); 8 + 9 + const revParse = await execFileAsync( 10 + 'git', 11 + ['rev-parse', '--show-toplevel'], 12 + { cwd, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 } 13 + ); 14 + 15 + if (!revParse || typeof revParse.stdout !== 'string') { 16 + throw new Error('expected rev-parse stdout string'); 17 + } 18 + if (!revParse.stdout.trim().endsWith('/ant')) { 19 + throw new Error(`unexpected rev-parse output: ${JSON.stringify(revParse.stdout)}`); 20 + } 21 + 22 + const status = await execFileAsync( 23 + 'git', 24 + ['status', '--porcelain=v1', '-z', '--untracked-files=normal'], 25 + { cwd, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 } 26 + ); 27 + 28 + if (!status || typeof status.stdout !== 'string') { 29 + throw new Error('expected status stdout string'); 30 + } 31 + 32 + console.log('util.promisify(execFile) handles git with options'); 33 + } 34 + 35 + main().catch((err) => { 36 + console.error(err && err.stack ? err.stack : String(err)); 37 + process.exit(1); 38 + });
+36
tests/test_child_process_execfile_promisify.cjs
··· 1 + const { promisify } = require('util'); 2 + const { execFile } = require('child_process'); 3 + const path = require('path'); 4 + 5 + async function main() { 6 + if (typeof promisify.custom !== 'symbol') { 7 + throw new Error('expected util.promisify.custom to be a symbol'); 8 + } 9 + 10 + if (typeof execFile[promisify.custom] !== 'function') { 11 + throw new Error('expected execFile to define util.promisify.custom'); 12 + } 13 + 14 + const execFileAsync = promisify(execFile); 15 + const child = path.join(__dirname, 'fixtures', 'execfile_promisify_child.cjs'); 16 + const result = await execFileAsync(process.execPath, [child]); 17 + 18 + if (!result || typeof result !== 'object' || Array.isArray(result)) { 19 + throw new Error(`expected object result, got ${Object.prototype.toString.call(result)}`); 20 + } 21 + 22 + if (result.stdout !== 'OUT') { 23 + throw new Error(`expected stdout to be OUT, got ${JSON.stringify(result.stdout)}`); 24 + } 25 + 26 + if (result.stderr !== 'ERR') { 27 + throw new Error(`expected stderr to be ERR, got ${JSON.stringify(result.stderr)}`); 28 + } 29 + 30 + console.log('util.promisify(execFile) resolves { stdout, stderr }'); 31 + } 32 + 33 + main().catch((err) => { 34 + console.error(err && err.stack ? err.stack : String(err)); 35 + process.exit(1); 36 + });
+189
tests/test_closure_forwarder_reentrant_callbacks.cjs
··· 1 + function afterTransform(er, data) { 2 + const ts = this._transformState; 3 + ts.transforming = false; 4 + const cb = ts.writecb; 5 + if (cb === null) throw new Error('multiple callback'); 6 + ts.writechunk = null; 7 + ts.writecb = null; 8 + cb(er); 9 + const rs = this._readableState; 10 + rs.reading = false; 11 + if (rs.needReadable || rs.length < rs.highWaterMark) { 12 + this._read(rs.highWaterMark); 13 + } 14 + } 15 + 16 + function doWrite(stream, state, len, chunk, encoding, cb) { 17 + state.writelen = len; 18 + state.writecb = cb; 19 + state.writing = true; 20 + stream._write(chunk, encoding, state.onwrite); 21 + } 22 + 23 + function onwriteStateUpdate(state) { 24 + state.writing = false; 25 + state.writecb = null; 26 + state.length -= state.writelen; 27 + state.writelen = 0; 28 + } 29 + 30 + function afterWrite(stream, state, cb) { 31 + state.pendingcb--; 32 + cb(); 33 + } 34 + 35 + function clearBuffer(stream, state) { 36 + state.bufferProcessing = true; 37 + let entry = state.bufferedRequest; 38 + while (entry) { 39 + const chunk = entry.chunk; 40 + const encoding = entry.encoding; 41 + const cb = entry.callback; 42 + const len = chunk.length; 43 + doWrite(stream, state, len, chunk, encoding, cb); 44 + entry = entry.next; 45 + state.bufferedRequestCount--; 46 + if (state.writing) break; 47 + } 48 + if (entry === null) state.lastBufferedRequest = null; 49 + state.bufferedRequest = entry; 50 + state.bufferProcessing = false; 51 + } 52 + 53 + function onwrite(stream, er) { 54 + const state = stream._writableState; 55 + const cb = state.writecb; 56 + if (typeof cb !== 'function') throw new Error('bad cb'); 57 + onwriteStateUpdate(state); 58 + if (er) throw er; 59 + if (!state.corked && !state.bufferProcessing && state.bufferedRequest) { 60 + clearBuffer(stream, state); 61 + } 62 + afterWrite(stream, state, cb); 63 + } 64 + 65 + function onwriteForward(er) { 66 + return onwrite(onwriteForward.owner, er); 67 + } 68 + 69 + function afterTransformForward(er, data) { 70 + return afterTransform.call(afterTransformForward.owner, er, data); 71 + } 72 + 73 + function writeOrBuffer(stream, state, chunk, encoding, cb) { 74 + const len = chunk.length; 75 + state.length += len; 76 + if (state.writing || state.corked) { 77 + const req = { chunk, encoding, callback: cb, next: null }; 78 + const last = state.lastBufferedRequest; 79 + state.lastBufferedRequest = req; 80 + if (last) last.next = req; 81 + else state.bufferedRequest = req; 82 + state.bufferedRequestCount++; 83 + } else { 84 + doWrite(stream, state, len, chunk, encoding, cb); 85 + } 86 + return true; 87 + } 88 + 89 + function makeState(owner, onwriteFn, afterTransformFn) { 90 + owner._readableState = { 91 + needReadable: true, 92 + reading: false, 93 + length: 0, 94 + highWaterMark: 16, 95 + }; 96 + owner._transformState = { 97 + afterTransform: afterTransformFn, 98 + needTransform: false, 99 + transforming: false, 100 + writecb: null, 101 + writechunk: null, 102 + writeencoding: null, 103 + }; 104 + owner._writableState = { 105 + length: 0, 106 + highWaterMark: 16, 107 + writing: false, 108 + corked: 0, 109 + bufferProcessing: false, 110 + writecb: null, 111 + writelen: 0, 112 + bufferedRequest: null, 113 + lastBufferedRequest: null, 114 + bufferedRequestCount: 0, 115 + pendingcb: 0, 116 + onwrite: onwriteFn, 117 + }; 118 + } 119 + 120 + function ClosureForwardingTransformLike() { 121 + makeState( 122 + this, 123 + (er) => onwrite(this, er), 124 + (er, data) => afterTransform.call(this, er, data), 125 + ); 126 + } 127 + 128 + function OwnerForwardingTransformLike() { 129 + makeState(this, onwriteForward, afterTransformForward); 130 + this._writableState.onwrite.owner = this; 131 + this._transformState.afterTransform.owner = this; 132 + } 133 + 134 + ClosureForwardingTransformLike.prototype.write = OwnerForwardingTransformLike.prototype.write = 135 + function(chunk, cb) { 136 + const state = this._writableState; 137 + state.pendingcb++; 138 + return writeOrBuffer(this, state, chunk, 'utf8', cb); 139 + }; 140 + 141 + ClosureForwardingTransformLike.prototype._write = OwnerForwardingTransformLike.prototype._write = 142 + function(chunk, encoding, cb) { 143 + const ts = this._transformState; 144 + ts.writecb = cb; 145 + ts.writechunk = chunk; 146 + ts.writeencoding = encoding; 147 + if (!ts.transforming) { 148 + const rs = this._readableState; 149 + if (ts.needTransform || rs.needReadable || rs.length < rs.highWaterMark) { 150 + this._read(rs.highWaterMark); 151 + } 152 + } 153 + }; 154 + 155 + ClosureForwardingTransformLike.prototype._read = OwnerForwardingTransformLike.prototype._read = 156 + function() { 157 + const ts = this._transformState; 158 + if (ts.writechunk !== null && !ts.transforming) { 159 + ts.transforming = true; 160 + this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform); 161 + } else { 162 + ts.needTransform = true; 163 + } 164 + }; 165 + 166 + ClosureForwardingTransformLike.prototype._transform = OwnerForwardingTransformLike.prototype._transform = 167 + function(chunk, encoding, cb) { 168 + cb(null, chunk); 169 + }; 170 + 171 + function exercise(instance, count) { 172 + let callbacks = 0; 173 + 174 + for (let i = 0; i < count; i++) { 175 + instance.write('x', (err) => { 176 + if (err) throw err; 177 + callbacks++; 178 + }); 179 + } 180 + 181 + if (callbacks !== count) { 182 + throw new Error(`expected ${count} callbacks, got ${callbacks}`); 183 + } 184 + } 185 + 186 + exercise(new OwnerForwardingTransformLike(), 512); 187 + exercise(new ClosureForwardingTransformLike(), 128); 188 + 189 + console.log('closure forwarder reentrant callbacks ok');
+42
tests/test_events_listeners_api.cjs
··· 1 + const { EventEmitter } = require('events'); 2 + 3 + function assert(condition, message) { 4 + if (!condition) throw new Error(message); 5 + } 6 + 7 + const ee = new EventEmitter(); 8 + const calls = []; 9 + 10 + function onFoo(value) { 11 + calls.push(['on', value]); 12 + } 13 + 14 + function onceFoo(value) { 15 + calls.push(['once', value]); 16 + } 17 + 18 + ee.on('foo', onFoo); 19 + ee.once('foo', onceFoo); 20 + 21 + const listenersBefore = ee.listeners('foo'); 22 + assert(Array.isArray(listenersBefore), 'listeners() should return an array'); 23 + assert(listenersBefore.length === 2, `expected 2 listeners, got ${listenersBefore.length}`); 24 + assert(listenersBefore[0] === onFoo, 'listeners() should return the original persistent listener'); 25 + assert(listenersBefore[1] === onceFoo, 'listeners() should unwrap once listeners to the original callback'); 26 + 27 + const rawBefore = ee.rawListeners('foo'); 28 + assert(rawBefore.length === 2, `expected 2 raw listeners, got ${rawBefore.length}`); 29 + assert(rawBefore[0] === onFoo, 'rawListeners() should return the original persistent listener'); 30 + assert(rawBefore[1] !== onceFoo, 'rawListeners() should expose the once wrapper'); 31 + 32 + ee.emit('foo', 1); 33 + 34 + const listenersAfter = ee.listeners('foo'); 35 + assert(listenersAfter.length === 1, `expected 1 listener after once emission, got ${listenersAfter.length}`); 36 + assert(listenersAfter[0] === onFoo, 'persistent listener should remain after once emission'); 37 + 38 + assert(calls.length === 2, `expected 2 calls, got ${calls.length}`); 39 + assert(calls[0][0] === 'on' && calls[0][1] === 1, 'persistent listener should fire first'); 40 + assert(calls[1][0] === 'once' && calls[1][1] === 1, 'once listener should fire once'); 41 + 42 + console.log('events listeners api ok');
+57
tests/test_fs_promises_filehandle.cjs
··· 1 + const fs = require('node:fs/promises'); 2 + const path = require('node:path'); 3 + 4 + async function main() { 5 + const file = path.join(__dirname, 'tmp_filehandle.txt'); 6 + 7 + try { 8 + await fs.unlink(file); 9 + } catch {} 10 + 11 + const writer = await fs.open(file, 'w'); 12 + if (!writer || typeof writer !== 'object') { 13 + throw new Error(`expected FileHandle object, got ${Object.prototype.toString.call(writer)}`); 14 + } 15 + if (typeof writer.fd !== 'number') { 16 + throw new Error(`expected numeric fd, got ${typeof writer.fd}`); 17 + } 18 + if (typeof writer.writeFile !== 'function') { 19 + throw new Error('expected FileHandle.writeFile()'); 20 + } 21 + if (typeof writer.sync !== 'function') { 22 + throw new Error('expected FileHandle.sync()'); 23 + } 24 + if (typeof writer.close !== 'function') { 25 + throw new Error('expected FileHandle.close()'); 26 + } 27 + 28 + await writer.writeFile('hello'); 29 + await writer.sync(); 30 + const writerStat = await writer.stat(); 31 + if (!writerStat || typeof writerStat.size !== 'number' || writerStat.size !== 5) { 32 + throw new Error(`expected stat.size === 5, got ${writerStat && writerStat.size}`); 33 + } 34 + await writer.close(); 35 + 36 + const reader = await fs.open(file, 'r'); 37 + const buf = Buffer.alloc(5); 38 + const readResult = await reader.read(buf, 0, buf.length, 0); 39 + if (!readResult || readResult.bytesRead !== 5) { 40 + throw new Error(`expected bytesRead === 5, got ${readResult && readResult.bytesRead}`); 41 + } 42 + if (readResult.buffer !== buf) { 43 + throw new Error('expected read() to return the original buffer'); 44 + } 45 + if (buf.toString('utf8') !== 'hello') { 46 + throw new Error(`expected "hello", got ${JSON.stringify(buf.toString('utf8'))}`); 47 + } 48 + await reader.close(); 49 + await fs.unlink(file); 50 + 51 + console.log('fs/promises FileHandle works'); 52 + } 53 + 54 + main().catch((err) => { 55 + console.error(err && err.stack ? err.stack : String(err)); 56 + process.exit(1); 57 + });
+34
tests/test_process_nexttick_bound_receiver.cjs
··· 1 + async function main() { 2 + const receiver = { 3 + tag: 'receiver', 4 + seen: [], 5 + callback(arg) { 6 + this.seen.push({ thisTag: this.tag, arg }); 7 + }, 8 + }; 9 + 10 + const bound = receiver.callback.bind(receiver); 11 + process.nextTick(bound, 'queued'); 12 + 13 + await new Promise((resolve) => setTimeout(resolve, 0)); 14 + 15 + if (receiver.seen.length !== 1) { 16 + throw new Error(`expected 1 callback invocation, got ${receiver.seen.length}`); 17 + } 18 + 19 + const [entry] = receiver.seen; 20 + if (entry.thisTag !== 'receiver') { 21 + throw new Error(`expected bound receiver, got ${JSON.stringify(entry.thisTag)}`); 22 + } 23 + 24 + if (entry.arg !== 'queued') { 25 + throw new Error(`expected queued arg, got ${JSON.stringify(entry.arg)}`); 26 + } 27 + 28 + console.log('process.nextTick preserves bound receiver and queued args'); 29 + } 30 + 31 + main().catch((err) => { 32 + console.error(err && err.stack ? err.stack : String(err)); 33 + process.exit(1); 34 + });
+11
tests/test_process_nexttick_order.cjs
··· 1 + const out = []; 2 + 3 + Promise.resolve().then(() => out.push("promise")); 4 + process.nextTick(() => out.push("tick")); 5 + 6 + setTimeout(() => { 7 + const got = JSON.stringify(out); 8 + const want = JSON.stringify(["tick", "promise"]); 9 + if (got !== want) throw new Error(`expected ${want}, got ${got}`); 10 + console.log("process.nextTick ordering ok"); 11 + }, 0);
+35
tests/test_stream_readable_data_listener_order.cjs
··· 1 + const { Readable } = require('node:stream'); 2 + 3 + async function main() { 4 + const seen = []; 5 + 6 + const readable = new Readable({ 7 + read() { 8 + this.push('x'); 9 + this.push(null); 10 + }, 11 + }); 12 + 13 + readable.on('data', (chunk) => { 14 + seen.push(['data', chunk.toString('utf8')]); 15 + }); 16 + 17 + readable.on('end', () => { 18 + seen.push(['end']); 19 + }); 20 + 21 + await new Promise((resolve) => setTimeout(resolve, 0)); 22 + 23 + const got = JSON.stringify(seen); 24 + const want = JSON.stringify([['data', 'x'], ['end']]); 25 + if (got !== want) { 26 + throw new Error(`expected ${want}, got ${got}`); 27 + } 28 + 29 + console.log('stream data listeners keep deferred flowing semantics'); 30 + } 31 + 32 + main().catch((err) => { 33 + console.error(err && err.stack ? err.stack : String(err)); 34 + process.exit(1); 35 + });
+37
tests/test_stream_transform_async_callback.cjs
··· 1 + const { Transform } = require('node:stream'); 2 + 3 + async function main() { 4 + let output = ''; 5 + 6 + const tr = new Transform({ 7 + transform(chunk, encoding, cb) { 8 + Promise.resolve().then(() => { 9 + cb(null, chunk.toString('utf8').toUpperCase()); 10 + }); 11 + }, 12 + }); 13 + 14 + tr.on('data', (chunk) => { 15 + output += chunk.toString('utf8'); 16 + }); 17 + 18 + const finished = new Promise((resolve, reject) => { 19 + tr.on('finish', resolve); 20 + tr.on('error', reject); 21 + }); 22 + 23 + tr.write('abc'); 24 + tr.end(); 25 + await finished; 26 + 27 + if (output !== 'ABC') { 28 + throw new Error(`expected "ABC", got ${JSON.stringify(output)}`); 29 + } 30 + 31 + console.log('stream Transform async callback preserves receiver state'); 32 + } 33 + 34 + main().catch((err) => { 35 + console.error(err && err.stack ? err.stack : String(err)); 36 + process.exit(1); 37 + });
+111
tests/test_tty_readstream_da_timeout.cjs
··· 1 + const { spawnSync } = require('child_process'); 2 + const path = require('path'); 3 + 4 + function fail(message) { 5 + throw new Error(message); 6 + } 7 + 8 + function runInPty() { 9 + const helper = path.join(__dirname, 'fixtures', 'tty_readstream_da_timeout_child.cjs'); 10 + const script = ` 11 + import os, select, signal, sys, time 12 + 13 + exec_path, helper = sys.argv[1], sys.argv[2] 14 + pid, master = os.forkpty() 15 + 16 + if pid == 0: 17 + os.execv(exec_path, [exec_path, helper]) 18 + 19 + buf = bytearray() 20 + replied = False 21 + exit_code = None 22 + deadline = time.time() + 7.0 23 + 24 + while time.time() < deadline: 25 + done, status = os.waitpid(pid, os.WNOHANG) 26 + if done == pid: 27 + exit_code = os.waitstatus_to_exitcode(status) 28 + break 29 + 30 + r, _, _ = select.select([master], [], [], 0.1) 31 + if master not in r: 32 + continue 33 + 34 + try: 35 + chunk = os.read(master, 4096) 36 + except OSError: 37 + break 38 + 39 + if not chunk: 40 + break 41 + 42 + buf.extend(chunk) 43 + if (not replied) and b'\\x1b[c' in bytes(buf): 44 + os.write(master, b'\\x1b[?1;2c') 45 + replied = True 46 + 47 + if exit_code is None: 48 + os.kill(pid, signal.SIGKILL) 49 + _, status = os.waitpid(pid, 0) 50 + exit_code = os.waitstatus_to_exitcode(status) 51 + 52 + while True: 53 + r, _, _ = select.select([master], [], [], 0.05) 54 + if master not in r: 55 + break 56 + try: 57 + chunk = os.read(master, 4096) 58 + except OSError: 59 + break 60 + if not chunk: 61 + break 62 + buf.extend(chunk) 63 + 64 + sys.stdout.buffer.write(bytes(buf)) 65 + sys.exit(exit_code) 66 + `; 67 + 68 + if (process.platform === 'win32') { 69 + console.log('skipping tty DA timeout test on win32'); 70 + process.exit(0); 71 + } 72 + 73 + return spawnSync('python3', ['-c', script, process.execPath, helper], { 74 + encoding: 'utf8', 75 + timeout: 9000, 76 + }); 77 + } 78 + 79 + const result = runInPty(); 80 + 81 + if (result.error && result.error.code === 'ENOENT') { 82 + console.log('skipping tty DA timeout test because `python3` is unavailable'); 83 + process.exit(0); 84 + } 85 + 86 + if (result.error) throw result.error; 87 + 88 + const output = `${result.stdout || ''}${result.stderr || ''}`; 89 + 90 + if (output.includes('setRawMode EPERM') || output.includes('Failed to set raw mode')) { 91 + console.log('skipping tty DA timeout test because raw mode is unavailable in this PTY environment'); 92 + process.exit(0); 93 + } 94 + 95 + if (result.status !== 0) { 96 + fail(`child exited ${result.status}\n${output}`); 97 + } 98 + 99 + if (!output.includes('READY')) { 100 + fail(`expected READY banner\n${output}`); 101 + } 102 + 103 + if (!output.includes('DATA "\\u001b[?1;2c"')) { 104 + fail(`expected immediate DA response before timeout\n${output}`); 105 + } 106 + 107 + if (output.includes('TIMEOUT')) { 108 + fail(`did not expect timeout before DA response\n${output}`); 109 + } 110 + 111 + console.log('tty.ReadStream(/dev/tty) receives DA1 before short timeout');
+102
tests/test_tty_readstream_dev_tty.cjs
··· 1 + const { spawnSync } = require('child_process'); 2 + const path = require('path'); 3 + 4 + function fail(message) { 5 + throw new Error(message); 6 + } 7 + 8 + function runInPty() { 9 + const helper = path.join(__dirname, 'fixtures', 'tty_readstream_dev_tty_child.cjs'); 10 + const script = ` 11 + import os, select, signal, sys, time 12 + 13 + exec_path, helper = sys.argv[1], sys.argv[2] 14 + pid, master = os.forkpty() 15 + 16 + if pid == 0: 17 + os.execv(exec_path, [exec_path, helper]) 18 + 19 + buf = bytearray() 20 + sent = False 21 + exit_code = None 22 + deadline = time.time() + 7.0 23 + 24 + while time.time() < deadline: 25 + done, status = os.waitpid(pid, os.WNOHANG) 26 + if done == pid: 27 + exit_code = os.waitstatus_to_exitcode(status) 28 + break 29 + 30 + r, _, _ = select.select([master], [], [], 0.1) 31 + if master not in r: 32 + continue 33 + 34 + try: 35 + chunk = os.read(master, 4096) 36 + except OSError: 37 + break 38 + 39 + if not chunk: 40 + break 41 + 42 + buf.extend(chunk) 43 + if (not sent) and b'READY' in buf: 44 + os.write(master, b'hello\\n') 45 + sent = True 46 + 47 + if exit_code is None: 48 + os.kill(pid, signal.SIGKILL) 49 + _, status = os.waitpid(pid, 0) 50 + exit_code = os.waitstatus_to_exitcode(status) 51 + 52 + while True: 53 + r, _, _ = select.select([master], [], [], 0.05) 54 + if master not in r: 55 + break 56 + try: 57 + chunk = os.read(master, 4096) 58 + except OSError: 59 + break 60 + if not chunk: 61 + break 62 + buf.extend(chunk) 63 + 64 + sys.stdout.buffer.write(bytes(buf)) 65 + sys.exit(exit_code) 66 + `; 67 + 68 + if (process.platform === 'win32') { 69 + console.log('skipping tty.ReadStream(/dev/tty) pty test on win32'); 70 + process.exit(0); 71 + } 72 + 73 + return spawnSync('python3', ['-c', script, process.execPath, helper], { 74 + encoding: 'utf8', 75 + timeout: 9000, 76 + }); 77 + } 78 + 79 + const result = runInPty(); 80 + 81 + if (result.error && result.error.code === 'ENOENT') { 82 + console.log('skipping tty.ReadStream(/dev/tty) pty test because `python3` is unavailable'); 83 + process.exit(0); 84 + } 85 + 86 + if (result.error) throw result.error; 87 + 88 + const output = `${result.stdout || ''}${result.stderr || ''}`; 89 + 90 + if (result.status !== 0) { 91 + fail(`child exited ${result.status}\n${output}`); 92 + } 93 + 94 + if (!output.includes('READY')) { 95 + fail(`expected READY banner\n${output}`); 96 + } 97 + 98 + if (!output.includes('DATA "hello')) { 99 + fail(`expected tty data payload in output\n${output}`); 100 + } 101 + 102 + console.log('tty.ReadStream(/dev/tty) receives data via pty');
+102
tests/test_tty_readstream_dev_tty_on_data_flowing.cjs
··· 1 + const { spawnSync } = require('child_process'); 2 + const path = require('path'); 3 + 4 + function fail(message) { 5 + throw new Error(message); 6 + } 7 + 8 + function runInPty() { 9 + const helper = path.join(__dirname, 'fixtures', 'tty_readstream_dev_tty_on_data_flowing_child.cjs'); 10 + const script = ` 11 + import os, select, signal, sys, time 12 + 13 + exec_path, helper = sys.argv[1], sys.argv[2] 14 + pid, master = os.forkpty() 15 + 16 + if pid == 0: 17 + os.execv(exec_path, [exec_path, helper]) 18 + 19 + buf = bytearray() 20 + sent = False 21 + exit_code = None 22 + deadline = time.time() + 7.0 23 + 24 + while time.time() < deadline: 25 + done, status = os.waitpid(pid, os.WNOHANG) 26 + if done == pid: 27 + exit_code = os.waitstatus_to_exitcode(status) 28 + break 29 + 30 + r, _, _ = select.select([master], [], [], 0.1) 31 + if master not in r: 32 + continue 33 + 34 + try: 35 + chunk = os.read(master, 4096) 36 + except OSError: 37 + break 38 + 39 + if not chunk: 40 + break 41 + 42 + buf.extend(chunk) 43 + if (not sent) and b'READY' in buf: 44 + os.write(master, b'probe\\n') 45 + sent = True 46 + 47 + if exit_code is None: 48 + os.kill(pid, signal.SIGKILL) 49 + _, status = os.waitpid(pid, 0) 50 + exit_code = os.waitstatus_to_exitcode(status) 51 + 52 + while True: 53 + r, _, _ = select.select([master], [], [], 0.05) 54 + if master not in r: 55 + break 56 + try: 57 + chunk = os.read(master, 4096) 58 + except OSError: 59 + break 60 + if not chunk: 61 + break 62 + buf.extend(chunk) 63 + 64 + sys.stdout.buffer.write(bytes(buf)) 65 + sys.exit(exit_code) 66 + `; 67 + 68 + if (process.platform === 'win32') { 69 + console.log('skipping tty.ReadStream(/dev/tty) flowing listener test on win32'); 70 + process.exit(0); 71 + } 72 + 73 + return spawnSync('python3', ['-c', script, process.execPath, helper], { 74 + encoding: 'utf8', 75 + timeout: 9000, 76 + }); 77 + } 78 + 79 + const result = runInPty(); 80 + 81 + if (result.error && result.error.code === 'ENOENT') { 82 + console.log('skipping tty.ReadStream(/dev/tty) flowing listener test because `python3` is unavailable'); 83 + process.exit(0); 84 + } 85 + 86 + if (result.error) throw result.error; 87 + 88 + const output = `${result.stdout || ''}${result.stderr || ''}`; 89 + 90 + if (result.status !== 0) { 91 + fail(`child exited ${result.status}\n${output}`); 92 + } 93 + 94 + if (!output.includes('READY')) { 95 + fail(`expected READY banner\n${output}`); 96 + } 97 + 98 + if (!output.includes('DATA "probe')) { 99 + fail(`expected tty data payload without explicit resume()\n${output}`); 100 + } 101 + 102 + console.log('tty.ReadStream(/dev/tty) starts flowing on data listener');