]> git.kaiwu.me - nginx.git/commitdiff
Core: make channel file descriptor errors non-fatal
authorRoman Arutyunyan <arut@nginx.com>
Tue, 18 Aug 2026 16:58:42 +0000 (20:58 +0400)
committerRoman Arutyunyan <arutyunyan.roman@gmail.com>
Tue, 25 Aug 2026 17:33:21 +0000 (21:33 +0400)
If a worker process' file descriptor table is full, recvmsg() is not
able to read out the channel fd sent from the master.  This results in
different behavior on different platforms.

On Linux, recvmsg() succeeds, returns the payload and drops the fd.
On BSD systems, the first recvmsg() call fails with EMSGSIZE/EMFILE,
but the next call succeeds and returns the payload without the fd.
On Solaris, recvmsg() fails with EMFILE and drops both the payload
and the fd.

Previously, all of these paths resulted in fatal errors.  The worker
channel was closed and the worker was no longer capable of receiving
messages from the master.  In particular, the NGX_CMD_QUIT command
could not be delivered anymore.  As a result, the worker never exited
after nginx reload and kept running and accepting new connections
forever until killed by a signal.

Fix is to consider those errors non-fatal.  An additional check is
added to the NGX_CMD_CLOSE_CHANNEL handler to make sure the worker
has received a file descriptor for this entry before. Also, the
MSG_CTRUNC check is removed since a proper ancillary data size
validation is now implemented.

src/os/unix/ngx_channel.c
src/os/unix/ngx_process_cycle.c

index 1efa0667cb86e645b2b55b99c32b0bd1a61e2213..b03bbbb153348c88ac3b0ba0ea73c23b987d08f8 100644 (file)
@@ -134,6 +134,13 @@ ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size, ngx_log_t *log)
         }
 
         ngx_log_error(NGX_LOG_ALERT, log, err, "recvmsg() failed");
+
+        if (err == NGX_EMSGSIZE || err == NGX_EMFILE) {
+            /* file descriptor table is full */
+            ngx_memzero(ch, size);
+            return 0;
+        }
+
         return NGX_ERROR;
     }
 
@@ -152,27 +159,29 @@ ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size, ngx_log_t *log)
 
     if (ch->command == NGX_CMD_OPEN_CHANNEL) {
 
-        if (cmsg.cm.cmsg_len < (socklen_t) CMSG_LEN(sizeof(int))) {
+        if (msg.msg_controllen < (socklen_t) CMSG_LEN(sizeof(int))) {
             ngx_log_error(NGX_LOG_ALERT, log, 0,
                           "recvmsg() returned too small ancillary data");
-            return NGX_ERROR;
-        }
+            ch->fd = -1;
 
-        if (cmsg.cm.cmsg_level != SOL_SOCKET || cmsg.cm.cmsg_type != SCM_RIGHTS)
+        } else if (cmsg.cm.cmsg_level != SOL_SOCKET
+                   || cmsg.cm.cmsg_type != SCM_RIGHTS)
         {
             ngx_log_error(NGX_LOG_ALERT, log, 0,
                           "recvmsg() returned invalid ancillary data "
                           "level %d or type %d",
                           cmsg.cm.cmsg_level, cmsg.cm.cmsg_type);
             return NGX_ERROR;
-        }
 
-        /* ch->fd = *(int *) CMSG_DATA(&cmsg.cm); */
+        } else {
 
-        ngx_memcpy(&ch->fd, CMSG_DATA(&cmsg.cm), sizeof(int));
+            /* ch->fd = *(int *) CMSG_DATA(&cmsg.cm); */
+
+            ngx_memcpy(&ch->fd, CMSG_DATA(&cmsg.cm), sizeof(int));
+        }
     }
 
-    if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) {
+    if (msg.msg_flags & MSG_TRUNC) {
         ngx_log_error(NGX_LOG_ALERT, log, 0,
                       "recvmsg() truncated data");
     }
@@ -183,10 +192,11 @@ ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size, ngx_log_t *log)
         if (msg.msg_accrightslen != sizeof(int)) {
             ngx_log_error(NGX_LOG_ALERT, log, 0,
                           "recvmsg() returned no ancillary data");
-            return NGX_ERROR;
-        }
+            ch->fd = -1;
 
-        ch->fd = fd;
+        } else {
+            ch->fd = fd;
+        }
     }
 
 #endif
index 5bc5ce9792df0d251b6a9dc943a0f2370335f4ca..33c25487bdada3d7a6bbd338a11983a427b7d247 100644 (file)
@@ -1073,6 +1073,12 @@ ngx_channel_handler(ngx_event_t *ev)
                            ch.slot, ch.pid, ngx_processes[ch.slot].pid,
                            ngx_processes[ch.slot].channel[0]);
 
+            if (ngx_processes[ch.slot].channel[0] == -1
+                || ngx_processes[ch.slot].pid != ch.pid)
+            {
+                break;
+            }
+
             if (close(ngx_processes[ch.slot].channel[0]) == -1) {
                 ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
                               "close() channel failed");