[PATCH 2 of 2] Added "max_shutdown_workers" directive

Roman Arutyunyan arut at nginx.com
Mon Aug 28 12:30:44 UTC 2023


# HG changeset patch
# User Roman Arutyunyan <arut at nginx.com>
# Date 1693218974 -14400
#      Mon Aug 28 14:36:14 2023 +0400
# Node ID 42c3166b675a6a7624bdf3d78c0d4685ce8172e3
# Parent  fdad00808cb485ab83e919be59e9211ef06ac56a
Added "max_shutdown_workers" directive.

The directive sets maximum number of workers in shutdown state while reloading
nginx configuration.  Upon reaching this value, workers restart is postponed
until shutdown workers start exiting.  This allows for lower peak resource
consumption at the cost of slower reconfiguration.

diff --git a/src/core/nginx.c b/src/core/nginx.c
--- a/src/core/nginx.c
+++ b/src/core/nginx.c
@@ -83,6 +83,13 @@ static ngx_command_t  ngx_core_commands[
       0,
       NULL },
 
+    { ngx_string("max_shutdown_workers"),
+      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      0,
+      offsetof(ngx_core_conf_t, max_shutdown_workers),
+      NULL },
+
     { ngx_string("debug_points"),
       NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
       ngx_conf_set_enum_slot,
@@ -1117,6 +1124,7 @@ ngx_core_module_create_conf(ngx_cycle_t 
     ccf->shutdown_timeout = NGX_CONF_UNSET_MSEC;
 
     ccf->worker_processes = NGX_CONF_UNSET;
+    ccf->max_shutdown_workers = NGX_CONF_UNSET;
     ccf->debug_points = NGX_CONF_UNSET;
 
     ccf->rlimit_nofile = NGX_CONF_UNSET;
@@ -1146,6 +1154,7 @@ ngx_core_module_init_conf(ngx_cycle_t *c
     ngx_conf_init_msec_value(ccf->shutdown_timeout, 0);
 
     ngx_conf_init_value(ccf->worker_processes, 1);
+    ngx_conf_init_value(ccf->max_shutdown_workers, 0);
     ngx_conf_init_value(ccf->debug_points, 0);
 
 #if (NGX_HAVE_CPU_AFFINITY)
diff --git a/src/core/ngx_cycle.h b/src/core/ngx_cycle.h
--- a/src/core/ngx_cycle.h
+++ b/src/core/ngx_cycle.h
@@ -94,6 +94,7 @@ typedef struct {
     ngx_msec_t                shutdown_timeout;
 
     ngx_int_t                 worker_processes;
+    ngx_int_t                 max_shutdown_workers;
     ngx_int_t                 debug_points;
 
     ngx_int_t                 rlimit_nofile;
diff --git a/src/os/unix/ngx_process.c b/src/os/unix/ngx_process.c
--- a/src/os/unix/ngx_process.c
+++ b/src/os/unix/ngx_process.c
@@ -216,6 +216,7 @@ ngx_spawn_process(ngx_cycle_t *cycle, ng
     ngx_processes[s].data = data;
     ngx_processes[s].name = name;
     ngx_processes[s].exiting = 0;
+    ngx_processes[s].old = 0;
 
     switch (respawn) {
 
diff --git a/src/os/unix/ngx_process.h b/src/os/unix/ngx_process.h
--- a/src/os/unix/ngx_process.h
+++ b/src/os/unix/ngx_process.h
@@ -33,6 +33,7 @@ typedef struct {
     unsigned            detached:1;
     unsigned            exiting:1;
     unsigned            exited:1;
+    unsigned            old:1;
 } ngx_process_t;
 
 
diff --git a/src/os/unix/ngx_process_cycle.c b/src/os/unix/ngx_process_cycle.c
--- a/src/os/unix/ngx_process_cycle.c
+++ b/src/os/unix/ngx_process_cycle.c
@@ -15,6 +15,7 @@ static void ngx_start_worker_processes(n
     ngx_int_t type);
 static void ngx_start_worker_process(ngx_cycle_t *cycle, ngx_int_t i,
     ngx_int_t type);
+static void ngx_restart_worker_processes(ngx_cycle_t *cycle);
 static void ngx_start_cache_manager_processes(ngx_cycle_t *cycle,
     ngx_uint_t respawn);
 static void ngx_pass_open_channel(ngx_cycle_t *cycle);
@@ -22,6 +23,7 @@ static void ngx_signal_worker_processes(
 static void ngx_signal_worker_process(ngx_cycle_t *cycle, ngx_int_t i,
     int signo);
 static ngx_uint_t ngx_reap_children(ngx_cycle_t *cycle);
+static ngx_uint_t ngx_restart_worker_process(ngx_cycle_t *cycle);
 static void ngx_master_process_exit(ngx_cycle_t *cycle);
 static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data);
 static void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker);
@@ -235,8 +237,8 @@ ngx_master_process_cycle(ngx_cycle_t *cy
             ngx_cycle = cycle;
             ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
                                                    ngx_core_module);
-            ngx_start_worker_processes(cycle, ccf->worker_processes,
-                                       NGX_PROCESS_JUST_RESPAWN);
+            ngx_restart_worker_processes(cycle);
+
             ngx_start_cache_manager_processes(cycle, 1);
 
             /* allow new processes to start */
@@ -360,6 +362,70 @@ ngx_start_worker_process(ngx_cycle_t *cy
 
 
 static void
+ngx_restart_worker_processes(ngx_cycle_t *cycle)
+{
+    ngx_int_t         i, n, m, worker;
+    ngx_core_conf_t  *ccf;
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    n = 0;
+    m = ccf->max_shutdown_workers;
+
+    if (m == 0) {
+        goto start;
+    }
+
+    for (i = 0; i < ngx_last_process; i++) {
+
+        if (ngx_processes[i].pid == -1
+            || ngx_processes[i].proc != ngx_worker_process_cycle)
+        {
+            continue;
+        }
+
+        ngx_processes[i].old = 0;
+        worker = (intptr_t) ngx_processes[i].data;
+
+        if (!ngx_processes[i].exiting && worker < ccf->worker_processes) {
+            n++;
+
+        } else if (m > 0) {
+            m--;
+        }
+    }
+
+    n = (n > m) ? n - m : 0;
+
+    if (n == 0) {
+        goto start;
+    }
+
+    for (i = 0; i < ngx_last_process; i++) {
+
+        if (ngx_processes[i].pid == -1
+            || ngx_processes[i].proc != ngx_worker_process_cycle)
+        {
+            continue;
+        }
+
+        ngx_processes[i].old = 1;
+        worker = (intptr_t) ngx_processes[i].data;
+
+        if (!ngx_processes[i].exiting && worker < n) {
+            ngx_processes[i].just_spawn = 1;
+        }
+    }
+
+start:
+
+    for (i = n; i < ccf->worker_processes; i++) {
+        ngx_start_worker_process(cycle, i, NGX_PROCESS_JUST_RESPAWN);
+    }
+}
+
+
+static void
 ngx_start_cache_manager_processes(ngx_cycle_t *cycle, ngx_uint_t respawn)
 {
     ngx_uint_t    i, manager, loader;
@@ -486,15 +552,16 @@ ngx_signal_worker_process(ngx_cycle_t *c
     ch.fd = -1;
 
 
-    ngx_log_debug7(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
-                   "child: %i %P e:%d t:%d d:%d r:%d j:%d",
+    ngx_log_debug8(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                   "child: %i %P e:%d t:%d d:%d r:%d j:%d o:%d",
                    i,
                    ngx_processes[i].pid,
                    ngx_processes[i].exiting,
                    ngx_processes[i].exited,
                    ngx_processes[i].detached,
                    ngx_processes[i].respawn,
-                   ngx_processes[i].just_spawn);
+                   ngx_processes[i].just_spawn,
+                   ngx_processes[i].old);
 
     if (ngx_processes[i].detached || ngx_processes[i].pid == -1) {
         return;
@@ -563,15 +630,16 @@ ngx_reap_children(ngx_cycle_t *cycle)
     live = 0;
     for (i = 0; i < ngx_last_process; i++) {
 
-        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
-                       "child: %i %P e:%d t:%d d:%d r:%d j:%d",
+        ngx_log_debug8(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
+                       "child: %i %P e:%d t:%d d:%d r:%d j:%d o:%d",
                        i,
                        ngx_processes[i].pid,
                        ngx_processes[i].exiting,
                        ngx_processes[i].exited,
                        ngx_processes[i].detached,
                        ngx_processes[i].respawn,
-                       ngx_processes[i].just_spawn);
+                       ngx_processes[i].just_spawn,
+                       ngx_processes[i].old);
 
         if (ngx_processes[i].pid == -1) {
             continue;
@@ -660,6 +728,16 @@ ngx_reap_children(ngx_cycle_t *cycle)
                 ngx_processes[i].pid = -1;
             }
 
+            if (ngx_processes[i].old
+                && ngx_processes[i].exiting
+                && !ngx_terminate
+                && !ngx_quit)
+            {
+                if (ngx_restart_worker_process(cycle)) {
+                    live = 1;
+                }
+            }
+
         } else if (ngx_processes[i].exiting || !ngx_processes[i].detached) {
             live = 1;
         }
@@ -669,6 +747,53 @@ ngx_reap_children(ngx_cycle_t *cycle)
 }
 
 
+static ngx_uint_t
+ngx_restart_worker_process(ngx_cycle_t *cycle)
+{
+    ngx_int_t         i, j, m;
+    ngx_core_conf_t  *ccf;
+
+    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
+
+    j = -1;
+    m = ccf->max_shutdown_workers;
+
+    if (m == 0) {
+        return 0;
+    }
+
+    for (i = 0; i < ngx_last_process; i++) {
+
+        if (ngx_processes[i].pid == -1 || !ngx_processes[i].old) {
+            continue;
+        }
+
+        if (!ngx_processes[i].exiting && !ngx_processes[i].exited) {
+            j = i;
+            continue;
+        }
+
+        if (--m == 0) {
+            return 0;
+        }
+    }
+
+    if (j == -1) {
+        return 0;
+    }
+
+    ngx_start_worker_process(cycle, (intptr_t) ngx_processes[j].data,
+                             NGX_PROCESS_RESPAWN);
+
+    ngx_msleep(100);
+
+    ngx_signal_worker_process(cycle, j,
+                              ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
+
+    return 1;
+}
+
+
 static void
 ngx_master_process_exit(ngx_cycle_t *cycle)
 {


More information about the nginx-devel mailing list