From: Paul Hänsch Date: Thu, 12 Mar 2026 23:36:40 +0000 (+0100) Subject: introduce timeout option X-Git-Url: https://git.plutz.net/?a=commitdiff_plain;h=66dbf6ff92598d29e2cc08f9abf23cf0b1a6dfa2;p=quickjs_net introduce timeout option --- diff --git a/socket.c b/socket.c index 2b5c9ff..43858f1 100644 --- a/socket.c +++ b/socket.c @@ -134,10 +134,37 @@ static JSClassID socket_cid; typedef struct { int fd; int type; + double timeout; struct sockaddr_storage bind; 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_IsNull(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; + + return JS_UNDEFINED; +} +static JSValue 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_accept( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv ) { @@ -147,25 +174,31 @@ static JSValue sock_accept( socklen_t ps = sizeof(data->bind); if ( newdata - && (newdata->fd = accept( - data->fd, (struct sockaddr *) &(newdata->peer), &ps - )) >= 0 + && ( newdata->fd = + accept( data->fd, (struct sockaddr *) &(newdata->peer), &ps) + ) >= 0 ) { memcpy(&(newdata->bind), &(data->bind), sizeof(data->bind)); JS_SetOpaque(new, newdata); + if (data->timeout >= 0) + (void) sock_set_timeout(ctx, new, JS_NewFloat64(ctx, data->timeout)); + else newdata->timeout = -1; + return new; + } else { - new = libc_error(ctx); js_free(ctx, newdata); JS_FreeValue(ctx, new); + if ( errno == EWOULDBLOCK || errno == EAGAIN ) + return JS_UNDEFINED; + else return libc_error(ctx); } - - return new; } static JSValue sock_send( JSContext *ctx, JSValueConst this, int argc, JSValueConst *argv ) { SocketData * data = JS_GetOpaque2(ctx, this, socket_cid); - uint8_t *buf; size_t len; + // XXX: how portable is MSG_NOSIGNAL ? (POSIX.1-2008) + uint8_t *buf; size_t len; int flags = MSG_NOSIGNAL; const char *host, *port; int gai_err; struct addrinfo hints = { .ai_family = data->bind.ss_family, .ai_socktype = data->type }; struct addrinfo * dest; struct sockaddr * to; @@ -187,15 +220,15 @@ 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])) ) { - // XXX: how portable is MSG_NOSIGNAL ? (POSIX.1-2008) - len = sendto(data->fd, buf, len, MSG_NOSIGNAL, to, to ? sizeof(*to) : 0); + len = sendto(data->fd, buf, len, flags, to, to ? sizeof(*to) : 0); } else if ( (buf = (uint8_t*) JS_ToCStringLen(ctx, &len, argv[0])) ) { - len = sendto(data->fd, buf, len, MSG_NOSIGNAL, to, to ? sizeof(*to) : 0); + len = sendto(data->fd, buf, len, flags, to, to ? sizeof(*to) : 0); JS_FreeCString(ctx, (char *) buf); } else return JS_ThrowTypeError(ctx, @@ -215,39 +248,38 @@ static JSValue 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; + uint8_t *buf; int len; JSValue jbuf; int flags = 0; socklen_t ps = sizeof(data->peer); - // see how much data is queued up if no length argument was given + // check current queue size if no valid length was given // XXX: how portable is ioctl/FIONREAD ? if ( (JS_ToInt32(ctx, &len, argv[0]) || len <= 0) && ioctl(data->fd, FIONREAD, &len) ) return libc_error(ctx); - // return immediately if 0 data was requested (and queue is empty) - if (len == 0 && str) - return JS_NewStringLen(ctx, NULL, 0); - else if (len == 0) - return JS_NewArrayBufferCopy(ctx, NULL, 0); + if (len <= 0) len = 65535; // if no data is queued, wait for next packet + if (data->timeout == 0) flags |= MSG_DONTWAIT; - else if ( len > 0 - && (buf = js_malloc(ctx, len)) - && ((len = recvfrom(data->fd, buf, len, 0, + if ( ( buf = js_malloc(ctx, len) ) + && ((len = recvfrom(data->fd, buf, len, flags, (struct sockaddr *) &(data->peer), &ps)) >= 0) ) { - // make sure the buffer is resized after a short read - // avoid realloc(p, 0) as recommended by glibc + // resize/clear buffer in case of short read, if (len) js_realloc(ctx, buf, len); else { js_free(ctx, buf); buf = NULL; } // XXX: what does the opaque field in NewArryBuffer do? Is it important? - if (str) jbuf = JS_NewStringLen(ctx, (char*) buf, len); - else jbuf = JS_NewArrayBuffer(ctx, buf, len, js_array_buffer_free, NULL, 0); - return jbuf; - + if (str) { + jbuf = JS_NewStringLen(ctx, (char*) buf, len); + js_free(ctx, buf); + return jbuf; + } else + return JS_NewArrayBuffer(ctx, buf, len, js_array_buffer_free, NULL, 0); } else { js_free(ctx, buf); - return libc_error(ctx); + if ( errno == EWOULDBLOCK || errno == EAGAIN ) + return JS_UNDEFINED; + else return libc_error(ctx); } } @@ -302,7 +334,7 @@ static const JSCFunctionListEntry socket_ptype[] = { 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", ) // TODO: send / receive timeout + JS_CGETSET_DEF("timeout", sock_get_timeout, sock_set_timeout), // JS_CFUNC_DEF("reconnect", ) // TODO }; @@ -332,6 +364,7 @@ static JSValue ip_listen( && ( hints.ai_socktype == SOCK_DGRAM || !listen(data->fd, 1)) ) { data->type = self->ai_socktype; + data->timeout = -1; memcpy(&(data->bind), self->ai_addr, sizeof(struct sockaddr)); JS_SetOpaque(new, data); } else { @@ -369,6 +402,7 @@ static JSValue ip_connect( && !ai_connect(data->fd, peer) ) { data->type = peer->ai_socktype; + data->timeout = -1; memcpy(&(data->peer), peer->ai_addr, sizeof(struct sockaddr)); JS_SetOpaque(new, data); } else { @@ -406,6 +440,7 @@ static JSValue unix_bind( ) && (c || !listen(data->fd, 1)) ) { data->type = SOCK_STREAM; + data->timeout = -1; JS_SetOpaque(new, data); } else { if (data && data->fd >= 0) close(data->fd); @@ -440,7 +475,6 @@ static const JSCFunctionListEntry net_funcs[] = { JS_CFUNC_MAGIC_DEF("tcp6Connect", 2, ip_connect, 3), JS_CFUNC_MAGIC_DEF("unixListen", 1, unix_bind, 0), JS_CFUNC_MAGIC_DEF("unixConnect", 1, unix_bind, 1), - // JS_CGETSET_DEF("timeout", ) // TODO: connect timeout }; // static const JSCFunctionListEntry net_obj[] = {