[PATCH] Core: prefer ioctl(FIOCLEX) over fcntl(FD_CLOEXEC)

Ben Noordhuis info at bnoordhuis.nl
Thu Oct 25 13:46:47 UTC 2012


Use ioctl(FIOCLEX) to set the close-on-exec flag on platforms where it makes
sense: Linux, Darwin, the BSDs, *not* Solaris.

ioctl(FIOCLEX) is generally slightly faster than fcntl(FD_CLOEXEC), on the
order of 3-4%.

Ad-hoc benchmark:

  #ifndef __linux__
  #include <sys/filio.h>
  #endif
  #include <sys/ioctl.h>
  #include <unistd.h>
  #include <fcntl.h>
  #include <stdio.h>
  #include <stdlib.h>

  int main(int argc, char **argv)
  {
    int fd = open("/dev/null", O_RDWR);
    int n = 1e8;

    if (argc < 2 || atoi(argv[1]) == 0)
      while (n--)
        fcntl(fd, F_SETFD, FD_CLOEXEC);
    else
      while (n--)
        ioctl(fd, FIOCLEX);

    return 0;
  }

Results, median-of-10 style:

  $ time tmp/cloexec 0 # FD_CLOEXEC
  real    0m5.558s
  user    0m2.388s
  sys     0m3.152s

  $ time tmp/cloexec 1 # FIOCLEX
  real    0m5.373s
  user    0m1.548s
  sys     0m3.808s

The difference is small but consistent.

Caveat emptor: This change slows down the close-on-exec operation by about 4-5%
when a 32 bits nginx talks to a 64 bits kernel. The 32/64 bits conversion layer
has to translate the 32 bits ioctl() system call to its 64 bits counterpart,
which unfortunately is a slow operation.
---
 auto/unix                 |   21 +++++++++++++++++++++
 src/core/ngx_cycle.c      |   10 ++++------
 src/os/unix/ngx_process.c |   10 ++++------
 src/os/unix/ngx_socket.h  |   19 +++++++++++++++++++
 4 files changed, 48 insertions(+), 12 deletions(-)

diff --git a/auto/unix b/auto/unix
index b0a0e4c..9784a1f 100755
--- a/auto/unix
+++ b/auto/unix
@@ -720,6 +720,27 @@ ngx_feature_test="int i = FIONBIO; printf(\"%d\", i)"
 . auto/feature
 
 
+ngx_feature="ioctl(FIOCLEX)"
+ngx_feature_name="NGX_HAVE_FIOCLEX"
+ngx_feature_run=no
+ngx_feature_incs="#include <sys/ioctl.h>
+                  #include <stdio.h>
+                  $NGX_INCLUDE_SYS_FILIO_H"
+ngx_feature_path=
+ngx_feature_libs=
+ngx_feature_test="#ifdef __sun
+                  /* With some version of SunOS, FIOCLEX simply doesn't work.
+                   * With other versions it's roughly 3x times slower than
+                   * fcntl(FD_CLOEXEC).
+                   */
+                  fail
+                  #else
+                  int i = FIOCLEX, k = FIONCLEX; printf(\"%d %d\", i, k);
+                  #endif
+                  "
+. auto/feature
+
+
 ngx_feature="struct tm.tm_gmtoff"
 ngx_feature_name="NGX_HAVE_GMTOFF"
 ngx_feature_run=no
diff --git a/src/core/ngx_cycle.c b/src/core/ngx_cycle.c
index f153729..ba6b3c0 100644
--- a/src/core/ngx_cycle.c
+++ b/src/core/ngx_cycle.c
@@ -383,10 +383,9 @@ ngx_init_cycle(ngx_cycle_t *old_cycle)
         }
 
 #if !(NGX_WIN32)
-        if (fcntl(file[i].fd, F_SETFD, FD_CLOEXEC) == -1) {
+        if (ngx_cloexec(file[i].fd) == -1) {
             ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
-                          "fcntl(FD_CLOEXEC) \"%s\" failed",
-                          file[i].name.data);
+                          ngx_cloexec_n " \"%s\" failed", file[i].name.data);
             goto failed;
         }
 #endif
@@ -1219,10 +1218,9 @@ ngx_reopen_files(ngx_cycle_t *cycle, ngx_uid_t user)
             }
         }
 
-        if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) {
+        if (ngx_cloexec(fd) == -1) {
             ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
-                          "fcntl(FD_CLOEXEC) \"%s\" failed",
-                          file[i].name.data);
+                          ngx_cloexec_n " \"%s\" failed", file[i].name.data);
 
             if (ngx_close_file(fd) == NGX_FILE_ERROR) {
                 ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
diff --git a/src/os/unix/ngx_process.c b/src/os/unix/ngx_process.c
index 4ef3582..4969f06 100644
--- a/src/os/unix/ngx_process.c
+++ b/src/os/unix/ngx_process.c
@@ -157,18 +157,16 @@ ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
             return NGX_INVALID_PID;
         }
 
-        if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
+        if (ngx_cloexec(ngx_processes[s].channel[0]) == -1) {
             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
-                          "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
-                           name);
+                          ngx_cloexec_n " failed while spawning \"%s\"", name);
             ngx_close_channel(ngx_processes[s].channel, cycle->log);
             return NGX_INVALID_PID;
         }
 
-        if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
+        if (ngx_cloexec(ngx_processes[s].channel[1]) == -1) {
             ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
-                          "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
-                           name);
+                          ngx_cloexec_n " failed while spawning \"%s\"", name);
             ngx_close_channel(ngx_processes[s].channel, cycle->log);
             return NGX_INVALID_PID;
         }
diff --git a/src/os/unix/ngx_socket.h b/src/os/unix/ngx_socket.h
index fcc5153..4263114 100644
--- a/src/os/unix/ngx_socket.h
+++ b/src/os/unix/ngx_socket.h
@@ -20,6 +20,25 @@ typedef int  ngx_socket_t;
 #define ngx_socket_n        "socket()"
 
 
+#if (NGX_HAVE_FIOCLEX)
+
+#define ngx_cloexec(fd)     ioctl(fd, FIOCLEX)
+#define ngx_nocloexec(fd)   ioctl(fd, FIONCLEX)
+
+#define ngx_cloexec_n       "ioctl(FIOCLEX)"
+#define ngx_nocloexec_n     "ioctl(FIONCLEX)"
+
+#else
+
+#define ngx_cloexec(fd)     fcntl(fd, F_SETFD, FD_CLOEXEC)
+#define ngx_nocloexec(fd)   fcntl(fd, F_SETFD, 0)
+
+#define ngx_cloexec_n       "fcntl(FD_CLOEXEC)"
+#define ngx_nocloexec_n     "fcntl(!FD_CLOEXEC)"
+
+#endif
+
+
 #if (NGX_HAVE_FIONBIO)
 
 int ngx_nonblocking(ngx_socket_t s);
-- 
1.7.9.5



More information about the nginx-devel mailing list