A new user interface for you! Read more...

File 0368-erts-Handle-EMFILE-errors-in-forker_driver-for-write.patch of Package erlang

From 48bbcab1b0d51ed19ab5676aa712e976bc76ee97 Mon Sep 17 00:00:00 2001
From: Lukas Larsson <lukas@erlang.org>
Date: Tue, 12 Jun 2018 15:25:38 +0200
Subject: [PATCH] erts: Handle EMFILE errors in forker_driver for write

Before this change, if a write to the uds failed due to
EMFILE to ETOOMANYREFS the entire vm would crash. This
change makes it so that an SIGCHLD is simulated to that
the error is propagated to the user instead of terminating
the VM.
---
 erts/emulator/sys/unix/sys_drivers.c | 107 ++++++++++++++++++++---------------
 erts/emulator/sys/unix/sys_uds.c     |  13 ++++-
 2 files changed, 74 insertions(+), 46 deletions(-)

diff --git a/erts/emulator/sys/unix/sys_drivers.c b/erts/emulator/sys/unix/sys_drivers.c
index 872c3a80b1..d0498f0bd5 100644
--- a/erts/emulator/sys/unix/sys_drivers.c
+++ b/erts/emulator/sys/unix/sys_drivers.c
@@ -685,7 +685,7 @@ static ErlDrvData spawn_start(ErlDrvPort port_num, char* name,
 
         /* we send the request to do the fork */
         if ((res = writev(ofd[1], io_vector, iov_len > MAXIOV ? MAXIOV : iov_len)) < 0) {
-            if (errno == ERRNO_BLOCK) {
+            if (errno == ERRNO_BLOCK || errno == EINTR) {
                 res = 0;
             } else {
                 int err = errno;
@@ -1257,6 +1257,8 @@ static int port_inp_failure(ErtsSysDriverData *dd, int res)
         }
        driver_failure_eof(dd->port_num);
     } else if (dd->ifd) {
+        if (dd->alive == -1)
+            errno = dd->status;
         erl_drv_init_ack(dd->port_num, ERL_DRV_ERROR_ERRNO);
     } else {
 	driver_failure_posix(dd->port_num, err);
@@ -1287,10 +1289,10 @@ static void ready_input(ErlDrvData e, ErlDrvEvent ready_fd)
         int res;
 
         if((res = read(ready_fd, &proto, sizeof(proto))) <= 0) {
+            if (res < 0 && (errno == ERRNO_BLOCK || errno == EINTR))
+                return;
             /* hmm, child setup seems to have closed the pipe too early...
                we close the port as there is not much else we can do */
-            if (res < 0 && errno == ERRNO_BLOCK)
-                return;
             driver_select(port_num, ready_fd, ERL_DRV_READ, 0);
             if (res == 0)
                 errno = EPIPE;
@@ -1424,7 +1426,7 @@ static void ready_input(ErlDrvData e, ErlDrvEvent ready_fd)
 		    continue;
 		}
 		else {		/* The last message we got was split */
-		        char *buf = erts_alloc_fnf(ERTS_ALC_T_FD_ENTRY_BUF, h);
+                    char *buf = erts_alloc_fnf(ERTS_ALC_T_FD_ENTRY_BUF, h);
 		    if (!buf) {
 			errno = ENOMEM;
 			port_inp_failure(dd, -1);
@@ -1670,15 +1672,37 @@ static void forker_stop(ErlDrvData e)
        the port has been closed by the user. */
 }
 
+static ErlDrvSizeT forker_deq(ErlDrvPort port_num, ErtsSysForkerProto *proto)
+{
+    close(proto->u.start.fds[0]);
+    close(proto->u.start.fds[1]);
+    if (proto->u.start.fds[1] != proto->u.start.fds[2])
+        close(proto->u.start.fds[2]);
+
+    return driver_deq(port_num, sizeof(*proto));
+}
+
+static void forker_sigchld(Eterm port_id, int error)
+{
+    ErtsSysForkerProto *proto = erts_alloc(ERTS_ALC_T_DRV_CTRL_DATA, sizeof(*proto));
+    proto->action = ErtsSysForkerProtoAction_SigChld;
+    proto->u.sigchld.error_number = error;
+    proto->u.sigchld.port_id = port_id;
+
+    /* ideally this would be a port_command call, but as command is
+       already used by the spawn_driver, we use control instead.
+       Note that when using erl_drv_port_control it is an asynchronous
+       control. */
+    erl_drv_port_control(port_id, 'S', (char*)proto, sizeof(*proto));
+}
+
 static void forker_ready_input(ErlDrvData e, ErlDrvEvent fd)
 {
     int res;
-    ErtsSysForkerProto *proto;
+    ErtsSysForkerProto proto;
 
-    proto = erts_alloc(ERTS_ALC_T_DRV_CTRL_DATA, sizeof(*proto));
-
-    if ((res = read(fd, proto, sizeof(*proto))) < 0) {
-        if (errno == ERRNO_BLOCK)
+    if ((res = read(fd, &proto, sizeof(proto))) < 0) {
+        if (errno == ERRNO_BLOCK || errno == EINTR)
             return;
         erts_exit(ERTS_DUMP_EXIT, "Failed to read from erl_child_setup: %d\n", errno);
     }
@@ -1686,10 +1710,10 @@ static void forker_ready_input(ErlDrvData e, ErlDrvEvent fd)
     if (res == 0)
         erts_exit(ERTS_DUMP_EXIT, "erl_child_setup closed\n");
 
-    ASSERT(res == sizeof(*proto));
+    ASSERT(res == sizeof(proto));
 
 #ifdef FORKER_PROTO_START_ACK
-    if (proto->action == ErtsSysForkerProtoAction_StartAck) {
+    if (proto.action == ErtsSysForkerProtoAction_StartAck) {
         /* Ideally we would like to not have to ack each Start
            command being sent over the uds, but it would seem
            that some operating systems (only observed on FreeBSD)
@@ -1699,28 +1723,15 @@ static void forker_ready_input(ErlDrvData e, ErlDrvEvent fd)
         ErlDrvPort port_num = (ErlDrvPort)e;
         int vlen;
         SysIOVec *iov = driver_peekq(port_num, &vlen);
-        ErtsSysForkerProto *proto = (ErtsSysForkerProto *)iov[0].iov_base;
-
-        close(proto->u.start.fds[0]);
-        close(proto->u.start.fds[1]);
-        if (proto->u.start.fds[1] != proto->u.start.fds[2])
-            close(proto->u.start.fds[2]);
+        ErtsSysForkerProto *qproto = (ErtsSysForkerProto *)iov[0].iov_base;
 
-        driver_deq(port_num, sizeof(*proto));
-
-        if (driver_sizeq(port_num) > 0)
+        if (forker_deq(port_num, qproto))
             driver_select(port_num, forker_fd, ERL_DRV_WRITE|ERL_DRV_USE, 1);
     } else
 #endif
     {
-        ASSERT(proto->action == ErtsSysForkerProtoAction_SigChld);
-
-        /* ideally this would be a port_command call, but as command is
-           already used by the spawn_driver, we use control instead.
-           Note that when using erl_drv_port_control it is an asynchronous
-           control. */
-        erl_drv_port_control(proto->u.sigchld.port_id, 'S',
-                             (char*)proto, sizeof(*proto));
+        ASSERT(proto.action == ErtsSysForkerProtoAction_SigChld);
+        forker_sigchld(proto.u.sigchld.port_id, proto.u.sigchld.error_number);
     }
 
 }
@@ -1730,7 +1741,8 @@ static void forker_ready_output(ErlDrvData e, ErlDrvEvent fd)
     ErlDrvPort port_num = (ErlDrvPort)e;
 
 #ifndef FORKER_PROTO_START_ACK
-    while (driver_sizeq(port_num) > 0) {
+    int loops = 10;
+    while (driver_sizeq(port_num) > 0 && --loops) {
 #endif
         int vlen;
         SysIOVec *iov = driver_peekq(port_num, &vlen);
@@ -1738,20 +1750,24 @@ static void forker_ready_output(ErlDrvData e, ErlDrvEvent fd)
         ASSERT(iov[0].iov_len >= (sizeof(*proto)));
         if (sys_uds_write(forker_fd, (char*)proto, sizeof(*proto),
                           proto->u.start.fds, 3, 0) < 0) {
-            if (errno == ERRNO_BLOCK)
+            if (errno == ERRNO_BLOCK || errno == EINTR) {
                 return;
-            erts_exit(ERTS_DUMP_EXIT, "Failed to write to erl_child_setup: %d\n", errno);
+            } else if (errno == EMFILE) {
+                forker_sigchld(proto->u.start.port_id, errno);
+                if (forker_deq(port_num, proto) == 0)
+                    driver_select(port_num, forker_fd, ERL_DRV_WRITE, 0);
+                return;
+            } else {
+                erts_exit(ERTS_DUMP_EXIT, "Failed to write to erl_child_setup: %d\n", errno);
+            }
         }
 #ifndef FORKER_PROTO_START_ACK
-        close(proto->u.start.fds[0]);
-        close(proto->u.start.fds[1]);
-        if (proto->u.start.fds[1] != proto->u.start.fds[2])
-            close(proto->u.start.fds[2]);
-        driver_deq(port_num, sizeof(*proto));
+        if (forker_deq(port_num, proto) == 0)
+            driver_select(port_num, forker_fd, ERL_DRV_WRITE, 0);
     }
-#endif
-
+#else
     driver_select(port_num, forker_fd, ERL_DRV_WRITE, 0);
+#endif
 }
 
 static ErlDrvSSizeT forker_control(ErlDrvData e, unsigned int cmd, char *buf,
@@ -1777,20 +1793,21 @@ static ErlDrvSSizeT forker_control(ErlDrvData e, unsigned int cmd, char *buf,
 
     if ((res = sys_uds_write(forker_fd, (char*)proto, sizeof(*proto),
                              proto->u.start.fds, 3, 0)) < 0) {
-        if (errno == ERRNO_BLOCK) {
+        if (errno == ERRNO_BLOCK || errno == EINTR) {
             driver_select(port_num, forker_fd, ERL_DRV_WRITE|ERL_DRV_USE, 1);
             return 0;
+        } else if (errno == EMFILE) {
+            forker_sigchld(proto->u.start.port_id, errno);
+            forker_deq(port_num, proto);
+            return 0;
+        } else {
+            erts_exit(ERTS_DUMP_EXIT, "Failed to write to erl_child_setup: %d\n", errno);
         }
-        erts_exit(ERTS_DUMP_EXIT, "Failed to write to erl_child_setup: %d\n", errno);
     }
 
 #ifndef FORKER_PROTO_START_ACK
     ASSERT(res == sizeof(*proto));
-    close(proto->u.start.fds[0]);
-    close(proto->u.start.fds[1]);
-    if (proto->u.start.fds[1] != proto->u.start.fds[2])
-        close(proto->u.start.fds[2]);
-    driver_deq(port_num, sizeof(*proto));
+    forker_deq(port_num, proto);
 #endif
 
     return 0;
diff --git a/erts/emulator/sys/unix/sys_uds.c b/erts/emulator/sys/unix/sys_uds.c
index c328fd00bb..39a4866065 100644
--- a/erts/emulator/sys/unix/sys_uds.c
+++ b/erts/emulator/sys/unix/sys_uds.c
@@ -132,7 +132,7 @@ sys_uds_writev(int fd, struct iovec *iov, size_t iov_len,
 
     struct msghdr msg;
     struct cmsghdr *cmsg = NULL;
-    int res, i;
+    int res, i, error;
 
     /* initialize socket message */
     memset(&msg, 0, sizeof(struct msghdr));
@@ -173,11 +173,22 @@ sys_uds_writev(int fd, struct iovec *iov, size_t iov_len,
 
     res = sendmsg(fd, &msg, flags);
 
+#ifdef ETOOMANYREFS
+    /* Linux may give ETOOMANYREFS when there are too many fds in transit.
+       We map this to EMFILE as bsd and other use this error code and we want
+       the behaviour to be the same on all OSs */
+    if (errno == ETOOMANYREFS)
+        errno = EMFILE;
+#endif
+    error = errno;
+
     if (iov_len > MAXIOV)
         free(iov[0].iov_base);
 
     free(msg.msg_control);
 
+    errno = error;
+
     return res;
 }
 
-- 
2.16.4