From 5839dc87154e7c27d43281a31b27b99ae739061a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Paul=20H=C3=A4nsch?= Date: Mon, 16 Mar 2026 00:53:28 +0100 Subject: [PATCH] improved timeout handling --- socket.c | 155 +++++++++++++++++++++++++++---------------------------- 1 file changed, 75 insertions(+), 80 deletions(-) diff --git a/socket.c b/socket.c index 31ad4cb..cca14f4 100644 --- a/socket.c +++ b/socket.c @@ -134,59 +134,58 @@ typedef struct { struct sockaddr_storage peer; } SocketData; -static JSValue sock_set_timeout( JSContext *ctx, JSValueConst this, JSValueConst val ) { - SocketData * data = JS_GetOpaque2(ctx, this, socket_cid); - struct timeval t; double delay; - - if (!JS_IsNumber(val) - || JS_ToFloat64(ctx, &delay, val) || delay < 0) { - t.tv_sec = 0; t.tv_usec = 0; - delay = -1; - } else { - t.tv_sec = (int) delay; - t.tv_usec = (int) ((delay - t.tv_sec) * 1000000); - } - - if ( setsockopt(data->fd, SOL_SOCKET, SO_SNDTIMEO, &t, sizeof(t)) - || setsockopt(data->fd, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t)) - ) return libc_error(ctx); - - data->timeout = delay; - - if ( delay == 0 - && fcntl(data->fd, F_SETFL, O_NONBLOCK) - ) return libc_error(ctx); - else if ( delay != 0 - && fcntl(data->fd, F_SETFL, 0) - ) return libc_error(ctx); +static int sock_set_timeout(SocketData *so, double timeout) { + struct timeval t = { .tv_sec = 0, .tv_usec = 0 }; int flags = 0; + + if ( timeout == 0 ) flags = O_NONBLOCK; + else if ( timeout > 0 ) { + t.tv_sec = (int) timeout; + t.tv_usec = (int) ((timeout - t.tv_sec) * 1000000); + } else timeout = -1; + + if ( !setsockopt(so->fd, SOL_SOCKET, SO_SNDTIMEO, &t, sizeof(t)) + && !setsockopt(so->fd, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t)) + && !fcntl(so->fd, F_SETFL, flags) + ) { + so->timeout = timeout; + return 0; + } else return 1; +} +static JSValue js_sock_set_timeout( + JSContext *ctx, JSValueConst this, JSValueConst val +) { + double timeout; + SocketData * data = JS_GetOpaque2(ctx, this, socket_cid); + if (!JS_IsNumber(val) || JS_ToFloat64(ctx, &timeout, val)) timeout = -1; - return JS_UNDEFINED; + if (! sock_set_timeout(data, timeout) ) return JS_UNDEFINED; + else return libc_error(ctx); } -static JSValue sock_get_timeout( JSContext *ctx, JSValueConst this ) { +static JSValue js_sock_get_timeout( JSContext *ctx, JSValueConst this ) { SocketData * data = JS_GetOpaque2(ctx, this, socket_cid); if ( data->timeout == -1 ) return JS_UNDEFINED; else return JS_NewFloat64(ctx, data->timeout); } -static JSValue sock_get_fd( JSContext *ctx, JSValueConst this ) { +static JSValue js_sock_get_fd( JSContext *ctx, JSValueConst this ) { SocketData * data = JS_GetOpaque2(ctx, this, socket_cid); return JS_NewInt32(ctx, data->fd); } -static JSValue sock_get_family( JSContext *ctx, JSValueConst this ) { +static JSValue js_sock_get_family( JSContext *ctx, JSValueConst this ) { SocketData * data = JS_GetOpaque2(ctx, this, socket_cid); return JS_NewInt32(ctx, data->bind.ss_family); } -static JSValue sock_get_type( JSContext *ctx, JSValueConst this ) { +static JSValue js_sock_get_type( JSContext *ctx, JSValueConst this ) { SocketData * data = JS_GetOpaque2(ctx, this, socket_cid); return JS_NewInt32(ctx, data->type); } -static JSValue sock_get_connected( JSContext *ctx, JSValueConst this ) { +static JSValue js_sock_get_connected( JSContext *ctx, JSValueConst this ) { SocketData * data = JS_GetOpaque2(ctx, this, socket_cid); return JS_NewBool(ctx, data->connected); } -static JSValue sock_accept( +static JSValue js_sock_accept( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv ) { SocketData * data = JS_GetOpaque2(ctx, this, socket_cid); @@ -202,9 +201,7 @@ static JSValue sock_accept( memcpy(&(newdata->bind), &(data->bind), sizeof(data->bind)); newdata->connected = 1; JS_SetOpaque(new, newdata); - if (data->timeout >= 0) - (void) sock_set_timeout(ctx, new, JS_NewFloat64(ctx, data->timeout)); - else newdata->timeout = -1; + (void) sock_set_timeout(newdata, data->timeout); return new; } else { @@ -215,7 +212,7 @@ static JSValue sock_accept( else return libc_error(ctx); } } -static JSValue sock_send( +static JSValue js_sock_send( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv ) { SocketData * data = JS_GetOpaque2(ctx, this, socket_cid); @@ -243,8 +240,6 @@ static JSValue sock_send( // destination should not be given for SOCK_STREAM sockets } else to = NULL; - if (data->timeout == 0) flags |= MSG_DONTWAIT; - // Read Argument either as ArrayBuffer or String if ( (buf = JS_GetArrayBuffer(ctx, &len, argv[0])) ) { len = sendto(data->fd, buf, len, flags, to, to ? sizeof(*to) : 0); @@ -268,11 +263,11 @@ static void js_array_buffer_free(JSRuntime *rt, void *junk, void *buf) { js_free_rt(rt, buf); } -static JSValue sock_recv( +static JSValue js_sock_recv( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv, int str ) { SocketData * data = JS_GetOpaque2(ctx, this, socket_cid); - uint8_t *buf; int len; JSValue jbuf; int flags = 0; + uint8_t *buf; int len; JSValue jbuf; socklen_t ps = sizeof(data->peer); // check current queue size if no valid length was given @@ -282,10 +277,9 @@ static JSValue sock_recv( ) return libc_error(ctx); if (len <= 0) len = 65535; // if no data is queued, wait for next packet - if (data->timeout == 0) flags |= MSG_DONTWAIT; if ( ( buf = js_malloc(ctx, len) ) - && ((len = recvfrom(data->fd, buf, len, flags, + && ((len = recvfrom(data->fd, buf, len, 0, (struct sockaddr *) &(data->peer), &ps)) >= 0) ) { // resize/clear buffer in case of short read, @@ -311,7 +305,7 @@ static JSValue sock_recv( } } -static JSValue sock_get_addr( JSContext *ctx, JSValueConst this, int magic ) { +static JSValue js_sock_get_addr( JSContext *ctx, JSValueConst this, int magic ) { SocketData * data = JS_GetOpaque2(ctx, this, socket_cid); struct sockaddr_storage * addr = (magic&2) ? &(data->peer) : &(data->bind); char host[40]; @@ -338,7 +332,7 @@ static JSValue sock_get_addr( JSContext *ctx, JSValueConst this, int magic ) { } else return JS_UNDEFINED; } -static JSValue sock_close( +static JSValue js_sock_close( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv ) { SocketData *data = JS_GetOpaque2(ctx, this, socket_cid); @@ -357,20 +351,20 @@ static void sock_destroy(JSRuntime *rt, JSValue this){ }; static const JSCFunctionListEntry socket_ptype[] = { - JS_CFUNC_DEF("accept", 0, sock_accept), - JS_CFUNC_DEF("send", 1, sock_send), - JS_CFUNC_DEF("close", 0, sock_close), - JS_CFUNC_MAGIC_DEF("recv", 0, sock_recv, 0), - JS_CFUNC_MAGIC_DEF("recvString", 0, sock_recv, 1), - JS_CGETSET_MAGIC_DEF("localName", sock_get_addr, NULL, 0), - JS_CGETSET_MAGIC_DEF("localPort", sock_get_addr, NULL, 1), - JS_CGETSET_MAGIC_DEF("peerName", sock_get_addr, NULL, 2), - JS_CGETSET_MAGIC_DEF("peerPort", sock_get_addr, NULL, 3), - JS_CGETSET_DEF("timeout", sock_get_timeout, sock_set_timeout), - JS_CGETSET_DEF("fd", sock_get_fd, NULL), - JS_CGETSET_DEF("family", sock_get_family, NULL), - JS_CGETSET_DEF("type", sock_get_type, NULL), - JS_CGETSET_DEF("connected", sock_get_connected, NULL), + JS_CFUNC_DEF("accept", 0, js_sock_accept), + JS_CFUNC_DEF("send", 1, js_sock_send), + JS_CFUNC_DEF("close", 0, js_sock_close), + JS_CFUNC_MAGIC_DEF("recv", 0, js_sock_recv, 0), + JS_CFUNC_MAGIC_DEF("recvString", 0, js_sock_recv, 1), + JS_CGETSET_MAGIC_DEF("localName", js_sock_get_addr, NULL, 0), + JS_CGETSET_MAGIC_DEF("localPort", js_sock_get_addr, NULL, 1), + JS_CGETSET_MAGIC_DEF("peerName", js_sock_get_addr, NULL, 2), + JS_CGETSET_MAGIC_DEF("peerPort", js_sock_get_addr, NULL, 3), + JS_CGETSET_DEF("timeout", js_sock_get_timeout, js_sock_set_timeout), + JS_CGETSET_DEF("fd", js_sock_get_fd, NULL), + JS_CGETSET_DEF("family", js_sock_get_family, NULL), + JS_CGETSET_DEF("type", js_sock_get_type, NULL), + JS_CGETSET_DEF("connected", js_sock_get_connected, NULL), }; static JSClassDef socket_class = { @@ -397,10 +391,10 @@ static int net_ip_listen(SocketData *so, int type) { if ( (so->fd = socket(so->bind.ss_family, type, 0)) >= 0 && !bind(so->fd, (struct sockaddr *) &(so->bind), sizeof(so->bind)) + && !sock_set_timeout(so, net_timeout) && (type == SOCK_DGRAM || !listen(so->fd, 1)) ){ so->type = type; - so->timeout = -1; return 0; } else { if (so->fd >= 0) close(so->fd); @@ -442,11 +436,11 @@ static int net_ip_connect(SocketData *so, int type) { if (!so) return 1; if ( (so->fd = socket(so->peer.ss_family, type, 0)) >= 0 + && !sock_set_timeout(so, net_timeout) && !connect(so->fd, (struct sockaddr *) &(so->peer), ps) && !getsockname(so->fd, (struct sockaddr *) &(so->bind), &ps) ){ so->type = type; - so->timeout = -1; so->connected = 1; return 0; } else { @@ -486,20 +480,26 @@ static JSValue js_net_ip_connect( static int net_unix_bind(SocketData *so, const char *path, size_t plen, int con) { struct sockaddr_un * addr; - if (!so) return 1; + if (!so || plen >= sizeof(addr->sun_path)) return 1; addr = (struct sockaddr_un *) (con ? &(so->peer) : &(so->bind)); addr->sun_family = AF_UNIX; - if (plen < sizeof(addr->sun_path)) strncpy(addr->sun_path, path, plen); + strncpy(addr->sun_path, path, plen); + so->type = SOCK_STREAM; - if ( (so->fd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0 - && !( con ? connect(so->fd,(struct sockaddr *) addr ,sizeof(*addr)) - : bind(so->fd,(struct sockaddr *) addr ,sizeof(*addr)) - ) && (con || !listen(so->fd, 1)) + if ( con + && (so->fd = socket(AF_UNIX, so->type, 0)) >= 0 + && !sock_set_timeout(so, net_timeout) + && !connect(so->fd,(struct sockaddr *) addr ,sizeof(*addr)) + ) { + so->connected = 1; + return 0; + } else if (!con + && (so->fd = socket(AF_UNIX, so->type, 0)) >= 0 + && !bind(so->fd,(struct sockaddr *) addr ,sizeof(*addr)) + && !sock_set_timeout(so, net_timeout) + && !listen(so->fd, 1) ) { - so->type = SOCK_STREAM; - so->connected = con; - so->timeout = -1; return 0; } else { if (so->fd >= 0) close(so->fd); @@ -556,16 +556,16 @@ static const JSCFunctionListEntry net_funcs[] = { JS_PROP_INT32_DEF("AF_INET6", AF_INET6, JS_PROP_CONFIGURABLE), JS_PROP_INT32_DEF("SOCK_STREAM", SOCK_STREAM, JS_PROP_CONFIGURABLE), JS_PROP_INT32_DEF("SOCK_DGRAM", SOCK_DGRAM, JS_PROP_CONFIGURABLE), - JS_CFUNC_MAGIC_DEF("udpListen", 2, js_net_ip_listen, 0), - JS_CFUNC_MAGIC_DEF("tcpListen", 2, js_net_ip_listen, 1), - JS_CFUNC_MAGIC_DEF("udp6Listen", 2, js_net_ip_listen, 2), - JS_CFUNC_MAGIC_DEF("tcp6Listen", 2, js_net_ip_listen, 3), + JS_CFUNC_MAGIC_DEF("udpListen", 2, js_net_ip_listen, 0), + JS_CFUNC_MAGIC_DEF("tcpListen", 2, js_net_ip_listen, 1), + JS_CFUNC_MAGIC_DEF("udp6Listen", 2, js_net_ip_listen, 2), + JS_CFUNC_MAGIC_DEF("tcp6Listen", 2, js_net_ip_listen, 3), JS_CFUNC_MAGIC_DEF("udpConnect", 2, js_net_ip_connect, 0), JS_CFUNC_MAGIC_DEF("tcpConnect", 2, js_net_ip_connect, 1), JS_CFUNC_MAGIC_DEF("udp6Connect", 2, js_net_ip_connect, 2), JS_CFUNC_MAGIC_DEF("tcp6Connect", 2, js_net_ip_connect, 3), - JS_CFUNC_MAGIC_DEF("unixListen", 1, js_net_unix_bind, 0), - JS_CFUNC_MAGIC_DEF("unixConnect", 1, js_net_unix_bind, 1), + JS_CFUNC_MAGIC_DEF("unixListen", 1, js_net_unix_bind, 0), + JS_CFUNC_MAGIC_DEF("unixConnect", 1, js_net_unix_bind, 1), // XXX: How does one use a get/set property in a module provided object? // JS_CGETSET_DEF("timeout", net_get_timeout, net_set_timeout), @@ -573,10 +573,6 @@ static const JSCFunctionListEntry net_funcs[] = { JS_CFUNC_DEF("getConnectTimeout", 1, net_get_timeout), }; -// static const JSCFunctionListEntry net_obj[] = { -// JS_OBJECT_DEF("net", net_funcs, countof(net_funcs), JS_PROP_CONFIGURABLE) -// }; - static int sock_modinit(JSContext *ctx, JSModuleDef *mod ) { JSValue proto = JS_NewObject(ctx); @@ -596,7 +592,6 @@ JSModuleDef *js_init_module(JSContext *ctx, const char *name) { if (mod) { JS_AddModuleExportList(ctx, mod, net_funcs, countof(net_funcs)); - // JS_SetPropertyFunctionList(ctx, global, net_obj, countof(net_obj)); if ( !JS_IsUndefined(os) ) JS_SetPropertyFunctionList(ctx, os, os_socket_funcs, countof(os_socket_funcs)); -- 2.39.5