From b2c54543e86aed2b353b02717ed02e4c58b9410e Mon Sep 17 00:00:00 2001 From: theMackabu Date: Sat, 9 May 2026 13:08:20 -0700 Subject: [PATCH] fix misc memory leaks --- include/net/connection.h | 1 + src/modules/stream.c | 13 +++ src/modules/timer.c | 88 +++++++++++++---- src/modules/tls.c | 39 +++++++- src/net/connection.c | 121 +++++++++++++++++------- src/repl.c | 12 ++- tests/test_net_create_connection.cjs | 13 ++- tests/test_stream_is_errored.cjs | 41 ++++++++ tests/test_timer_fired_timeout_gc.cjs | 22 +++++ tests/test_timer_refresh.cjs | 21 ++++ tests/test_tls_secure_connect_event.cjs | 25 ++++- 11 files changed, 340 insertions(+), 56 deletions(-) create mode 100644 tests/test_stream_is_errored.cjs create mode 100644 tests/test_timer_fired_timeout_gc.cjs diff --git a/include/net/connection.h b/include/net/connection.h index b069fc8..d02093f 100644 --- a/include/net/connection.h +++ b/include/net/connection.h @@ -37,6 +37,7 @@ struct ant_conn_s { uint64_t bytes_written; int close_handles; + bool resolving; bool closing; bool read_paused; bool read_eof; diff --git a/src/modules/stream.c b/src/modules/stream.c index fc281a6..00a04c2 100644 --- a/src/modules/stream.c +++ b/src/modules/stream.c @@ -406,6 +406,7 @@ static void stream_init_readable(ant_t *js, ant_value_t obj, ant_value_t raw_opt js_set(js, state, "endEmitted", js_false); js_set(js, state, "endScheduled", js_false); js_set(js, state, "dataEmitted", js_false); + js_set(js, state, "errored", js_mkundef()); js_set(js, state, "flowing", js_false); js_set(js, state, "flowingReadScheduled", js_false); js_set(js, state, "reading", js_false); @@ -436,6 +437,7 @@ static void stream_init_writable(ant_t *js, ant_value_t obj, ant_value_t raw_opt js_set(js, state, "objectMode", js_bool(object_mode)); js_set(js, state, "finished", js_false); js_set(js, state, "ended", js_false); + js_set(js, state, "errored", js_mkundef()); js_set(js, obj, "_writableState", state); if (is_callable(write_fn)) js_set(js, obj, "_write", write_fn); @@ -459,8 +461,17 @@ static ant_value_t stream_emit_named(ant_t *js, ant_value_t stream_obj, const ch return js_bool(eventemitter_emit_args(js, stream_obj, event_name, NULL, 0)); } +static void stream_set_errored(ant_t *js, ant_value_t stream_obj, ant_value_t error) { + ant_value_t state = stream_readable_state(js, stream_obj); + if (is_object_type(state)) js_set(js, state, "errored", error); + + state = stream_writable_state(js, stream_obj); + if (is_object_type(state)) js_set(js, state, "errored", error); +} + static void stream_emit_error(ant_t *js, ant_value_t stream_obj, ant_value_t error) { ant_value_t args[1]; + stream_set_errored(js, stream_obj, error); args[0] = error; eventemitter_emit_args(js, stream_obj, "error", args, 1); } @@ -701,6 +712,7 @@ static ant_value_t js_stream_destroy(ant_t *js, ant_value_t *args, int nargs) { if (js_truthy(js, js_get(js, stream_obj, "destroyed"))) return stream_obj; js_set(js, stream_obj, "destroyed", js_true); + if (!is_undefined(error) && !is_null(error)) stream_set_errored(js, stream_obj, error); done_state = js_mkobj(js); js_set(js, done_state, "stream", stream_obj); @@ -1059,6 +1071,7 @@ static ant_value_t stream_writable_write_impl( js_truthy(js, js_get(js, stream_obj, "destroyed")) ) { ant_value_t err = js_mkerr(js, "write after end"); + stream_set_errored(js, stream_obj, err); if (is_callable(callback)) stream_call_callback(js, callback, &err, 1); else stream_emit_error(js, stream_obj, err); return js_false; diff --git a/src/modules/timer.c b/src/modules/timer.c index 61ec342..a2e21a8 100644 --- a/src/modules/timer.c +++ b/src/modules/timer.c @@ -116,6 +116,45 @@ static int timer_copy_args(timer_entry_t *entry, ant_value_t *args, int nargs) { return 0; } +static void timer_release_args(timer_entry_t *entry) { + if (!entry) return; + if (entry->args) { + free(entry->args); + entry->args = NULL; + } + entry->nargs = 0; +} + +static void timer_release_callback_args(timer_entry_t *entry) { + if (!entry) return; + entry->callback = js_mkundef(); + timer_release_args(entry); +} + +static int timer_copy_args_from_object(ant_t *js, timer_entry_t *entry, ant_value_t obj) { + ant_value_t args_arr = js_get_slot(obj, SLOT_AUX); + ant_offset_t len = vtype(args_arr) == T_ARR ? js_arr_len(js, args_arr) : 0; + + timer_release_args(entry); + if (len == 0) return 0; + + entry->args = ant_calloc(sizeof(ant_value_t) * (size_t)len); + if (!entry->args) return -1; + entry->nargs = (int)len; + for (ant_offset_t i = 0; i < len; i++) entry->args[i] = js_arr_get(js, args_arr, i); + return 0; +} + +static ant_value_t timer_make_args_array(ant_t *js, ant_value_t *args, int nargs) { + ant_value_t arr = js_mkundef(); + int arg_count = nargs > 2 ? nargs - 2 : 0; + + if (arg_count <= 0) return arr; + arr = js_mkarr(js); + for (int i = 0; i < arg_count; i++) js_arr_push(js, arr, args[i + 2]); + return arr; +} + static ant_value_t timer_to_primitive(ant_t *js, ant_value_t *args, int nargs) { return js_get_slot(js_getthis(js), SLOT_DATA); } @@ -167,7 +206,14 @@ static ant_value_t js_timer_has_ref(ant_t *js, ant_value_t *args, int nargs) { return js_bool(uv_has_ref((const uv_handle_t *)&entry->handle) != 0); } -static ant_value_t timer_make_object(ant_t *js, int id, double delay_ms, int is_interval, ant_value_t callback) { +static ant_value_t timer_make_object( + ant_t *js, + int id, + double delay_ms, + int is_interval, + ant_value_t callback, + ant_value_t timer_args +) { ant_value_t obj = js_mkobj(js); ant_value_t proto = is_interval ? g_interval_proto : g_timeout_proto; @@ -180,6 +226,7 @@ static ant_value_t timer_make_object(ant_t *js, int id, double delay_ms, int is_ js_set_descriptor(js, obj, "callback", 8, JS_DESC_W | JS_DESC_C); js_set_slot(obj, SLOT_DATA, js_mknum((double)id)); + js_set_slot_wb(js, obj, SLOT_AUX, timer_args); js_set_sym(js, obj, get_toPrimitive_sym(), js_mkfun(timer_to_primitive)); return obj; @@ -197,15 +244,9 @@ static void timer_close_cb(uv_handle_t *h) { if (timer_entry_is_registered(entry)) remove_timer_entry(entry); entry->closed = 1; entry->active = 0; - entry->callback = 0; + timer_release_callback_args(entry); entry->next = NULL; entry->prev = NULL; - - if (entry->args) { - free(entry->args); - entry->args = NULL; - } - entry->nargs = 0; } static void timer_callback(uv_timer_t *handle) { @@ -213,16 +254,19 @@ static void timer_callback(uv_timer_t *handle) { if (!entry || entry->closed || !timer_entry_is_registered(entry) || !entry->active) return; ant_t *js = timer_state.js; + ant_value_t callback = entry->callback; if (!entry->is_interval) { entry->active = 0; timer_state.active_timer_count--; } - sv_vm_call(js->vm, js, entry->callback, js_mkundef(), entry->args, entry->nargs, NULL, false); + GC_ROOT_SAVE(root_mark, js); + GC_ROOT_PIN(js, callback); + for (int i = 0; i < entry->nargs; i++) GC_ROOT_PIN(js, entry->args[i]); + sv_vm_call(js->vm, js, callback, js_mkundef(), entry->args, entry->nargs, NULL, false); + GC_ROOT_RESTORE(js, root_mark); + if (!entry->is_interval && !entry->active) timer_release_callback_args(entry); process_microtasks(js); - - if (!entry->is_interval && !entry->active) - if (!uv_is_closing((uv_handle_t *)&entry->handle)) uv_close((uv_handle_t *)&entry->handle, timer_close_cb); } static ant_value_t js_timer_refresh(ant_t *js, ant_value_t *args, int nargs) { @@ -232,6 +276,12 @@ static ant_value_t js_timer_refresh(ant_t *js, ant_value_t *args, int nargs) { if (!entry || entry->closed || uv_is_closing((uv_handle_t *)&entry->handle)) return this_obj; if (!entry->active) { + if (vtype(entry->callback) == T_UNDEF) { + entry->callback = js_get(js, this_obj, "callback"); + if (!is_callable(entry->callback)) return this_obj; + if (timer_copy_args_from_object(js, entry, this_obj) != 0) + return js_mkerr(js, "failed to allocate timer args"); + } entry->active = 1; timer_state.active_timer_count++; } @@ -255,6 +305,7 @@ static ant_value_t js_set_timeout(ant_t *js, ant_value_t *args, int nargs) { ant_value_t callback = args[0]; double delay_ms = nargs > 1 ? js_getnum(args[1]) : 0; uint64_t ms = delay_ms >= 1 ? (uint64_t)delay_ms : 0; + ant_value_t timer_args = timer_make_args_array(js, args, nargs); timer_entry_t *entry = ant_calloc(sizeof(timer_entry_t)); if (entry == NULL) return js_mkerr(js, "failed to allocate timer"); @@ -277,7 +328,7 @@ static ant_value_t js_set_timeout(ant_t *js, ant_value_t *args, int nargs) { timer_state.active_timer_count++; uv_timer_start(&entry->handle, timer_callback, ms, 0); - return timer_make_object(js, entry->timer_id, delay_ms, 0, callback); + return timer_make_object(js, entry->timer_id, delay_ms, 0, callback, timer_args); } // setInterval(callback, delay, ...args) @@ -289,6 +340,7 @@ static ant_value_t js_set_interval(ant_t *js, ant_value_t *args, int nargs) { ant_value_t callback = args[0]; double delay_ms = nargs > 1 ? js_getnum(args[1]) : 0; uint64_t ms = delay_ms >= 1 ? (uint64_t)delay_ms : 1; + ant_value_t timer_args = timer_make_args_array(js, args, nargs); timer_entry_t *entry = ant_calloc(sizeof(timer_entry_t)); if (entry == NULL) return js_mkerr(js, "failed to allocate timer"); @@ -311,7 +363,7 @@ static ant_value_t js_set_interval(ant_t *js, ant_value_t *args, int nargs) { timer_state.active_timer_count++; uv_timer_start(&entry->handle, timer_callback, ms, ms); - return timer_make_object(js, entry->timer_id, delay_ms, 1, callback); + return timer_make_object(js, entry->timer_id, delay_ms, 1, callback, timer_args); } // clearTimeout(timerId | timerObject) @@ -320,8 +372,11 @@ static ant_value_t js_clear_timeout(ant_t *js, ant_value_t *args, int nargs) { int timer_id = timer_id_from_arg(js, args[0]); for (timer_entry_t *entry = timer_state.timers; entry != NULL; entry = entry->next) { - if (entry->timer_id == timer_id && entry->active) { - entry->active = 0; timer_state.active_timer_count--; + if (entry->timer_id == timer_id && !entry->closed) { + if (entry->active) { + entry->active = 0; + timer_state.active_timer_count--; + } if (!uv_is_closing((uv_handle_t *)&entry->handle)) uv_close((uv_handle_t *)&entry->handle, timer_close_cb); break; }} @@ -868,6 +923,7 @@ void gc_mark_timers(ant_t *js, gc_mark_fn mark) { if (is_object_type(g_timeout_proto)) mark(js, g_timeout_proto); if (is_object_type(g_interval_proto)) mark(js, g_interval_proto); for (timer_entry_t *t = timer_state.timers; t; t = t->next) { + if (!t->active) continue; mark(js, t->callback); for (int i = 0; i < t->nargs; i++) mark(js, t->args[i]); } diff --git a/src/modules/tls.c b/src/modules/tls.c index 6c5bddc..50a9caa 100644 --- a/src/modules/tls.c +++ b/src/modules/tls.c @@ -34,6 +34,7 @@ typedef struct ant_tls_context_wrap_s { tls_context *ctx; tlsuv_private_key_t key; tlsuv_certificate_t cert; + unsigned refs; bool closed; } ant_tls_context_wrap_t; @@ -60,6 +61,8 @@ typedef struct ant_tls_socket_s { tlsuv_stream_t stream; uv_connect_t connect_req; tls_context *ctx; + ant_tls_context_wrap_t *ctx_wrap; + ant_value_t secure_context; char *host; char *servername; int port; @@ -94,9 +97,8 @@ static ant_value_t g_tls_socket_proto = 0; static ant_value_t g_tls_socket_ctor = 0; static ant_tls_socket_t *g_active_tls_sockets = NULL; -static void tls_context_free(ant_tls_context_wrap_t *wrap) { - if (!wrap || wrap->closed) return; - wrap->closed = true; +static void tls_context_dispose(ant_tls_context_wrap_t *wrap) { + if (!wrap) return; if (wrap->cert && wrap->cert->free) wrap->cert->free(wrap->cert); if (wrap->key && wrap->key->free) wrap->key->free(wrap->key); @@ -107,6 +109,23 @@ static void tls_context_free(ant_tls_context_wrap_t *wrap) { wrap->ctx = NULL; } +static void tls_context_free(ant_tls_context_wrap_t *wrap) { + if (!wrap || wrap->closed) return; + wrap->closed = true; + if (wrap->refs > 0) return; + tls_context_dispose(wrap); +} + +static void tls_context_retain(ant_tls_context_wrap_t *wrap) { + if (wrap) wrap->refs++; +} + +static void tls_context_release(ant_tls_context_wrap_t *wrap) { + if (!wrap || wrap->refs == 0) return; + wrap->refs--; + if (wrap->refs == 0 && wrap->closed) tls_context_dispose(wrap); +} + static ant_tls_context_wrap_t *tls_context_data(ant_value_t value) { return (ant_tls_context_wrap_t *)js_get_native(value, TLS_CONTEXT_NATIVE_TAG); } @@ -355,6 +374,7 @@ static void tls_socket_free(ant_tls_socket_t *socket) { if (is_object_type(socket->obj)) js_clear_native(socket->obj, TLS_SOCKET_NATIVE_TAG); tls_socket_free_read_queue(socket); tls_socket_free_alpn(socket); + if (socket->ctx_wrap) tls_context_release(socket->ctx_wrap); if (socket->owns_ctx && socket->ctx && socket->ctx->free_ctx) socket->ctx->free_ctx(socket->ctx); free(socket->host); free(socket->servername); @@ -720,8 +740,14 @@ static ant_value_t js_tls_socket_resume(ant_t *js, ant_value_t *args, int nargs) static ant_value_t js_tls_socket_setEncoding(ant_t *js, ant_value_t *args, int nargs) { ant_tls_socket_t *socket = tls_require_socket(js, js_getthis(js)); + ant_value_t encoding = js_mkundef(); + if (!socket) return js->thrown_value; - socket->encoding = nargs > 0 && vtype(args[0]) != T_UNDEF ? js_tostring_val(js, args[0]) : js_mkundef(); + if (nargs > 0 && vtype(args[0]) != T_UNDEF) { + encoding = js_tostring_val(js, args[0]); + if (is_err(encoding)) return encoding; + } + socket->encoding = encoding; return js_getthis(js); } @@ -1131,6 +1157,7 @@ static ant_value_t js_tls_connect_options(ant_t *js, ant_value_t options, ant_va socket->js = js; socket->encoding = js_mkundef(); + socket->secure_context = js_mkundef(); socket->host = strdup(host ? host : "localhost"); socket->servername = strdup(servername ? servername : (host ? host : "localhost")); socket->port = port > 0 ? port : 443; @@ -1144,7 +1171,10 @@ static ant_value_t js_tls_connect_options(ant_t *js, ant_value_t options, ant_va ctx_wrap = tls_context_data(value); if (ctx_wrap && !ctx_wrap->closed && ctx_wrap->ctx) { socket->ctx = ctx_wrap->ctx; + socket->ctx_wrap = ctx_wrap; + socket->secure_context = value; socket->owns_ctx = false; + tls_context_retain(ctx_wrap); } else { socket->ctx = default_tls_context(NULL, 0); socket->owns_ctx = true; @@ -1270,6 +1300,7 @@ void gc_mark_tls(ant_t *js, gc_mark_fn mark) { for (socket = g_active_tls_sockets; socket; socket = socket->next_active) { mark(js, socket->obj); if (vtype(socket->encoding) != T_UNDEF) mark(js, socket->encoding); + if (vtype(socket->secure_context) != T_UNDEF) mark(js, socket->secure_context); for (tls_write_req_t *write = socket->writes; write; write = write->next) if (vtype(write->callback) != T_UNDEF) mark(js, write->callback); } diff --git a/src/net/connection.c b/src/net/connection.c index 504d48c..ed078df 100644 --- a/src/net/connection.c +++ b/src/net/connection.c @@ -30,14 +30,17 @@ typedef struct { } ant_conn_shutdown_req_t; typedef struct { - uv_connect_t req; + uv_connect_t connect_req; + uv_getaddrinfo_t resolver_req; ant_conn_t *conn; ant_conn_connect_cb cb; void *user_data; + int port; } ant_conn_connect_req_t; static void ant_conn_restart_timer(ant_conn_t *conn); static void ant_conn_close_cb(uv_handle_t *handle); +static void ant_conn_finish_close(ant_conn_t *conn); static void ant_listener_remove_conn(ant_listener_t *listener, ant_conn_t *conn) { ant_conn_t **it = NULL; @@ -211,12 +214,10 @@ static void ant_conn_shutdown_cb(uv_shutdown_t *req, int status) { ant_conn_close(conn); } -static void ant_conn_close_cb(uv_handle_t *handle) { - ant_conn_t *conn = (ant_conn_t *)handle->data; +static void ant_conn_finish_close(ant_conn_t *conn) { ant_listener_t *listener = conn ? conn->listener : NULL; if (!conn || !listener) return; - if (--conn->close_handles > 0) return; ant_listener_remove_conn(listener, conn); if (listener->callbacks.on_conn_close) @@ -226,6 +227,13 @@ static void ant_conn_close_cb(uv_handle_t *handle) { free(conn); } +static void ant_conn_close_cb(uv_handle_t *handle) { + ant_conn_t *conn = (ant_conn_t *)handle->data; + if (!conn) return; + if (--conn->close_handles > 0) return; + ant_conn_finish_close(conn); +} + ant_conn_t *ant_conn_create_tcp(ant_listener_t *listener, uint64_t timeout_ms) { ant_conn_t *conn = NULL; @@ -298,7 +306,7 @@ int ant_conn_accept(ant_conn_t *conn, uv_stream_t *server_stream) { } static void ant_conn_connect_cb_impl(uv_connect_t *req, int status) { - ant_conn_connect_req_t *cr = (ant_conn_connect_req_t *)req; + ant_conn_connect_req_t *cr = req ? (ant_conn_connect_req_t *)req->data : NULL; ant_conn_t *conn = cr ? cr->conn : NULL; if (conn && status == 0 && conn->kind == ANT_CONN_KIND_TCP) { @@ -310,42 +318,72 @@ static void ant_conn_connect_cb_impl(uv_connect_t *req, int status) { free(cr); } -static int sockaddr_from_hostname(const char *hostname, int port, struct sockaddr_storage *out) { - struct addrinfo hints = {0}, *res = NULL; - int rc = 0; - - if (!hostname || !out) return UV_EINVAL; +static int sockaddr_from_addrinfo(const struct addrinfo *res, int port, struct sockaddr_storage *out) { + if (!res || !out) return UV_EINVAL; memset(out, 0, sizeof(*out)); - rc = uv_ip4_addr(hostname, port, (struct sockaddr_in *)out); - if (rc == 0) return 0; - - rc = uv_ip6_addr(hostname, port, (struct sockaddr_in6 *)out); - if (rc == 0) return 0; - - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - rc = getaddrinfo(hostname, NULL, &hints, &res); - if (rc != 0 || !res) return UV_ENOENT; - if (res->ai_family == AF_INET) { struct sockaddr_in sa; memcpy(&sa, res->ai_addr, sizeof(sa)); sa.sin_port = htons((uint16_t)port); memcpy(out, &sa, sizeof(sa)); - rc = 0; - } else if (res->ai_family == AF_INET6) { + return 0; + } + + if (res->ai_family == AF_INET6) { struct sockaddr_in6 sa6; memcpy(&sa6, res->ai_addr, sizeof(sa6)); sa6.sin6_port = htons((uint16_t)port); memcpy(out, &sa6, sizeof(sa6)); - rc = 0; - } else rc = UV_ENOENT; + return 0; + } + + return UV_ENOENT; +} + +static int sockaddr_from_ip_literal(const char *hostname, int port, struct sockaddr_storage *out) { + int rc = 0; + + if (!hostname || !out) return UV_EINVAL; + memset(out, 0, sizeof(*out)); + + rc = uv_ip4_addr(hostname, port, (struct sockaddr_in *)out); + if (rc == 0) return 0; - freeaddrinfo(res); + rc = uv_ip6_addr(hostname, port, (struct sockaddr_in6 *)out); return rc; } +static void ant_conn_resolved_cb(uv_getaddrinfo_t *resolver, int status, struct addrinfo *res) { + ant_conn_connect_req_t *cr = resolver ? (ant_conn_connect_req_t *)resolver->data : NULL; + ant_conn_t *conn = cr ? cr->conn : NULL; + struct sockaddr_storage addr; + int rc = status; + + if (conn) conn->resolving = false; + + if (conn && conn->closing) { + if (res) uv_freeaddrinfo(res); + free(cr); + if (--conn->close_handles == 0) ant_conn_finish_close(conn); + return; + } + + if (status == 0) { + rc = sockaddr_from_addrinfo(res, cr ? cr->port : 0, &addr); + if (rc == 0) { + cr->connect_req.data = cr; + rc = uv_tcp_connect(&cr->connect_req, &conn->handle.tcp, (const struct sockaddr *)&addr, ant_conn_connect_cb_impl); + } + } + + if (res) uv_freeaddrinfo(res); + if (rc != 0) { + if (cr && cr->cb) cr->cb(conn, rc, cr->user_data); + free(cr); + } +} + int ant_conn_connect_tcp( ant_conn_t *conn, const char *hostname, @@ -355,12 +393,10 @@ int ant_conn_connect_tcp( ) { ant_conn_connect_req_t *req = NULL; struct sockaddr_storage addr; + struct addrinfo hints = {0}; int rc = 0; - if (!conn || conn->kind != ANT_CONN_KIND_TCP || !hostname || port <= 0) return UV_EINVAL; - - rc = sockaddr_from_hostname(hostname, port, &addr); - if (rc != 0) return rc; + if (!conn || conn->kind != ANT_CONN_KIND_TCP || !hostname || port <= 0 || port > 65535) return UV_EINVAL; req = calloc(1, sizeof(*req)); if (!req) return UV_ENOMEM; @@ -368,9 +404,26 @@ int ant_conn_connect_tcp( req->conn = conn; req->cb = cb; req->user_data = user_data; + req->port = port; + + rc = sockaddr_from_ip_literal(hostname, port, &addr); + if (rc == 0) { + req->connect_req.data = req; + rc = uv_tcp_connect(&req->connect_req, &conn->handle.tcp, (const struct sockaddr *)&addr, ant_conn_connect_cb_impl); + if (rc != 0) { + free(req); + return rc; + } + return 0; + } - rc = uv_tcp_connect(&req->req, &conn->handle.tcp, (const struct sockaddr *)&addr, ant_conn_connect_cb_impl); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + conn->resolving = true; + req->resolver_req.data = req; + rc = uv_getaddrinfo(conn->listener ? conn->listener->loop : uv_default_loop(), &req->resolver_req, ant_conn_resolved_cb, hostname, NULL, &hints); if (rc != 0) { + conn->resolving = false; free(req); return rc; } @@ -396,7 +449,8 @@ int ant_conn_connect_pipe( req->cb = cb; req->user_data = user_data; - uv_pipe_connect(&req->req, &conn->handle.pipe, path, ant_conn_connect_cb_impl); + req->connect_req.data = req; + uv_pipe_connect(&req->connect_req, &conn->handle.pipe, path, ant_conn_connect_cb_impl); return rc; } @@ -555,7 +609,8 @@ void ant_conn_close(ant_conn_t *conn) { conn->close_handles++; } - if (conn->close_handles == 0) ant_conn_close_cb((uv_handle_t *)ant_conn_stream(conn)); + if (conn->resolving) conn->close_handles++; + if (conn->close_handles == 0) ant_conn_finish_close(conn); } int ant_conn_write(ant_conn_t *conn, char *data, size_t len, ant_conn_write_cb cb, void *user_data) { diff --git a/src/repl.c b/src/repl.c index c2adf90..73acdb8 100644 --- a/src/repl.c +++ b/src/repl.c @@ -79,12 +79,14 @@ static void repl_read_async_close_cb(uv_handle_t *handle) { static void *repl_read_thread_main(void *data) { repl_read_job_t *job = (repl_read_job_t *)data; - sigset_t sigint_set; char *line = NULL; +#ifndef _WIN32 + sigset_t sigint_set; sigemptyset(&sigint_set); sigaddset(&sigint_set, SIGINT); pthread_sigmask(SIG_UNBLOCK, &sigint_set, NULL); +#endif ant_readline_result_t status = ant_readline( job->history, job->prompt, @@ -127,9 +129,11 @@ static ant_readline_result_t repl_readline_async( }; pthread_t thread; +#ifndef _WIN32 sigset_t sigint_set; sigset_t old_sigmask; bool sigint_blocked = false; +#endif if (out_line) *out_line = NULL; if (pthread_mutex_init(&job.mutex, NULL) != 0) @@ -142,12 +146,16 @@ static ant_readline_result_t repl_readline_async( job.async.data = &job; job.async_initialized = true; +#ifndef _WIN32 sigemptyset(&sigint_set); sigaddset(&sigint_set, SIGINT); sigint_blocked = pthread_sigmask(SIG_BLOCK, &sigint_set, &old_sigmask) == 0; +#endif if (pthread_create(&thread, NULL, repl_read_thread_main, &job) != 0) { +#ifndef _WIN32 if (sigint_blocked) pthread_sigmask(SIG_SETMASK, &old_sigmask, NULL); +#endif if (job.async_initialized) uv_close((uv_handle_t *)&job.async, repl_read_async_close_cb); while (job.async_initialized) uv_run(uv_default_loop(), UV_RUN_ONCE); @@ -161,7 +169,9 @@ static ant_readline_result_t repl_readline_async( } pthread_join(thread, NULL); +#ifndef _WIN32 if (sigint_blocked) pthread_sigmask(SIG_SETMASK, &old_sigmask, NULL); +#endif if (job.async_initialized) uv_close((uv_handle_t *)&job.async, repl_read_async_close_cb); diff --git a/tests/test_net_create_connection.cjs b/tests/test_net_create_connection.cjs index d2c0368..67cbe2d 100644 --- a/tests/test_net_create_connection.cjs +++ b/tests/test_net_create_connection.cjs @@ -5,8 +5,9 @@ let connected = false; let serverSawData = false; let clientSawData = false; let invalidHostError = false; +let invalidPortError = false; const timeout = setTimeout(() => { - if (!connected || !serverSawData || !clientSawData || !invalidHostError) { + if (!connected || !serverSawData || !clientSawData || !invalidHostError || !invalidPortError) { throw new Error('net.createConnection timed out'); } }, 2000); @@ -17,6 +18,16 @@ function finishWithInvalidHostCheck() { invalidHostError = true; assert(error instanceof Error); assert.strictEqual(typeof error.message, 'string'); + finishWithInvalidPortCheck(); + }); +} + +function finishWithInvalidPortCheck() { + const socket = net.connect({ host: '127.0.0.1', port: 65536 }); + socket.on('error', (error) => { + invalidPortError = true; + assert(error instanceof Error); + assert.strictEqual(typeof error.message, 'string'); clearTimeout(timeout); console.log('net:create-connection:ok'); }); diff --git a/tests/test_stream_is_errored.cjs b/tests/test_stream_is_errored.cjs new file mode 100644 index 0000000..9fd2e28 --- /dev/null +++ b/tests/test_stream_is_errored.cjs @@ -0,0 +1,41 @@ +const assert = require('node:assert'); +const stream = require('node:stream'); + +function tick() { + return new Promise((resolve) => setImmediate(resolve)); +} + +(async () => { + { + const err = new Error('readable boom'); + const readable = new stream.Readable({ read() {} }); + readable.on('error', () => {}); + + assert.strictEqual(stream.isErrored(readable), false); + readable.destroy(err); + assert.strictEqual(readable._readableState.errored, err); + await tick(); + assert.strictEqual(stream.isErrored(readable), true); + } + + { + const err = new Error('writable boom'); + const writable = new stream.Writable({ + write(_chunk, _encoding, callback) { + callback(err); + }, + }); + writable.on('error', () => {}); + + assert.strictEqual(stream.isErrored(writable), false); + writable.write('x', () => {}); + assert.strictEqual(writable._writableState.errored, err); + await tick(); + assert.strictEqual(stream.isErrored(writable), true); + } + + console.log('stream-is-errored:ok'); +})().catch((err) => { + console.error(err && err.stack ? err.stack : String(err)); + process.exit(1); +}); diff --git a/tests/test_timer_fired_timeout_gc.cjs b/tests/test_timer_fired_timeout_gc.cjs new file mode 100644 index 0000000..613c5dd --- /dev/null +++ b/tests/test_timer_fired_timeout_gc.cjs @@ -0,0 +1,22 @@ +const assert = require('node:assert'); + +let ref; + +{ + let callback = () => {}; + ref = new WeakRef(callback); + setTimeout(callback, 0); + callback = null; +} + +function forceAllocations() { + for (let i = 0; i < 200000; i++) { + ({ i, value: `timer-gc-${i}` }); + } +} + +setTimeout(() => { + forceAllocations(); + assert.strictEqual(ref.deref(), undefined); + console.log('timer:fired-timeout-gc:ok'); +}, 10); diff --git a/tests/test_timer_refresh.cjs b/tests/test_timer_refresh.cjs index a94a346..411b7b9 100644 --- a/tests/test_timer_refresh.cjs +++ b/tests/test_timer_refresh.cjs @@ -28,7 +28,28 @@ const selfRefreshing = setTimeout(() => { if (selfRefreshFired === 1) selfRefreshing.refresh(); }, 5); +let postFireRefreshFired = 0; +const postFireRefresh = setTimeout(() => { + postFireRefreshFired++; +}, 5); + +const postFireArgs = []; +const postFireWithArgs = setTimeout((value) => { + postFireArgs.push(value); +}, 5, 'kept-arg'); + +setTimeout(() => { + assert.strictEqual(postFireRefreshFired, 1); + postFireRefresh.refresh(); + assert.deepStrictEqual(postFireArgs, ['kept-arg']); + postFireWithArgs.refresh(); +}, 20); + setTimeout(() => { assert.strictEqual(selfRefreshFired, 2); + assert.strictEqual(postFireRefreshFired, 2); + assert.deepStrictEqual(postFireArgs, ['kept-arg', 'kept-arg']); + clearTimeout(postFireRefresh); + clearTimeout(postFireWithArgs); console.log('timer:refresh:ok'); }, 80); diff --git a/tests/test_tls_secure_connect_event.cjs b/tests/test_tls_secure_connect_event.cjs index 1fa03af..db4bef6 100644 --- a/tests/test_tls_secure_connect_event.cjs +++ b/tests/test_tls_secure_connect_event.cjs @@ -38,7 +38,15 @@ const server = net.createServer((socket) => { server.listen(0, '127.0.0.1', () => { const address = server.address(); - const socket = tls.connect(address.port, '127.0.0.1'); + const secureContext = tls.createSecureContext(); + const socket = tls.connect({ + port: address.port, + host: '127.0.0.1', + secureContext, + }); + assert.strictEqual(tls.isSecureContext(secureContext), true); + assert.strictEqual(secureContext.close(), secureContext); + assert.strictEqual(tls.isSecureContext(secureContext), false); assert(socket instanceof tls.TLSSocket); assert(socket instanceof net.Socket); assert.strictEqual(typeof socket.renegotiate, 'function'); @@ -46,6 +54,21 @@ server.listen(0, '127.0.0.1', () => { assert.strictEqual(socket.unref(), socket); assert.strictEqual(socket.cork(), socket); assert.strictEqual(socket.uncork(), socket); + assert.strictEqual(socket.setEncoding('utf8'), socket); + const badEncoding = { + toString() { + throw new Error('encoding coercion failed'); + }, + }; + let sawEncodingError = false; + try { + socket.setEncoding(badEncoding); + } catch (err) { + sawEncodingError = true; + assert.match(err.message, /encoding coercion failed/); + } + assert.strictEqual(sawEncodingError, true); + assert.strictEqual(socket.setEncoding(), socket); socket.on('error', (err) => { sawError = true; -- 2.51.2