From 74133fba7c6cd15c726a69b7af30cd49c17d6e8a Mon Sep 17 00:00:00 2001 From: Ava Hahn Date: Tue, 30 Dec 2025 19:35:18 +0000 Subject: [PATCH] Control API: reload support. 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 --- src/os/unix/ngx_control.c | 142 +++++++++++++++++++++++++++++++- src/os/unix/ngx_process_cycle.c | 16 +++- 2 files changed, 153 insertions(+), 5 deletions(-) diff --git a/src/os/unix/ngx_control.c b/src/os/unix/ngx_control.c index ddecfe640..b60738995 100644 --- a/src/os/unix/ngx_control.c +++ b/src/os/unix/ngx_control.c @@ -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) { diff --git a/src/os/unix/ngx_process_cycle.c b/src/os/unix/ngx_process_cycle.c index e3605ed03..22a0e07f2 100644 --- a/src/os/unix/ngx_process_cycle.c +++ b/src/os/unix/ngx_process_cycle.c @@ -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