]> git.plutz.net Git - quickjs_net/commitdiff
introduce timeout option
authorPaul Hänsch <paul@plutz.net>
Thu, 12 Mar 2026 23:36:40 +0000 (00:36 +0100)
committerPaul Hänsch <paul@plutz.net>
Thu, 12 Mar 2026 23:36:40 +0000 (00:36 +0100)
socket.c

index 2b5c9ff9e0e79c31efe74b295228a9058aee2c71..43858f1dc4aafa02a9fe53a29370591822f76d17 100644 (file)
--- 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[] = {