From 00aa701fb08db8914161a70c8883ca46e0490bf8 Mon Sep 17 00:00:00 2001 From: theMackabu Date: Sun, 29 Mar 2026 01:02:50 -0700 Subject: [PATCH] rewrite entire async engine --- examples/npm/express/ant.lockb | Bin 0 -> 17256 bytes examples/npm/express/index.cjs | 12 ++ examples/npm/express/package.json | 13 ++ include/ant.h | 8 +- include/http/http1_parser.h | 34 +++- include/internal.h | 5 + include/modules/headers.h | 1 + include/modules/request.h | 22 ++- include/modules/timer.h | 5 +- include/object.h | 11 ++ include/output.h | 34 ++++ include/silver/engine.h | 58 ++++-- include/silver/vm.h | 2 + include/sugar.h | 18 ++ src/ant.c | 255 ++++++++++++++++++++----- src/gc/objects.c | 86 +++++---- src/http/http1_parser.c | 128 ++++++++++--- src/modules/fetch.c | 15 +- src/modules/headers.c | 53 ++++-- src/modules/io.c | 291 ++++++++++++++++++---------- src/modules/navigator.c | 35 ++-- src/modules/process.c | 25 ++- src/modules/request.c | 161 +++++++++++++--- src/modules/server.c | 306 ++++++++++++++++++++++-------- src/modules/timer.c | 49 ++++- src/net/connection.c | 3 +- src/output.c | 124 ++++++++++++ src/silver/compiler.c | 25 +++ src/silver/engine.c | 100 ++++++++-- src/silver/ops/async.h | 272 +++++++++++++++++++++++--- src/silver/ops/exceptions.h | 5 +- src/streams/pipes.c | 6 +- src/streams/readable.c | 43 +---- src/streams/transform.c | 24 +-- src/streams/writable.c | 24 +-- src/sugar.c | 51 ++++- tests/hono_dispatch_repro.js | 198 +++++++++++++++++++ tests/non_async_loop_repro.js | 57 ++++++ 38 files changed, 2034 insertions(+), 525 deletions(-) create mode 100644 examples/npm/express/ant.lockb create mode 100644 examples/npm/express/index.cjs create mode 100644 examples/npm/express/package.json create mode 100644 include/output.h create mode 100644 src/output.c create mode 100644 tests/hono_dispatch_repro.js create mode 100644 tests/non_async_loop_repro.js diff --git a/examples/npm/express/ant.lockb b/examples/npm/express/ant.lockb new file mode 100644 index 0000000000000000000000000000000000000000..c0d316cc8a2fdcfb070a400bd1b74d184707f7c3 GIT binary patch literal 17256 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 E0Eh}pm;e9( literal 0 HcmV?d00001 diff --git a/examples/npm/express/index.cjs b/examples/npm/express/index.cjs new file mode 100644 index 0000000..99dcf10 --- /dev/null +++ b/examples/npm/express/index.cjs @@ -0,0 +1,12 @@ +const express = require('express'); + +let app = express(); +let port = 3000; + +app.get('/', (_req, res) => { + res.send('Hello World!'); +}); + +app.listen(port, () => { + console.log(`Example app listening on port ${port}`); +}); diff --git a/examples/npm/express/package.json b/examples/npm/express/package.json new file mode 100644 index 0000000..3c94cc8 --- /dev/null +++ b/examples/npm/express/package.json @@ -0,0 +1,13 @@ +{ + "name": "express", + "version": "1.0.0", + "type": "commonjs", + "main": "index.cjs", + "scripts": { + "start": "ant index.cjs" + }, + "dependencies": { + "express": "^5.2.1" + }, + "devDependencies": {} +} diff --git a/include/ant.h b/include/ant.h index e3c6541..c47cf07 100644 --- a/include/ant.h +++ b/include/ant.h @@ -153,11 +153,13 @@ void *js_gettypedarray(ant_value_t val); ant_value_t js_mkffi(unsigned int index); int js_getffi(ant_value_t val); -void js_resolve_promise(ant_t *js, ant_value_t promise, ant_value_t value); -void js_reject_promise(ant_t *js, ant_value_t promise, ant_value_t value); void js_check_unhandled_rejections(ant_t *js); -void js_process_promise_handlers(ant_t *js, ant_value_t promise); void js_setup_import_meta(ant_t *js, const char *filename); +void js_process_promise_handlers(ant_t *js, ant_value_t promise); +void js_mark_promise_trigger_dequeued(ant_t *js, ant_value_t promise); +bool js_mark_promise_trigger_queued(ant_t *js, ant_value_t promise); +void js_reject_promise(ant_t *js, ant_value_t promise, ant_value_t value); +void js_resolve_promise(ant_t *js, ant_value_t promise, ant_value_t value); typedef ant_value_t (*js_getter_fn)(ant_t *js, ant_value_t obj, const char *key, size_t key_len); typedef ant_value_t (*js_keys_fn)(ant_t *js, ant_value_t obj); diff --git a/include/http/http1_parser.h b/include/http/http1_parser.h index fa467b2..35d7812 100644 --- a/include/http/http1_parser.h +++ b/include/http/http1_parser.h @@ -4,7 +4,10 @@ #include #include #include +#include + #include "modules/http.h" +#include "http/http1_writer.h" typedef struct { char *method; @@ -22,12 +25,35 @@ typedef struct { bool keep_alive; } ant_http1_parsed_request_t; +typedef struct { + ant_http1_parsed_request_t req; + ant_http1_buffer_t method; + ant_http1_buffer_t target; + ant_http1_buffer_t header_field; + ant_http1_buffer_t header_value; + ant_http1_buffer_t body; + ant_http_header_t **header_tail; + bool message_complete; +} ant_http1_parser_ctx_t; + +typedef struct { + llhttp_t parser; + ant_http1_parser_ctx_t ctx; + size_t fed_len; +} ant_http1_conn_parser_t; + typedef enum { ANT_HTTP1_PARSE_INCOMPLETE = 0, ANT_HTTP1_PARSE_OK, ANT_HTTP1_PARSE_ERROR, } ant_http1_parse_result_t; + +void ant_http1_free_parsed_request(ant_http1_parsed_request_t *req); +void ant_http1_conn_parser_init(ant_http1_conn_parser_t *cp); +void ant_http1_conn_parser_reset(ant_http1_conn_parser_t *cp); +void ant_http1_conn_parser_free(ant_http1_conn_parser_t *cp); + ant_http1_parse_result_t ant_http1_parse_request( const char *data, size_t len, @@ -36,6 +62,12 @@ ant_http1_parse_result_t ant_http1_parse_request( const char **error_code ); -void ant_http1_free_parsed_request(ant_http1_parsed_request_t *req); +ant_http1_parse_result_t ant_http1_conn_parser_execute( + ant_http1_conn_parser_t *cp, + const char *data, + size_t len, + ant_http1_parsed_request_t *out, + size_t *consumed_out +); #endif diff --git a/include/internal.h b/include/internal.h index f79621a..1494e35 100644 --- a/include/internal.h +++ b/include/internal.h @@ -4,6 +4,7 @@ #include "ant.h" #include "object.h" #include "pool.h" +#include "sugar.h" #include "arena.h" #include "descriptors.h" #include "esm/loader.h" @@ -209,6 +210,10 @@ struct ant_isolate_t { uint32_t jit_active_depth; #endif + uint32_t vm_exec_depth; + bool microtasks_draining; + struct coroutine *active_async_coro; + struct { ant_value_t *items; size_t len; diff --git a/include/modules/headers.h b/include/modules/headers.h index 4874ab4..c912c27 100644 --- a/include/modules/headers.h +++ b/include/modules/headers.h @@ -37,6 +37,7 @@ ant_value_t headers_create_empty(ant_t *js); ant_value_t headers_create_from_init(ant_t *js, ant_value_t init); ant_value_t headers_get_value(ant_t *js, ant_value_t hdrs, const char *name); ant_value_t headers_append_value(ant_t *js, ant_value_t hdrs, ant_value_t name_v, ant_value_t value_v); +ant_value_t headers_append_literal(ant_t *js, ant_value_t hdrs, const char *name, const char *value); headers_guard_t headers_get_guard(ant_value_t hdrs); diff --git a/include/modules/request.h b/include/modules/request.h index 38b9c96..a560ca2 100644 --- a/include/modules/request.h +++ b/include/modules/request.h @@ -33,7 +33,13 @@ void init_request_module(void); request_data_t *request_get_data(ant_value_t obj); ant_value_t request_get_headers(ant_value_t obj); -ant_value_t request_get_signal(ant_value_t obj); +ant_value_t request_get_signal(ant_t *js, ant_value_t obj); + +ant_value_t request_create_from_input_init( + ant_t *js, + ant_value_t input, + ant_value_t init +); ant_value_t request_create( ant_t *js, @@ -45,10 +51,18 @@ ant_value_t request_create( const char *body_type ); -ant_value_t request_create_from_input_init( +ant_value_t request_create_server( ant_t *js, - ant_value_t input, - ant_value_t init + const char *method, + const char *target, + bool absolute_target, + const char *host, + const char *server_hostname, + int server_port, + ant_value_t headers, + const uint8_t *body, + size_t body_len, + const char *body_type ); #endif diff --git a/include/modules/timer.h b/include/modules/timer.h index 8ba641e..55af51b 100644 --- a/include/modules/timer.h +++ b/include/modules/timer.h @@ -7,7 +7,10 @@ void init_timer_module(void); void process_microtasks(ant_t *js); void process_immediates(ant_t *js); void queue_microtask(ant_t *js, ant_value_t callback); -void queue_promise_trigger(ant_value_t promise); +void queue_promise_trigger(ant_t *js, ant_value_t promise); + +bool js_maybe_drain_microtasks(ant_t *js); +bool js_maybe_drain_microtasks_after_async_settle(ant_t *js); int has_pending_timers(void); int has_pending_microtasks(void); diff --git a/include/object.h b/include/object.h index 96df188..c84de1e 100644 --- a/include/object.h +++ b/include/object.h @@ -2,6 +2,7 @@ #define ANT_OBJECT_H #include "types.h" +#include "sugar.h" #include "shapes.h" #include @@ -15,12 +16,22 @@ typedef struct { bool (*deleter)(ant_t *, ant_value_t, const char *, size_t); } ant_exotic_ops_t; +typedef struct promise_handler { + ant_value_t onFulfilled; + ant_value_t onRejected; + ant_value_t nextPromise; + struct coroutine *await_coro; +} promise_handler_t; + typedef struct { ant_value_t value; ant_value_t trigger_parent; + promise_handler_t inline_handler; UT_array *handlers; uint32_t promise_id; + uint16_t handler_count; uint8_t state; + bool trigger_queued; bool has_rejection_handler; bool processing; bool unhandled_reported; diff --git a/include/output.h b/include/output.h new file mode 100644 index 0000000..3dab2fd --- /dev/null +++ b/include/output.h @@ -0,0 +1,34 @@ +#ifndef ANT_OUTPUT_H +#define ANT_OUTPUT_H + +#include +#include +#include +#include + +typedef struct { + char *data; + size_t len; + size_t cap; + bool failed; +} ant_output_buffer_t; + +typedef struct { + FILE *stream; + ant_output_buffer_t buffer; +} ant_output_stream_t; + +ant_output_stream_t *ant_output_stream(FILE *stream); +void ant_output_stream_begin(ant_output_stream_t *out); + +bool ant_output_stream_reserve(ant_output_stream_t *out, size_t extra); +bool ant_output_stream_append(ant_output_stream_t *out, const void *data, size_t len); +bool ant_output_stream_append_cstr(ant_output_stream_t *out, const char *str); +bool ant_output_stream_putc(ant_output_stream_t *out, char ch); +bool ant_output_stream_appendfv(ant_output_stream_t *out, const char *fmt, va_list ap); + +__attribute__((format(printf, 2, 3))) +bool ant_output_stream_appendf(ant_output_stream_t *out, const char *fmt, ...); +bool ant_output_stream_flush(ant_output_stream_t *out); + +#endif diff --git a/include/silver/engine.h b/include/silver/engine.h index 42bbcb9..c0f83d7 100644 --- a/include/silver/engine.h +++ b/include/silver/engine.h @@ -6,11 +6,13 @@ #include "runtime.h" #include "errors.h" #include "gc/objects.h" +#include "modules/timer.h" #include #include #include #include +#include typedef enum { #define OP_DEF(name, size, n_pop, n_push, f) OP_##name, @@ -149,6 +151,7 @@ struct sv_func { bool is_strict; bool is_arrow; bool is_async; + bool has_await; bool is_generator; bool is_method; bool is_tla; @@ -326,6 +329,15 @@ struct sv_vm { sv_handler_t handler_stack[SV_HANDLER_MAX]; sv_upvalue_t *open_upvalues; int handler_depth; + + // TODO: move to nested struct + bool suspended; + bool suspended_resume_pending; + bool suspended_resume_is_error; + + int suspended_entry_fp; + int suspended_saved_fp; + ant_value_t suspended_resume_value; #ifdef ANT_JIT struct { @@ -436,6 +448,11 @@ static inline ant_value_t sv_vm_call( ant_value_t *out_this, bool is_construct_call ); +static inline void sv_vm_maybe_checkpoint_microtasks(ant_t *js) { + if (!js || js->microtasks_draining || js->vm_exec_depth != 0) return; + js_maybe_drain_microtasks(js); +} + typedef struct { ant_value_t this_val; ant_value_t super_val; @@ -816,21 +833,33 @@ static inline ant_value_t sv_vm_call( if (!is_construct_call) js->new_target = js_mkundef(); if (out_this) *out_this = this_val; - if (is_construct_call && vtype(func) == T_OBJ && is_proxy(func)) - return js_proxy_construct(js, func, args, argc, sv_vm_get_new_target(vm, js)); + if (is_construct_call && vtype(func) == T_OBJ && is_proxy(func)) { + ant_value_t result = js_proxy_construct(js, func, args, argc, sv_vm_get_new_target(vm, js)); + sv_vm_maybe_checkpoint_microtasks(js); + return result; + } + if (is_construct_call && !js_is_constructor(js, func)) return js_mkerr_typed(js, JS_ERR_TYPE, "not a constructor"); - if (!is_construct_call && vtype(func) == T_OBJ && is_proxy(func)) - return js_proxy_apply(js, func, this_val, args, argc); + if (!is_construct_call && vtype(func) == T_OBJ && is_proxy(func)) { + ant_value_t result = js_proxy_apply(js, func, this_val, args, argc); + sv_vm_maybe_checkpoint_microtasks(js); + return result; + } if (vtype(func) == T_CFUNC) { ant_value_t cfunc_this = sv_is_nullish_this(this_val) ? js->global : this_val; - return sv_call_cfunc(js, func, cfunc_this, args, argc); + ant_value_t result = sv_call_cfunc(js, func, cfunc_this, args, argc); + sv_vm_maybe_checkpoint_microtasks(js); + return result; } - if (vtype(func) != T_FUNC) - return sv_call_native(js, func, this_val, args, argc); + if (vtype(func) != T_FUNC) { + ant_value_t result = sv_call_native(js, func, this_val, args, argc); + sv_vm_maybe_checkpoint_microtasks(js); + return result; + } sv_closure_t *closure = js_func_closure(func); @@ -845,14 +874,21 @@ static inline ant_value_t sv_vm_call( if (is_construct_call) ctx.this_val = this_val; if (out_this) *out_this = ctx.this_val; - if (closure->call_flags & SV_CALL_IS_DEFAULT_CTOR) - return sv_call_default_ctor(vm, js, closure, &ctx, out_this); + if (closure->call_flags & SV_CALL_IS_DEFAULT_CTOR) { + ant_value_t result = sv_call_default_ctor(vm, js, closure, &ctx, out_this); + sv_vm_maybe_checkpoint_microtasks(js); + return result; + } - if (closure->func != NULL) - return sv_call_resolve_closure(vm, js, closure, func, &ctx, out_this); + if (closure->func != NULL) { + ant_value_t result = sv_call_resolve_closure(vm, js, closure, func, &ctx, out_this); + sv_vm_maybe_checkpoint_microtasks(js); + return result; + } ant_value_t result = sv_call_native(js, func, ctx.this_val, ctx.args, ctx.argc); sv_call_cleanup(js, &ctx); + sv_vm_maybe_checkpoint_microtasks(js); return result; } diff --git a/include/silver/vm.h b/include/silver/vm.h index e36a624..744227f 100644 --- a/include/silver/vm.h +++ b/include/silver/vm.h @@ -38,4 +38,6 @@ ant_value_t sv_execute_entry_tla( ant_value_t this_val ); +ant_value_t sv_resume_suspended(sv_vm_t *vm); + #endif diff --git a/include/sugar.h b/include/sugar.h index 5def6ea..00e9387 100644 --- a/include/sugar.h +++ b/include/sugar.h @@ -3,6 +3,8 @@ #include "types.h" +#include +#include #include #include #include @@ -49,6 +51,7 @@ typedef struct coroutine { ant_value_t awaited_promise; ant_value_t async_promise; + struct coroutine *active_parent; struct coroutine *prev; struct coroutine *next; @@ -64,6 +67,7 @@ typedef struct coroutine { bool is_done; bool mco_started; bool is_ready; + bool did_suspend; } coroutine_t; typedef struct { @@ -71,6 +75,17 @@ typedef struct { coroutine_t *tail; } coroutine_queue_t; +typedef enum { + JS_AWAIT_PENDING = 0, + JS_AWAIT_FULFILLED, + JS_AWAIT_REJECTED, +} js_await_state_t; + +typedef struct { + js_await_state_t state; + ant_value_t value; +} js_await_result_t; + extern coroutine_queue_t pending_coroutines; extern uint32_t coros_this_tick; @@ -82,6 +97,9 @@ ant_value_t start_async_in_coroutine(ant_t *js, const char *code, size_t code_le ant_value_t resume_coroutine_wrapper(ant_t *js, ant_value_t *args, int nargs); ant_value_t reject_coroutine_wrapper(ant_t *js, ant_value_t *args, int nargs); +js_await_result_t js_promise_await_coroutine(ant_t *js, ant_value_t promise, coroutine_t *coro); +void settle_and_resume_coroutine(ant_t *js, coroutine_t *coro, ant_value_t value, bool is_error); + bool has_ready_coroutines(void); bool has_pending_coroutines(void); diff --git a/src/ant.c b/src/ant.c index 4cecaf3..506d619 100644 --- a/src/ant.c +++ b/src/ant.c @@ -10,6 +10,7 @@ #include "tokens.h" #include "common.h" #include "utils.h" +#include "sugar.h" #include "base64.h" #include "runtime.h" #include "internal.h" @@ -150,12 +151,6 @@ static bool intern_table_rehash(size_t new_bucket_count) { return true; } -typedef struct promise_handler { - ant_value_t onFulfilled; - ant_value_t onRejected; - ant_value_t nextPromise; -} promise_handler_t; - static const UT_icd promise_handler_icd = { .sz = sizeof(promise_handler_t), .init = NULL, @@ -169,6 +164,55 @@ static uint32_t get_promise_id(ant_t *js, ant_value_t p); static ant_promise_state_t *get_promise_data(ant_t *js, ant_value_t promise, bool create); static ant_proxy_state_t *get_proxy_data(ant_value_t obj); +static inline uint32_t promise_handler_count(const ant_promise_state_t *pd) { + return pd ? (uint32_t)pd->handler_count : 0; +} + +static inline bool promise_has_handlers(const ant_promise_state_t *pd) { + return promise_handler_count(pd) != 0; +} + +static bool promise_handler_append(ant_promise_state_t *pd, const promise_handler_t *handler) { + if (!pd || !handler) return false; + + if (pd->handler_count == 0) { + pd->inline_handler = *handler; + pd->handler_count = 1; + return true; + } + + if (pd->handler_count == 1) { + if (!pd->handlers) utarray_new(pd->handlers, &promise_handler_icd); + if (!pd->handlers) return false; + utarray_push_back(pd->handlers, &pd->inline_handler); + utarray_push_back(pd->handlers, handler); + pd->handler_count = 2; + return true; + } + + if (!pd->handlers) utarray_new(pd->handlers, &promise_handler_icd); + if (!pd->handlers) return false; + + utarray_push_back(pd->handlers, handler); + pd->handler_count++; + + return true; +} + +static inline promise_handler_t *promise_handler_at(ant_promise_state_t *pd, uint32_t index) { + if (!pd || index >= pd->handler_count) return NULL; + if (pd->handler_count == 1) return &pd->inline_handler; + if (!pd->handlers) return NULL; + return (promise_handler_t *)utarray_eltptr(pd->handlers, (unsigned int)index); +} + +static inline void promise_handlers_clear(ant_promise_state_t *pd) { + if (!pd) return; + pd->handler_count = 0; + pd->inline_handler = (promise_handler_t){ 0 }; + if (pd->handlers) utarray_clear(pd->handlers); +} + typedef struct { bool has_getter; bool has_setter; @@ -437,7 +481,7 @@ const char *typestr(uint8_t t) { [T_NUM] = "number", [T_BIGINT] = "bigint", [T_STR] = "string", [T_SYMBOL] = "symbol", [T_OBJ] = "object", [T_ARR] = "object", [T_FUNC] = "function", [T_CFUNC] = "function", [T_CLOSURE] = "closure", - [T_PROMISE] = "promise", [T_GENERATOR] = "generator", + [T_PROMISE] = "object", [T_GENERATOR] = "generator", [T_ERR] = "err", [T_TYPEDARRAY] = "typedarray", [T_FFI] = "ffi", [T_NTARG] = "ntarg" }; @@ -9740,12 +9784,15 @@ static ant_promise_state_t *get_promise_data(ant_t *js, ant_value_t promise, boo if (!entry) return NULL; entry->promise_id = next_promise_id++; entry->trigger_parent = js_mkundef(); + entry->inline_handler = (promise_handler_t){ 0 }; + entry->handlers = NULL; + entry->handler_count = 0; entry->state = 0; entry->value = js_mkundef(); + entry->trigger_queued = false; entry->has_rejection_handler = false; entry->processing = false; entry->unhandled_reported = false; - utarray_new(entry->handlers, &promise_handler_icd); obj->promise_state = entry; obj->type_tag = T_PROMISE; return entry; @@ -9756,6 +9803,20 @@ static uint32_t get_promise_id(ant_t *js, ant_value_t p) { return pd ? pd->promise_id : 0; } +bool js_mark_promise_trigger_queued(ant_t *js, ant_value_t promise) { + ant_promise_state_t *pd = get_promise_data(js, promise, false); + if (!pd) return true; + if (pd->trigger_queued) return false; + pd->trigger_queued = true; + return true; +} + +void js_mark_promise_trigger_dequeued(ant_t *js, ant_value_t promise) { + ant_promise_state_t *pd = get_promise_data(js, promise, false); + if (!pd) return; + pd->trigger_queued = false; +} + static ant_value_t make_data_cfunc( ant_t *js, ant_value_t data, ant_value_t (*fn)(ant_t *, ant_value_t *, int) @@ -9808,8 +9869,48 @@ ant_value_t js_promise_then(ant_t *js, ant_value_t promise, ant_value_t on_fulfi return result; } -static inline void trigger_handlers(ant_t *js, ant_value_t p) { - queue_promise_trigger(p); +js_await_result_t js_promise_await_coroutine(ant_t *js, ant_value_t promise, coroutine_t *coro) { + js_await_result_t result = { + .state = JS_AWAIT_PENDING, + .value = js_mkundef(), + }; + + if (vtype(promise) != T_PROMISE || !coro) return result; + + ant_promise_state_t *pd = get_promise_data(js, promise, false); + if (!pd) { + result.state = JS_AWAIT_FULFILLED; + return result; + } + + if (pd->state == 1) { + result.state = JS_AWAIT_FULFILLED; + result.value = pd->value; + return result; + } + + if (pd->state == 2) { + if (pd->unhandled_reported) js_fire_rejection_handled(js, promise, pd->value); + pd->has_rejection_handler = true; + pd->unhandled_reported = false; + result.state = JS_AWAIT_REJECTED; + result.value = pd->value; + return result; + } + + promise_handler_t h = { js_mkundef(), js_mkundef(), js_mkundef(), coro }; + if (!promise_handler_append(pd, &h)) { + result.state = JS_AWAIT_REJECTED; + result.value = js_mkerr(js, "out of memory"); + return result; + } + + if (pd->unhandled_reported) js_fire_rejection_handled(js, promise, pd->value); + pd->has_rejection_handler = true; + pd->unhandled_reported = false; + + gc_root_pending_promise(js_obj_ptr(js_as_obj(promise))); + return result; } void js_process_promise_handlers(ant_t *js, ant_value_t promise) { @@ -9820,13 +9921,19 @@ void js_process_promise_handlers(ant_t *js, ant_value_t promise) { int state = pd->state; ant_value_t val = pd->value; - unsigned int len = utarray_len(pd->handlers); - if (len == 0) { return; } + uint32_t len = promise_handler_count(pd); + if (len == 0) return; gc_root_pending_promise(pobj); pd->processing = true; - for (unsigned int i = 0; i < len; i++) { - promise_handler_t *h = (promise_handler_t *)utarray_eltptr(pd->handlers, i); + for (uint32_t i = 0; i < len; i++) { + promise_handler_t *h = promise_handler_at(pd, i); + if (!h) continue; + if (h->await_coro) { + settle_and_resume_coroutine(js, h->await_coro, val, state != 1); + continue; + } + ant_value_t handler = (state == 1) ? h->onFulfilled : h->onRejected; if (vtype(handler) == T_FUNC || vtype(handler) == T_CFUNC) { @@ -9854,7 +9961,7 @@ void js_process_promise_handlers(ant_t *js, ant_value_t promise) { } pd->processing = false; - utarray_clear(pd->handlers); + promise_handlers_clear(pd); gc_unroot_pending_promise(js_obj_ptr(promise)); } @@ -9878,37 +9985,45 @@ void js_resolve_promise(ant_t *js, ant_value_t p, ant_value_t val) { return; } - ant_value_t res_fn = make_data_cfunc(js, p, builtin_resolve_internal); - GC_ROOT_PIN(js, res_fn); - if (is_err(res_fn)) { - js_reject_promise(js, p, res_fn); + ant_promise_state_t *src_pd = get_promise_data(js, val, false); + if (!src_pd) { + pd->state = 1; + pd->value = val; + gc_write_barrier(js, js_obj_ptr(js_as_obj(p)), val); GC_ROOT_RESTORE(js, root_mark); return; } - ant_value_t rej_fn = make_data_cfunc(js, p, builtin_reject_internal); - GC_ROOT_PIN(js, rej_fn); - if (is_err(rej_fn)) { - js_reject_promise(js, p, rej_fn); - GC_ROOT_RESTORE(js, root_mark); - return; - } + pd->trigger_parent = val; - ant_value_t then_prop = js_get(js, val, "then"); - GC_ROOT_PIN(js, then_prop); - - if (vtype(then_prop) == T_FUNC || vtype(then_prop) == T_CFUNC) { - ant_value_t call_args[] = { res_fn, rej_fn }; - (void)sv_vm_call(js->vm, js, then_prop, val, call_args, 2, NULL, false); + promise_handler_t h = { js_mkundef(), js_mkundef(), p, NULL }; + if (!promise_handler_append(src_pd, &h)) { + pd->state = 2; + pd->value = js_mkerr(js, "out of memory"); + gc_write_barrier(js, js_obj_ptr(js_as_obj(p)), pd->value); GC_ROOT_RESTORE(js, root_mark); return; } + + gc_write_barrier(js, js_obj_ptr(js_as_obj(val)), p); + if (src_pd->unhandled_reported) js_fire_rejection_handled(js, val, src_pd->value); + + src_pd->has_rejection_handler = true; + src_pd->unhandled_reported = false; + + if (src_pd->state == 0) gc_root_pending_promise(js_obj_ptr(js_as_obj(val))); + else queue_promise_trigger(js, val); + + GC_ROOT_RESTORE(js, root_mark); + + return; } pd->state = 1; pd->value = val; + gc_write_barrier(js, js_obj_ptr(js_as_obj(p)), val); - trigger_handlers(js, p); + if (promise_has_handlers(pd)) queue_promise_trigger(js, p); GC_ROOT_RESTORE(js, root_mark); } @@ -9929,7 +10044,7 @@ void js_reject_promise(ant_t *js, ant_value_t p, ant_value_t val) { if (js->pending_rejections.len < js->pending_rejections.cap) js->pending_rejections.items[js->pending_rejections.len++] = p; - trigger_handlers(js, p); + queue_promise_trigger(js, p); } static ant_value_t builtin_resolve_internal(ant_t *js, ant_value_t *args, int nargs) { @@ -10104,8 +10219,11 @@ static ant_value_t builtin_promise_then(ant_t *js, ant_value_t *args, int nargs) ant_promise_state_t *pd = get_promise_data(js, p, false); if (pd) { - promise_handler_t h = { onFulfilled, onRejected, nextP }; - utarray_push_back(pd->handlers, &h); + promise_handler_t h = { onFulfilled, onRejected, nextP, NULL }; + if (!promise_handler_append(pd, &h)) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr(js, "out of memory"); + } gc_write_barrier(js, js_obj_ptr(js_as_obj(p)), nextP); gc_write_barrier(js, js_obj_ptr(js_as_obj(p)), onFulfilled); @@ -10121,8 +10239,9 @@ static ant_value_t builtin_promise_then(ant_t *js, ant_value_t *args, int nargs) gc_root_pending_promise(js_obj_ptr(p)); } - if (pd && pd->state != 0) trigger_handlers(js, p); + if (pd && pd->state != 0) queue_promise_trigger(js, p); GC_ROOT_RESTORE(js, root_mark); + return nextP; } @@ -10169,22 +10288,42 @@ static ant_value_t finally_fulfilled_wrapper(ant_t *js, ant_value_t *args, int n } GC_ROOT_PIN(js, result); - if (vtype(result) == T_PROMISE || (vtype(result) == T_OBJ && vtype(js_get(js, result, "then")) == T_FUNC)) { + if (vtype(result) == T_PROMISE) { ant_value_t thunk_fn = make_data_cfunc(js, value, finally_value_thunk); GC_ROOT_PIN(js, thunk_fn); + if (is_err(thunk_fn)) { GC_ROOT_RESTORE(js, root_mark); return thunk_fn; } ant_value_t identity_rej_fn = js_mkfun(finally_identity_reject); - ant_value_t then_fn = js_get(js, result, "then"); - GC_ROOT_PIN(js, then_fn); + ant_value_t ret = js_promise_then(js, result, thunk_fn, identity_rej_fn); + GC_ROOT_RESTORE(js, root_mark); + + return ret; + } + + if (vtype(result) == T_OBJ) { + ant_value_t then_fn = js_get(js, result, "then"); + GC_ROOT_PIN(js, then_fn); + + if (vtype(then_fn) == T_FUNC || vtype(then_fn) == T_CFUNC) { + ant_value_t thunk_fn = make_data_cfunc(js, value, finally_value_thunk); + GC_ROOT_PIN(js, thunk_fn); + + if (is_err(thunk_fn)) { + GC_ROOT_RESTORE(js, root_mark); + return thunk_fn; + } + + ant_value_t identity_rej_fn = js_mkfun(finally_identity_reject); ant_value_t call_args[] = { thunk_fn, identity_rej_fn }; ant_value_t ret = sv_vm_call(js->vm, js, then_fn, result, call_args, 2, NULL, false); GC_ROOT_RESTORE(js, root_mark); + return ret; - } + }} GC_ROOT_RESTORE(js, root_mark); return value; @@ -10192,9 +10331,11 @@ static ant_value_t finally_fulfilled_wrapper(ant_t *js, ant_value_t *args, int n static ant_value_t finally_rejected_wrapper(ant_t *js, ant_value_t *args, int nargs) { GC_ROOT_SAVE(root_mark, js); + ant_value_t me = js->current_func; ant_value_t callback = get_slot(me, SLOT_DATA); ant_value_t reason = nargs > 0 ? args[0] : js_mkundef(); + GC_ROOT_PIN(js, callback); GC_ROOT_PIN(js, reason); @@ -10208,7 +10349,7 @@ static ant_value_t finally_rejected_wrapper(ant_t *js, ant_value_t *args, int na } GC_ROOT_PIN(js, result); - if (vtype(result) == T_PROMISE || (vtype(result) == T_OBJ && vtype(js_get(js, result, "then")) == T_FUNC)) { + if (vtype(result) == T_PROMISE) { ant_value_t thrower_fn = make_data_cfunc(js, reason, finally_thrower); GC_ROOT_PIN(js, thrower_fn); if (is_err(thrower_fn)) { @@ -10216,19 +10357,37 @@ static ant_value_t finally_rejected_wrapper(ant_t *js, ant_value_t *args, int na return thrower_fn; } ant_value_t identity_rej_fn = js_mkfun(finally_identity_reject); - ant_value_t then_prop = js_get(js, result, "then"); - GC_ROOT_PIN(js, then_prop); - ant_value_t call_args[] = { thrower_fn, identity_rej_fn }; + ant_value_t ret = js_promise_then(js, result, thrower_fn, identity_rej_fn); + GC_ROOT_RESTORE(js, root_mark); + return ret; + } + if (vtype(result) == T_OBJ) { + ant_value_t then_prop = js_get(js, result, "then"); + GC_ROOT_PIN(js, then_prop); + + if (vtype(then_prop) == T_FUNC || vtype(then_prop) == T_CFUNC) { + ant_value_t thrower_fn = make_data_cfunc(js, reason, finally_thrower); + GC_ROOT_PIN(js, thrower_fn); + + if (is_err(thrower_fn)) { + GC_ROOT_RESTORE(js, root_mark); + return thrower_fn; + } + + ant_value_t identity_rej_fn = js_mkfun(finally_identity_reject); + ant_value_t call_args[] = { thrower_fn, identity_rej_fn }; ant_value_t ret = sv_vm_call(js->vm, js, then_prop, result, call_args, 2, NULL, false); GC_ROOT_RESTORE(js, root_mark); + return ret; - } + }} ant_value_t rejected = js_mkpromise(js); GC_ROOT_PIN(js, rejected); js_reject_promise(js, rejected, reason); GC_ROOT_RESTORE(js, root_mark); + return rejected; } @@ -10254,15 +10413,18 @@ static ant_value_t builtin_promise_finally(ant_t *js, ant_value_t *args, int nar ant_value_t args_then[] = { fulfilled_fn, rejected_fn }; ant_value_t ret = builtin_promise_then(js, args_then, 2); GC_ROOT_RESTORE(js, root_mark); + return ret; } static ant_value_t builtin_Promise_try(ant_t *js, ant_value_t *args, int nargs) { if (nargs == 0) return builtin_Promise_resolve(js, args, 0); + ant_value_t fn = args[0]; ant_value_t *call_args = nargs > 1 ? &args[1] : NULL; int call_nargs = nargs > 1 ? nargs - 1 : 0; ant_value_t res = sv_vm_call(js->vm, js, fn, js_mkundef(), call_args, call_nargs, NULL, false); + if (is_err(res)) { ant_value_t reject_val = js->thrown_value; if (vtype(reject_val) == T_UNDEF) reject_val = res; @@ -10272,6 +10434,7 @@ static ant_value_t builtin_Promise_try(ant_t *js, ant_value_t *args, int nargs) ant_value_t rej_args[] = { reject_val }; return builtin_Promise_reject(js, rej_args, 1); } + ant_value_t res_args[] = { res }; return builtin_Promise_resolve(js, res_args, 1); } diff --git a/src/gc/objects.c b/src/gc/objects.c index e7bc60b..185afde 100644 --- a/src/gc/objects.c +++ b/src/gc/objects.c @@ -121,15 +121,31 @@ void gc_remember_add(ant_t *js, ant_object_t *obj) { js->remember_set[js->remember_set_len++] = obj; } -typedef struct { - ant_value_t onFulfilled; - ant_value_t onRejected; - ant_value_t nextPromise; -} promise_handler_t; - #define GC_MARK_STACK_INIT 4096 void gc_mark_value(ant_t *js, ant_value_t v); +static inline void gc_mark_promise_handler(ant_t *js, const promise_handler_t *h) { + if (!h) return; + gc_mark_value(js, h->onFulfilled); + gc_mark_value(js, h->onRejected); + gc_mark_value(js, h->nextPromise); +} + +static inline void gc_mark_promise_handlers(ant_t *js, ant_promise_state_t *pd) { + if (!pd) return; + + if (pd->handler_count == 1) { + gc_mark_promise_handler(js, &pd->inline_handler); + return; + } + + if (pd->handler_count <= 1 || !pd->handlers) return; + + promise_handler_t *h = NULL; + while ((h = (promise_handler_t *)utarray_next(pd->handlers, h))) + gc_mark_promise_handler(js, h); +} + static ant_object_t **gc_mark_stack = NULL; static size_t gc_mark_sp = 0; static size_t gc_mark_cap = 0; @@ -250,15 +266,14 @@ static void gc_scan_obj(ant_t *js, ant_object_t *obj) { } if (obj->shape) { - uint32_t count = ant_shape_count(obj->shape); - for (uint32_t i = 0; i < count && i < obj->prop_count; i++) - gc_mark_value(js, ant_object_prop_get_unchecked(obj, i)); - for (uint32_t i = 0; i < count; i++) { - const ant_shape_prop_t *prop = ant_shape_prop_at(obj->shape, i); - if (prop && prop->has_getter) gc_mark_value(js, prop->getter); - if (prop && prop->has_setter) gc_mark_value(js, prop->setter); - } - } + uint32_t count = ant_shape_count(obj->shape); + for (uint32_t i = 0; i < count && i < obj->prop_count; i++) + gc_mark_value(js, ant_object_prop_get_unchecked(obj, i)); + for (uint32_t i = 0; i < count; i++) { + const ant_shape_prop_t *prop = ant_shape_prop_at(obj->shape, i); + if (prop && prop->has_getter) gc_mark_value(js, prop->getter); + if (prop && prop->has_setter) gc_mark_value(js, prop->setter); + }} if (obj->extra_slots) { ant_extra_slot_t *entries = (ant_extra_slot_t *)obj->extra_slots; @@ -270,16 +285,11 @@ static void gc_scan_obj(ant_t *js, ant_object_t *obj) { for (uint32_t i = 0; i < n; i++) gc_mark_value(js, obj->u.array.data[i]); } - if (obj->promise_state) { - gc_mark_value(js, obj->promise_state->value); - gc_mark_value(js, obj->promise_state->trigger_parent); - if (obj->promise_state->handlers) { - promise_handler_t *h = NULL; - while ((h = (promise_handler_t *)utarray_next(obj->promise_state->handlers, h))) { - gc_mark_value(js, h->onFulfilled); - gc_mark_value(js, h->onRejected); - gc_mark_value(js, h->nextPromise); - }} + ant_promise_state_t *pd = obj->promise_state; + if (pd) { + gc_mark_value(js, pd->value); + gc_mark_value(js, pd->trigger_parent); + gc_mark_promise_handlers(js, pd); } if (obj->proxy_state) { @@ -404,20 +414,24 @@ static void gc_scan_other_stacks(ant_t *js) { } } +static void gc_mark_coroutine(ant_t *js, coroutine_t *c) { + if (!c) return; + gc_scan_vm_stack(js, c->sv_vm); + gc_mark_value(js, c->this_val); + gc_mark_value(js, c->async_func); + gc_mark_value(js, c->async_promise); + gc_mark_value(js, c->awaited_promise); + gc_mark_value(js, c->result); + gc_mark_value(js, c->yield_value); + gc_mark_value(js, c->super_val); + gc_mark_value(js, c->new_target); +} + static void gc_mark_roots(ant_t *js) { gc_scan_vm_stack(js, js->vm); - for (coroutine_t *c = pending_coroutines.head; c; c = c->next) { - gc_scan_vm_stack(js, c->sv_vm); - gc_mark_value(js, c->this_val); - gc_mark_value(js, c->async_func); - gc_mark_value(js, c->async_promise); - gc_mark_value(js, c->awaited_promise); - gc_mark_value(js, c->result); - gc_mark_value(js, c->yield_value); - gc_mark_value(js, c->super_val); - gc_mark_value(js, c->new_target); - } + for (coroutine_t *c = pending_coroutines.head; c; c = c->next) gc_mark_coroutine(js, c); + for (coroutine_t *c = js->active_async_coro; c; c = c->active_parent) gc_mark_coroutine(js, c); gc_mark_value(js, js->global); gc_mark_value(js, js->object); diff --git a/src/http/http1_parser.c b/src/http/http1_parser.c index 598e3fe..0e1d67c 100644 --- a/src/http/http1_parser.c +++ b/src/http/http1_parser.c @@ -8,16 +8,7 @@ #include "http/http1_parser.h" #include "http/http1_writer.h" -typedef struct { - ant_http1_parsed_request_t req; - ant_http1_buffer_t method; - ant_http1_buffer_t target; - ant_http1_buffer_t header_field; - ant_http1_buffer_t header_value; - ant_http1_buffer_t body; - ant_http_header_t **header_tail; - bool message_complete; -} parser_ctx_t; +typedef ant_http1_parser_ctx_t parser_ctx_t; static void parser_ctx_free(parser_ctx_t *ctx) { if (!ctx) return; @@ -108,6 +99,16 @@ static int parser_on_message_complete(llhttp_t *parser) { return HPE_PAUSED; } +static llhttp_settings_t g_request_settings = { + .on_method = parser_on_method, + .on_url = parser_on_url, + .on_header_field = parser_on_header_field, + .on_header_value = parser_on_header_value, + .on_headers_complete = parser_on_headers_complete, + .on_body = parser_on_body, + .on_message_complete = parser_on_message_complete, +}; + ant_http1_parse_result_t ant_http1_parse_request( const char *data, size_t len, @@ -116,13 +117,12 @@ ant_http1_parse_result_t ant_http1_parse_request( const char **error_code ) { llhttp_t parser; - llhttp_settings_t settings; llhttp_errno_t err = HPE_OK; parser_ctx_t ctx = {0}; if (error_reason) *error_reason = NULL; if (error_code) *error_code = NULL; - + memset(out, 0, sizeof(*out)); ctx.header_tail = &ctx.req.headers; @@ -132,16 +132,7 @@ ant_http1_parse_result_t ant_http1_parse_request( ant_http1_buffer_init(&ctx.header_value); ant_http1_buffer_init(&ctx.body); - llhttp_settings_init(&settings); - settings.on_method = parser_on_method; - settings.on_url = parser_on_url; - settings.on_header_field = parser_on_header_field; - settings.on_header_value = parser_on_header_value; - settings.on_headers_complete = parser_on_headers_complete; - settings.on_body = parser_on_body; - settings.on_message_complete = parser_on_message_complete; - - llhttp_init(&parser, HTTP_REQUEST, &settings); + llhttp_init(&parser, HTTP_REQUEST, &g_request_settings); parser.data = &ctx; err = llhttp_execute(&parser, data, len); @@ -192,3 +183,96 @@ void ant_http1_free_parsed_request(ant_http1_parsed_request_t *req) { ant_http_headers_free(req->headers); memset(req, 0, sizeof(*req)); } + +void ant_http1_conn_parser_init(ant_http1_conn_parser_t *cp) { + if (!cp) return; + + memset(cp, 0, sizeof(*cp)); + cp->ctx.header_tail = &cp->ctx.req.headers; + llhttp_init(&cp->parser, HTTP_REQUEST, &g_request_settings); + cp->parser.data = &cp->ctx; +} + +void ant_http1_conn_parser_reset(ant_http1_conn_parser_t *cp) { + if (!cp) return; + + ant_http1_buffer_free(&cp->ctx.method); + ant_http1_buffer_free(&cp->ctx.target); + ant_http1_buffer_free(&cp->ctx.header_field); + ant_http1_buffer_free(&cp->ctx.header_value); + ant_http1_buffer_free(&cp->ctx.body); + + memset(&cp->ctx, 0, sizeof(cp->ctx)); + cp->ctx.header_tail = &cp->ctx.req.headers; + cp->fed_len = 0; + llhttp_reset(&cp->parser); + cp->parser.data = &cp->ctx; +} + +void ant_http1_conn_parser_free(ant_http1_conn_parser_t *cp) { + if (!cp) return; + + ant_http1_buffer_free(&cp->ctx.method); + ant_http1_buffer_free(&cp->ctx.target); + ant_http1_buffer_free(&cp->ctx.header_field); + ant_http1_buffer_free(&cp->ctx.header_value); + ant_http1_buffer_free(&cp->ctx.body); + ant_http1_free_parsed_request(&cp->ctx.req); +} + +ant_http1_parse_result_t ant_http1_conn_parser_execute( + ant_http1_conn_parser_t *cp, + const char *data, + size_t len, + ant_http1_parsed_request_t *out, + size_t *consumed_out +) { + const char *new_data = NULL; + size_t new_len = 0; + size_t old_fed = 0; + + const char *errpos = NULL; + llhttp_errno_t err = HPE_OK; + + if (!cp || !data || !out) return ANT_HTTP1_PARSE_ERROR; + + memset(out, 0, sizeof(*out)); + if (consumed_out) *consumed_out = 0; + if (cp->fed_len > len) return ANT_HTTP1_PARSE_ERROR; + + new_data = data + cp->fed_len; + new_len = len - cp->fed_len; + old_fed = cp->fed_len; + if (new_len == 0) return ANT_HTTP1_PARSE_INCOMPLETE; + + err = llhttp_execute(&cp->parser, new_data, new_len); + errpos = llhttp_get_error_pos(&cp->parser); + if (errpos && consumed_out) + *consumed_out = (size_t)(errpos - new_data) + old_fed; + cp->fed_len = len; + + if (err != HPE_OK && err != HPE_PAUSED) + return ANT_HTTP1_PARSE_ERROR; + + if (!cp->ctx.message_complete) + return ANT_HTTP1_PARSE_INCOMPLETE; + + cp->ctx.req.method = ant_http1_buffer_take(&cp->ctx.method, NULL); + cp->ctx.req.target = ant_http1_buffer_take(&cp->ctx.target, NULL); + cp->ctx.req.body = (uint8_t *)ant_http1_buffer_take(&cp->ctx.body, &cp->ctx.req.body_len); + + if (!cp->ctx.req.method || !cp->ctx.req.target) + return ANT_HTTP1_PARSE_ERROR; + + cp->ctx.req.absolute_target = + strncmp(cp->ctx.req.target, "http://", 7) == 0 || + strncmp(cp->ctx.req.target, "https://", 8) == 0; + + cp->ctx.req.keep_alive = llhttp_should_keep_alive(&cp->parser) == 1; + cp->ctx.req.http_major = cp->parser.http_major; + cp->ctx.req.http_minor = cp->parser.http_minor; + + *out = cp->ctx.req; + memset(&cp->ctx.req, 0, sizeof(cp->ctx.req)); + return ANT_HTTP1_PARSE_OK; +} diff --git a/src/modules/fetch.c b/src/modules/fetch.c index 12a5986..1f6577f 100644 --- a/src/modules/fetch.c +++ b/src/modules/fetch.c @@ -61,10 +61,13 @@ static void remove_pending_request(fetch_request_t *req) { } static void destroy_fetch_request(fetch_request_t *req) { + ant_value_t signal = 0; + if (!req) return; + signal = request_get_signal(req->js, req->request_obj); - if (abort_signal_is_signal(request_get_signal(req->request_obj)) && is_callable(req->abort_listener)) - abort_signal_remove_listener(req->js, request_get_signal(req->request_obj), req->abort_listener); + if (abort_signal_is_signal(signal) && is_callable(req->abort_listener)) + abort_signal_remove_listener(req->js, signal, req->abort_listener); remove_pending_request(req); free(req); @@ -348,7 +351,7 @@ static void fetch_http_on_body(ant_http_request_t *http_req, const uint8_t *chun static ant_value_t fetch_transport_reason(fetch_request_t *req, ant_http_result_t result, const char *error_message) { if (result == ANT_HTTP_RESULT_ABORTED && req->aborted) { - ant_value_t signal = request_get_signal(req->request_obj); + ant_value_t signal = request_get_signal(req->js, req->request_obj); return abort_signal_get_reason(signal); } @@ -632,7 +635,7 @@ static ant_value_t fetch_abort_listener(ant_t *js, ant_value_t *args, int nargs) if (!req || req->aborted) return js_mkundef(); req->aborted = true; - signal = request_get_signal(req->request_obj); + signal = request_get_signal(js, req->request_obj); reason = abort_signal_get_reason(signal); if (req->http_req) ant_http_request_cancel(req->http_req); @@ -679,14 +682,14 @@ static ant_value_t js_fetch(ant_t *js, ant_value_t *args, int nargs) { req->refs = 1; utarray_push_back(pending_requests, &req); - signal = request_get_signal(request_obj); + signal = request_get_signal(js, request_obj); if (abort_signal_is_signal(signal)) { if (abort_signal_is_aborted(signal)) { fetch_reject(req, abort_signal_get_reason(signal)); fetch_request_release(req); return promise; } - + req->abort_listener = js_heavy_mkfun(js, fetch_abort_listener, ANT_PTR(req)); abort_signal_add_listener(js, signal, req->abort_listener); } diff --git a/src/modules/headers.c b/src/modules/headers.c index 39bfd22..92e28f7 100644 --- a/src/modules/headers.c +++ b/src/modules/headers.c @@ -370,20 +370,7 @@ static void free_sorted_view(sorted_pair_t *v, size_t n) { free(v); } -static ant_value_t headers_append_pair(ant_t *js, hdr_list_t *l, ant_value_t name_v, ant_value_t value_v) { - if (vtype(name_v) != T_STR) { - name_v = js_tostring_val(js, name_v); - if (is_err(name_v)) return name_v; - } - - if (vtype(value_v) != T_STR) { - value_v = js_tostring_val(js, value_v); - if (is_err(value_v)) return value_v; - } - - const char *name = js_getstr(js, name_v, NULL); - const char *value = js_getstr(js, value_v, NULL); - +static ant_value_t headers_append_name_value(ant_t *js, hdr_list_t *l, const char *name, const char *value) { if (!is_valid_name(name)) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid header name: %s", name ? name : ""); @@ -402,12 +389,48 @@ static ant_value_t headers_append_pair(ant_t *js, hdr_list_t *l, ant_value_t nam return js_mkundef(); } +static ant_value_t headers_append_pair(ant_t *js, hdr_list_t *l, ant_value_t name_v, ant_value_t value_v) { + const char *name = NULL; + const char *value = NULL; + + if (vtype(name_v) != T_STR) { + name_v = js_tostring_val(js, name_v); + if (is_err(name_v)) return name_v; + } + + if (vtype(value_v) != T_STR) { + value_v = js_tostring_val(js, value_v); + if (is_err(value_v)) return value_v; + } + + name = js_getstr(js, name_v, NULL); + value = js_getstr(js, value_v, NULL); + return headers_append_name_value(js, l, name, value); +} + ant_value_t headers_append_value(ant_t *js, ant_value_t hdrs, ant_value_t name_v, ant_value_t value_v) { hdr_list_t *l = get_list(hdrs); + ant_value_t r = 0; + + if (!l) return js_mkerr(js, "Invalid Headers object"); + r = headers_append_pair(js, l, name_v, value_v); + + if (is_err(r)) return r; + list_apply_guard(l, get_guard(hdrs)); + + return js_mkundef(); +} + +ant_value_t headers_append_literal(ant_t *js, ant_value_t hdrs, const char *name, const char *value) { + hdr_list_t *l = get_list(hdrs); + ant_value_t r = 0; + if (!l) return js_mkerr(js, "Invalid Headers object"); - ant_value_t r = headers_append_pair(js, l, name_v, value_v); + r = headers_append_name_value(js, l, name, value); + if (is_err(r)) return r; list_apply_guard(l, get_guard(hdrs)); + return js_mkundef(); } diff --git a/src/modules/io.c b/src/modules/io.c index 8bdc927..2657f84 100644 --- a/src/modules/io.c +++ b/src/modules/io.c @@ -10,6 +10,7 @@ #include "common.h" #include "errors.h" +#include "output.h" #include "internal.h" #include "runtime.h" #include "silver/engine.h" @@ -33,10 +34,9 @@ static inline bool io_is_digit_ascii(char c) { return c >= '0' && c <= '9'; } -static void io_print(const char *str, FILE *stream) { +static bool io_print_to_output(const char *str, ant_output_stream_t *out) { if (!io_no_color) { - fputs(str, stream); - return; + return ant_output_stream_append_cstr(out, str); } static void *states[] = {&&normal, &&esc, &&csi, &&done}; @@ -48,7 +48,7 @@ static void io_print(const char *str, FILE *stream) { c = *p++; if (!c) goto *states[3]; if (c == '\x1b') goto *states[1]; - fputc(c, stream); + if (!ant_output_stream_putc(out, c)) return false; goto *states[0]; } @@ -56,8 +56,8 @@ static void io_print(const char *str, FILE *stream) { c = *p++; if (!c) goto *states[3]; if (c == '[') goto *states[2]; - fputc('\x1b', stream); - fputc(c, stream); + if (!ant_output_stream_putc(out, '\x1b')) return false; + if (!ant_output_stream_putc(out, c)) return false; goto *states[0]; } @@ -65,11 +65,11 @@ static void io_print(const char *str, FILE *stream) { c = *p++; if (!c) goto *states[3]; if ((c >= '0' && c <= '9') || c == ';') goto *states[2]; - if (c != 'm') fputc(c, stream); + if (c != 'm' && !ant_output_stream_putc(out, c)) return false; goto *states[0]; } - done: return; + done: return true; } enum char_class { @@ -143,24 +143,24 @@ static int io_iso_utc_token_len(const char *p) { #define KEYWORD(kw, color) \ if (memcmp(p, kw, sizeof(kw) - 1) == 0 && !isalnum((unsigned char)p[sizeof(kw) - 1]) && p[sizeof(kw) - 1] != '_') { \ - fputs(color, stream); fputs(kw, stream); fputs(C_RESET, stream); \ + ant_output_stream_append_cstr(out, color); ant_output_stream_append_cstr(out, kw); ant_output_stream_append_cstr(out, C_RESET); \ p += sizeof(kw) - 1; goto next; \ } #define EMIT_UNTIL(end_char, color) \ - fputs(color, stream); \ - while (*p && *p != end_char) fputc(*p++, stream); \ - if (*p == end_char) fputc(*p++, stream); \ - fputs(C_RESET, stream); goto next; + ant_output_stream_append_cstr(out, color); \ + while (*p && *p != end_char) ant_output_stream_putc(out, *p++); \ + if (*p == end_char) ant_output_stream_putc(out, *p++); \ + ant_output_stream_append_cstr(out, C_RESET); goto next; #define EMIT_TYPE(tag, len, color) \ if (!(is_key && brace_depth > 0) && memcmp(p, tag, len) == 0) { \ - fputs(color, stream); fputs(tag, stream); fputs(C_RESET, stream); \ + ant_output_stream_append_cstr(out, color); ant_output_stream_append_cstr(out, tag); ant_output_stream_append_cstr(out, C_RESET); \ p += len; goto next; \ } -void print_value_colored(const char *str, FILE *stream) { - if (io_no_color) { io_print(str, stream); return; } +static void print_value_colored_to_output(const char *str, ant_output_stream_t *out) { + if (io_no_color) { io_print_to_output(str, out); return; } static void *dispatch[] = { [CC_NUL] = &&done, [CC_QUOTE] = &"e, [CC_ESCAPE] = &&other, @@ -186,22 +186,22 @@ done: quote: string_char = *p; - fputs((is_key && brace_depth > 0) ? JSON_KEY : JSON_STRING, stream); - fputc(*p++, stream); + ant_output_stream_append_cstr(out, (is_key && brace_depth > 0) ? JSON_KEY : JSON_STRING); + ant_output_stream_putc(out, *p++); while (*p) { - if (*p == '\\' && p[1]) { fputc(*p++, stream); fputc(*p++, stream); continue; } - if (*p == string_char) { fputc(*p++, stream); break; } - fputc(*p++, stream); + if (*p == '\\' && p[1]) { ant_output_stream_putc(out, *p++); ant_output_stream_putc(out, *p++); continue; } + if (*p == string_char) { ant_output_stream_putc(out, *p++); break; } + ant_output_stream_putc(out, *p++); } - fputs(C_RESET, stream); + ant_output_stream_append_cstr(out, C_RESET); goto next; lbrace: - fputs(JSON_BRACE, stream); fputc(*p++, stream); fputs(C_RESET, stream); + ant_output_stream_append_cstr(out, JSON_BRACE); ant_output_stream_putc(out, *p++); ant_output_stream_append_cstr(out, C_RESET); brace_depth++; is_key = true; goto next; rbrace: - fputs(JSON_BRACE, stream); fputc(*p++, stream); fputs(C_RESET, stream); + ant_output_stream_append_cstr(out, JSON_BRACE); ant_output_stream_putc(out, *p++); ant_output_stream_append_cstr(out, C_RESET); brace_depth--; is_key = false; goto next; lbrack: @@ -218,59 +218,57 @@ lbrack: case 'U': if (memcmp(p + 2, "int8Contents]", 13) == 0) { EMIT_UNTIL(']', JSON_FUNC) } break; case 'P': if (memcmp(p + 2, "romise]", 7) == 0) { EMIT_UNTIL(']', JSON_TAG) } break; } - fputs(JSON_BRACE, stream); fputc(*p++, stream); fputs(C_RESET, stream); + ant_output_stream_append_cstr(out, JSON_BRACE); ant_output_stream_putc(out, *p++); ant_output_stream_append_cstr(out, C_RESET); array_depth++; is_key = false; goto next; rbrack: - fputs(JSON_BRACE, stream); fputc(*p++, stream); fputs(C_RESET, stream); + ant_output_stream_append_cstr(out, JSON_BRACE); ant_output_stream_putc(out, *p++); ant_output_stream_append_cstr(out, C_RESET); array_depth--; is_key = false; goto next; colon: - fputc(*p++, stream); is_key = false; goto next; + ant_output_stream_putc(out, *p++); is_key = false; goto next; separator: - fputc(*p++, stream); + ant_output_stream_putc(out, *p++); is_key = (brace_depth > 0 && array_depth == 0); goto next; -number: - { +number: { int iso_len = io_iso_utc_token_len(p); if (iso_len > 0) { - fputs(C_MAGENTA, stream); - for (int k = 0; k < iso_len; k++) fputc(*p++, stream); - fputs(C_RESET, stream); + ant_output_stream_append_cstr(out, C_MAGENTA); + for (int k = 0; k < iso_len; k++) ant_output_stream_putc(out, *p++); + ant_output_stream_append_cstr(out, C_RESET); goto next; } } - fputs(JSON_NUMBER, stream); + ant_output_stream_append_cstr(out, JSON_NUMBER); while ((*p >= '0' && *p <= '9') || *p == '.' || *p == 'e' || *p == 'E' || *p == '+' || *p == '-') - fputc(*p++, stream); - fputs(C_RESET, stream); + ant_output_stream_putc(out, *p++); + ant_output_stream_append_cstr(out, C_RESET); goto next; -minus: - { +minus: { int iso_len = io_iso_utc_token_len(p); if (iso_len > 0) { - fputs(C_MAGENTA, stream); - for (int k = 0; k < iso_len; k++) fputc(*p++, stream); - fputs(C_RESET, stream); + ant_output_stream_append_cstr(out, C_MAGENTA); + for (int k = 0; k < iso_len; k++) ant_output_stream_putc(out, *p++); + ant_output_stream_append_cstr(out, C_RESET); goto next; } } if (memcmp(p + 1, "Infinity", 8) == 0 && !isalnum((unsigned char)p[9]) && p[9] != '_') { - fputs(JSON_NUMBER, stream); fputs("-Infinity", stream); fputs(C_RESET, stream); + ant_output_stream_append_cstr(out, JSON_NUMBER); ant_output_stream_append_cstr(out, "-Infinity"); ant_output_stream_append_cstr(out, C_RESET); p += 9; goto next; } if (p[1] >= '0' && p[1] <= '9') { - fputs(JSON_NUMBER, stream); fputc(*p++, stream); + ant_output_stream_append_cstr(out, JSON_NUMBER); ant_output_stream_putc(out, *p++); while ((*p >= '0' && *p <= '9') || *p == '.' || *p == 'e' || *p == 'E' || *p == '+' || *p == '-') - fputc(*p++, stream); - fputs(C_RESET, stream); + ant_output_stream_putc(out, *p++); + ant_output_stream_append_cstr(out, C_RESET); goto next; } - fputc(*p++, stream); goto next; + ant_output_stream_putc(out, *p++); goto next; lt: if (memcmp(p, "', JSON_REF) } @@ -278,19 +276,19 @@ lt: if (memcmp(p, "', C_CYAN) } if (p[1] == '>' || (isxdigit((unsigned char)p[1]) && isxdigit((unsigned char)p[2]))) { - fputs(JSON_BRACE, stream); fputc(*p++, stream); - fputs(JSON_WHITE, stream); - while (*p && *p != '>') fputc(*p++, stream); - fputs(C_RESET, stream); - if (*p == '>') { fputs(JSON_BRACE, stream); fputc(*p++, stream); fputs(C_RESET, stream); } + ant_output_stream_append_cstr(out, JSON_BRACE); ant_output_stream_putc(out, *p++); + ant_output_stream_append_cstr(out, JSON_WHITE); + while (*p && *p != '>') ant_output_stream_putc(out, *p++); + ant_output_stream_append_cstr(out, C_RESET); + if (*p == '>') { ant_output_stream_append_cstr(out, JSON_BRACE); ant_output_stream_putc(out, *p++); ant_output_stream_append_cstr(out, C_RESET); } goto next; } - fputs(JSON_BRACE, stream); fputc(*p++, stream); fputs(C_RESET, stream); + ant_output_stream_append_cstr(out, JSON_BRACE); ant_output_stream_putc(out, *p++); ant_output_stream_append_cstr(out, C_RESET); goto next; gt: - fputs(JSON_BRACE, stream); fputc(*p++, stream); fputs(C_RESET, stream); + ant_output_stream_append_cstr(out, JSON_BRACE); ant_output_stream_putc(out, *p++); ant_output_stream_append_cstr(out, C_RESET); goto next; alpha: @@ -309,81 +307,134 @@ alpha: ident: if (is_key && brace_depth > 0) { - fputs(JSON_KEY, stream); - while (isalnum((unsigned char)*p) || *p == '_' || *p == '$') fputc(*p++, stream); - fputs(C_RESET, stream); + ant_output_stream_append_cstr(out, JSON_KEY); + while (isalnum((unsigned char)*p) || *p == '_' || *p == '$') ant_output_stream_putc(out, *p++); + ant_output_stream_append_cstr(out, C_RESET); goto next; } - fputc(*p++, stream); goto next; + ant_output_stream_putc(out, *p++); goto next; other: if (*p == '+') { int iso_len = io_iso_utc_token_len(p); if (iso_len > 0) { - fputs(C_MAGENTA, stream); - for (int k = 0; k < iso_len; k++) fputc(*p++, stream); - fputs(C_RESET, stream); + ant_output_stream_append_cstr(out, C_MAGENTA); + for (int k = 0; k < iso_len; k++) ant_output_stream_putc(out, *p++); + ant_output_stream_append_cstr(out, C_RESET); goto next; } } - fputc(*p++, stream); goto next; + ant_output_stream_putc(out, *p++); goto next; } #undef KEYWORD #undef EMIT_UNTIL +#undef EMIT_TYPE + +void print_value_colored(const char *str, FILE *stream) { + ant_output_stream_t *out = ant_output_stream(stream); + ant_output_stream_begin(out); + print_value_colored_to_output(str, out); + ant_output_stream_flush(out); +} void print_repl_value(ant_t *js, ant_value_t val, FILE *stream) { + ant_output_stream_t *out = ant_output_stream(stream); + if (vtype(val) == T_STR) { char *str = js_getstr(js, val, NULL); - fprintf(stream, "%s'%s'%s\n", C(JSON_STRING), str ? str : "", C(C_RESET)); + ant_output_stream_begin(out); + ant_output_stream_append_cstr(out, C(JSON_STRING)); + ant_output_stream_putc(out, '\''); + ant_output_stream_append_cstr(out, str ? str : ""); + ant_output_stream_putc(out, '\''); + ant_output_stream_append_cstr(out, C(C_RESET)); + ant_output_stream_putc(out, '\n'); + ant_output_stream_flush(out); return; } if (vtype(val) == T_OBJ && vtype(js_get_slot(val, SLOT_ERR_TYPE)) != T_UNDEF) { - const char *stack = get_str_prop(js, val, "stack", 5, NULL); - if (stack) { io_print(stack, stream); fputc('\n', stream); return; } - } + const char *stack = get_str_prop(js, val, "stack", 5, NULL); + + if (stack) { + ant_output_stream_begin(out); + io_print_to_output(stack, out); + ant_output_stream_putc(out, '\n'); + ant_output_stream_flush(out); + return; + }} char cbuf[512]; js_cstr_t cstr = js_to_cstr(js, val, cbuf, sizeof(cbuf)); - if (vtype(val) == T_ERR) fprintf(stderr, "%s\n", cstr.ptr); else { - print_value_colored(cstr.ptr, stream); - fputc('\n', stream); + if (vtype(val) == T_ERR) { + ant_output_stream_t *err_out = ant_output_stream(stderr); + ant_output_stream_begin(err_out); + ant_output_stream_append_cstr(err_out, cstr.ptr); + ant_output_stream_putc(err_out, '\n'); + ant_output_stream_flush(err_out); + } else { + ant_output_stream_begin(out); + print_value_colored_to_output(cstr.ptr, out); + ant_output_stream_putc(out, '\n'); + ant_output_stream_flush(out); } if (cstr.needs_free) free((void *)cstr.ptr); } ant_value_t console_print(ant_t *js, ant_value_t *args, int nargs, const char *color, FILE *stream) { - if (color && !io_no_color) fputs(color, stream); + ant_output_stream_t *out = ant_output_stream(stream); + ant_output_stream_begin(out); + if (color && !io_no_color) ant_output_stream_append_cstr(out, color); for (int i = 0; i < nargs; i++) { - if (i) fputc(' ', stream); - + if (i) ant_output_stream_putc(out, ' '); if (vtype(args[i]) == T_OBJ) { const char *stack = get_str_prop(js, args[i], "stack", 5, NULL); - if (stack) { io_print(stack, stream); continue; } + if (stack) { io_print_to_output(stack, out); continue; } } - + char cbuf[512]; js_cstr_t cstr = js_to_cstr(js, args[i], cbuf, sizeof(cbuf)); - - if (vtype(args[i]) == T_STR) io_print(cstr.ptr, stream); else { - if (color && !io_no_color) fputs(C_RESET, stream); - print_value_colored(cstr.ptr, stream); - if (color && !io_no_color) fputs(color, stream); + + if (vtype(args[i]) == T_STR) io_print_to_output(cstr.ptr, out); else { + if (color && !io_no_color) ant_output_stream_append_cstr(out, C_RESET); + print_value_colored_to_output(cstr.ptr, out); + if (color && !io_no_color) ant_output_stream_append_cstr(out, color); } if (cstr.needs_free) free((void *)cstr.ptr); } - if (color && !io_no_color) fputs(C_RESET, stream); - fputc('\n', stream); + if (color && !io_no_color) ant_output_stream_append_cstr(out, C_RESET); + ant_output_stream_putc(out, '\n'); + ant_output_stream_flush(out); return js_mkundef(); } +static void console_write_args_to_output(ant_t *js, ant_output_stream_t *out, ant_value_t *args, int nargs) { +for (int i = 0; i < nargs; i++) { + if (i) ant_output_stream_putc(out, ' '); + + if (vtype(args[i]) == T_OBJ) { + const char *stack = get_str_prop(js, args[i], "stack", 5, NULL); + if (stack) { + io_print_to_output(stack, out); + continue; + }} + + char cbuf[512]; + js_cstr_t cstr = js_to_cstr(js, args[i], cbuf, sizeof(cbuf)); + + if (vtype(args[i]) == T_STR) io_print_to_output(cstr.ptr, out); + else print_value_colored_to_output(cstr.ptr, out); + + if (cstr.needs_free) free((void *)cstr.ptr); +}} + static ant_value_t js_console_log(ant_t *js, ant_value_t *args, int nargs) { return console_print(js, args, nargs, NULL, stdout); } @@ -397,30 +448,45 @@ static ant_value_t js_console_warn(ant_t *js, ant_value_t *args, int nargs) { } static ant_value_t js_console_assert(ant_t *js, ant_value_t *args, int nargs) { + ant_output_stream_t *out = ant_output_stream(stderr); + if (nargs < 1) return js_mkundef(); bool is_truthy = js_truthy(js, args[0]); if (is_truthy) return js_mkundef(); - fputs("Assertion failed", stderr); + ant_output_stream_begin(out); + ant_output_stream_append_cstr(out, "Assertion failed"); + if (nargs > 1) { - fputs(": ", stderr); - console_print(js, args + 1, nargs - 1, NULL, stderr); + ant_output_stream_append_cstr(out, ": "); + console_write_args_to_output(js, out, args + 1, nargs - 1); + ant_output_stream_putc(out, '\n'); + ant_output_stream_flush(out); return js_mkundef(); } - fputc('\n', stderr); + ant_output_stream_putc(out, '\n'); + ant_output_stream_flush(out); + return js_mkundef(); } static ant_value_t js_console_trace(ant_t *js, ant_value_t *args, int nargs) { - fputs("Trace", stderr); + ant_output_stream_t *out = ant_output_stream(stderr); + + ant_output_stream_begin(out); + ant_output_stream_append_cstr(out, "Trace"); + if (nargs > 0) { - fputs(": ", stderr); - console_print(js, args, nargs, NULL, stderr); - } else fputc('\n', stderr); + ant_output_stream_append_cstr(out, ": "); + console_write_args_to_output(js, out, args, nargs); + ant_output_stream_putc(out, '\n'); + } else ant_output_stream_putc(out, '\n'); + ant_output_stream_flush(out); js_print_stack_trace_vm(js, stderr); + return js_mkundef(); } @@ -434,8 +500,10 @@ static ant_value_t js_console_debug(ant_t *js, ant_value_t *args, int nargs) { static ant_value_t js_console_clear(ant_t *js, ant_value_t *args, int nargs) { if (!io_no_color) { - fprintf(stdout, "\033[2J\033[H"); - fflush(stdout); + ant_output_stream_t *out = ant_output_stream(stdout); + ant_output_stream_begin(out); + ant_output_stream_append_cstr(out, "\033[2J\033[H"); + ant_output_stream_flush(out); } return js_mkundef(); } @@ -450,11 +518,13 @@ static ant_value_t js_console_time(ant_t *js, ant_value_t *args, int nargs) { } for (int i = 0; i < console_timer_count; i++) { - if (strcmp(console_timers[i].label, label) == 0) { - fprintf(stderr, "Timer '%s' already exists\n", label); - return js_mkundef(); - } - } + if (strcmp(console_timers[i].label, label) == 0) { + ant_output_stream_t *out = ant_output_stream(stderr); + ant_output_stream_begin(out); + ant_output_stream_appendf(out, "Timer '%s' already exists\n", label); + ant_output_stream_flush(out); + return js_mkundef(); + }} if (console_timer_count < 64) { console_timers[console_timer_count].label = strdup(label); @@ -472,19 +542,30 @@ static ant_value_t js_console_timeEnd(ant_t *js, ant_value_t *args, int nargs) { } for (int i = 0; i < console_timer_count; i++) { - if (strcmp(console_timers[i].label, label) == 0) { - double elapsed = ((double)uv_hrtime() / 1e6) - console_timers[i].start_time; - fprintf(stdout, "%s: %.3fms\n", label, elapsed); - free(console_timers[i].label); - for (int j = i; j < console_timer_count - 1; j++) { - console_timers[j] = console_timers[j + 1]; - } - console_timer_count--; - return js_mkundef(); + if (strcmp(console_timers[i].label, label) == 0) { + double elapsed = ((double)uv_hrtime() / 1e6) - console_timers[i].start_time; + ant_output_stream_t *out = ant_output_stream(stdout); + + ant_output_stream_begin(out); + ant_output_stream_appendf(out, "%s: %.3fms\n", label, elapsed); + ant_output_stream_flush(out); + free(console_timers[i].label); + + for (int j = i; j < console_timer_count - 1; j++) { + console_timers[j] = console_timers[j + 1]; } + + console_timer_count--; + return js_mkundef(); + }} + + { + ant_output_stream_t *out = ant_output_stream(stderr); + ant_output_stream_begin(out); + ant_output_stream_appendf(out, "Timer '%s' does not exist\n", label); + ant_output_stream_flush(out); } - fprintf(stderr, "Timer '%s' does not exist\n", label); return js_mkundef(); } diff --git a/src/modules/navigator.c b/src/modules/navigator.c index 04cb61e..78d3c88 100644 --- a/src/modules/navigator.c +++ b/src/modules/navigator.c @@ -184,19 +184,11 @@ static void execute_lock_callback(ant_t *js, const char *name, lock_mode_t mode, } if (vtype(result) == T_PROMISE) { - ant_value_t then_fn = js_get(js, result, "then"); - int fn_type = vtype(then_fn); - - if (fn_type == T_FUNC || fn_type == T_CFUNC) { - ant_value_t name_str = js_mkstr(js, name, strlen(name)); - - ant_value_t on_resolve = make_lock_handler(js, js_mkfun(lock_then_handler), name_str, outer_promise); - ant_value_t on_reject = make_lock_handler(js, js_mkfun(lock_catch_handler), name_str, outer_promise); - - ant_value_t then_args[2] = { on_resolve, on_reject }; - sv_vm_call(js->vm, js, then_fn, result, then_args, 2, NULL, false); - return; - } + ant_value_t name_str = js_mkstr(js, name, strlen(name)); + ant_value_t on_resolve = make_lock_handler(js, js_mkfun(lock_then_handler), name_str, outer_promise); + ant_value_t on_reject = make_lock_handler(js, js_mkfun(lock_catch_handler), name_str, outer_promise); + js_promise_then(js, result, on_resolve, on_reject); + return; } release_lock(name); @@ -283,16 +275,12 @@ static ant_value_t locks_request(ant_t *js, ant_value_t *args, int nargs) { ant_value_t result = sv_vm_call(js->vm, js, callback, js_mkundef(), &null_val, 1, NULL, false); if (vtype(result) == T_PROMISE) { - ant_value_t then_fn = js_get(js, result, "then"); - int fn_type = vtype(then_fn); - if (fn_type == T_FUNC || fn_type == T_CFUNC) { - ant_value_t on_resolve = make_lock_handler( - js, js_mkfun(lock_then_handler), - js_mkstr(js, "", 0), promise - ); - sv_vm_call(js->vm, js, then_fn, result, &on_resolve, 1, NULL, false); - return promise; - } + ant_value_t on_resolve = make_lock_handler( + js, js_mkfun(lock_then_handler), + js_mkstr(js, "", 0), promise + ); + js_promise_then(js, result, on_resolve, js_mkundef()); + return promise; } js_resolve_promise(js, promise, result); return promise; @@ -398,4 +386,3 @@ void gc_mark_navigator(ant_t *js, gc_mark_fn mark) { mark(js, req->promise); } } - diff --git a/src/modules/process.c b/src/modules/process.c index a07eda9..6ea1d3f 100644 --- a/src/modules/process.c +++ b/src/modules/process.c @@ -29,6 +29,7 @@ #include "ant.h" #include "errors.h" +#include "output.h" #include "utils.h" #include "internal.h" #include "descriptors.h" @@ -800,10 +801,15 @@ static ant_value_t js_stdout_write(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_false; size_t len = 0; char *data = js_getstr(js, args[0], &len); + + ant_output_stream_t *out = NULL; if (!data) return js_false; - fwrite(data, 1, len, stdout); - fflush(stdout); - return js_true; + + out = ant_output_stream(stdout); + ant_output_stream_begin(out); + + if (!ant_output_stream_append(out, data, len)) return js_false; + return ant_output_stream_flush(out) ? js_true : js_false; } static ant_value_t js_stdout_on(ant_t *js, ant_value_t *args, int nargs) { @@ -900,16 +906,19 @@ static ant_value_t js_stdout_columns_getter(ant_t *js, ant_value_t *args, int na return js_mknum(cols); } - - static ant_value_t js_stderr_write(ant_t *js, ant_value_t *args, int nargs) { if (nargs < 1) return js_false; size_t len = 0; char *data = js_getstr(js, args[0], &len); + + ant_output_stream_t *out = NULL; if (!data) return js_false; - fwrite(data, 1, len, stderr); - fflush(stderr); - return js_true; + + out = ant_output_stream(stderr); + ant_output_stream_begin(out); + + if (!ant_output_stream_append(out, data, len)) return js_false; + return ant_output_stream_flush(out) ? js_true : js_false; } static ant_value_t js_stderr_on(ant_t *js, ant_value_t *args, int nargs) { diff --git a/src/modules/request.c b/src/modules/request.c index d6589c1..143e13f 100644 --- a/src/modules/request.c +++ b/src/modules/request.c @@ -41,8 +41,14 @@ ant_value_t request_get_headers(ant_value_t obj) { return js_get_slot(obj, SLOT_REQUEST_HEADERS); } -ant_value_t request_get_signal(ant_value_t obj) { - return js_get_slot(obj, SLOT_REQUEST_SIGNAL); +ant_value_t request_get_signal(ant_t *js, ant_value_t obj) { + ant_value_t signal = js_get_slot(obj, SLOT_REQUEST_SIGNAL); + + if (vtype(signal) != T_UNDEF) return signal; + signal = abort_signal_create_dependent(js, js_mkundef()); + if (is_err(signal)) return signal; + js_set_slot_wb(js, obj, SLOT_REQUEST_SIGNAL, signal); + return signal; } static void data_free(request_data_t *d) { @@ -80,6 +86,102 @@ static request_data_t *data_new(void) { return d; } +static request_data_t *data_new_server(const char *method) { + request_data_t *d = calloc(1, sizeof(request_data_t)); + if (!d) return NULL; + + d->method = strdup(method ? method : "GET"); + d->referrer = strdup("client"); + d->referrer_policy = strdup(""); + d->mode = strdup("same-origin"); + d->credentials = strdup("same-origin"); + d->cache = strdup("default"); + d->redirect = strdup("follow"); + d->integrity = strdup(""); + + if (!d->method + || !d->referrer + || !d->referrer_policy + || !d->mode + || !d->credentials + || !d->cache + || !d->redirect + || !d->integrity + ) { data_free(d); return NULL; } + + return d; +} + +static ant_value_t request_create_object(ant_t *js, request_data_t *req, ant_value_t headers_obj, bool create_signal) { + ant_value_t obj = js_mkobj(js); + ant_value_t hdrs = is_object_type(headers_obj) + ? headers_obj + : headers_create_empty(js); + + js_set_proto_init(obj, g_request_proto); + js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_REQUEST)); + js_set_slot(obj, SLOT_DATA, ANT_PTR(req)); + + headers_set_guard(hdrs, + strcmp(req->mode, "no-cors") == 0 + ? HEADERS_GUARD_REQUEST_NO_CORS + : HEADERS_GUARD_REQUEST + ); + + headers_apply_guard(hdrs); + js_set_slot_wb(js, obj, SLOT_REQUEST_HEADERS, hdrs); + js_set_slot_wb(js, obj, SLOT_REQUEST_SIGNAL, create_signal + ? abort_signal_create_dependent(js, js_mkundef()) + : js_mkundef() + ); + + return obj; +} + +static char *request_build_server_base_url(const char *host, const char *server_hostname, int server_port) { + const char *authority = (host && host[0]) ? host : server_hostname; + size_t authority_len = authority ? strlen(authority) : 0; + bool include_port = (!host || !host[0]) && server_port > 0 && server_port != 80; + size_t cap = sizeof("http://") - 1 + authority_len + 1 + 16 + 1; + + char *base = malloc(cap); + int written = 0; + + if (!base) return NULL; + if (include_port) written = snprintf(base, cap, "http://%s:%d/", authority ? authority : "", server_port); + else written = snprintf(base, cap, "http://%s/", authority ? authority : ""); + + if (written < 0 || (size_t)written >= cap) { + free(base); + return NULL; + } + + return base; +} + +static int request_parse_server_url( + const char *target, + bool absolute_target, + const char *host, + const char *server_hostname, + int server_port, + url_state_t *out +) { + char *base = NULL; + int rc = 0; + + if (absolute_target) + return parse_url_to_state(target, NULL, out); + + base = request_build_server_base_url(host, server_hostname, server_port); + if (!base) return -1; + + rc = parse_url_to_state(target, base, out); + free(base); + + return rc; +} + static request_data_t *data_dup(const request_data_t *src) { request_data_t *d = calloc(1, sizeof(request_data_t)); if (!d) return NULL; @@ -713,7 +815,7 @@ REQ_GETTER_START(is_history_navigation) REQ_GETTER_END REQ_GETTER_START(signal) - return js_get_slot(this, SLOT_REQUEST_SIGNAL); + return request_get_signal(js, this); REQ_GETTER_END REQ_GETTER_START(duplex) @@ -767,7 +869,7 @@ static ant_value_t js_request_clone(ant_t *js, ant_value_t *args, int nargs) { if (!nd) return js_mkerr(js, "out of memory"); ant_value_t src_headers = js_get_slot(this, SLOT_REQUEST_HEADERS); - ant_value_t src_signal = js_get_slot(this, SLOT_REQUEST_SIGNAL); + ant_value_t src_signal = request_get_signal(js, this); ant_value_t new_headers = headers_create_empty(js); if (is_err(new_headers)) { data_free(nd); return new_headers; } @@ -860,7 +962,7 @@ static ant_value_t request_new_from_input( req = data_dup(src); if (!req) return js_mkerr(js, "out of memory"); req->body_used = false; - *out_input_signal = js_get_slot(input, SLOT_REQUEST_SIGNAL); + *out_input_signal = request_get_signal(js, input); } *out_req = req; @@ -1283,29 +1385,40 @@ ant_value_t request_create(ant_t *js, req->body_type = body_type ? strdup(body_type) : NULL; } req->body_is_stream = false; + return request_create_object(js, req, headers_obj, true); +} - ant_value_t obj = js_mkobj(js); - js_set_proto_init(obj, g_request_proto); - js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_REQUEST)); - js_set_slot(obj, SLOT_DATA, ANT_PTR(req)); +ant_value_t request_create_server( + ant_t *js, + const char *method, + const char *target, + bool absolute_target, + const char *host, + const char *server_hostname, + int server_port, + ant_value_t headers_obj, + const uint8_t *body, + size_t body_len, + const char *body_type +) { + request_data_t *req = data_new_server(method); + if (!req) return js_mkerr(js, "out of memory"); - ant_value_t hdrs = is_object_type(headers_obj) - ? headers_obj - : headers_create_empty(js); - - headers_set_guard(hdrs, - strcmp(req->mode, "no-cors") == 0 - ? HEADERS_GUARD_REQUEST_NO_CORS - : HEADERS_GUARD_REQUEST - ); - - headers_apply_guard(hdrs); - js_set_slot_wb(js, obj, SLOT_REQUEST_HEADERS, hdrs); + if (target && request_parse_server_url(target, absolute_target, host, server_hostname, server_port, &req->url) != 0) + url_state_clear(&req->url); - ant_value_t sig = abort_signal_create_dependent(js, js_mkundef()); - js_set_slot_wb(js, obj, SLOT_REQUEST_SIGNAL, sig); + if (body) req->has_body = true; - return obj; + if (body && body_len > 0) { + req->body_data = malloc(body_len); + if (!req->body_data) { data_free(req); return js_mkerr(js, "out of memory"); } + memcpy(req->body_data, body, body_len); + req->body_size = body_len; + req->body_type = body_type ? strdup(body_type) : NULL; + } + req->body_is_stream = false; + + return request_create_object(js, req, headers_obj, false); } void init_request_module(void) { diff --git a/src/modules/server.c b/src/modules/server.c index 42d141c..7729776 100644 --- a/src/modules/server.c +++ b/src/modules/server.c @@ -27,8 +27,9 @@ #include "modules/response.h" #include "modules/server.h" -typedef struct server_runtime_s server_runtime_t; -typedef struct server_request_s server_request_t; +typedef struct server_runtime_s server_runtime_t; +typedef struct server_request_s server_request_t; +typedef struct server_conn_state_s server_conn_state_t; static server_runtime_t *g_server = NULL; @@ -41,6 +42,7 @@ typedef enum { SERVER_WRITE_NONE = 0, SERVER_WRITE_CLOSE_CLIENT, SERVER_WRITE_STREAM_READ, + SERVER_WRITE_KEEP_ALIVE, } server_write_action_t; typedef struct { @@ -51,31 +53,55 @@ typedef struct { struct server_request_s { server_runtime_t *server; + server_conn_state_t *conn_state; ant_conn_t *conn; + ant_value_t request_obj; ant_value_t response_obj; ant_value_t response_promise; ant_value_t response_reader; ant_value_t response_read_promise; + + struct server_request_s *next; + size_t consumed_len; + int refs; + bool keep_alive; bool response_started; - struct server_request_s *next; +}; + +struct server_conn_state_s { + server_runtime_t *server; + ant_conn_t *conn; + + ant_http1_conn_parser_t parser; + uv_timer_t drain_timer; + server_request_t request; + server_request_t *active_req; + + bool drain_timer_closed; + bool drain_scheduled; }; struct server_runtime_s { ant_t *js; + ant_value_t export_obj; ant_value_t fetch_fn; ant_value_t server_ctx; uv_loop_t *loop; + ant_listener_t listener; uv_signal_t sigint_handle; uv_signal_t sigterm_handle; stop_waiter_t *stop_waiters; server_request_t *requests; + char *hostname; + uint64_t request_timeout_ms; + uint64_t idle_timeout_ms; + int port; - int idle_timeout_secs; bool sigint_closed; bool sigterm_closed; bool stopping; @@ -136,13 +162,42 @@ static void server_remove_request(server_runtime_t *server, server_request_t *re }} } +static void server_request_reset(server_request_t *req) { + server_runtime_t *server = NULL; + server_conn_state_t *conn_state = NULL; + + if (!req) return; + server = req->server; + conn_state = req->conn_state; + + *req = (server_request_t){ + .server = server, + .conn_state = conn_state, + .conn = conn_state ? conn_state->conn : NULL, + .request_obj = js_mkundef(), + .response_obj = js_mkundef(), + .response_promise = js_mkundef(), + .response_reader = js_mkundef(), + .response_read_promise = js_mkundef(), + }; +} + +static void server_conn_state_maybe_free(server_conn_state_t *cs) { + if (!cs) return; + if (cs->conn) return; + if (cs->active_req) return; + if (cs->request.refs > 0) return; + if (!cs->drain_timer_closed) return; + free(cs); +} static void server_request_release(server_request_t *req) { if (!req) return; if (--req->refs > 0) return; server_remove_request(req->server, req); - free(req); + server_request_reset(req); + server_conn_state_maybe_free(req->conn_state); } static server_request_t *server_find_request(server_runtime_t *server, ant_value_t request_obj) { @@ -206,41 +261,13 @@ static void server_signal_cb(uv_signal_t *handle, int signum) { server_begin_stop(server, false); } -static char *server_build_request_url(server_runtime_t *server, const ant_http1_parsed_request_t *req) { - ant_http1_buffer_t url; - ant_http1_buffer_init(&url); - - if (req->absolute_target) return strdup(req->target); - if (!ant_http1_buffer_append_cstr(&url, "http://")) goto oom; - if (req->host && req->host[0]) { - if (!ant_http1_buffer_append_cstr(&url, req->host)) goto oom; - } else { - if (!ant_http1_buffer_append_cstr(&url, server->hostname)) goto oom; - if (server->port != 80 && server->port > 0) { - if (!ant_http1_buffer_appendf(&url, ":%d", server->port)) goto oom; - } - } - - if (!ant_http1_buffer_append_cstr(&url, req->target)) goto oom; - return ant_http1_buffer_take(&url, NULL); - -oom: - ant_http1_buffer_free(&url); - return NULL; -} - static ant_value_t server_headers_from_parsed(ant_t *js, const ant_http1_parsed_request_t *parsed) { ant_value_t headers = headers_create_empty(js); const ant_http_header_t *hdr = NULL; if (is_err(headers)) return headers; for (hdr = parsed->headers; hdr; hdr = hdr->next) { - ant_value_t step = headers_append_value( - js, - headers, - js_mkstr(js, hdr->name, strlen(hdr->name)), - js_mkstr(js, hdr->value, strlen(hdr->value)) - ); + ant_value_t step = headers_append_literal(js, headers, hdr->name, hdr->value); if (is_err(step)) return step; } return headers; @@ -277,8 +304,40 @@ static bool server_response_chunk(server_request_t *req, ant_value_t value, cons } static void server_start_stream_read(server_request_t *req); +static void server_on_read(ant_conn_t *conn, ssize_t nread, void *user_data); static void server_write_cb(ant_conn_t *conn, int status, void *user_data); +static void server_on_deferred_drain(uv_timer_t *handle) { + server_conn_state_t *cs = handle ? (server_conn_state_t *)handle->data : NULL; + + if (!cs) return; + cs->drain_scheduled = false; + + if (!cs->conn || cs->active_req) return; + if (ant_conn_is_closing(cs->conn)) return; + if (ant_conn_buffer_len(cs->conn) == 0) return; + + server_on_read(cs->conn, (ssize_t)ant_conn_buffer_len(cs->conn), cs->server); +} + +static void server_on_drain_timer_close(uv_handle_t *handle) { + server_conn_state_t *cs = handle ? (server_conn_state_t *)handle->data : NULL; + + if (!cs) return; + cs->drain_timer_closed = true; + cs->drain_scheduled = false; + server_conn_state_maybe_free(cs); +} + +static void server_schedule_drain(server_conn_state_t *cs) { + if (!cs || !cs->conn) return; + if (cs->drain_scheduled) return; + + cs->drain_scheduled = true; + if (uv_timer_start(&cs->drain_timer, server_on_deferred_drain, 0, 0) != 0) + cs->drain_scheduled = false; +} + static bool server_queue_write(ant_conn_t *conn, server_request_t *req, char *data, size_t len, server_write_action_t action) { server_write_req_t *wr = calloc(1, sizeof(*wr)); int rc = 0; @@ -378,7 +437,7 @@ static void server_finish_with_response(server_request_t *req, ant_value_t respo status_text = (resp->status_text && resp->status_text[0]) ? resp->status_text : ant_http1_default_status_text(resp->status); ant_http1_buffer_init(&buf); - if (!ant_http1_write_response_head(&buf, resp->status, status_text, headers, body_is_stream, resp->body_size, false)) { + if (!ant_http1_write_response_head(&buf, resp->status, status_text, headers, body_is_stream, resp->body_size, req->keep_alive)) { ant_http1_buffer_free(&buf); ant_conn_close(req->conn); return; @@ -391,10 +450,17 @@ static void server_finish_with_response(server_request_t *req, ant_value_t respo } out = ant_http1_buffer_take(&buf, &out_len); - if (!server_queue_write(req->conn, req, out, out_len, body_is_stream ? SERVER_WRITE_STREAM_READ : SERVER_WRITE_CLOSE_CLIENT)) + if (body_is_stream) { + if (!server_queue_write(req->conn, req, out, out_len, SERVER_WRITE_STREAM_READ)) return; + if (head_only) ant_conn_close(req->conn); return; + } - if (body_is_stream && head_only) ant_conn_close(req->conn); + server_queue_write( + req->conn, + req, out, out_len, + req->keep_alive ? SERVER_WRITE_KEEP_ALIVE : SERVER_WRITE_CLOSE_CLIENT + ); } static ant_value_t server_on_response_reject(ant_t *js, ant_value_t *args, int nargs) { @@ -504,8 +570,14 @@ static ant_value_t server_stream_read_fulfill(ant_t *js, ant_value_t *args, int server_request_release(req); return js_mkundef(); } + out = ant_http1_buffer_take(&buf, &out_len); - server_queue_write(req->conn, req, out, out_len, SERVER_WRITE_CLOSE_CLIENT); + server_queue_write( + req->conn, + req, out, out_len, + req->keep_alive ? SERVER_WRITE_KEEP_ALIVE : SERVER_WRITE_CLOSE_CLIENT + ); + server_request_release(req); return js_mkundef(); } @@ -575,6 +647,7 @@ static bool server_request_ensure_reader(server_request_t *req) { static void server_write_cb(ant_conn_t *conn, int status, void *user_data) { server_write_req_t *wr = (server_write_req_t *)user_data; server_request_t *req = wr->request; + server_conn_state_t *cs = conn ? (server_conn_state_t *)ant_conn_get_user_data(conn) : NULL; if (status < 0 && conn && !ant_conn_is_closing(conn)) ant_conn_close(conn); @@ -588,6 +661,17 @@ static void server_write_cb(ant_conn_t *conn, int status, void *user_data) { } server_start_stream_read(req); break; + + case SERVER_WRITE_KEEP_ALIVE: + if (cs && req) { + ant_conn_consume(conn, req->consumed_len); + ant_http1_conn_parser_reset(&cs->parser); + cs->active_req = NULL; + ant_conn_set_timeout_ms(conn, cs->server->idle_timeout_ms); + server_request_release(req); + if (ant_conn_buffer_len(conn) > 0) server_schedule_drain(cs); + } + break; case SERVER_WRITE_CLOSE_CLIENT: ant_conn_close(conn); @@ -654,34 +738,51 @@ static ant_value_t server_stop(ant_t *js, ant_value_t *args, int nargs) { return promise; } -static void server_process_client_request(ant_conn_t *conn, ant_http1_parsed_request_t *parsed) { - server_runtime_t *server = (server_runtime_t *)ant_listener_get_user_data(ant_conn_listener(conn)); +static void server_process_client_request( + ant_conn_t *conn, + ant_http1_parsed_request_t *parsed, + size_t consumed_len +) { + server_conn_state_t *cs = (server_conn_state_t *)ant_conn_get_user_data(conn); + server_runtime_t *server = cs ? cs->server : NULL; + server_request_t *req = NULL; - ant_t *js = server->js; + ant_t *js = NULL; ant_value_t headers = 0; ant_value_t request_obj = 0; ant_value_t result = 0; - - char *url = NULL; - server_request_t *req = NULL; + bool keep_alive = false; - url = server_build_request_url(server, parsed); - if (!url) { + if (!server || !cs) { ant_http1_free_parsed_request(parsed); - server_send_internal_error(conn, NULL); + ant_conn_close(conn); return; } + js = server->js; + req = &cs->request; + keep_alive = parsed->keep_alive; headers = server_headers_from_parsed(js, parsed); + if (is_err(headers)) { - free(url); ant_http1_free_parsed_request(parsed); server_send_internal_error(conn, NULL); return; } - request_obj = request_create(js, parsed->method, url, headers, parsed->body, parsed->body_len, parsed->content_type); - free(url); + request_obj = request_create_server( + js, + parsed->method, + parsed->target, + parsed->absolute_target, + parsed->host, + server->hostname, + server->port, + headers, + parsed->body, + parsed->body_len, + parsed->content_type + ); ant_http1_free_parsed_request(parsed); if (is_err(request_obj)) { @@ -689,44 +790,50 @@ static void server_process_client_request(ant_conn_t *conn, ant_http1_parsed_req return; } - req = calloc(1, sizeof(*req)); - if (!req) { - server_send_internal_error(conn, NULL); - return; - } - + server_request_reset(req); req->server = server; + req->conn_state = cs; req->conn = conn; req->request_obj = request_obj; - req->response_obj = js_mkundef(); - req->response_promise = js_mkundef(); - req->response_reader = js_mkundef(); - req->response_read_promise = js_mkundef(); + req->consumed_len = consumed_len; + req->keep_alive = keep_alive; req->refs = 1; req->next = server->requests; server->requests = req; - ant_conn_set_user_data(conn, req); + cs->active_req = req; + ant_conn_set_timeout_ms(conn, server->request_timeout_ms); result = server_call_fetch(server, request_obj); server_handle_fetch_result(req, result); } static void server_on_read(ant_conn_t *conn, ssize_t nread, void *user_data) { + server_conn_state_t *cs = (server_conn_state_t *)ant_conn_get_user_data(conn); ant_http1_parsed_request_t parsed = {0}; ant_http1_parse_result_t parse_result = ANT_HTTP1_PARSE_INCOMPLETE; - if (!conn) return; - - parse_result = ant_http1_parse_request(ant_conn_buffer(conn), ant_conn_buffer_len(conn), &parsed, NULL, NULL); + size_t consumed = 0; + + if (!conn || !cs) return; + if (cs->active_req) return; + if (ant_conn_buffer_len(conn) == 0) return; + + ant_conn_set_timeout_ms(conn, cs->server->request_timeout_ms); + parse_result = ant_http1_conn_parser_execute( + &cs->parser, + ant_conn_buffer(conn), + ant_conn_buffer_len(conn), + &parsed, + &consumed + ); + if (parse_result == ANT_HTTP1_PARSE_ERROR) { ant_http1_free_parsed_request(&parsed); server_send_text_response(conn, 400, "Bad Request", "Bad Request"); return; } - if (parse_result == ANT_HTTP1_PARSE_OK) { - ant_conn_pause_read(conn); - server_process_client_request(conn, &parsed); - } + if (parse_result != ANT_HTTP1_PARSE_OK) return; + server_process_client_request(conn, &parsed, consumed); } static void server_on_end(ant_conn_t *conn, void *user_data) { @@ -735,12 +842,21 @@ static void server_on_end(ant_conn_t *conn, void *user_data) { static void server_on_conn_close(ant_conn_t *conn, void *user_data) { server_runtime_t *server = (server_runtime_t *)user_data; - server_request_t *req = (server_request_t *)ant_conn_get_user_data(conn); + server_conn_state_t *cs = (server_conn_state_t *)ant_conn_get_user_data(conn); - if (req) { - req->conn = NULL; + if (cs) { ant_conn_set_user_data(conn, NULL); - server_request_release(req); + cs->conn = NULL; + ant_http1_conn_parser_free(&cs->parser); + if (!uv_is_closing((uv_handle_t *)&cs->drain_timer)) + uv_close((uv_handle_t *)&cs->drain_timer, server_on_drain_timer_close); + if (cs->active_req) { + cs->active_req->conn = NULL; + cs->active_req = NULL; + server_request_release(&cs->request); + cs = NULL; + } + if (cs) server_conn_state_maybe_free(cs); } server_maybe_finish_stop(server); @@ -750,6 +866,39 @@ static void server_on_listener_close(ant_listener_t *listener, void *user_data) server_maybe_finish_stop((server_runtime_t *)user_data); } +static void server_on_accept(ant_listener_t *listener, ant_conn_t *conn, void *user_data) { + server_runtime_t *server = (server_runtime_t *)user_data; + server_conn_state_t *cs = NULL; + + (void)listener; + if (!conn || !server) return; + + cs = calloc(1, sizeof(*cs)); + if (!cs) { + ant_conn_close(conn); + return; + } + + cs->server = server; + cs->conn = conn; + cs->drain_timer_closed = true; + cs->request.server = server; + cs->request.conn_state = cs; + server_request_reset(&cs->request); + + if (uv_timer_init(server->loop, &cs->drain_timer) != 0) { + free(cs); + ant_conn_close(conn); + return; + } + + cs->drain_timer.data = cs; + cs->drain_timer_closed = false; + ant_http1_conn_parser_init(&cs->parser); + ant_conn_set_user_data(conn, cs); + ant_conn_set_no_delay(conn, true); +} + static bool server_export_has_fetch_handler(ant_t *js, ant_value_t default_export, bool *looks_like_config) { ant_value_t fetch = 0; @@ -826,7 +975,8 @@ ant_value_t server_start_from_export(ant_t *js, ant_value_t default_export) { .fetch_fn = js_get(js, default_export, "fetch"), .hostname = strdup("0.0.0.0"), .port = 3000, - .idle_timeout_secs = 10, + .request_timeout_ms = 5000, + .idle_timeout_ms = 5000, .loop = uv_default_loop(), }; @@ -885,17 +1035,20 @@ ant_value_t server_start_from_export(ant_t *js, ant_value_t default_export) { } if (vtype(timeout_v) != T_UNDEF && vtype(timeout_v) != T_NULL) { + double timeout = 0; if (vtype(timeout_v) != T_NUM) { free(server->hostname); free(server); return js_mkerr_typed(js, JS_ERR_TYPE, "server idleTimeout must be a number"); } - server->idle_timeout_secs = (int)js_getnum(timeout_v); - if (server->idle_timeout_secs < 0) { + timeout = js_getnum(timeout_v); + if (timeout < 0) { free(server->hostname); free(server); return js_mkerr_typed(js, JS_ERR_RANGE, "server idleTimeout must be >= 0"); } + server->request_timeout_ms = (uint64_t)(timeout * 1000.0); + server->idle_timeout_ms = server->request_timeout_ms; } uv_signal_init(server->loop, &server->sigint_handle); @@ -903,6 +1056,7 @@ ant_value_t server_start_from_export(ant_t *js, ant_value_t default_export) { server->sigint_handle.data = server; server->sigterm_handle.data = server; + callbacks.on_accept = server_on_accept; callbacks.on_read = server_on_read; callbacks.on_end = server_on_end; callbacks.on_conn_close = server_on_conn_close; @@ -911,7 +1065,7 @@ ant_value_t server_start_from_export(ant_t *js, ant_value_t default_export) { rc = ant_listener_listen_tcp( &server->listener, server->loop, server->hostname, server->port, - 128, (uint64_t)server->idle_timeout_secs * 1000ULL, &callbacks, server + 128, server->idle_timeout_ms, &callbacks, server ); if (rc != 0) { diff --git a/src/modules/timer.c b/src/modules/timer.c index af30ba2..8b26967 100644 --- a/src/modules/timer.c +++ b/src/modules/timer.c @@ -299,9 +299,14 @@ void queue_microtask(ant_t *js, ant_value_t callback) { } } -void queue_promise_trigger(ant_value_t promise) { +void queue_promise_trigger(ant_t *js, ant_value_t promise) { + if (!js_mark_promise_trigger_queued(js, promise)) return; + microtask_entry_t *entry = ant_calloc(sizeof(microtask_entry_t)); - if (entry == NULL) return; + if (entry == NULL) { + js_mark_promise_trigger_dequeued(js, promise); + return; + } entry->callback = js_mkundef(); entry->promise = promise; @@ -316,7 +321,12 @@ void queue_promise_trigger(ant_value_t promise) { } } -void process_microtasks(ant_t *js) { +static void process_microtasks_internal(ant_t *js, bool check_unhandled_rejections) { + if (!js || js->microtasks_draining) return; + js->microtasks_draining = true; + + microtask_entry_t *batch_stop = timer_state.microtasks_tail; + while (timer_state.microtasks != NULL) { microtask_entry_t *entry = timer_state.microtasks; timer_state.microtasks = entry->next; @@ -324,11 +334,12 @@ void process_microtasks(ant_t *js) { if (timer_state.microtasks == NULL) { timer_state.microtasks_tail = NULL; } - + if (vtype(entry->promise) == T_PROMISE) { GC_ROOT_SAVE(root_mark, js); ant_value_t promise = entry->promise; GC_ROOT_PIN(js, promise); + js_mark_promise_trigger_dequeued(js, promise); js_process_promise_handlers(js, promise); GC_ROOT_RESTORE(js, root_mark); } else { @@ -340,10 +351,36 @@ void process_microtasks(ant_t *js) { GC_ROOT_RESTORE(js, root_mark); } + bool reached_batch_end = (entry == batch_stop); free(entry); + if (reached_batch_end) break; } - - js_check_unhandled_rejections(js); + + if (check_unhandled_rejections) js_check_unhandled_rejections(js); + js->microtasks_draining = false; +} + +void process_microtasks(ant_t *js) { + process_microtasks_internal(js, true); +} + +bool js_maybe_drain_microtasks(ant_t *js) { + if (!js) return false; + if (js->microtasks_draining) return false; + if (js->vm_exec_depth != 0) return false; + if (!has_pending_microtasks()) return false; + process_microtasks_internal(js, true); + return true; +} + +bool js_maybe_drain_microtasks_after_async_settle(ant_t *js) { + if (!js) return false; + + if (js->microtasks_draining) return false; + if (!has_pending_microtasks()) return false; + + process_microtasks_internal(js, false); + return true; } void process_immediates(ant_t *js) { diff --git a/src/net/connection.c b/src/net/connection.c index 99f0466..737fec6 100644 --- a/src/net/connection.c +++ b/src/net/connection.c @@ -110,8 +110,7 @@ static void ant_conn_timeout_cb(uv_timer_t *handle) { static void ant_conn_restart_timer(ant_conn_t *conn) { if (!conn || conn->closing) return; - uv_timer_stop(&conn->timer); - if (conn->timeout_ms == 0) return; + if (conn->timeout_ms == 0) { uv_timer_stop(&conn->timer); return; } uv_timer_start(&conn->timer, ant_conn_timeout_cb, conn->timeout_ms, 0); } diff --git a/src/output.c b/src/output.c new file mode 100644 index 0000000..7fc190a --- /dev/null +++ b/src/output.c @@ -0,0 +1,124 @@ +#include // IWYU pragma: keep + +#include +#include +#include +#include + +#include "output.h" + +#define ANT_OUTPUT_INITIAL_CAP 65536 + +static ant_output_stream_t g_stdout_writer = { .stream = NULL }; +static ant_output_stream_t g_stderr_writer = { .stream = NULL }; + +ant_output_stream_t *ant_output_stream(FILE *stream) { + ant_output_stream_t *out = (stream == stderr) ? &g_stderr_writer : &g_stdout_writer; + out->stream = stream; + return out; +} + +void ant_output_stream_begin(ant_output_stream_t *out) { + if (!out) return; + out->buffer.len = 0; + out->buffer.failed = false; +} + +bool ant_output_stream_reserve(ant_output_stream_t *out, size_t extra) { + size_t needed = 0; + size_t next_cap = 0; + char *next = NULL; + + if (!out || out->buffer.failed) return false; + + needed = out->buffer.len + extra; + if (needed <= out->buffer.cap) return true; + + next_cap = out->buffer.cap ? out->buffer.cap * 2 : ANT_OUTPUT_INITIAL_CAP; + while (next_cap < needed) next_cap *= 2; + + next = realloc(out->buffer.data, next_cap); + if (!next) { + out->buffer.failed = true; + return false; + } + + out->buffer.data = next; + out->buffer.cap = next_cap; + return true; +} + +bool ant_output_stream_append(ant_output_stream_t *out, const void *data, size_t len) { + if (!ant_output_stream_reserve(out, len)) return false; + if (len > 0) memcpy(out->buffer.data + out->buffer.len, data, len); + out->buffer.len += len; + return true; +} + +bool ant_output_stream_append_cstr(ant_output_stream_t *out, const char *str) { + return ant_output_stream_append(out, str, strlen(str)); +} + +bool ant_output_stream_putc(ant_output_stream_t *out, char ch) { + return ant_output_stream_append(out, &ch, 1); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" + +bool ant_output_stream_appendfv(ant_output_stream_t *out, const char *fmt, va_list ap) { + char stack[256]; + va_list ap_copy; + int written = 0; + + if (!out || out->buffer.failed) return false; + + va_copy(ap_copy, ap); + written = vsnprintf(stack, sizeof(stack), fmt, ap_copy); + va_end(ap_copy); + + if (written < 0) { + out->buffer.failed = true; + return false; + } + + if ((size_t)written < sizeof(stack)) + return ant_output_stream_append(out, stack, (size_t)written); + + if (!ant_output_stream_reserve(out, (size_t)written + 1)) return false; + vsnprintf(out->buffer.data + out->buffer.len, out->buffer.cap - out->buffer.len, fmt, ap); + out->buffer.len += (size_t)written; + + return true; +} + +bool ant_output_stream_appendf(ant_output_stream_t *out, const char *fmt, ...) { + va_list ap; + bool ok = false; + + va_start(ap, fmt); + ok = ant_output_stream_appendfv(out, fmt, ap); + va_end(ap); + return ok; +} + +#pragma GCC diagnostic pop + +bool ant_output_stream_flush(ant_output_stream_t *out) { + size_t len = 0; + size_t wrote = 0; + + if (!out || !out->stream) return false; + if (out->buffer.failed) { + out->buffer.len = 0; + out->buffer.failed = false; + return false; + } + if (out->buffer.len == 0) return fflush(out->stream) == 0; + + len = out->buffer.len; + wrote = fwrite(out->buffer.data, 1, len, out->stream); + out->buffer.len = 0; + + return wrote == len && fflush(out->stream) == 0; +} diff --git a/src/silver/compiler.c b/src/silver/compiler.c index fb56168..af7c425 100644 --- a/src/silver/compiler.c +++ b/src/silver/compiler.c @@ -90,6 +90,7 @@ typedef struct sv_compiler { int param_count; bool is_arrow; + bool is_async; bool is_strict; sv_compile_mode_t mode; @@ -2631,6 +2632,13 @@ static void compile_tail_return_expr(sv_compiler_t *c, sv_ast_t *expr) { return; } + if (expr->type == N_AWAIT && c->is_async && c->try_depth == 0 && !c->is_tla) { + compile_expr(c, expr->right); + emit_close_upvals(c); + emit_op(c, OP_RETURN); + return; + } + if (is_tail_callable(c, expr)) { compile_tail_call(c, expr); return; @@ -4053,6 +4061,7 @@ static sv_func_t *compile_function_body( comp.enclosing = enclosing; comp.scope_depth = 0; comp.is_arrow = !!(node->flags & FN_ARROW); + comp.is_async = !!(node->flags & FN_ASYNC); comp.is_strict = enclosing->is_strict; comp.mode = mode; comp.strict_args_local = -1; @@ -4428,6 +4437,7 @@ static sv_func_t *compile_function_body( func->is_arrow = comp.is_arrow; func->is_async = !!(node->flags & FN_ASYNC); + func->has_await = false; func->is_generator = !!(node->flags & FN_GENERATOR); func->is_method = !!(node->flags & FN_METHOD); func->is_tla = comp.is_tla; @@ -4445,6 +4455,21 @@ static sv_func_t *compile_function_body( func->name = name; } + if (func->is_async) { + const uint8_t *ip = func->code; + const uint8_t *end = func->code + func->code_len; + while (ip < end) { + sv_op_t op = (sv_op_t)*ip; + if (op == OP_AWAIT || op == OP_FOR_AWAIT_OF || op == OP_AWAIT_ITER_NEXT) { + func->has_await = true; + break; + } + int sz = sv_op_size[op]; + if (sz <= 0) break; + ip += sz; + } + } + free(comp.code); free(comp.constants); free(comp.atoms); diff --git a/src/silver/engine.c b/src/silver/engine.c index 650c0c5..f4cd849 100644 --- a/src/silver/engine.c +++ b/src/silver/engine.c @@ -244,7 +244,7 @@ static inline ant_value_t sv_execute_entry_common( ant_value_t result = sv_execute_frame(vm, func, this_val, super_val, args, argc); if (out_this) *out_this = vm->frames[vm->fp].this; - vm->fp = saved_fp; + if (!vm->suspended) vm->fp = saved_fp; return result; } @@ -268,34 +268,54 @@ ant_value_t sv_execute_closure_entry( ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant_value_t super_val, ant_value_t *args, int argc) { ant_t *js = vm->js; - uint8_t *ip = func->code; - int entry_fp = vm->fp; + bool resuming = vm->suspended && vm->suspended_resume_pending; + uint8_t *ip = resuming ? NULL : func->code; + + int entry_fp = resuming ? vm->suspended_entry_fp : vm->fp; ant_value_t vm_result = js_mkundef(); + ant_value_t suspended_resume_value = js_mkundef(); + + bool suspended_resume_is_error = false; + js->vm_exec_depth++; // TODO: shorthand? sv_frame_t *frame = &vm->frames[vm->fp]; - frame->ip = ip; - frame->func = func; - frame->this = sv_normalize_this_for_frame(js, func, this); - frame->new_target = js->new_target; - frame->super_val = super_val; - frame->prev_sp = vm->sp; - frame->handler_base = vm->handler_depth; - frame->handler_top = vm->handler_depth; - frame->argc = argc; - frame->completion.kind = SV_COMPLETION_NONE; - frame->completion.value = js_mkundef(); - frame->with_obj = js_mkundef(); + if (!resuming) { + frame->ip = ip; + frame->func = func; + frame->this = sv_normalize_this_for_frame(js, func, this); + frame->new_target = js->new_target; + frame->super_val = super_val; + frame->prev_sp = vm->sp; + frame->handler_base = vm->handler_depth; + frame->handler_top = vm->handler_depth; + frame->argc = argc; + frame->completion.kind = SV_COMPLETION_NONE; + frame->completion.value = js_mkundef(); + frame->with_obj = js_mkundef(); + } else { + func = frame->func; + ip = frame->ip; + suspended_resume_value = vm->suspended_resume_value; + suspended_resume_is_error = vm->suspended_resume_is_error; + vm->suspended = false; + vm->suspended_resume_pending = false; + vm->suspended_resume_is_error = false; + vm->suspended_resume_value = js_mkundef(); + } ant_value_t *entry_bp = NULL; ant_value_t *entry_lp = NULL; + + if (!resuming) { ant_value_t stage_err = sv_stage_frame_args(vm, js, func, args, argc, &entry_bp, &entry_lp); if (is_err(stage_err)) { vm_result = stage_err; goto sv_leave; - } + }} + #ifdef ANT_JIT - if (vm->jit_resume.active) { + if (!resuming && vm->jit_resume.active) { ip = func->code + vm->jit_resume.ip_offset; frame->ip = ip; int64_t rl = @@ -312,14 +332,17 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant }} } #endif - frame->bp = entry_bp; - frame->lp = entry_lp; + + if (!resuming) { + frame->bp = entry_bp; + frame->lp = entry_lp; + } ant_value_t *bp = frame->bp; ant_value_t *lp = frame->lp; #ifdef ANT_JIT - if (vm->jit_resume.active) { + if (!resuming && vm->jit_resume.active) { for (int64_t i = 0; i < vm->jit_resume.vstack_sp; i++) vm->stack[vm->sp++] = vm->jit_resume.vstack[i]; @@ -414,6 +437,13 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant #else #define JIT_OSR_BACK_EDGE() ((void)0) #endif + if (resuming) { + if (suspended_resume_is_error) { + sv_err = js_throw(js, suspended_resume_value); + goto sv_throw; + } + vm->stack[vm->sp++] = suspended_resume_value; + } DISPATCH(); L_CONST: { sv_op_const(vm, func, ip); NEXT(5); } @@ -1208,6 +1238,12 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant L_AWAIT: { ant_value_t await_val = vm->stack[--vm->sp]; ant_value_t result = sv_await_value(js, await_val); + if (vm->suspended) { + vm->suspended_entry_fp = entry_fp; + vm->suspended_saved_fp = entry_fp - 1; + frame->ip = ip + 1; + goto sv_leave; + } if (is_err(result)) { sv_err = result; goto sv_throw; } vm->stack[vm->sp++] = result; NEXT(1); @@ -1284,6 +1320,10 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant } sv_leave: + if (vm->suspended) { + if (js->vm_exec_depth > 0) js->vm_exec_depth--; + return vm_result; + } for (int f = vm->fp; f >= entry_fp; f--) { ant_value_t *drop_bp = vm->frames[f].bp; if (drop_bp) sv_close_upvalues_from_slot(vm, drop_bp); @@ -1291,6 +1331,7 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant vm->fp = entry_fp; vm->sp = vm->frames[entry_fp].prev_sp; vm->handler_depth = vm->frames[entry_fp].handler_base; + if (js->vm_exec_depth > 0) js->vm_exec_depth--; return vm_result; @@ -1303,3 +1344,22 @@ ant_value_t sv_execute_frame(sv_vm_t *vm, sv_func_t *func, ant_value_t this, ant (void)bp; (void)sv_op_size; } + +ant_value_t sv_resume_suspended(sv_vm_t *vm) { + if (!vm || !vm->suspended || !vm->suspended_resume_pending || vm->fp < 0) + return mkval(T_ERR, 0); + + int saved_fp = vm->suspended_saved_fp; + sv_frame_t *frame = &vm->frames[vm->fp]; + ant_value_t result = sv_execute_frame( + vm, frame->func, frame->this, frame->super_val, NULL, frame->argc + ); + + if (!vm->suspended) { + vm->fp = saved_fp; + vm->suspended_entry_fp = 0; + vm->suspended_saved_fp = -1; + } + + return result; +} diff --git a/src/silver/ops/async.h b/src/silver/ops/async.h index d729ab9..c6fa4ab 100644 --- a/src/silver/ops/async.h +++ b/src/silver/ops/async.h @@ -2,11 +2,11 @@ #define SV_ASYNC_H #include "ant.h" +#include "gc/roots.h" #include "sugar.h" #include "silver/engine.h" #include - typedef struct { ant_t *js; coroutine_t *coro; @@ -27,6 +27,26 @@ typedef struct { sv_vm_t *vm; } sv_async_ctx_t; +static inline bool sv_async_func_supports_lazy_start(sv_func_t *func) { + if (!func || !func->has_await || func->is_generator) return false; + + for (int off = 0; off < func->code_len;) { + sv_op_t op = (sv_op_t)func->code[off]; + if ( + op == OP_AWAIT_ITER_NEXT || + op == OP_YIELD || + op == OP_YIELD_STAR || + op == OP_INITIAL_YIELD + ) return false; + + int size = sv_op_size[op]; + if (size <= 0) return false; + + off += size; + } + + return true; +} static void sv_mco_async_entry(mco_coro *mco) { sv_async_ctx_t *ctx = (sv_async_ctx_t *)mco_get_user_data(mco); ant_t *js = ctx->js; @@ -52,7 +72,11 @@ static void sv_mco_async_entry(mco_coro *mco) { js->thrown_exists = false; js->thrown_value = js_mkundef(); js_reject_promise(js, promise, reject_value); - } else js_resolve_promise(js, promise, result); + js_maybe_drain_microtasks_after_async_settle(js); + } else { + js_resolve_promise(js, promise, result); + js_maybe_drain_microtasks_after_async_settle(js); + } } typedef struct { @@ -78,7 +102,11 @@ static void sv_mco_tla_entry(mco_coro *mco) { js->thrown_exists = false; js->thrown_value = js_mkundef(); js_reject_promise(js, promise, reject_value); - } else js_resolve_promise(js, promise, result); + js_maybe_drain_microtasks_after_async_settle(js); + } else { + js_resolve_promise(js, promise, result); + js_maybe_drain_microtasks_after_async_settle(js); + } } static inline ant_value_t sv_start_tla(ant_t *js, sv_func_t *func, ant_value_t this_val) { @@ -89,6 +117,78 @@ static inline ant_value_t sv_start_tla(ant_t *js, sv_func_t *func, ant_value_t t } ant_value_t promise = js_mkpromise(js); + + if (func && (!func->has_await || sv_async_func_supports_lazy_start(func))) { + GC_ROOT_SAVE(root_mark, js); + GC_ROOT_PIN(js, this_val); + + sv_vm_t *async_vm = sv_vm_create(js, SV_VM_ASYNC); + if (!async_vm) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr(js, "out of memory for TLA VM"); + } + + coroutine_t *coro = (coroutine_t *)CORO_MALLOC(sizeof(coroutine_t)); + if (!coro) { + sv_vm_destroy(async_vm); + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr(js, "out of memory for TLA coroutine"); + } + + GC_ROOT_PIN(js, promise); + *coro = (coroutine_t){ + .js = js, + .type = CORO_ASYNC_AWAIT, + .this_val = this_val, + .super_val = js_mkundef(), + .new_target = js_mkundef(), + .awaited_promise = js_mkundef(), + .result = js_mkundef(), + .async_func = js_mkundef(), + .args = NULL, + .nargs = 0, + .active_parent = NULL, + .is_settled = false, + .is_error = false, + .is_done = false, + .resume_point = 0, + .yield_value = js_mkundef(), + .async_promise = promise, + .next = NULL, + .mco = NULL, + .mco_started = false, + .is_ready = false, + .did_suspend = false, + .sv_vm = async_vm, + }; + + coro->active_parent = js->active_async_coro; + js->active_async_coro = coro; + + ant_value_t result = sv_execute_entry(async_vm, func, this_val, NULL, 0); + js->active_async_coro = coro->active_parent; + coro->active_parent = NULL; + + if (async_vm->suspended) { + enqueue_coroutine(coro); + GC_ROOT_RESTORE(js, root_mark); + return promise; + } + + if (is_err(result)) { + ant_value_t reject_value = js->thrown_value; + if (vtype(reject_value) == T_UNDEF) reject_value = result; + js->thrown_exists = false; + js->thrown_value = js_mkundef(); + js_reject_promise(js, promise, reject_value); + } else js_resolve_promise(js, promise, result); + + free_coroutine(coro); + GC_ROOT_RESTORE(js, root_mark); + + return promise; + } + sv_tla_ctx_t *ctx = (sv_tla_ctx_t *)CORO_MALLOC(sizeof(sv_tla_ctx_t)); if (!ctx) return js_mkerr(js, "out of memory for TLA context"); @@ -136,6 +236,7 @@ static inline ant_value_t sv_start_tla(ant_t *js, sv_func_t *func, ant_value_t t .async_func = js_mkundef(), .args = NULL, .nargs = 0, + .active_parent = NULL, .is_settled = false, .is_error = false, .is_done = false, @@ -146,6 +247,7 @@ static inline ant_value_t sv_start_tla(ant_t *js, sv_func_t *func, ant_value_t t .mco = mco, .mco_started = false, .is_ready = true, + .did_suspend = false, .sv_vm = async_vm, }; @@ -179,6 +281,116 @@ static inline ant_value_t sv_start_async_closure( "Maximum async operations per tick exceeded"); } + if (caller_vm && closure && closure->func && !closure->func->has_await) { + GC_ROOT_SAVE(root_mark, js); + GC_ROOT_PIN(js, callee_func); + GC_ROOT_PIN(js, super_val); + GC_ROOT_PIN(js, this_val); + + if (args) { + for (int i = 0; i < argc; i++) GC_ROOT_PIN(js, args[i]); + } + + ant_value_t promise = js_mkpromise(js); + GC_ROOT_PIN(js, promise); + + ant_value_t result = sv_execute_closure_entry( + caller_vm, closure, callee_func, super_val, this_val, args, argc, NULL + ); + + if (is_err(result)) { + ant_value_t reject_value = js->thrown_value; + if (vtype(reject_value) == T_UNDEF) reject_value = result; + js->thrown_exists = false; + js->thrown_value = js_mkundef(); + js_reject_promise(js, promise, reject_value); + } else js_resolve_promise(js, promise, result); + + GC_ROOT_RESTORE(js, root_mark); + return promise; + } + + if (closure && closure->func && sv_async_func_supports_lazy_start(closure->func)) { + GC_ROOT_SAVE(root_mark, js); + GC_ROOT_PIN(js, callee_func); + GC_ROOT_PIN(js, super_val); + GC_ROOT_PIN(js, this_val); + + if (args) { + for (int i = 0; i < argc; i++) GC_ROOT_PIN(js, args[i]); + } + + ant_value_t promise = js_mkpromise(js); + sv_vm_t *async_vm = sv_vm_create(js, SV_VM_ASYNC); + if (!async_vm) { + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr(js, "out of memory for async VM"); + } + + coroutine_t *coro = (coroutine_t *)CORO_MALLOC(sizeof(coroutine_t)); + if (!coro) { + sv_vm_destroy(async_vm); + GC_ROOT_RESTORE(js, root_mark); + return js_mkerr(js, "out of memory for coroutine"); + } + + GC_ROOT_PIN(js, promise); + *coro = (coroutine_t){ + .js = js, + .type = CORO_ASYNC_AWAIT, + .this_val = this_val, + .super_val = super_val, + .new_target = js->new_target, + .awaited_promise = js_mkundef(), + .result = js_mkundef(), + .async_func = callee_func, + .args = NULL, + .nargs = argc, + .active_parent = NULL, + .is_settled = false, + .is_error = false, + .is_done = false, + .resume_point = 0, + .yield_value = js_mkundef(), + .async_promise = promise, + .next = NULL, + .mco = NULL, + .mco_started = false, + .is_ready = false, + .did_suspend = false, + .sv_vm = async_vm, + }; + + coro->active_parent = js->active_async_coro; + js->active_async_coro = coro; + + ant_value_t result = sv_execute_closure_entry( + async_vm, closure, callee_func, super_val, this_val, args, argc, NULL + ); + + js->active_async_coro = coro->active_parent; + coro->active_parent = NULL; + + if (async_vm->suspended) { + enqueue_coroutine(coro); + GC_ROOT_RESTORE(js, root_mark); + return promise; + } + + if (is_err(result)) { + ant_value_t reject_value = js->thrown_value; + if (vtype(reject_value) == T_UNDEF) reject_value = result; + js->thrown_exists = false; + js->thrown_value = js_mkundef(); + js_reject_promise(js, promise, reject_value); + } else js_resolve_promise(js, promise, result); + + free_coroutine(coro); + GC_ROOT_RESTORE(js, root_mark); + + return promise; + } + ant_value_t promise = js_mkpromise(js); sv_async_ctx_t *ctx = (sv_async_ctx_t *)CORO_MALLOC(sizeof(sv_async_ctx_t)); if (!ctx) return js_mkerr(js, "out of memory for async context"); @@ -242,6 +454,7 @@ static inline ant_value_t sv_start_async_closure( .async_func = callee_func, .args = ctx->args, .nargs = argc, + .active_parent = NULL, .is_settled = false, .is_error = false, .is_done = false, @@ -252,21 +465,23 @@ static inline ant_value_t sv_start_async_closure( .mco = mco, .mco_started = false, .is_ready = true, + .did_suspend = false, .sv_vm = async_vm, }; ctx->coro = coro; enqueue_coroutine(coro); MCO_RESUME_SAVE(js, mco, res); + mco_state start_status = mco_status(mco); - if (res != MCO_SUCCESS && mco_status(mco) != MCO_DEAD) { + if (res != MCO_SUCCESS && start_status != MCO_DEAD) { remove_coroutine(coro); free_coroutine(coro); return js_mkerr(js, "failed to start async coroutine"); } coro->mco_started = true; - if (mco_status(mco) == MCO_DEAD) { + if (start_status == MCO_DEAD) { remove_coroutine(coro); free_coroutine(coro); } @@ -279,39 +494,40 @@ static inline ant_value_t sv_await_value(ant_t *js, ant_value_t value) { mco_coro *current_mco = mco_running(); if (!current_mco) - return js_mkerr(js, "await can only be used inside async functions"); + current_mco = NULL; coroutine_t *coro = NULL; - sv_coro_header_t *hdr = (sv_coro_header_t *)mco_get_user_data(current_mco); - if (hdr) coro = hdr->coro; + if (current_mco) { + sv_coro_header_t *hdr = (sv_coro_header_t *)mco_get_user_data(current_mco); + if (hdr) coro = hdr->coro; + } else if (js->active_async_coro) coro = js->active_async_coro; if (!coro) - return js_mkerr(js, "invalid async context"); + return js_mkerr(js, "await can only be used inside async functions"); coro->awaited_promise = value; coro->is_settled = false; coro->is_ready = false; - - ant_value_t resume_obj = mkobj(js, 0); - js_set_slot(resume_obj, SLOT_CORO, tov((double)(uintptr_t)coro)); - js_set_slot(resume_obj, SLOT_CFUNC, js_mkfun(resume_coroutine_wrapper)); - - ant_value_t reject_obj = mkobj(js, 0); - js_set_slot(reject_obj, SLOT_CORO, tov((double)(uintptr_t)coro)); - js_set_slot(reject_obj, SLOT_CFUNC, js_mkfun(reject_coroutine_wrapper)); - - ant_value_t then_fn = js_getprop_fallback(js, value, "then"); - if (vtype(then_fn) == T_FUNC || vtype(then_fn) == T_CFUNC) { - ant_value_t then_args[] = { - js_obj_to_func(resume_obj), - js_obj_to_func(reject_obj) - }; - sv_vm_call( - js->vm, js, then_fn, value, - then_args, 2, NULL, false - ); + js_await_result_t await_result = js_promise_await_coroutine(js, value, coro); + + if (await_result.state == JS_AWAIT_FULFILLED) { + coro->is_settled = false; + coro->awaited_promise = js_mkundef(); + return await_result.value; + } + + if (await_result.state == JS_AWAIT_REJECTED) { + coro->is_settled = false; + coro->awaited_promise = js_mkundef(); + return js_throw(js, await_result.value); } + coro->did_suspend = true; + if (!current_mco) { + if (coro->sv_vm) coro->sv_vm->suspended = true; + return js_mkundef(); + } + mco_result mco_res = mco_yield(current_mco); if (mco_res != MCO_SUCCESS) return js_mkerr(js, "failed to yield coroutine"); diff --git a/src/silver/ops/exceptions.h b/src/silver/ops/exceptions.h index 63d865b..8660337 100644 --- a/src/silver/ops/exceptions.h +++ b/src/silver/ops/exceptions.h @@ -150,13 +150,12 @@ static inline uint8_t *sv_vm_throw(sv_vm_t *vm, ant_value_t err, int min_fp) { } ant_value_t caught = err; - if (vtype(err) == T_ERR && js->thrown_exists && - vtype(js->thrown_value) != T_UNDEF) { + if (vtype(err) == T_ERR && js->thrown_exists) { caught = js->thrown_value; js->thrown_value = js_mkundef(); js->thrown_exists = false; } - + vm->sp = h->saved_sp; vm->fp = f; vm->frames[f].completion.kind = SV_COMPLETION_THROW; diff --git a/src/streams/pipes.c b/src/streams/pipes.c index 0d312b5..5be45a0 100644 --- a/src/streams/pipes.c +++ b/src/streams/pipes.c @@ -23,11 +23,7 @@ static void pipes_chain_promise( js_resolve_promise(js, promise, value); } - ant_value_t then_fn = js_get(js, promise, "then"); - if (!is_callable(then_fn)) return; - - ant_value_t then_args[2] = { on_resolve, on_reject }; - sv_vm_call(js->vm, js, then_fn, promise, then_args, 2, NULL, false); + js_promise_then(js, promise, on_resolve, on_reject); } typedef struct { diff --git a/src/streams/readable.c b/src/streams/readable.c index aa64645..028d349 100644 --- a/src/streams/readable.c +++ b/src/streams/readable.c @@ -307,11 +307,7 @@ ant_value_t readable_stream_cancel(ant_t *js, ant_value_t stream_obj, ant_value_ } else if (vtype(result) == T_PROMISE) { ant_value_t res_fn = js_heavy_mkfun(js, rs_cancel_resolve, p); ant_value_t rej_fn = js_heavy_mkfun(js, rs_cancel_reject, p); - ant_value_t then_fn = js_get(js, result, "then"); - if (is_callable(then_fn)) { - ant_value_t then_args[2] = { res_fn, rej_fn }; - sv_vm_call(js->vm, js, then_fn, result, then_args, 2, NULL, false); - } + js_promise_then(js, result, res_fn, rej_fn); } else js_resolve_promise(js, p, js_mkundef()); return p; @@ -360,11 +356,7 @@ void rs_default_controller_call_pull_if_needed(ant_t *js, ant_value_t controller if (vtype(result) == T_PROMISE) { ant_value_t resolve_fn = js_heavy_mkfun(js, rs_pull_resolve_handler, controller_obj); ant_value_t reject_fn = js_heavy_mkfun(js, rs_pull_reject_handler, controller_obj); - ant_value_t then_fn = js_get(js, result, "then"); - if (is_callable(then_fn)) { - ant_value_t then_args[2] = { resolve_fn, reject_fn }; - sv_vm_call(js->vm, js, then_fn, result, then_args, 2, NULL, false); - } + js_promise_then(js, result, resolve_fn, reject_fn); } else if (is_err(result)) { if (stream->state == RS_STATE_READABLE) { ant_value_t thrown = js->thrown_value; @@ -375,11 +367,7 @@ void rs_default_controller_call_pull_if_needed(ant_t *js, ant_value_t controller js_resolve_promise(js, resolved, js_mkundef()); ant_value_t resolve_fn = js_heavy_mkfun(js, rs_pull_resolve_handler, controller_obj); ant_value_t reject_fn = js_heavy_mkfun(js, rs_pull_reject_handler, controller_obj); - ant_value_t then_fn = js_get(js, resolved, "then"); - if (is_callable(then_fn)) { - ant_value_t then_args[2] = { resolve_fn, reject_fn }; - sv_vm_call(js->vm, js, then_fn, resolved, then_args, 2, NULL, false); - } + js_promise_then(js, resolved, resolve_fn, reject_fn); } } else ctrl->pulling = false; } @@ -885,11 +873,7 @@ static ant_value_t js_rs_ctor(ant_t *js, ant_value_t *args, int nargs) { if (vtype(start_result) == T_PROMISE) { ant_value_t resolve_fn = js_heavy_mkfun(js, rs_start_resolve_handler, ctrl_obj); ant_value_t reject_fn = js_heavy_mkfun(js, rs_start_reject_handler, ctrl_obj); - ant_value_t then_fn = js_get(js, start_result, "then"); - if (is_callable(then_fn)) { - ant_value_t then_args[2] = { resolve_fn, reject_fn }; - sv_vm_call(js->vm, js, then_fn, start_result, then_args, 2, NULL, false); - } + js_promise_then(js, start_result, resolve_fn, reject_fn); } if (vtype(start_result) != T_PROMISE) { @@ -897,22 +881,14 @@ static ant_value_t js_rs_ctor(ant_t *js, ant_value_t *args, int nargs) { js_resolve_promise(js, resolved, js_mkundef()); ant_value_t res_fn = js_heavy_mkfun(js, rs_start_resolve_handler, ctrl_obj); ant_value_t rej_fn = js_heavy_mkfun(js, rs_start_reject_handler, ctrl_obj); - ant_value_t then_fn = js_get(js, resolved, "then"); - if (is_callable(then_fn)) { - ant_value_t then_args[2] = { res_fn, rej_fn }; - sv_vm_call(js->vm, js, then_fn, resolved, then_args, 2, NULL, false); - } + js_promise_then(js, resolved, res_fn, rej_fn); } } else { ant_value_t resolved = js_mkpromise(js); js_resolve_promise(js, resolved, js_mkundef()); ant_value_t res_fn = js_heavy_mkfun(js, rs_start_resolve_handler, ctrl_obj); ant_value_t rej_fn = js_heavy_mkfun(js, rs_start_reject_handler, ctrl_obj); - ant_value_t then_fn = js_get(js, resolved, "then"); - if (is_callable(then_fn)) { - ant_value_t then_args[2] = { res_fn, rej_fn }; - sv_vm_call(js->vm, js, then_fn, resolved, then_args, 2, NULL, false); - } + js_promise_then(js, resolved, res_fn, rej_fn); } return obj; @@ -936,12 +912,7 @@ ant_value_t rs_create_stream(ant_t *js, ant_value_t pull_fn, ant_value_t cancel_ js_resolve_promise(js, resolved, js_mkundef()); ant_value_t res_fn = js_heavy_mkfun(js, rs_start_resolve_handler, ctrl_obj); ant_value_t rej_fn = js_heavy_mkfun(js, rs_start_reject_handler, ctrl_obj); - ant_value_t then_fn = js_get(js, resolved, "then"); - - if (is_callable(then_fn)) { - ant_value_t then_args[2] = { res_fn, rej_fn }; - sv_vm_call(js->vm, js, then_fn, resolved, then_args, 2, NULL, false); - } + js_promise_then(js, resolved, res_fn, rej_fn); return obj; } diff --git a/src/streams/transform.c b/src/streams/transform.c index 875ab03..7d41219 100644 --- a/src/streams/transform.c +++ b/src/streams/transform.c @@ -217,11 +217,7 @@ static void ts_chain_promise(ant_t *js, ant_value_t val, ant_value_t res_fn, ant js_resolve_promise(js, promise, val); } - ant_value_t then_fn = js_get(js, promise, "then"); - if (!is_callable(then_fn)) return; - - ant_value_t then_args[2] = { res_fn, rej_fn }; - ant_value_t then_result = sv_vm_call(js->vm, js, then_fn, promise, then_args, 2, NULL, false); + ant_value_t then_result = js_promise_then(js, promise, res_fn, rej_fn); promise_mark_handled(then_result); } @@ -1030,11 +1026,7 @@ ant_value_t js_ts_ctor(ant_t *js, ant_value_t *args, int nargs) { if (vtype(start_result) == T_PROMISE) { ant_value_t resolve_fn = js_heavy_mkfun(js, ts_start_resolve, ts_obj); ant_value_t reject_fn = js_heavy_mkfun(js, ts_start_reject, ts_obj); - ant_value_t then_fn = js_get(js, start_result, "then"); - if (is_callable(then_fn)) { - ant_value_t then_args[2] = { resolve_fn, reject_fn }; - sv_vm_call(js->vm, js, then_fn, start_result, then_args, 2, NULL, false); - } + js_promise_then(js, start_result, resolve_fn, reject_fn); } if (vtype(start_result) != T_PROMISE) { @@ -1042,22 +1034,14 @@ ant_value_t js_ts_ctor(ant_t *js, ant_value_t *args, int nargs) { js_resolve_promise(js, resolved, js_mkundef()); ant_value_t res_fn = js_heavy_mkfun(js, ts_start_resolve, ts_obj); ant_value_t rej_fn = js_heavy_mkfun(js, ts_start_reject, ts_obj); - ant_value_t then_fn = js_get(js, resolved, "then"); - if (is_callable(then_fn)) { - ant_value_t then_args[2] = { res_fn, rej_fn }; - sv_vm_call(js->vm, js, then_fn, resolved, then_args, 2, NULL, false); - } + js_promise_then(js, resolved, res_fn, rej_fn); } } else { ant_value_t resolved = js_mkpromise(js); js_resolve_promise(js, resolved, js_mkundef()); ant_value_t res_fn = js_heavy_mkfun(js, ts_start_resolve, ts_obj); ant_value_t rej_fn = js_heavy_mkfun(js, ts_start_reject, ts_obj); - ant_value_t then_fn = js_get(js, resolved, "then"); - if (is_callable(then_fn)) { - ant_value_t then_args[2] = { res_fn, rej_fn }; - sv_vm_call(js->vm, js, then_fn, resolved, then_args, 2, NULL, false); - } + js_promise_then(js, resolved, res_fn, rej_fn); } return ts_obj; diff --git a/src/streams/writable.c b/src/streams/writable.c index 53e0b70..8eec972 100644 --- a/src/streams/writable.c +++ b/src/streams/writable.c @@ -203,11 +203,7 @@ static void ws_chain_promise(ant_t *js, ant_value_t val, ant_value_t res_fn, ant js_resolve_promise(js, promise, val); } - ant_value_t then_fn = js_get(js, promise, "then"); - if (!is_callable(then_fn)) return; - - ant_value_t then_args[2] = { res_fn, rej_fn }; - ant_value_t then_result = sv_vm_call(js->vm, js, then_fn, promise, then_args, 2, NULL, false); + ant_value_t then_result = js_promise_then(js, promise, res_fn, rej_fn); promise_mark_handled(then_result); } @@ -1208,11 +1204,7 @@ static ant_value_t js_ws_ctor(ant_t *js, ant_value_t *args, int nargs) { if (vtype(start_result) == T_PROMISE) { ant_value_t resolve_fn = js_heavy_mkfun(js, ws_start_resolve_handler, ctrl_obj); ant_value_t reject_fn = js_heavy_mkfun(js, ws_start_reject_handler, ctrl_obj); - ant_value_t then_fn = js_get(js, start_result, "then"); - if (is_callable(then_fn)) { - ant_value_t then_args[2] = { resolve_fn, reject_fn }; - sv_vm_call(js->vm, js, then_fn, start_result, then_args, 2, NULL, false); - } + js_promise_then(js, start_result, resolve_fn, reject_fn); } if (vtype(start_result) != T_PROMISE) { @@ -1220,22 +1212,14 @@ static ant_value_t js_ws_ctor(ant_t *js, ant_value_t *args, int nargs) { js_resolve_promise(js, resolved, js_mkundef()); ant_value_t res_fn = js_heavy_mkfun(js, ws_start_resolve_handler, ctrl_obj); ant_value_t rej_fn = js_heavy_mkfun(js, ws_start_reject_handler, ctrl_obj); - ant_value_t then_fn = js_get(js, resolved, "then"); - if (is_callable(then_fn)) { - ant_value_t then_args[2] = { res_fn, rej_fn }; - sv_vm_call(js->vm, js, then_fn, resolved, then_args, 2, NULL, false); - } + js_promise_then(js, resolved, res_fn, rej_fn); } } else { ant_value_t resolved = js_mkpromise(js); js_resolve_promise(js, resolved, js_mkundef()); ant_value_t res_fn = js_heavy_mkfun(js, ws_start_resolve_handler, ctrl_obj); ant_value_t rej_fn = js_heavy_mkfun(js, ws_start_reject_handler, ctrl_obj); - ant_value_t then_fn = js_get(js, resolved, "then"); - if (is_callable(then_fn)) { - ant_value_t then_args[2] = { res_fn, rej_fn }; - sv_vm_call(js->vm, js, then_fn, resolved, then_args, 2, NULL, false); - } + js_promise_then(js, resolved, res_fn, rej_fn); } return obj; diff --git a/src/sugar.c b/src/sugar.c index 1a9e944..13b6488 100644 --- a/src/sugar.c +++ b/src/sugar.c @@ -1,5 +1,7 @@ #include "internal.h" #include "sugar.h" +#include "modules/timer.h" +#include "silver/engine.h" #include "silver/vm.h" #define MCO_USE_VMEM_ALLOCATOR @@ -55,7 +57,6 @@ void free_coroutine(coroutine_t *coro) { if (!coro) return; if (coro->mco) { - if (mco_running() == coro->mco) fprintf(stderr, "WARNING: Attempting to free a running coroutine\n"); void *ctx = mco_get_user_data(coro->mco); if (ctx) CORO_FREE(ctx); mco_destroy(coro->mco); @@ -90,13 +91,50 @@ static inline void settle_coroutine(coroutine_t *coro, ant_value_t *args, int na } static void resume_coroutine_if_suspended(ant_t *js, coroutine_t *coro) { - if (!coro || !coro->mco || mco_status(coro->mco) != MCO_SUSPENDED) return; + if (!coro) return; + + if (!coro->mco) { + if (!coro->sv_vm || !coro->sv_vm->suspended) return; + + coro->is_ready = false; + coro->sv_vm->suspended_resume_value = coro->result; + coro->sv_vm->suspended_resume_is_error = coro->is_error; + coro->sv_vm->suspended_resume_pending = true; + + coro->active_parent = js->active_async_coro; + js->active_async_coro = coro; + ant_value_t result = sv_resume_suspended(coro->sv_vm); + + js->active_async_coro = coro->active_parent; + coro->active_parent = NULL; + + coro->is_settled = false; + coro->awaited_promise = js_mkundef(); + if (coro->sv_vm->suspended) return; + remove_coroutine(coro); + + if (is_err(result)) { + ant_value_t reject_value = js->thrown_value; + if (vtype(reject_value) == T_UNDEF) reject_value = result; + js->thrown_exists = false; + js->thrown_value = js_mkundef(); + js_reject_promise(js, coro->async_promise, reject_value); + } else js_resolve_promise(js, coro->async_promise, result); + + js_maybe_drain_microtasks_after_async_settle(js); + free_coroutine(coro); + + return; + } + + if (mco_status(coro->mco) != MCO_SUSPENDED) return; coro->is_ready = false; mco_result res; MCO_RESUME_SAVE(js, coro->mco, res); + mco_state status = mco_status(coro->mco); - if (res != MCO_SUCCESS || mco_status(coro->mco) == MCO_DEAD) { + if (res != MCO_SUCCESS || status == MCO_DEAD) { remove_coroutine(coro); free_coroutine(coro); } @@ -130,3 +168,10 @@ ant_value_t reject_coroutine_wrapper(ant_t *js, ant_value_t *args, int nargs) { return js_mkundef(); } + +void settle_and_resume_coroutine(ant_t *js, coroutine_t *coro, ant_value_t value, bool is_error) { + if (!coro) return; + ant_value_t args[1] = { value }; + settle_coroutine(coro, args, 1, is_error); + resume_coroutine_if_suspended(js, coro); +} diff --git a/tests/hono_dispatch_repro.js b/tests/hono_dispatch_repro.js new file mode 100644 index 0000000..43a7be5 --- /dev/null +++ b/tests/hono_dispatch_repro.js @@ -0,0 +1,198 @@ +const TOTAL = Number(process.env.TOTAL || 20000); +const CONCURRENCY = Number(process.env.CONCURRENCY || 64); +const MODE = process.env.MODE || "composeAwaitNext"; + +function nowMs() { + return performance.now(); +} + +function percentile(sorted, p) { + if (sorted.length === 0) return 0; + const index = Math.min(sorted.length - 1, Math.floor(sorted.length * p)); + return sorted[index]; +} + +function makeContext() { + return { + finalized: false, + req: { + routeIndex: -1 + }, + res: null, + text(body, status = 200) { + this.finalized = true; + this.res = { body, status }; + return this.res; + } + }; +} + +function composeLike(middleware, onError, onNotFound) { + return (context, next) => { + let index = -1; + return dispatch(0); + + async function dispatch(i) { + if (i <= index) { + throw new Error("next() called multiple times"); + } + index = i; + + let res; + let isError = false; + let handler; + + if (middleware[i]) { + handler = middleware[i]; + context.req.routeIndex = i; + } else { + handler = i === middleware.length && next || void 0; + } + + if (handler) { + try { + res = await handler(context, () => dispatch(i + 1)); + } catch (err) { + if (err instanceof Error && onError) { + res = await onError(err, context); + isError = true; + } else { + throw err; + } + } + } else if (context.finalized === false && onNotFound) { + res = await onNotFound(context); + } + + if (res && (context.finalized === false || isError)) { + context.res = res; + } + return context; + } + }; +} + +function routeSync(c) { + return c.text("ok"); +} + +function routeResolved(c) { + return Promise.resolve(c.text("ok")); +} + +function routeAsync(c) { + return (async () => c.text("ok"))(); +} + +function middlewareReturnNext(_c, next) { + return next(); +} + +async function middlewareAwaitNext(_c, next) { + return await next(); +} + +async function middlewareAwaitResolvedAround(_c, next) { + await Promise.resolve(); + const out = await next(); + await Promise.resolve(); + return out; +} + +function makeRunner(mode) { + switch (mode) { + case "directSync": + return async () => routeSync(makeContext()); + case "directResolved": + return async () => routeResolved(makeContext()); + case "directAsync": + return async () => routeAsync(makeContext()); + case "composeRouteSync": { + const app = composeLike([routeSync]); + return async () => app(makeContext()); + } + case "composeRouteResolved": { + const app = composeLike([routeResolved]); + return async () => app(makeContext()); + } + case "composeRouteAsync": { + const app = composeLike([routeAsync]); + return async () => app(makeContext()); + } + case "composeReturnNext": { + const app = composeLike([middlewareReturnNext, routeSync]); + return async () => app(makeContext()); + } + case "composeAwaitNext": { + const app = composeLike([middlewareAwaitNext, routeSync]); + return async () => app(makeContext()); + } + case "composeAwaitResolvedAround": { + const app = composeLike([middlewareAwaitResolvedAround, routeSync]); + return async () => app(makeContext()); + } + case "composeReturnNextResolvedRoute": { + const app = composeLike([middlewareReturnNext, routeResolved]); + return async () => app(makeContext()); + } + case "composeAwaitNextResolvedRoute": { + const app = composeLike([middlewareAwaitNext, routeResolved]); + return async () => app(makeContext()); + } + case "composeReturnNextAsyncRoute": { + const app = composeLike([middlewareReturnNext, routeAsync]); + return async () => app(makeContext()); + } + case "composeAwaitNextAsyncRoute": { + const app = composeLike([middlewareAwaitNext, routeAsync]); + return async () => app(makeContext()); + } + default: + throw new Error(`Unknown MODE: ${mode}`); + } +} + +async function main() { + const runOne = makeRunner(MODE); + const latencies = new Array(TOTAL); + let nextIndex = 0; + let completed = 0; + const startedAt = nowMs(); + + async function worker() { + for (;;) { + const index = nextIndex++; + if (index >= TOTAL) return; + + const opStart = nowMs(); + await runOne(); + latencies[index] = nowMs() - opStart; + completed++; + } + } + + const workers = []; + for (let i = 0; i < CONCURRENCY; i++) { + workers.push(worker()); + } + await Promise.all(workers); + + const elapsedMs = nowMs() - startedAt; + latencies.sort((a, b) => a - b); + + console.log(`mode=${MODE}`); + console.log(`total=${TOTAL}`); + console.log(`concurrency=${CONCURRENCY}`); + console.log(`elapsed_ms=${elapsedMs.toFixed(3)}`); + console.log(`avg_ms=${(elapsedMs / TOTAL).toFixed(3)}`); + console.log(`rps=${(TOTAL / (elapsedMs / 1000)).toFixed(1)}`); + console.log(`p50_ms=${percentile(latencies, 0.50).toFixed(3)}`); + console.log(`p95_ms=${percentile(latencies, 0.95).toFixed(3)}`); + console.log(`p99_ms=${percentile(latencies, 0.99).toFixed(3)}`); + console.log(`completed=${completed}`); +} + +main().catch((err) => { + console.error(err && err.stack ? err.stack : String(err)); + process.exitCode = 1; +}); diff --git a/tests/non_async_loop_repro.js b/tests/non_async_loop_repro.js new file mode 100644 index 0000000..4777608 --- /dev/null +++ b/tests/non_async_loop_repro.js @@ -0,0 +1,57 @@ +const TOTAL = Number(process.env.TOTAL || 5000000); +const MODE = process.env.MODE || "routeSync"; + +function nowMs() { + return performance.now(); +} + +function makeContext() { + return { + finalized: false, + req: { + routeIndex: -1 + }, + res: null, + text(body, status = 200) { + this.finalized = true; + this.res = { body, status }; + return this.res; + } + }; +} + +function routeSync(c) { + return c.text("ok"); +} + +function runTightLoop() { + let acc = 0; + for (let i = 0; i < TOTAL; i++) { + acc += i; + } + return acc; +} + +function runRouteSyncLoop() { + let finalized = 0; + for (let i = 0; i < TOTAL; i++) { + const ctx = makeContext(); + routeSync(ctx); + if (ctx.finalized) finalized++; + } + return finalized; +} + +function main() { + const startedAt = nowMs(); + const result = MODE === "tightLoop" ? runTightLoop() : runRouteSyncLoop(); + const elapsedMs = nowMs() - startedAt; + + console.log(`mode=${MODE}`); + console.log(`total=${TOTAL}`); + console.log(`elapsed_ms=${elapsedMs.toFixed(3)}`); + console.log(`ops_per_sec=${(TOTAL / (elapsedMs / 1000)).toFixed(1)}`); + console.log(`result=${result}`); +} + +main(); -- 2.51.2