]> git.kaiwu.me - nginx.git/commitdiff
Control API: reload support.
authorAva Hahn <a.hahn@f5.com>
Tue, 30 Dec 2025 19:35:18 +0000 (19:35 +0000)
committerAva Hahn <110854134+avahahn@users.noreply.github.com>
Tue, 1 Sep 2026 17:34:56 +0000 (10:34 -0700)
A new /control/config endpoint provides configuration reload function
with feedback returned to the user on the outcome.

Example:

$ curl 127.0.0.1:1234/1/control/config -X PATCH -v | jq
< HTTP/1.1 200 OK
< ...
<
{
  "logs": []
}

$ curl 127.0.0.1:1234/1/control/config -X PATCH -v | jq
< HTTP/1.1 422 Unprocessable Entity
< ...
<
{
  "logs": [
  "...: invalid number of arguments in \"listen\" directive"
  ]
}

Co-authored-by: Pavel Pautov <p.pautov@f5.com>
src/os/unix/ngx_control.c
src/os/unix/ngx_process_cycle.c

index ddecfe6408b4b9878000abeffa34937e72040a34..b607389959ff8d4ac4c0502e1fa6bd33765b1454 100644 (file)
@@ -36,6 +36,8 @@ typedef struct {
 
     ngx_buf_t    *in;
     ngx_chain_t  *out;
+
+    ngx_uint_t    reloaded;
 } ngx_control_request_t;
 
 
@@ -60,6 +62,9 @@ static ngx_int_t ngx_control_api_endpoints(ngx_control_request_t *r,
     const char **paths, ngx_int_t npaths);
 static ngx_int_t ngx_control_show_processes(ngx_control_request_t *r);
 static ngx_int_t ngx_control_show_version(ngx_control_request_t *r);
+static ngx_int_t ngx_control_reload_config(ngx_control_request_t *r);
+static void ngx_control_log_capture(ngx_log_t *log, ngx_uint_t l,
+    u_char *buf, size_t len);
 
 static ngx_int_t ngx_control_parse_request_line(ngx_control_request_t *r);
 static ngx_int_t ngx_control_send_json(ngx_control_request_t *r, ngx_int_t code,
@@ -288,7 +293,7 @@ ngx_int_t
 ngx_control_handle_events(void)
 {
     int                     ready, revents;
-    ngx_uint_t              i;
+    ngx_uint_t              i, reloaded;
     struct pollfd          *pfd;
     ngx_control_request_t  *r;
 
@@ -307,6 +312,8 @@ ngx_control_handle_events(void)
         return NGX_OK;
     }
 
+    reloaded = 0;
+
     for (i = 0; i < ngx_control_pollfd_n; /* void */) {
 
         r = &ngx_control_requests[i];
@@ -317,6 +324,10 @@ ngx_control_handle_events(void)
                        "control: i:%ui fd:%d e:%d re:%d",
                        i, pfd->fd, pfd->events, pfd->revents);
 
+        if (i) {
+            r->pool->log = ngx_cycle->log;
+        }
+
         if ((revents & (POLLERR|POLLHUP|POLLNVAL)) && i != 0) {
             ngx_control_close(r);
             continue;
@@ -330,9 +341,12 @@ ngx_control_handle_events(void)
             }
 
             if (ngx_control_handle_read(r) != NGX_AGAIN) {
+                reloaded |= r->reloaded;
                 ngx_control_close(r);
                 continue;
             }
+
+            reloaded |= r->reloaded;
         }
 
         if (revents & POLLOUT) {
@@ -345,7 +359,7 @@ ngx_control_handle_events(void)
         i++;
     }
 
-    return NGX_OK;
+    return reloaded ? NGX_DONE : NGX_OK;
 }
 
 
@@ -562,7 +576,7 @@ ngx_control_content(ngx_control_request_t *r)
 
     static const char  *root_endpoints[] = { "1" };
     static const char  *api_endpoints[] = { "control", "nginx" };
-    static const char  *control_endpoints[] = { "processes" };
+    static const char  *control_endpoints[] = { "processes", "config" };
 
     p = r->path.data;
     len = r->path.len;
@@ -609,7 +623,18 @@ ngx_control_content(ngx_control_request_t *r)
                 return ngx_control_send_response(r, NGX_CTRL_NOT_ALLOWED, NULL);
             }
 
-            return ngx_control_api_endpoints(r, control_endpoints, 1);
+            return ngx_control_api_endpoints(r, control_endpoints, 2);
+        }
+
+        break;
+
+    case 17:
+        if (ngx_strncmp(p, "/1/control/config", len) == 0) {
+            if (r->method != NGX_CTRL_PATCH) {
+                return ngx_control_send_response(r, NGX_CTRL_NOT_ALLOWED, NULL);
+            }
+
+            return ngx_control_reload_config(r);
         }
 
         break;
@@ -836,6 +861,115 @@ ngx_control_show_version(ngx_control_request_t *r)
 }
 
 
+static ngx_data_item_t *
+ngx_control_json_logs(ngx_array_t *logs)
+{
+    ngx_str_t        *l;
+    ngx_uint_t        i;
+    ngx_data_item_t  *obj, *log_list, *log;
+
+    static ngx_str_t  logs_field = ngx_string("logs");
+
+    obj = ngx_data_new_object(logs->pool);
+    if (obj == NULL) {
+        return NULL;
+    }
+
+    log_list = ngx_data_new_list(logs->pool);
+    if (log_list == NULL) {
+        return NULL;
+    }
+
+    l = logs->elts;
+    for (i = 0; i < logs->nelts; i++) {
+        log = ngx_data_string_handler((uintptr_t) &l[i], logs->pool, NULL);
+        if (log == NULL) {
+            return NULL;
+        }
+
+        ngx_data_add_item(log_list, NULL, log);
+    }
+
+    ngx_data_add_item(obj, &logs_field, log_list);
+
+    return obj;
+}
+
+
+static ngx_int_t
+ngx_control_reload_config(ngx_control_request_t *r)
+{
+    ngx_int_t         status;
+    ngx_log_t         log, *tmp;
+    ngx_cycle_t      *cycle;
+    ngx_array_t       log_store;
+    ngx_data_item_t  *obj;
+
+    status = NGX_CTRL_OK;
+
+    if (ngx_array_init(&log_store, r->pool, 3, sizeof(ngx_str_t)) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    ngx_memzero(&log, sizeof(ngx_log_t));
+    log.wdata = &log_store;
+    log.writer = ngx_control_log_capture;
+    log.log_level = NGX_LOG_DEBUG;
+
+    tmp = ngx_cycle->log;
+    log.next = tmp;
+    ngx_cycle->log = &log;
+    cycle = ngx_init_cycle((ngx_cycle_t *) ngx_cycle);
+
+    if (cycle == NULL) {
+        status = NGX_CTRL_UNPROCESSABLE;
+        ngx_cycle->log = tmp;
+
+    } else {
+        ngx_cycle = cycle;
+        r->reloaded = 1;
+        r->pool->log = ngx_cycle->log;
+    }
+
+    obj = ngx_control_json_logs(&log_store);
+    if (obj == NULL) {
+        return NGX_ERROR;
+    }
+
+    return ngx_control_send_json(r, status, obj);
+}
+
+
+static void
+ngx_control_log_capture(ngx_log_t *log, ngx_uint_t lvl, u_char *buf, size_t len)
+{
+    u_char       *log_data;
+    ngx_str_t    *log_str;
+    ngx_array_t  *store;
+
+    if (lvl > NGX_LOG_WARN) {
+        return;
+    }
+
+    store = log->wdata;
+
+    log_data = ngx_pnalloc(store->pool, len);
+    if (log_data == NULL) {
+        return;
+    }
+
+    log_str = ngx_array_push(store);
+    if (log_str == NULL) {
+        return;
+    }
+
+    ngx_memcpy(log_data, buf, len);
+
+    log_str->data = log_data;
+    log_str->len = len;
+}
+
+
 static ngx_int_t
 ngx_control_parse_request_line(ngx_control_request_t *r)
 {
index e3605ed036fb9fccc1bfb0f67ac1e01a7f771bcc..22a0e07f2b945de475caa5fe31f49cd6d715fb25 100644 (file)
@@ -178,7 +178,21 @@ ngx_master_process_cycle(ngx_cycle_t *cycle)
         if (ngx_sigio) {
             ngx_sigio = 0;
 
-            ngx_control_handle_events();
+            if (ngx_control_handle_events() == NGX_DONE) {
+                cycle = (ngx_cycle_t *) ngx_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_start_cache_manager_processes(cycle, 1);
+
+                /* allow new processes to start */
+                ngx_msleep(100);
+
+                live = 1;
+                ngx_signal_worker_processes(cycle,
+                                        ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
+            }
         }
 #endif