From mdounin at mdounin.ru Wed Jan 7 15:38:30 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Wed, 07 Jan 2026 18:38:30 +0300 Subject: [nginx] Version bump. Message-ID: details: http://freenginx.org/hg/nginx/rev/0eb8c92b3891 branches: changeset: 9453:0eb8c92b3891 user: Maxim Dounin date: Wed Jan 07 18:22:24 2026 +0300 description: Version bump. diffstat: src/core/nginx.h | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diffs (14 lines): diff --git a/src/core/nginx.h b/src/core/nginx.h --- a/src/core/nginx.h +++ b/src/core/nginx.h @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1029004 -#define NGINX_VERSION "1.29.4" +#define nginx_version 1029005 +#define NGINX_VERSION "1.29.5" #define freenginx 1 From mdounin at mdounin.ru Wed Jan 7 15:38:30 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Wed, 07 Jan 2026 18:38:30 +0300 Subject: [nginx] Removed unneeded local variable in ngx_libc_crypt(). Message-ID: details: http://freenginx.org/hg/nginx/rev/beb93bda8aaa branches: changeset: 9454:beb93bda8aaa user: Maxim Dounin date: Wed Jan 07 18:22:29 2026 +0300 description: Removed unneeded local variable in ngx_libc_crypt(). diffstat: src/os/unix/ngx_user.c | 9 +++------ 1 files changed, 3 insertions(+), 6 deletions(-) diffs (26 lines): diff --git a/src/os/unix/ngx_user.c b/src/os/unix/ngx_user.c --- a/src/os/unix/ngx_user.c +++ b/src/os/unix/ngx_user.c @@ -46,9 +46,8 @@ ngx_libc_crypt(ngx_pool_t *pool, u_char ngx_int_t ngx_libc_crypt(ngx_pool_t *pool, u_char *key, u_char *salt, u_char **encrypted) { - char *value; - size_t len; - ngx_err_t err; + char *value; + size_t len; value = crypt((char *) key, (char *) salt); @@ -64,9 +63,7 @@ ngx_libc_crypt(ngx_pool_t *pool, u_char return NGX_OK; } - err = ngx_errno; - - ngx_log_error(NGX_LOG_CRIT, pool->log, err, "crypt() failed"); + ngx_log_error(NGX_LOG_CRIT, pool->log, ngx_errno, "crypt() failed"); return NGX_ERROR; } From mdounin at mdounin.ru Wed Jan 7 15:38:30 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Wed, 07 Jan 2026 18:38:30 +0300 Subject: [nginx] Fixed unexpected ngx_socket_errno usage. Message-ID: details: http://freenginx.org/hg/nginx/rev/a1141ed0eb84 branches: changeset: 9455:a1141ed0eb84 user: Maxim Dounin date: Wed Jan 07 18:22:53 2026 +0300 description: Fixed unexpected ngx_socket_errno usage. The particular error is reported because the protocol family of an inherited socket is not supported, and not because of a syscall error, so ngx_socket_errno contains unrelated data and should not be used. diffstat: src/core/ngx_connection.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c --- a/src/core/ngx_connection.c +++ b/src/core/ngx_connection.c @@ -197,7 +197,7 @@ ngx_set_inherited_sockets(ngx_cycle_t *c break; default: - ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_socket_errno, + ngx_log_error(NGX_LOG_CRIT, cycle->log, 0, "the inherited socket #%d has " "an unsupported protocol family", ls[i].fd); ls[i].ignore = 1; From mdounin at mdounin.ru Wed Jan 7 15:38:30 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Wed, 07 Jan 2026 18:38:30 +0300 Subject: [nginx] Win32: fixed ngx_errno vs. ngx_socket_errno usage. Message-ID: details: http://freenginx.org/hg/nginx/rev/a7b7067ffe3f branches: changeset: 9456:a7b7067ffe3f user: Maxim Dounin date: Wed Jan 07 18:23:21 2026 +0300 description: Win32: fixed ngx_errno vs. ngx_socket_errno usage. The ngx_socket_errno macro, and not ngx_errno, should be used on Windows to get an error after calling socket functions. And vice versa, after non-socket functions ngx_errno should be used. In modern Windows versions these are believed to be equivalent in practice, as WSAGetLastError() simply calls GetLastError(), so this is a mostly style change[1][2]. The documentation still suggests that WSAGetLastError() should be used after socket functions though. [1] https://devblogs.microsoft.com/oldnewthing/20050908-19/?p=34283 [2] https://github.com/tongzx/nt5src/blob/master/Source/XPSP1/NT/net/sockets/winsock2/ws2_32/src/perthrd.cpp diffstat: src/event/modules/ngx_win32_poll_module.c | 2 +- src/event/ngx_event_connectex.c | 2 +- src/event/ngx_event_openssl.c | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diffs (108 lines): diff --git a/src/event/modules/ngx_win32_poll_module.c b/src/event/modules/ngx_win32_poll_module.c --- a/src/event/modules/ngx_win32_poll_module.c +++ b/src/event/modules/ngx_win32_poll_module.c @@ -284,7 +284,7 @@ ngx_poll_process_events(ngx_cycle_t *cyc ready = WSAPoll(event_list, (u_int) nevents, (int) timer); - err = (ready == -1) ? ngx_errno : 0; + err = (ready == -1) ? ngx_socket_errno : 0; if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) { ngx_time_update(); diff --git a/src/event/ngx_event_connectex.c b/src/event/ngx_event_connectex.c --- a/src/event/ngx_event_connectex.c +++ b/src/event/ngx_event_connectex.c @@ -176,7 +176,7 @@ void ngx_iocp_wait_events(int main) if (PostQueuedCompletionStatus(iocp, 0, NGX_IOCP_CONNECT, &conn[n].write->ovlp) == 0) { - ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno, + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "PostQueuedCompletionStatus() failed"); continue; } diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -2423,7 +2423,7 @@ ngx_ssl_handshake(ngx_connection_t *c) return NGX_AGAIN; } - err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; + err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_socket_errno : 0; c->ssl->no_wait_shutdown = 1; c->ssl->no_send_shutdown = 1; @@ -2570,7 +2570,7 @@ ngx_ssl_try_early_data(ngx_connection_t return NGX_AGAIN; } - err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; + err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_socket_errno : 0; c->ssl->no_wait_shutdown = 1; c->ssl->no_send_shutdown = 1; @@ -3039,7 +3039,7 @@ ngx_ssl_handle_recv(ngx_connection_t *c, sslerr = SSL_get_error(c->ssl->connection, n); - err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; + err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_socket_errno : 0; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); @@ -3363,7 +3363,7 @@ ngx_ssl_write(ngx_connection_t *c, u_cha sslerr = SSL_ERROR_SYSCALL; } - err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; + err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_socket_errno : 0; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); @@ -3467,7 +3467,7 @@ ngx_ssl_write_early(ngx_connection_t *c, sslerr = SSL_get_error(c->ssl->connection, n); - err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; + err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_socket_errno : 0; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); @@ -3624,7 +3624,7 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng if (sslerr == SSL_ERROR_SSL && ERR_GET_REASON(ERR_peek_error()) == SSL_R_UNINITIALIZED - && ngx_errno != 0) + && ngx_socket_errno != 0) { /* * OpenSSL fails to return SSL_ERROR_SYSCALL if an error @@ -3635,7 +3635,7 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng sslerr = SSL_ERROR_SYSCALL; } - err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; + err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_socket_errno : 0; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); @@ -3656,7 +3656,7 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng #if (NGX_HAVE_SENDFILE_NODISKIO) - if (ngx_errno == EBUSY) { + if (ngx_socket_errno == EBUSY) { c->busy_count++; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -3848,7 +3848,7 @@ ngx_ssl_shutdown(ngx_connection_t *c) goto done; } - err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0; + err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_socket_errno : 0; ngx_ssl_connection_error(c, sslerr, err, "SSL_shutdown() failed"); From mdounin at mdounin.ru Wed Jan 7 15:38:31 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Wed, 07 Jan 2026 18:38:31 +0300 Subject: [nginx] Cleaned up unsafe ngx_errno and ngx_socket_errno usage. Message-ID: details: http://freenginx.org/hg/nginx/rev/d596a1fb8b9c branches: changeset: 9457:d596a1fb8b9c user: Maxim Dounin date: Wed Jan 07 18:27:50 2026 +0300 description: Cleaned up unsafe ngx_errno and ngx_socket_errno usage. Typical incorrect pattern is to call a function, then log some debugging information with a ngx_log_debugN() call, and then use ngx_errno if the function returned an error. This might lead to incorrect ngx_errno value being checked or logged, since ngx_log_debugN() might clobber errno if debugging is enabled. Fix is to save ngx_errno (and ngx_socket_errno) before debug logging, similarly to what we already do in many other places, or to reorder the code if appropriate. diffstat: src/core/ngx_cycle.c | 12 ++++- src/core/ngx_file.c | 4 +- src/core/ngx_log.c | 11 +++- src/core/ngx_resolver.c | 8 +- src/event/modules/ngx_epoll_module.c | 4 +- src/event/ngx_event_acceptex.c | 7 +- src/event/ngx_event_connect.c | 6 +- src/event/ngx_event_openssl.c | 76 +++++++++++++++++++++++++---------- src/os/unix/ngx_file_aio_read.c | 3 +- src/os/unix/ngx_freebsd_init.c | 10 +++- src/os/unix/ngx_recv.c | 4 +- src/os/unix/ngx_send.c | 4 +- src/os/unix/ngx_udp_recv.c | 4 +- src/os/unix/ngx_udp_send.c | 4 +- src/os/unix/ngx_writev_chain.c | 4 +- src/os/win32/ngx_process.c | 5 +- src/os/win32/ngx_shmem.c | 4 +- src/os/win32/ngx_udp_wsarecv.c | 5 +- src/os/win32/ngx_wsarecv.c | 5 +- src/os/win32/ngx_wsasend.c | 11 ++-- 20 files changed, 121 insertions(+), 70 deletions(-) diffs (799 lines): diff --git a/src/core/ngx_cycle.c b/src/core/ngx_cycle.c --- a/src/core/ngx_cycle.c +++ b/src/core/ngx_cycle.c @@ -42,8 +42,9 @@ ngx_init_cycle(ngx_cycle_t *old_cycle) { void *rv; char **senv; + ngx_err_t err; + ngx_log_t *log; ngx_uint_t i, n; - ngx_log_t *log; ngx_time_t *tp; ngx_conf_t conf; ngx_pool_t *pool; @@ -387,12 +388,14 @@ ngx_init_cycle(ngx_cycle_t *old_cycle) NGX_FILE_CREATE_OR_OPEN, NGX_FILE_DEFAULT_ACCESS); + err = ngx_errno; + ngx_log_debug3(NGX_LOG_DEBUG_CORE, log, 0, "log: %p %d \"%s\"", &file[i], file[i].fd, file[i].name.data); if (file[i].fd == NGX_INVALID_FILE) { - ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + ngx_log_error(NGX_LOG_EMERG, log, err, ngx_open_file_n " \"%s\" failed", file[i].name.data); goto failed; @@ -1238,6 +1241,7 @@ void ngx_reopen_files(ngx_cycle_t *cycle, ngx_uid_t user) { ngx_fd_t fd; + ngx_err_t err; ngx_uint_t i; ngx_list_part_t *part; ngx_open_file_t *file; @@ -1267,12 +1271,14 @@ ngx_reopen_files(ngx_cycle_t *cycle, ngx fd = ngx_open_file(file[i].name.data, NGX_FILE_APPEND, NGX_FILE_CREATE_OR_OPEN, NGX_FILE_DEFAULT_ACCESS); + err = ngx_errno; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reopen file \"%s\", old:%d new:%d", file[i].name.data, file[i].fd, fd); if (fd == NGX_INVALID_FILE) { - ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, + ngx_log_error(NGX_LOG_EMERG, cycle->log, err, ngx_open_file_n " \"%s\" failed", file[i].name.data); continue; } diff --git a/src/core/ngx_file.c b/src/core/ngx_file.c --- a/src/core/ngx_file.c +++ b/src/core/ngx_file.c @@ -201,6 +201,8 @@ ngx_create_temp_file(ngx_file_t *file, n file->fd = ngx_open_tempfile(file->name.data, persistent, access); + err = ngx_errno; + ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0, "temp fd:%d", file->fd); @@ -216,8 +218,6 @@ ngx_create_temp_file(ngx_file_t *file, n return NGX_OK; } - err = ngx_errno; - if (err == NGX_EEXIST_FILE) { n = (uint32_t) ngx_next_temp_number(1); continue; diff --git a/src/core/ngx_log.c b/src/core/ngx_log.c --- a/src/core/ngx_log.c +++ b/src/core/ngx_log.c @@ -387,8 +387,9 @@ ngx_log_check_rate(ngx_log_t *log, ngx_u ngx_log_t * ngx_log_init(u_char *prefix, u_char *error_log) { - u_char *p, *name; - size_t nlen, plen; + u_char *p, *name; + size_t nlen, plen; + ngx_err_t err; ngx_log.file = &ngx_log_file; ngx_log.log_level = NGX_LOG_NOTICE; @@ -448,11 +449,13 @@ ngx_log_init(u_char *prefix, u_char *err NGX_FILE_DEFAULT_ACCESS); if (ngx_log_file.fd == NGX_INVALID_FILE) { - ngx_log_stderr(ngx_errno, + err = ngx_errno; + + ngx_log_stderr(err, "[alert] could not open error log file: " ngx_open_file_n " \"%s\" failed", name); #if (NGX_WIN32) - ngx_event_log(ngx_errno, + ngx_event_log(err, "could not open error log file: " ngx_open_file_n " \"%s\" failed", name); #endif diff --git a/src/core/ngx_resolver.c b/src/core/ngx_resolver.c --- a/src/core/ngx_resolver.c +++ b/src/core/ngx_resolver.c @@ -4465,14 +4465,14 @@ ngx_udp_connect(ngx_resolver_connection_ s = ngx_socket(rec->sockaddr->sa_family, SOCK_DGRAM, 0); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, &rec->log, 0, "UDP socket %d", s); - if (s == (ngx_socket_t) -1) { ngx_log_error(NGX_LOG_ALERT, &rec->log, ngx_socket_errno, ngx_socket_n " failed"); return NGX_ERROR; } + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, &rec->log, 0, "UDP socket %d", s); + c = ngx_get_connection(s, &rec->log); if (c == NULL) { @@ -4553,14 +4553,14 @@ ngx_tcp_connect(ngx_resolver_connection_ s = ngx_socket(rec->sockaddr->sa_family, SOCK_STREAM, 0); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, &rec->log, 0, "TCP socket %d", s); - if (s == (ngx_socket_t) -1) { ngx_log_error(NGX_LOG_ALERT, &rec->log, ngx_socket_errno, ngx_socket_n " failed"); return NGX_ERROR; } + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, &rec->log, 0, "TCP socket %d", s); + c = ngx_get_connection(s, &rec->log); if (c == NULL) { diff --git a/src/event/modules/ngx_epoll_module.c b/src/event/modules/ngx_epoll_module.c --- a/src/event/modules/ngx_epoll_module.c +++ b/src/event/modules/ngx_epoll_module.c @@ -980,6 +980,8 @@ ngx_epoll_eventfd_handler(ngx_event_t *e events = io_getevents(ngx_aio_ctx, 1, 64, event, &ts); + err = ngx_errno; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ev->log, 0, "io_getevents: %d", events); @@ -1013,7 +1015,7 @@ ngx_epoll_eventfd_handler(ngx_event_t *e } /* events == -1 */ - ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno, + ngx_log_error(NGX_LOG_ALERT, ev->log, err, "io_getevents() failed"); return; } diff --git a/src/event/ngx_event_acceptex.c b/src/event/ngx_event_acceptex.c --- a/src/event/ngx_event_acceptex.c +++ b/src/event/ngx_event_acceptex.c @@ -106,16 +106,15 @@ ngx_event_post_acceptex(ngx_listening_t s = ngx_socket(ls->sockaddr->sa_family, ls->type, 0); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, &ls->log, 0, - ngx_socket_n " s:%d", s); - if (s == (ngx_socket_t) -1) { ngx_log_error(NGX_LOG_ALERT, &ls->log, ngx_socket_errno, ngx_socket_n " failed"); - return NGX_ERROR; } + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, &ls->log, 0, + ngx_socket_n " s:%d", s); + c = ngx_get_connection(s, &ls->log); if (c == NULL) { diff --git a/src/event/ngx_event_connect.c b/src/event/ngx_event_connect.c --- a/src/event/ngx_event_connect.c +++ b/src/event/ngx_event_connect.c @@ -40,15 +40,15 @@ ngx_event_connect_peer(ngx_peer_connecti s = ngx_socket(pc->sockaddr->sa_family, type, 0); - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, "%s socket %d", - (type == SOCK_STREAM) ? "stream" : "dgram", s); - if (s == (ngx_socket_t) -1) { ngx_log_error(NGX_LOG_ALERT, pc->log, ngx_socket_errno, ngx_socket_n " failed"); return NGX_ERROR; } + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, "%s socket %d", + (type == SOCK_STREAM) ? "stream" : "dgram", s); + c = ngx_get_connection(s, pc->log); diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -38,7 +38,8 @@ static void ngx_ssl_handshake_handler(ng static ssize_t ngx_ssl_recv_early(ngx_connection_t *c, u_char *buf, size_t size); #endif -static ngx_int_t ngx_ssl_handle_recv(ngx_connection_t *c, int n); +static ngx_int_t ngx_ssl_handle_recv(ngx_connection_t *c, int n, + ngx_err_t err); static void ngx_ssl_write_handler(ngx_event_t *wev); #ifdef SSL_READ_EARLY_DATA_SUCCESS static ssize_t ngx_ssl_write_early(ngx_connection_t *c, u_char *data, @@ -2324,6 +2325,8 @@ ngx_ssl_handshake(ngx_connection_t *c) n = SSL_do_handshake(c->ssl->connection); + err = ngx_socket_errno; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); if (n == 1) { @@ -2423,7 +2426,9 @@ ngx_ssl_handshake(ngx_connection_t *c) return NGX_AGAIN; } - err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_socket_errno : 0; + if (sslerr != SSL_ERROR_SYSCALL) { + err = 0; + } c->ssl->no_wait_shutdown = 1; c->ssl->no_send_shutdown = 1; @@ -2468,6 +2473,8 @@ ngx_ssl_try_early_data(ngx_connection_t n = SSL_read_early_data(c->ssl->connection, &buf, 1, &readbytes); + err = ngx_socket_errno; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_read_early_data: %d, %uz", n, readbytes); @@ -2570,7 +2577,9 @@ ngx_ssl_try_early_data(ngx_connection_t return NGX_AGAIN; } - err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_socket_errno : 0; + if (sslerr != SSL_ERROR_SYSCALL) { + err = 0; + } c->ssl->no_wait_shutdown = 1; c->ssl->no_send_shutdown = 1; @@ -2738,7 +2747,8 @@ ngx_ssl_recv_chain(ngx_connection_t *c, ssize_t ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size) { - int n, bytes; + int n, bytes; + ngx_err_t err; #ifdef SSL_READ_EARLY_DATA_SUCCESS if (c->ssl->in_early) { @@ -2771,13 +2781,15 @@ ngx_ssl_recv(ngx_connection_t *c, u_char n = SSL_read(c->ssl->connection, buf, size); + err = ngx_socket_errno; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_read: %d", n); if (n > 0) { bytes += n; } - c->ssl->last = ngx_ssl_handle_recv(c, n); + c->ssl->last = ngx_ssl_handle_recv(c, n, err); if (c->ssl->last == NGX_OK) { @@ -2870,6 +2882,7 @@ ngx_ssl_recv_early(ngx_connection_t *c, { int n, bytes; size_t readbytes; + ngx_err_t err; if (c->ssl->last == NGX_ERROR) { c->read->ready = 0; @@ -2919,12 +2932,14 @@ ngx_ssl_recv_early(ngx_connection_t *c, n = SSL_read_early_data(c->ssl->connection, buf, size, &readbytes); + err = ngx_socket_errno; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_read_early_data: %d, %uz", n, readbytes); if (n == SSL_READ_EARLY_DATA_SUCCESS) { - c->ssl->last = ngx_ssl_handle_recv(c, 1); + c->ssl->last = ngx_ssl_handle_recv(c, 1, 0); bytes += readbytes; size -= readbytes; @@ -2941,7 +2956,7 @@ ngx_ssl_recv_early(ngx_connection_t *c, if (n == SSL_READ_EARLY_DATA_FINISH) { - c->ssl->last = ngx_ssl_handle_recv(c, 1); + c->ssl->last = ngx_ssl_handle_recv(c, 1, 0); c->ssl->in_early = 0; if (bytes) { @@ -2954,7 +2969,7 @@ ngx_ssl_recv_early(ngx_connection_t *c, /* SSL_READ_EARLY_DATA_ERROR */ - c->ssl->last = ngx_ssl_handle_recv(c, 0); + c->ssl->last = ngx_ssl_handle_recv(c, 0, err); if (bytes) { if (c->ssl->last != NGX_AGAIN) { @@ -2987,10 +3002,9 @@ ngx_ssl_recv_early(ngx_connection_t *c, static ngx_int_t -ngx_ssl_handle_recv(ngx_connection_t *c, int n) +ngx_ssl_handle_recv(ngx_connection_t *c, int n, ngx_err_t err) { - int sslerr; - ngx_err_t err; + int sslerr; #if (!defined SSL_OP_NO_RENEGOTIATION \ && !defined SSL_OP_NO_CLIENT_RENEGOTIATION) @@ -3039,8 +3053,6 @@ ngx_ssl_handle_recv(ngx_connection_t *c, sslerr = SSL_get_error(c->ssl->connection, n); - err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_socket_errno : 0; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); if (sslerr == SSL_ERROR_WANT_READ) { @@ -3085,6 +3097,10 @@ ngx_ssl_handle_recv(ngx_connection_t *c, return NGX_AGAIN; } + if (sslerr != SSL_ERROR_SYSCALL) { + err = 0; + } + c->ssl->no_wait_shutdown = 1; c->ssl->no_send_shutdown = 1; @@ -3327,6 +3343,8 @@ ngx_ssl_write(ngx_connection_t *c, u_cha n = SSL_write(c->ssl->connection, data, size); + err = ngx_socket_errno; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_write: %d", n); if (n > 0) { @@ -3363,8 +3381,6 @@ ngx_ssl_write(ngx_connection_t *c, u_cha sslerr = SSL_ERROR_SYSCALL; } - err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_socket_errno : 0; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); if (sslerr == SSL_ERROR_WANT_WRITE) { @@ -3410,6 +3426,10 @@ ngx_ssl_write(ngx_connection_t *c, u_cha return NGX_AGAIN; } + if (sslerr != SSL_ERROR_SYSCALL) { + err = 0; + } + c->ssl->no_wait_shutdown = 1; c->ssl->no_send_shutdown = 1; c->write->error = 1; @@ -3437,6 +3457,8 @@ ngx_ssl_write_early(ngx_connection_t *c, n = SSL_write_early_data(c->ssl->connection, data, size, &written); + err = ngx_socket_errno; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_write_early_data: %d, %uz", n, written); @@ -3467,8 +3489,6 @@ ngx_ssl_write_early(ngx_connection_t *c, sslerr = SSL_get_error(c->ssl->connection, n); - err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_socket_errno : 0; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); if (sslerr == SSL_ERROR_WANT_WRITE) { @@ -3525,6 +3545,10 @@ ngx_ssl_write_early(ngx_connection_t *c, return NGX_AGAIN; } + if (sslerr != SSL_ERROR_SYSCALL) { + err = 0; + } + c->ssl->no_wait_shutdown = 1; c->ssl->no_send_shutdown = 1; c->write->error = 1; @@ -3569,6 +3593,8 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng n = SSL_sendfile(c->ssl->connection, file->file->fd, file->file_pos, size, flags); + err = ngx_socket_errno; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_sendfile: %z", n); if (n > 0) { @@ -3624,7 +3650,7 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng if (sslerr == SSL_ERROR_SSL && ERR_GET_REASON(ERR_peek_error()) == SSL_R_UNINITIALIZED - && ngx_socket_errno != 0) + && err != 0) { /* * OpenSSL fails to return SSL_ERROR_SYSCALL if an error @@ -3635,8 +3661,6 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng sslerr = SSL_ERROR_SYSCALL; } - err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_socket_errno : 0; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); if (sslerr == SSL_ERROR_WANT_WRITE) { @@ -3656,7 +3680,7 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng #if (NGX_HAVE_SENDFILE_NODISKIO) - if (ngx_socket_errno == EBUSY) { + if (err == EBUSY) { c->busy_count++; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -3699,6 +3723,10 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng return NGX_AGAIN; } + if (sslerr != SSL_ERROR_SYSCALL) { + err = 0; + } + c->ssl->no_wait_shutdown = 1; c->ssl->no_send_shutdown = 1; c->write->error = 1; @@ -3803,6 +3831,8 @@ ngx_ssl_shutdown(ngx_connection_t *c) n = SSL_shutdown(c->ssl->connection); + err = ngx_socket_errno; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_shutdown: %d", n); if (n == 1) { @@ -3848,7 +3878,9 @@ ngx_ssl_shutdown(ngx_connection_t *c) goto done; } - err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_socket_errno : 0; + if (sslerr != SSL_ERROR_SYSCALL) { + err = 0; + } ngx_ssl_connection_error(c, sslerr, err, "SSL_shutdown() failed"); diff --git a/src/os/unix/ngx_file_aio_read.c b/src/os/unix/ngx_file_aio_read.c --- a/src/os/unix/ngx_file_aio_read.c +++ b/src/os/unix/ngx_file_aio_read.c @@ -153,11 +153,12 @@ ngx_file_aio_result(ngx_file_t *file, ng n = aio_error(&aio->aiocb); + err = ngx_errno; + ngx_log_debug2(NGX_LOG_DEBUG_CORE, file->log, 0, "aio_error: fd:%d %d", file->fd, n); if (n == -1) { - err = ngx_errno; aio->err = err; ngx_log_error(NGX_LOG_ALERT, file->log, err, diff --git a/src/os/unix/ngx_freebsd_init.c b/src/os/unix/ngx_freebsd_init.c --- a/src/os/unix/ngx_freebsd_init.c +++ b/src/os/unix/ngx_freebsd_init.c @@ -109,10 +109,12 @@ ngx_os_specific_init(ngx_log_t *log) size = sizeof(ngx_freebsd_kern_ostype); if (sysctlbyname("kern.ostype", ngx_freebsd_kern_ostype, &size, NULL, 0) == -1) { - ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + err = ngx_errno; + + ngx_log_error(NGX_LOG_ALERT, log, err, "sysctlbyname(kern.ostype) failed"); - if (ngx_errno != NGX_ENOMEM) { + if (err != NGX_ENOMEM) { return NGX_ERROR; } @@ -122,7 +124,9 @@ ngx_os_specific_init(ngx_log_t *log) size = sizeof(ngx_freebsd_kern_osrelease); if (sysctlbyname("kern.osrelease", ngx_freebsd_kern_osrelease, &size, NULL, 0) == -1) { - ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + err = ngx_errno; + + ngx_log_error(NGX_LOG_ALERT, log, err, "sysctlbyname(kern.osrelease) failed"); if (ngx_errno != NGX_ENOMEM) { diff --git a/src/os/unix/ngx_recv.c b/src/os/unix/ngx_recv.c --- a/src/os/unix/ngx_recv.c +++ b/src/os/unix/ngx_recv.c @@ -70,6 +70,8 @@ ngx_unix_recv(ngx_connection_t *c, u_cha do { n = recv(c->fd, buf, size, 0); + err = ngx_socket_errno; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "recv: fd:%d %z of %uz", c->fd, n, size); @@ -179,8 +181,6 @@ ngx_unix_recv(ngx_connection_t *c, u_cha return n; } - err = ngx_socket_errno; - if (err == NGX_EAGAIN || err == NGX_EINTR) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, "recv() not ready"); diff --git a/src/os/unix/ngx_send.c b/src/os/unix/ngx_send.c --- a/src/os/unix/ngx_send.c +++ b/src/os/unix/ngx_send.c @@ -33,6 +33,8 @@ ngx_unix_send(ngx_connection_t *c, u_cha for ( ;; ) { n = send(c->fd, buf, size, 0); + err = ngx_socket_errno; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "send: fd:%d %z of %uz", c->fd, n, size); @@ -46,8 +48,6 @@ ngx_unix_send(ngx_connection_t *c, u_cha return n; } - err = ngx_socket_errno; - if (n == 0) { ngx_log_error(NGX_LOG_ALERT, c->log, err, "send() returned zero"); wev->ready = 0; diff --git a/src/os/unix/ngx_udp_recv.c b/src/os/unix/ngx_udp_recv.c --- a/src/os/unix/ngx_udp_recv.c +++ b/src/os/unix/ngx_udp_recv.c @@ -22,6 +22,8 @@ ngx_udp_unix_recv(ngx_connection_t *c, u do { n = recv(c->fd, buf, size, 0); + err = ngx_socket_errno; + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "recv: fd:%d %z of %uz", c->fd, n, size); @@ -48,8 +50,6 @@ ngx_udp_unix_recv(ngx_connection_t *c, u return n; } - err = ngx_socket_errno; - if (err == NGX_EAGAIN || err == NGX_EINTR) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, "recv() not ready"); diff --git a/src/os/unix/ngx_udp_send.c b/src/os/unix/ngx_udp_send.c --- a/src/os/unix/ngx_udp_send.c +++ b/src/os/unix/ngx_udp_send.c @@ -22,6 +22,8 @@ ngx_udp_unix_send(ngx_connection_t *c, u for ( ;; ) { n = sendto(c->fd, buf, size, 0, c->sockaddr, c->socklen); + err = ngx_socket_errno; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, "sendto: fd:%d %z of %uz to \"%V\"", c->fd, n, size, &c->addr_text); @@ -38,8 +40,6 @@ ngx_udp_unix_send(ngx_connection_t *c, u return n; } - err = ngx_socket_errno; - if (err == NGX_EAGAIN) { wev->ready = 0; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, NGX_EAGAIN, diff --git a/src/os/unix/ngx_writev_chain.c b/src/os/unix/ngx_writev_chain.c --- a/src/os/unix/ngx_writev_chain.c +++ b/src/os/unix/ngx_writev_chain.c @@ -188,12 +188,12 @@ eintr: n = writev(c->fd, vec->iovs, vec->count); + err = ngx_errno; + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "writev: %z of %uz", n, vec->size); if (n == -1) { - err = ngx_errno; - switch (err) { case NGX_EAGAIN: ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, diff --git a/src/os/win32/ngx_process.c b/src/os/win32/ngx_process.c --- a/src/os/win32/ngx_process.c +++ b/src/os/win32/ngx_process.c @@ -23,6 +23,7 @@ ngx_spawn_process(ngx_cycle_t *cycle, ch u_long rc, n, code; ngx_int_t s; ngx_pid_t pid; + ngx_err_t err; ngx_exec_ctx_t ctx; HANDLE events[2]; char file[MAX_PATH + 1]; @@ -86,6 +87,8 @@ ngx_spawn_process(ngx_cycle_t *cycle, ch rc = WaitForMultipleObjects(2, events, 0, 5000); + err = ngx_errno; + ngx_time_update(); ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0, @@ -150,7 +153,7 @@ ngx_spawn_process(ngx_cycle_t *cycle, ch goto failed; case WAIT_FAILED: - ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + ngx_log_error(NGX_LOG_ALERT, cycle->log, err, "WaitForSingleObject(\"%s\") failed", ngx_master_process_event_name); diff --git a/src/os/win32/ngx_shmem.c b/src/os/win32/ngx_shmem.c --- a/src/os/win32/ngx_shmem.c +++ b/src/os/win32/ngx_shmem.c @@ -73,12 +73,12 @@ ngx_shm_alloc(ngx_shm_t *shm) return NGX_ERROR; } - ngx_free(name); - if (ngx_errno == ERROR_ALREADY_EXISTS) { shm->exists = 1; } + ngx_free(name); + shm->addr = MapViewOfFileEx(shm->handle, FILE_MAP_WRITE, 0, 0, 0, base); if (shm->addr != NULL) { diff --git a/src/os/win32/ngx_udp_wsarecv.c b/src/os/win32/ngx_udp_wsarecv.c --- a/src/os/win32/ngx_udp_wsarecv.c +++ b/src/os/win32/ngx_udp_wsarecv.c @@ -26,6 +26,8 @@ ngx_udp_wsarecv(ngx_connection_t *c, u_c rc = WSARecv(c->fd, wsabuf, 1, &bytes, &flags, NULL, NULL); + err = ngx_socket_errno; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, "WSARecv: fd:%d rc:%d %ul of %z", c->fd, rc, bytes, size); @@ -33,7 +35,6 @@ ngx_udp_wsarecv(ngx_connection_t *c, u_c if (rc == -1) { rev->ready = 0; - err = ngx_socket_errno; if (err == WSAEWOULDBLOCK) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, @@ -112,13 +113,13 @@ ngx_udp_overlapped_wsarecv(ngx_connectio rc = WSARecv(c->fd, wsabuf, 1, &bytes, &flags, ovlp, NULL); rev->complete = 0; + err = ngx_socket_errno; ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, "WSARecv ovlp: fd:%d rc:%d %ul of %z", c->fd, rc, bytes, size); if (rc == -1) { - err = ngx_socket_errno; if (err == WSA_IO_PENDING) { rev->active = 1; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, diff --git a/src/os/win32/ngx_wsarecv.c b/src/os/win32/ngx_wsarecv.c --- a/src/os/win32/ngx_wsarecv.c +++ b/src/os/win32/ngx_wsarecv.c @@ -27,6 +27,8 @@ ngx_wsarecv(ngx_connection_t *c, u_char rc = WSARecv(c->fd, wsabuf, 1, &bytes, &flags, NULL, NULL); + err = ngx_socket_errno; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, "WSARecv: fd:%d rc:%d %ul of %z", c->fd, rc, bytes, size); @@ -34,7 +36,6 @@ ngx_wsarecv(ngx_connection_t *c, u_char if (rc == -1) { rev->ready = 0; - err = ngx_socket_errno; if (err == WSAEWOULDBLOCK) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, @@ -166,13 +167,13 @@ ngx_overlapped_wsarecv(ngx_connection_t rc = WSARecv(c->fd, wsabuf, 1, &bytes, &flags, ovlp, NULL); rev->complete = 0; + err = ngx_socket_errno; ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, "WSARecv ovlp: fd:%d rc:%d %ul of %z", c->fd, rc, bytes, size); if (rc == -1) { - err = ngx_socket_errno; if (err == WSA_IO_PENDING) { rev->active = 1; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, diff --git a/src/os/win32/ngx_wsasend.c b/src/os/win32/ngx_wsasend.c --- a/src/os/win32/ngx_wsasend.c +++ b/src/os/win32/ngx_wsasend.c @@ -37,6 +37,8 @@ ngx_wsasend(ngx_connection_t *c, u_char n = WSASend(c->fd, &wsabuf, 1, &sent, 0, NULL, NULL); + err = ngx_socket_errno; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, "WSASend: fd:%d, %d, %ul of %uz", c->fd, n, sent, size); @@ -50,8 +52,6 @@ ngx_wsasend(ngx_connection_t *c, u_char return sent; } - err = ngx_socket_errno; - if (err == WSAEWOULDBLOCK) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, "WSASend() not ready"); wev->ready = 0; @@ -103,11 +103,12 @@ ngx_overlapped_wsasend(ngx_connection_t n = WSASend(c->fd, &wsabuf, 1, &sent, 0, ovlp, NULL); + wev->complete = 0; + err = ngx_socket_errno; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, "WSASend: fd:%d, %d, %ul of %uz", c->fd, n, sent, size); - wev->complete = 0; - if (n == 0) { if (ngx_event_flags & NGX_USE_IOCP_EVENT) { @@ -130,8 +131,6 @@ ngx_overlapped_wsasend(ngx_connection_t return sent; } - err = ngx_socket_errno; - if (err == WSA_IO_PENDING) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, "WSASend() posted"); From mdounin at mdounin.ru Sun Jan 11 03:11:11 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Sun, 11 Jan 2026 06:11:11 +0300 Subject: [PATCH] Tests: fixed stop_SSL() usage in ssl.t Message-ID: # HG changeset patch # User Maxim Dounin # Date 1768100931 -10800 # Sun Jan 11 06:08:51 2026 +0300 # Node ID b51387cf0ef442b073a9cb93dc4c604001bdcd26 # Parent b79ae9f9b554a2a8e1bdc9fde7f5730914b363ee Tests: fixed stop_SSL() usage in ssl.t. IO::Socket::SSL's stop_SSL() is documented to return true on success, and not 1, so testing for 1 is wrong. This was missed during conversion from Net::SSLeay to IO::Socket::SSL in 1866:a797d7428fa5. Further, IO::Socket::SSL started to actually return an object on success in recently released version 2.096, breaking the test. Fix is to use ok() test instead of is(), properly testing for a true value being returned. diff --git a/ssl.t b/ssl.t --- a/ssl.t +++ b/ssl.t @@ -268,7 +268,7 @@ ok(get_ssl_socket(8085), 'ssl unexpected # close_notify is sent before lingering close -is(get_ssl_shutdown(8085), 1, 'ssl shutdown on lingering close'); +ok(get_ssl_shutdown(8085), 'ssl shutdown on lingering close'); $t->stop(); From mdounin at mdounin.ru Sun Jan 11 22:58:58 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Mon, 12 Jan 2026 01:58:58 +0300 Subject: [PATCH 0 of 2] improved upstream error response handling Message-ID: Hello! The following patch series introduces a couple of improvements to handling of HTTP error responses got from upstream servers: 1. If an error is listed in proxy_next_upstream (and friends), such as with "proxy_next_upstream http_502", balancer is now informed about the failure even if we weren't able to switch to the next upstream server due to additional restriction (such as number of tries or non-idempotent request). Notably, this fixes gRPC proxying with "grpc_next_upstream http_502", which previously never switched of a server which only returned 502 errors. 2. If an error is listed in proxy_next_upstream (and friends) and considered to be a failure, and there is a stale cached response with "Cache-Control: stale-if-error=...", such a response will be sent to the client, much like it happens with network errors. See relevant patches for more details. Review and testing appreciated. -- Maxim Dounin From mdounin at mdounin.ru Sun Jan 11 22:58:59 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Mon, 12 Jan 2026 01:58:59 +0300 Subject: [PATCH 1 of 2] Upstream: improved reporting of peer failures In-Reply-To: References: Message-ID: <59592781f10b2d4f7e63.1768172339@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1768085239 -10800 # Sun Jan 11 01:47:19 2026 +0300 # Node ID 59592781f10b2d4f7e63e4dfcf6b865b5ee19254 # Parent d596a1fb8b9c3c293a3eca2d505b1d385481d51c Upstream: improved reporting of peer failures. Previously, peer failures were not reported to the balancer if an error response listed in u->conf->next_upstream was received, but switching to the next upstream server was not possible due to other reasons (for example, when no u->peer.tries left, or due to unbuffered request body). In particular, this resulted in upstream servers never switched off due to 502 errors in configurations with "grpc_next_upstream http_502", as gRPC proxying implies unbuffered request body (https://freenginx.org/pipermail/nginx/2025-February/000150.html). Fix is to preserve information about the failure as detected by ngx_http_upstream_test_next(), and propagate it to the balancer when u->peer.free() is called during upstream request finalization. diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -2584,24 +2584,43 @@ ngx_http_upstream_test_next(ngx_http_req continue; } - timeout = u->conf->next_upstream_timeout; - - if (u->request_sent - && (r->method & (NGX_HTTP_POST|NGX_HTTP_LOCK|NGX_HTTP_PATCH))) - { - mask = un->mask | NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT; - - } else { - mask = un->mask; - } - - if (u->peer.tries > 1 - && ((u->conf->next_upstream & mask) == mask) - && !(u->request_sent && r->request_body_no_buffering) - && !(timeout && ngx_current_msec - u->peer.start_time >= timeout)) - { - ngx_http_upstream_next(r, u, un->mask); - return NGX_OK; + if (u->conf->next_upstream & un->mask) { + + timeout = u->conf->next_upstream_timeout; + + if (u->request_sent + && (r->method & (NGX_HTTP_POST|NGX_HTTP_LOCK|NGX_HTTP_PATCH))) + { + mask = un->mask | NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT; + + } else { + mask = un->mask; + } + + if (u->peer.tries > 1 + && ((u->conf->next_upstream & mask) == mask) + && !(u->request_sent && r->request_body_no_buffering) + && !(timeout + && ngx_current_msec - u->peer.start_time >= timeout)) + { + ngx_http_upstream_next(r, u, un->mask); + return NGX_OK; + } + + /* + * if we were expected to switch to the next server, but + * were not able to do so due to additional restrictions, + * remember that the peer failed + */ + + if (un->mask == NGX_HTTP_UPSTREAM_FT_HTTP_403 + || un->mask == NGX_HTTP_UPSTREAM_FT_HTTP_404) + { + u->peer_state = NGX_PEER_NEXT; + + } else { + u->peer_state = NGX_PEER_FAILED; + } } #if (NGX_HTTP_CACHE) @@ -4623,7 +4642,7 @@ ngx_http_upstream_finalize_request(ngx_h u->finalize_request(r, rc); if (u->peer.free && u->peer.sockaddr) { - u->peer.free(&u->peer, u->peer.data, 0); + u->peer.free(&u->peer, u->peer.data, u->peer_state); u->peer.sockaddr = NULL; } diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h --- a/src/http/ngx_http_upstream.h +++ b/src/http/ngx_http_upstream.h @@ -404,6 +404,8 @@ struct ngx_http_upstream_s { unsigned request_body_sent:1; unsigned request_body_blocked:1; unsigned header_sent:1; + + unsigned peer_state:3; }; From mdounin at mdounin.ru Sun Jan 11 22:59:00 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Mon, 12 Jan 2026 01:59:00 +0300 Subject: [PATCH 2 of 2] Upstream: improved stale-if-error handling In-Reply-To: References: Message-ID: # HG changeset patch # User Maxim Dounin # Date 1768085378 -10800 # Sun Jan 11 01:49:38 2026 +0300 # Node ID a62443b2ea523cb2ce2ff5c4f964f15e3a12e94e # Parent 59592781f10b2d4f7e63e4dfcf6b865b5ee19254 Upstream: improved stale-if-error handling. Following 7702:7015f26aef90, "Cache-Control: stale-if-error=..." is ignored for HTTP 4xx and 5xx responses, even if these are expected to be treated as errors per u->conf->next_upstream configuration, notably when switching to the next upstream server wasn't possible. This makes it non-trivial to configure things with stale response usage for a limited time per stale-if-error as long as an actual error is detected not by the cache itself, but by an intermediate proxy, even if the particular status code is configured to be mostly equivalent to network errors, such as with "proxy_next_upstream error timeout http_502". With this change, as long as the particular error is considered to be a failure per u->conf->next_upstream, stale-if-error information from the cached response is now used. diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -2626,7 +2626,9 @@ ngx_http_upstream_test_next(ngx_http_req #if (NGX_HTTP_CACHE) if (u->cache_status == NGX_HTTP_CACHE_EXPIRED - && (u->conf->cache_use_stale & un->mask)) + && ((u->conf->cache_use_stale & un->mask) + || ((u->peer_state & NGX_PEER_FAILED) + && r->cache->stale_error))) { ngx_int_t rc; From mdounin at mdounin.ru Sun Jan 11 23:20:54 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Mon, 12 Jan 2026 02:20:54 +0300 Subject: [PATCH] Tests: added proxy_next_upstream test with no next upstream In-Reply-To: References: Message-ID: <8d1b070ec8e78817f64e.1768173654@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1768172277 -10800 # Mon Jan 12 01:57:57 2026 +0300 # Node ID 8d1b070ec8e78817f64e48beec007e85ed77012e # Parent b51387cf0ef442b073a9cb93dc4c604001bdcd26 Tests: added proxy_next_upstream test with no next upstream. Previously, peer failures were not reported to the balancer if an error response listed in proxy_next_upstream was received, but switching to the next upstream server was not possible. diff --git a/proxy_next_upstream.t b/proxy_next_upstream.t --- a/proxy_next_upstream.t +++ b/proxy_next_upstream.t @@ -21,7 +21,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http proxy rewrite/)->plan(8); +my $t = Test::Nginx->new()->has(qw/http proxy rewrite/)->plan(11); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -50,6 +50,11 @@ http { server 127.0.0.1:8082 down; } + upstream u4 { + server 127.0.0.1:8081; + server 127.0.0.1:8082; + } + server { listen 127.0.0.1:8080; server_name localhost; @@ -74,6 +79,12 @@ http { proxy_pass http://u3; proxy_next_upstream http_404; } + + location /nonext { + proxy_pass http://u4/500; + proxy_next_upstream http_500; + proxy_next_upstream_tries 1; + } } server { @@ -148,4 +159,17 @@ like(http_get('/all/rr'), like(http_get('/down/'), qr/Not Found/, 'all tried with down'); +# make sure backend is switched off with http_500 +# if switching to next upstream is not possible + +like(http_get('/nonext'), qr/500 Internal|SEE-THIS/, 'request nonext'); +like(http_get('/nonext'), qr/500 Internal|SEE-THIS/, 'request nonext second'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.29.5'); + +like(http_get('/nonext'), qr/SEE-THIS/, 'down after nonext'); + +} + ############################################################################### From mdounin at mdounin.ru Sun Jan 11 23:21:24 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Mon, 12 Jan 2026 02:21:24 +0300 Subject: [PATCH] Tests: stale-if-error with "proxy_next_upstream http_500" In-Reply-To: References: Message-ID: # HG changeset patch # User Maxim Dounin # Date 1768173666 -10800 # Mon Jan 12 02:21:06 2026 +0300 # Node ID d4e542b13471f41d03f99f7f71663be5a9e46f8f # Parent 8d1b070ec8e78817f64e48beec007e85ed77012e Tests: stale-if-error with "proxy_next_upstream http_500". diff --git a/proxy_cache_use_stale.t b/proxy_cache_use_stale.t --- a/proxy_cache_use_stale.t +++ b/proxy_cache_use_stale.t @@ -94,6 +94,12 @@ http { proxy_cache_use_stale updating; } + location /next/ { + proxy_pass http://127.0.0.1:8081/; + + proxy_next_upstream http_500; + } + location /t7.html { proxy_pass http://127.0.0.1:8081; @@ -150,7 +156,7 @@ EOF $t->write_file('escape.html', 'SEE-THIS'); $t->write_file('regexp.html', 'SEE-THIS'); -$t->run()->plan(34); +$t->run()->plan(35); ############################################################################### @@ -171,20 +177,30 @@ http_get('/ssi.html'); get('/updating/t.html', 'max-age=1'); get('/updating/t2.html', 'max-age=1, stale-while-revalidate=2'); get('/updating/tt.html', 'max-age=1, stale-if-error=5'); +get('/next/tt.html', 'max-age=1, stale-if-error=5'); get('/t8.html', 'stale-while-revalidate=20'); get('/escape.htm%6C', 'max-age=1, stale-while-revalidate=20'); get('/regexp.html', 'max-age=1, stale-while-revalidate=20'); sleep 2; -# stale 5xx response is ignored since 1.19.3, -# "proxy_cache_use_stale updating;" allows to get it still +# if an upstream returns a valid HTTP response, stale cached response +# with "stale-if-error=..." is only used if the status code is listed +# in proxy_next_upstream (1.29.5) or using stale responses is enabled +# with proxy_cache_use_stale like(http_get('/t.html?e=1'), qr/ 500 /, 's-i-e - stale 5xx ignore'); like(http_get('/tt.html?e=1'), qr/ 500 /, 's-i-e - stale 5xx ignore 2'); like(http_get('/updating/tt.html'), qr/STALE/, 's-i-e - stale 5xx updating'); like(http_get('/t.html'), qr/REVALIDATED/, 's-i-e - revalidated'); +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.29.5'); + +like(http_get('/next/tt.html?e=1'), qr/STALE/, 's-i-e - stale 5xx next'); + +} + like(http_get('/t2.html?e=1'), qr/STALE/, 's-w-r - revalidate error'); like(http_get('/t2.html'), qr/STALE/, 's-w-r - stale while revalidate'); like(http_get('/t2.html'), qr/HIT/, 's-w-r - revalidated'); From mdounin at mdounin.ru Fri Jan 23 04:11:33 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Fri, 23 Jan 2026 07:11:33 +0300 Subject: [PATCH] Win32: fixed ngx_set_errno() vs. ngx_set_socket_errno() usage Message-ID: <19d3dfeb9eff63798cdd.1769141493@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1769138418 -10800 # Fri Jan 23 06:20:18 2026 +0300 # Node ID 19d3dfeb9eff63798cdd243400789c36564855ee # Parent d596a1fb8b9c3c293a3eca2d505b1d385481d51c Win32: fixed ngx_set_errno() vs. ngx_set_socket_errno() usage. Fixed usage of ngx_set_errno() instead of ngx_set_socket_errno() where ngx_socket_errno is used, missed in 9456:a7b7067ffe3f. diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -3576,7 +3576,7 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng "SSL to sendfile: @%O %uz", file->file_pos, size); - ngx_set_errno(0); + ngx_set_socket_errno(0); #if (NGX_HAVE_SENDFILE_NODISKIO) From mdounin at mdounin.ru Wed Jan 28 00:52:27 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 28 Jan 2026 03:52:27 +0300 Subject: [PATCH] SSL: optimized SSL_sendfile() usage on FreeBSD Message-ID: # HG changeset patch # User Maxim Dounin # Date 1769472200 -10800 # Tue Jan 27 03:03:20 2026 +0300 # Node ID c3f9073f36cc89ee57cc1e09d2de1449060e3687 # Parent 80f7b4a9363e71819963ecc3283c4f41caf12cfc SSL: optimized SSL_sendfile() usage on FreeBSD. On FreeBSD sendfile() syscall returns an error with errno set to EAGAIN when the socket buffer is full (along with the number of bytes sent). Unfortunately, the SSL_sendfile() interface loses this information, and an extra SSL_sendfile() call is needed to obtain EAGAIN, typically resulting in doubled sendfile() syscalls. With this change, if errno is set to EAGAIN after the SSL_sendfile() call, we assume it was set by sendfile() and the socket buffer is full. This is generally safe as we clear errno before the SSL_sendfile() call, and saves an extra sendfile() syscall. Prodded by Gleb Smirnoff. diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -3277,6 +3277,10 @@ ngx_ssl_send_chain(ngx_connection_t *c, send += n; flush = 0; + if (!c->write->ready) { + break; + } + continue; } @@ -3595,7 +3599,7 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng err = ngx_socket_errno; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_sendfile: %z", n); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, err, "SSL_sendfile: %z", n); if (n > 0) { @@ -3618,6 +3622,16 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng c->sent += n; + /* + * on FreeBSD sendfile(), along with the number of bytes sent, + * returns an error with errno set to EAGAIN when the socket buffer + * is full + */ + + if (err == EAGAIN) { + c->write->ready = 0; + } + return n; } From mdounin at mdounin.ru Wed Jan 28 01:02:39 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 28 Jan 2026 04:02:39 +0300 Subject: [PATCH] Tests: basic tests for SSL_sendfile() usage In-Reply-To: References: Message-ID: <9bf525096ac997f9b8da.1769562159@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1769562150 -10800 # Wed Jan 28 04:02:30 2026 +0300 # Node ID 9bf525096ac997f9b8da66443377977da167838f # Parent d4e542b13471f41d03f99f7f71663be5a9e46f8f Tests: basic tests for SSL_sendfile() usage. Note that these tests assume kernel TLS enabled in OS (usually the case now, see 7941:65946a191197 changeset for details). If kernel TLS is not enabled, tests will still succeed, though will use normal SSL_write() instead. Mostly usable with manual checking of the debug log. diff --git a/ssl_sendfile.t b/ssl_sendfile.t new file mode 100644 --- /dev/null +++ b/ssl_sendfile.t @@ -0,0 +1,88 @@ +#!/usr/bin/perl + +# (C) Maxim Dounin + +# Tests for http ssl module, SSL_sendfile() usage. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new() + ->has(qw/http http_ssl openssl:3.0.0 socket_ssl/) + ->has_daemon('openssl')->plan(4); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080 sndbuf=16k; + listen 127.0.0.1:8443 ssl sndbuf=16k; + server_name localhost; + + sendfile on; + ssl_conf_command Options KTLS; + + ssl_certificate localhost.crt; + ssl_certificate_key localhost.key; + } +} + +EOF + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('localhost') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->run(); + +$t->write_file('small', 'SEE-THIS'); +$t->write_file('big', ('123456789' x 30000) . 'SEE-THIS'); + +############################################################################### + +like(http_get('/small', SSL => 1), qr/SEE-THIS/, 'sendfile small'); +like(http_get('/big', SSL => 1, sleep => 0.5), + qr/^(123456789){30000}SEE-THIS$/m, 'sendfile big'); + +like(http_get('/small'), qr/SEE-THIS/, 'sendfile plain small'); +like(http_get('/big', sleep => 0.5), + qr/^(123456789){30000}SEE-THIS$/m, 'sendfile plain big'); + +############################################################################### From mdounin at mdounin.ru Wed Jan 28 21:13:54 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Thu, 29 Jan 2026 00:13:54 +0300 Subject: [PATCH] Tests: improved timeout handling on Windows Message-ID: # HG changeset patch # User Maxim Dounin # Date 1769631451 -10800 # Wed Jan 28 23:17:31 2026 +0300 # Node ID cfa6c3971b24c0fcf1dcc8708454cc9348906dde # Parent 9bf525096ac997f9b8da66443377977da167838f Tests: improved timeout handling on Windows. On Windows, the eval + alarm model does not work (see perlport(1) for alarm, as well as 1678:d0025a0dead7 and 1705:99a9b8b50f21 for previous related fixes). Most notably, trying to rely on it for timeout handling around accept() in the main test process results in infinite hangs on Windows if something goes wrong and the timeout is indeed needed. A similar issue in grpc.t was fixed in 1705:99a9b8b50f21, though other similar constructs, originally introduced in 542:e7e3ced702f5, remained in the code. At least grpc_ssl.t was seen hanging indefinitely after an SSL handshake failure with the "certificate is not yet valid" error due to the time adjusted backwards during the test. With this change, all eval + alarm constructs around accept() calls are replaced with IO::Select can_read() calls (with appropriate timeouts) to check if there is a connection to accept. Code in grpc.t, which was previously fixed in 1705:99a9b8b50f21, was slightly adjusted to match other tests. diff --git a/fastcgi_request_buffering.t b/fastcgi_request_buffering.t --- a/fastcgi_request_buffering.t +++ b/fastcgi_request_buffering.t @@ -12,6 +12,8 @@ use warnings; use strict; use Test::More; + +use IO::Select; use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -233,23 +235,14 @@ EOF $s = http($r, start => 1); - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - local $SIG{PIPE} = sub { die "sigpipe\n" }; - alarm(5); - - $client = $server->accept(); - - log2c("(new connection $client)"); - - alarm(0); - }; - alarm(0); - if ($@) { - log_in("died: $@"); + if (!IO::Select->new($server)->can_read(5)) { + log2c("timeout"); return undef; } + $client = $server->accept(); + log2c("(new connection $client)"); + $client->sysread(my $buf, 1024); log2i($buf); diff --git a/fastcgi_request_buffering_chunked.t b/fastcgi_request_buffering_chunked.t --- a/fastcgi_request_buffering_chunked.t +++ b/fastcgi_request_buffering_chunked.t @@ -13,6 +13,8 @@ use warnings; use strict; use Test::More; + +use IO::Select; use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -262,23 +264,14 @@ EOF $s = http($r, start => 1); - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - local $SIG{PIPE} = sub { die "sigpipe\n" }; - alarm(5); - - $client = $server->accept(); - - log2c("(new connection $client)"); - - alarm(0); - }; - alarm(0); - if ($@) { - log_in("died: $@"); + if (!IO::Select->new($server)->can_read(5)) { + log2c("timeout"); return undef; } + $client = $server->accept(); + log2c("(new connection $client)"); + $client->sysread(my $buf, 1024); log2i($buf); diff --git a/grpc.t b/grpc.t --- a/grpc.t +++ b/grpc.t @@ -715,16 +715,14 @@ sub grpc { { name => 'te', value => 'trailers', mode => 2 }]}); if (!$extra{reuse}) { - if (IO::Select->new($server)->can_read(5)) { - $client = $server->accept(); - - } else { - log_in("timeout"); + if (!IO::Select->new($server)->can_read(5)) { + log2c("timeout"); # connection could be unexpectedly reused goto reused if $client; return undef; } + $client = $server->accept(); log2c("(new connection $client)"); $n++; diff --git a/grpc_request_buffering.t b/grpc_request_buffering.t --- a/grpc_request_buffering.t +++ b/grpc_request_buffering.t @@ -133,20 +133,12 @@ sub grpc { { name => 'content-length', value => length($body) }]}); if (!$extra{reuse}) { - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - alarm(5); - - $client = $server->accept() or return; - - alarm(0); - }; - alarm(0); - if ($@) { - log_in("died: $@"); + if (!IO::Select->new($server)->can_read(5)) { + log2c("timeout"); return undef; } + $client = $server->accept() or return; log2c("(new connection $client)"); $client->sysread(my $buf, 24) == 24 or return; # preface diff --git a/grpc_ssl.t b/grpc_ssl.t --- a/grpc_ssl.t +++ b/grpc_ssl.t @@ -259,20 +259,12 @@ sub grpc { { name => 'te', value => 'trailers', mode => 2 }]}); if (!$extra{reuse}) { - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - alarm(5); - - $client = $server->accept() or return; - - alarm(0); - }; - alarm(0); - if ($@) { - log_in("died: $@"); + if (!IO::Select->new($server)->can_read(5)) { + log2c("timeout"); return undef; } - + + $client = $server->accept() or return; log2c("(new connection $client)"); $client->sysread(my $buf, 24) == 24 or return; # preface diff --git a/proxy_request_buffering.t b/proxy_request_buffering.t --- a/proxy_request_buffering.t +++ b/proxy_request_buffering.t @@ -12,6 +12,8 @@ use warnings; use strict; use Test::More; + +use IO::Select; use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -200,23 +202,14 @@ EOF $s = http($r, start => 1); - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - local $SIG{PIPE} = sub { die "sigpipe\n" }; - alarm(5); - - $client = $server->accept(); - - log2c("(new connection $client)"); - - alarm(0); - }; - alarm(0); - if ($@) { - log_in("died: $@"); + if (!IO::Select->new($server)->can_read(5)) { + log2c("timeout"); return undef; } + $client = $server->accept(); + log2c("(new connection $client)"); + $client->sysread(my $buf, 1024); log2i($buf); diff --git a/proxy_request_buffering_chunked.t b/proxy_request_buffering_chunked.t --- a/proxy_request_buffering_chunked.t +++ b/proxy_request_buffering_chunked.t @@ -12,6 +12,8 @@ use warnings; use strict; use Test::More; + +use IO::Select; use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -235,23 +237,14 @@ EOF $s = http($r, start => 1); - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - local $SIG{PIPE} = sub { die "sigpipe\n" }; - alarm(5); - - $client = $server->accept(); - - log2c("(new connection $client)"); - - alarm(0); - }; - alarm(0); - if ($@) { - log_in("died: $@"); + if (!IO::Select->new($server)->can_read(5)) { + log2c("timeout"); return undef; } + $client = $server->accept(); + log2c("(new connection $client)"); + $client->sysread(my $buf, 1024); log2i($buf); diff --git a/proxy_request_buffering_ssl.t b/proxy_request_buffering_ssl.t --- a/proxy_request_buffering_ssl.t +++ b/proxy_request_buffering_ssl.t @@ -12,6 +12,8 @@ use warnings; use strict; use Test::More; + +use IO::Select; use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -221,23 +223,14 @@ EOF $s = http($r, start => 1); - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - local $SIG{PIPE} = sub { die "sigpipe\n" }; - alarm(5); - - $client = $server->accept(); - - log2c("(new connection $client)"); - - alarm(0); - }; - alarm(0); - if ($@) { - log_in("died: $@"); + if (!IO::Select->new($server)->can_read(5)) { + log2c("timeout"); return undef; } + $client = $server->accept(); + log2c("(new connection $client)"); + $client->sysread(my $buf, 1024); log2i($buf); From mdounin at mdounin.ru Thu Jan 29 00:25:46 2026 From: mdounin at mdounin.ru (Maxim Dounin) Date: Thu, 29 Jan 2026 03:25:46 +0300 Subject: [PATCH] SSL: optimized SSL_sendfile() usage on FreeBSD In-Reply-To: References: Message-ID: Hello! On Wed, Jan 28, 2026 at 03:52:27AM +0300, Maxim Dounin wrote: > # HG changeset patch > # User Maxim Dounin > # Date 1769472200 -10800 > # Tue Jan 27 03:03:20 2026 +0300 > # Node ID c3f9073f36cc89ee57cc1e09d2de1449060e3687 > # Parent 80f7b4a9363e71819963ecc3283c4f41caf12cfc > SSL: optimized SSL_sendfile() usage on FreeBSD. > > On FreeBSD sendfile() syscall returns an error with errno set to EAGAIN > when the socket buffer is full (along with the number of bytes sent). > Unfortunately, the SSL_sendfile() interface loses this information, and > an extra SSL_sendfile() call is needed to obtain EAGAIN, typically > resulting in doubled sendfile() syscalls. > > With this change, if errno is set to EAGAIN after the SSL_sendfile() > call, we assume it was set by sendfile() and the socket buffer is full. > This is generally safe as we clear errno before the SSL_sendfile() call, > and saves an extra sendfile() syscall. > > Prodded by Gleb Smirnoff. > > diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c > --- a/src/event/ngx_event_openssl.c > +++ b/src/event/ngx_event_openssl.c > @@ -3277,6 +3277,10 @@ ngx_ssl_send_chain(ngx_connection_t *c, > send += n; > flush = 0; > > + if (!c->write->ready) { > + break; > + } > + > continue; > } > > @@ -3595,7 +3599,7 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng > > err = ngx_socket_errno; > > - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_sendfile: %z", n); > + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, err, "SSL_sendfile: %z", n); > > if (n > 0) { > > @@ -3618,6 +3622,16 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng > > c->sent += n; > > + /* > + * on FreeBSD sendfile(), along with the number of bytes sent, > + * returns an error with errno set to EAGAIN when the socket buffer > + * is full > + */ > + > + if (err == EAGAIN) { Should be: - if (err == EAGAIN) { + if (err == NGX_EAGAIN) { No real difference here, but in general NGX_E... error codes should be used to ensure portability. Fixed in the patch. > + c->write->ready = 0; > + } > + > return n; > } > > -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Thu Jan 29 00:30:15 2026 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Thu, 29 Jan 2026 03:30:15 +0300 Subject: [PATCH] SSL: style Message-ID: <259b19606760df1e17ab.1769646615@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1769635355 -10800 # Thu Jan 29 00:22:35 2026 +0300 # Node ID 259b19606760df1e17ab55b977455f4b275a75ae # Parent 7cdf7ba643ac11369a64b0785219834b989dc754 SSL: style. diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -3694,7 +3694,7 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng #if (NGX_HAVE_SENDFILE_NODISKIO) - if (err == EBUSY) { + if (err == NGX_EBUSY) { c->busy_count++; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, From mdounin at mdounin.ru Fri Jan 30 10:36:20 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Fri, 30 Jan 2026 13:36:20 +0300 Subject: [nginx-tests] Tests: fixed stop_SSL() usage in ssl.t. Message-ID: details: http://freenginx.org/hg/nginx-tests/rev/729a215c18f9 branches: changeset: 2037:729a215c18f9 user: Maxim Dounin date: Fri Jan 30 13:22:17 2026 +0300 description: Tests: fixed stop_SSL() usage in ssl.t. IO::Socket::SSL's stop_SSL() is documented to return true on success, and not 1, so testing for 1 is wrong. This was missed during conversion from Net::SSLeay to IO::Socket::SSL in 1866:a797d7428fa5. Further, IO::Socket::SSL started to actually return an object on success in recently released version 2.096, breaking the test. Fix is to use ok() test instead of is(), properly testing for a true value being returned. diffstat: ssl.t | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff --git a/ssl.t b/ssl.t --- a/ssl.t +++ b/ssl.t @@ -268,7 +268,7 @@ ok(get_ssl_socket(8085), 'ssl unexpected # close_notify is sent before lingering close -is(get_ssl_shutdown(8085), 1, 'ssl shutdown on lingering close'); +ok(get_ssl_shutdown(8085), 'ssl shutdown on lingering close'); $t->stop(); From mdounin at mdounin.ru Fri Jan 30 10:36:20 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Fri, 30 Jan 2026 13:36:20 +0300 Subject: [nginx-tests] Tests: improved timeout handling on Windows. Message-ID: details: http://freenginx.org/hg/nginx-tests/rev/e7094af5cc2d branches: changeset: 2038:e7094af5cc2d user: Maxim Dounin date: Fri Jan 30 13:22:30 2026 +0300 description: Tests: improved timeout handling on Windows. On Windows, the eval + alarm model does not work (see perlport(1) for alarm, as well as 1678:d0025a0dead7 and 1705:99a9b8b50f21 for previous related fixes). Most notably, trying to rely on it for timeout handling around accept() in the main test process results in infinite hangs on Windows if something goes wrong and the timeout is indeed needed. A similar issue in grpc.t was fixed in 1705:99a9b8b50f21, though other similar constructs, originally introduced in 542:e7e3ced702f5, remained in the code. At least grpc_ssl.t was seen hanging indefinitely after an SSL handshake failure with the "certificate is not yet valid" error due to the time adjusted backwards during the test. With this change, all eval + alarm constructs around accept() calls are replaced with IO::Select can_read() calls (with appropriate timeouts) to check if there is a connection to accept. Code in grpc.t, which was previously fixed in 1705:99a9b8b50f21, was slightly adjusted to match other tests. diffstat: fastcgi_request_buffering.t | 21 +++++++-------------- fastcgi_request_buffering_chunked.t | 21 +++++++-------------- grpc.t | 8 +++----- grpc_request_buffering.t | 14 +++----------- grpc_ssl.t | 16 ++++------------ proxy_request_buffering.t | 21 +++++++-------------- proxy_request_buffering_chunked.t | 21 +++++++-------------- proxy_request_buffering_ssl.t | 21 +++++++-------------- 8 files changed, 45 insertions(+), 98 deletions(-) diffs (283 lines): diff --git a/fastcgi_request_buffering.t b/fastcgi_request_buffering.t --- a/fastcgi_request_buffering.t +++ b/fastcgi_request_buffering.t @@ -12,6 +12,8 @@ use warnings; use strict; use Test::More; + +use IO::Select; use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -233,23 +235,14 @@ EOF $s = http($r, start => 1); - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - local $SIG{PIPE} = sub { die "sigpipe\n" }; - alarm(5); - - $client = $server->accept(); - - log2c("(new connection $client)"); - - alarm(0); - }; - alarm(0); - if ($@) { - log_in("died: $@"); + if (!IO::Select->new($server)->can_read(5)) { + log2c("timeout"); return undef; } + $client = $server->accept(); + log2c("(new connection $client)"); + $client->sysread(my $buf, 1024); log2i($buf); diff --git a/fastcgi_request_buffering_chunked.t b/fastcgi_request_buffering_chunked.t --- a/fastcgi_request_buffering_chunked.t +++ b/fastcgi_request_buffering_chunked.t @@ -13,6 +13,8 @@ use warnings; use strict; use Test::More; + +use IO::Select; use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -262,23 +264,14 @@ EOF $s = http($r, start => 1); - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - local $SIG{PIPE} = sub { die "sigpipe\n" }; - alarm(5); - - $client = $server->accept(); - - log2c("(new connection $client)"); - - alarm(0); - }; - alarm(0); - if ($@) { - log_in("died: $@"); + if (!IO::Select->new($server)->can_read(5)) { + log2c("timeout"); return undef; } + $client = $server->accept(); + log2c("(new connection $client)"); + $client->sysread(my $buf, 1024); log2i($buf); diff --git a/grpc.t b/grpc.t --- a/grpc.t +++ b/grpc.t @@ -715,16 +715,14 @@ sub grpc { { name => 'te', value => 'trailers', mode => 2 }]}); if (!$extra{reuse}) { - if (IO::Select->new($server)->can_read(5)) { - $client = $server->accept(); - - } else { - log_in("timeout"); + if (!IO::Select->new($server)->can_read(5)) { + log2c("timeout"); # connection could be unexpectedly reused goto reused if $client; return undef; } + $client = $server->accept(); log2c("(new connection $client)"); $n++; diff --git a/grpc_request_buffering.t b/grpc_request_buffering.t --- a/grpc_request_buffering.t +++ b/grpc_request_buffering.t @@ -133,20 +133,12 @@ sub grpc { { name => 'content-length', value => length($body) }]}); if (!$extra{reuse}) { - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - alarm(5); - - $client = $server->accept() or return; - - alarm(0); - }; - alarm(0); - if ($@) { - log_in("died: $@"); + if (!IO::Select->new($server)->can_read(5)) { + log2c("timeout"); return undef; } + $client = $server->accept() or return; log2c("(new connection $client)"); $client->sysread(my $buf, 24) == 24 or return; # preface diff --git a/grpc_ssl.t b/grpc_ssl.t --- a/grpc_ssl.t +++ b/grpc_ssl.t @@ -259,20 +259,12 @@ sub grpc { { name => 'te', value => 'trailers', mode => 2 }]}); if (!$extra{reuse}) { - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - alarm(5); - - $client = $server->accept() or return; - - alarm(0); - }; - alarm(0); - if ($@) { - log_in("died: $@"); + if (!IO::Select->new($server)->can_read(5)) { + log2c("timeout"); return undef; } - + + $client = $server->accept() or return; log2c("(new connection $client)"); $client->sysread(my $buf, 24) == 24 or return; # preface diff --git a/proxy_request_buffering.t b/proxy_request_buffering.t --- a/proxy_request_buffering.t +++ b/proxy_request_buffering.t @@ -12,6 +12,8 @@ use warnings; use strict; use Test::More; + +use IO::Select; use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -200,23 +202,14 @@ EOF $s = http($r, start => 1); - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - local $SIG{PIPE} = sub { die "sigpipe\n" }; - alarm(5); - - $client = $server->accept(); - - log2c("(new connection $client)"); - - alarm(0); - }; - alarm(0); - if ($@) { - log_in("died: $@"); + if (!IO::Select->new($server)->can_read(5)) { + log2c("timeout"); return undef; } + $client = $server->accept(); + log2c("(new connection $client)"); + $client->sysread(my $buf, 1024); log2i($buf); diff --git a/proxy_request_buffering_chunked.t b/proxy_request_buffering_chunked.t --- a/proxy_request_buffering_chunked.t +++ b/proxy_request_buffering_chunked.t @@ -12,6 +12,8 @@ use warnings; use strict; use Test::More; + +use IO::Select; use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -235,23 +237,14 @@ EOF $s = http($r, start => 1); - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - local $SIG{PIPE} = sub { die "sigpipe\n" }; - alarm(5); - - $client = $server->accept(); - - log2c("(new connection $client)"); - - alarm(0); - }; - alarm(0); - if ($@) { - log_in("died: $@"); + if (!IO::Select->new($server)->can_read(5)) { + log2c("timeout"); return undef; } + $client = $server->accept(); + log2c("(new connection $client)"); + $client->sysread(my $buf, 1024); log2i($buf); diff --git a/proxy_request_buffering_ssl.t b/proxy_request_buffering_ssl.t --- a/proxy_request_buffering_ssl.t +++ b/proxy_request_buffering_ssl.t @@ -12,6 +12,8 @@ use warnings; use strict; use Test::More; + +use IO::Select; use Socket qw/ CRLF /; BEGIN { use FindBin; chdir($FindBin::Bin); } @@ -221,23 +223,14 @@ EOF $s = http($r, start => 1); - eval { - local $SIG{ALRM} = sub { die "timeout\n" }; - local $SIG{PIPE} = sub { die "sigpipe\n" }; - alarm(5); - - $client = $server->accept(); - - log2c("(new connection $client)"); - - alarm(0); - }; - alarm(0); - if ($@) { - log_in("died: $@"); + if (!IO::Select->new($server)->can_read(5)) { + log2c("timeout"); return undef; } + $client = $server->accept(); + log2c("(new connection $client)"); + $client->sysread(my $buf, 1024); log2i($buf); From mdounin at mdounin.ru Fri Jan 30 10:36:20 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Fri, 30 Jan 2026 13:36:20 +0300 Subject: [nginx-tests] Tests: basic tests for SSL_sendfile() usage. Message-ID: details: http://freenginx.org/hg/nginx-tests/rev/873c967f713d branches: changeset: 2039:873c967f713d user: Maxim Dounin date: Fri Jan 30 13:23:25 2026 +0300 description: Tests: basic tests for SSL_sendfile() usage. Note that these tests assume kernel TLS enabled in OS (usually the case now, see 7941:65946a191197 changeset for details). If kernel TLS is not enabled, tests will still succeed, though will use normal SSL_write() instead. Mostly usable with manual checking of the debug log. diffstat: ssl_sendfile.t | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 88 insertions(+), 0 deletions(-) diffs (93 lines): diff --git a/ssl_sendfile.t b/ssl_sendfile.t new file mode 100644 --- /dev/null +++ b/ssl_sendfile.t @@ -0,0 +1,88 @@ +#!/usr/bin/perl + +# (C) Maxim Dounin + +# Tests for http ssl module, SSL_sendfile() usage. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new() + ->has(qw/http http_ssl openssl:3.0.0 socket_ssl/) + ->has_daemon('openssl')->plan(4); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8080 sndbuf=16k; + listen 127.0.0.1:8443 ssl sndbuf=16k; + server_name localhost; + + sendfile on; + ssl_conf_command Options KTLS; + + ssl_certificate localhost.crt; + ssl_certificate_key localhost.key; + } +} + +EOF + +$t->write_file('openssl.conf', <testdir(); + +foreach my $name ('localhost') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->run(); + +$t->write_file('small', 'SEE-THIS'); +$t->write_file('big', ('123456789' x 30000) . 'SEE-THIS'); + +############################################################################### + +like(http_get('/small', SSL => 1), qr/SEE-THIS/, 'sendfile small'); +like(http_get('/big', SSL => 1, sleep => 0.5), + qr/^(123456789){30000}SEE-THIS$/m, 'sendfile big'); + +like(http_get('/small'), qr/SEE-THIS/, 'sendfile plain small'); +like(http_get('/big', sleep => 0.5), + qr/^(123456789){30000}SEE-THIS$/m, 'sendfile plain big'); + +############################################################################### From mdounin at mdounin.ru Fri Jan 30 10:43:40 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Fri, 30 Jan 2026 13:43:40 +0300 Subject: [nginx] Win32: fixed ngx_set_errno() vs. ngx_set_socket_errno() ... Message-ID: details: http://freenginx.org/hg/nginx/rev/e2035ac498a3 branches: changeset: 9458:e2035ac498a3 user: Maxim Dounin date: Fri Jan 30 13:27:12 2026 +0300 description: Win32: fixed ngx_set_errno() vs. ngx_set_socket_errno() usage. Fixed usage of ngx_set_errno() instead of ngx_set_socket_errno() where ngx_socket_errno is used, missed in 9456:a7b7067ffe3f. diffstat: src/event/ngx_event_openssl.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -3576,7 +3576,7 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng "SSL to sendfile: @%O %uz", file->file_pos, size); - ngx_set_errno(0); + ngx_set_socket_errno(0); #if (NGX_HAVE_SENDFILE_NODISKIO) From mdounin at mdounin.ru Fri Jan 30 10:43:40 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Fri, 30 Jan 2026 13:43:40 +0300 Subject: [nginx] SSL: style. Message-ID: details: http://freenginx.org/hg/nginx/rev/a16d93b891d8 branches: changeset: 9459:a16d93b891d8 user: Maxim Dounin date: Fri Jan 30 13:27:26 2026 +0300 description: SSL: style. diffstat: src/event/ngx_event_openssl.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -3680,7 +3680,7 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng #if (NGX_HAVE_SENDFILE_NODISKIO) - if (err == EBUSY) { + if (err == NGX_EBUSY) { c->busy_count++; ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, From mdounin at mdounin.ru Fri Jan 30 10:43:40 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Fri, 30 Jan 2026 13:43:40 +0300 Subject: [nginx] SSL: optimized SSL_sendfile() usage on FreeBSD. Message-ID: details: http://freenginx.org/hg/nginx/rev/2d9f74de4dc2 branches: changeset: 9460:2d9f74de4dc2 user: Maxim Dounin date: Fri Jan 30 13:27:45 2026 +0300 description: SSL: optimized SSL_sendfile() usage on FreeBSD. On FreeBSD sendfile() syscall returns an error with errno set to EAGAIN when the socket buffer is full (along with the number of bytes sent). Unfortunately, the SSL_sendfile() interface loses this information, and an extra SSL_sendfile() call is needed to obtain EAGAIN, typically resulting in doubled sendfile() syscalls. With this change, if errno is set to EAGAIN after the SSL_sendfile() call, we assume it was set by sendfile() and the socket buffer is full. This is generally safe as we clear errno before the SSL_sendfile() call, and saves an extra sendfile() syscall. Prodded by Gleb Smirnoff. diffstat: src/event/ngx_event_openssl.c | 16 +++++++++++++++- 1 files changed, 15 insertions(+), 1 deletions(-) diffs (40 lines): diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -3277,6 +3277,10 @@ ngx_ssl_send_chain(ngx_connection_t *c, send += n; flush = 0; + if (!c->write->ready) { + break; + } + continue; } @@ -3595,7 +3599,7 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng err = ngx_socket_errno; - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_sendfile: %z", n); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, err, "SSL_sendfile: %z", n); if (n > 0) { @@ -3618,6 +3622,16 @@ ngx_ssl_sendfile(ngx_connection_t *c, ng c->sent += n; + /* + * on FreeBSD sendfile(), along with the number of bytes sent, + * returns an error with errno set to EAGAIN when the socket buffer + * is full + */ + + if (err == NGX_EAGAIN) { + c->write->ready = 0; + } + return n; } From mdounin at mdounin.ru Sat Jan 31 06:36:55 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Sat, 31 Jan 2026 09:36:55 +0300 Subject: [nginx] Upstream: improved reporting of peer failures. Message-ID: details: http://freenginx.org/hg/nginx/rev/cd3a3e585b29 branches: changeset: 9461:cd3a3e585b29 user: Maxim Dounin date: Sat Jan 31 09:27:09 2026 +0300 description: Upstream: improved reporting of peer failures. Previously, peer failures were not reported to the balancer if an error response listed in u->conf->next_upstream was received, but switching to the next upstream server was not possible due to other reasons (for example, when no u->peer.tries left, or due to unbuffered request body). In particular, this resulted in upstream servers never switched off due to 502 errors in configurations with "grpc_next_upstream http_502", as gRPC proxying implies unbuffered request body (https://freenginx.org/pipermail/nginx/2025-February/000150.html). Fix is to preserve information about the failure as detected by ngx_http_upstream_test_next(), and propagate it to the balancer when u->peer.free() is called during upstream request finalization. diffstat: src/http/ngx_http_upstream.c | 57 +++++++++++++++++++++++++++++-------------- src/http/ngx_http_upstream.h | 2 + 2 files changed, 40 insertions(+), 19 deletions(-) diffs (86 lines): diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -2584,24 +2584,43 @@ ngx_http_upstream_test_next(ngx_http_req continue; } - timeout = u->conf->next_upstream_timeout; - - if (u->request_sent - && (r->method & (NGX_HTTP_POST|NGX_HTTP_LOCK|NGX_HTTP_PATCH))) - { - mask = un->mask | NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT; - - } else { - mask = un->mask; - } - - if (u->peer.tries > 1 - && ((u->conf->next_upstream & mask) == mask) - && !(u->request_sent && r->request_body_no_buffering) - && !(timeout && ngx_current_msec - u->peer.start_time >= timeout)) - { - ngx_http_upstream_next(r, u, un->mask); - return NGX_OK; + if (u->conf->next_upstream & un->mask) { + + timeout = u->conf->next_upstream_timeout; + + if (u->request_sent + && (r->method & (NGX_HTTP_POST|NGX_HTTP_LOCK|NGX_HTTP_PATCH))) + { + mask = un->mask | NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT; + + } else { + mask = un->mask; + } + + if (u->peer.tries > 1 + && ((u->conf->next_upstream & mask) == mask) + && !(u->request_sent && r->request_body_no_buffering) + && !(timeout + && ngx_current_msec - u->peer.start_time >= timeout)) + { + ngx_http_upstream_next(r, u, un->mask); + return NGX_OK; + } + + /* + * if we were expected to switch to the next server, but + * were not able to do so due to additional restrictions, + * remember that the peer failed + */ + + if (un->mask == NGX_HTTP_UPSTREAM_FT_HTTP_403 + || un->mask == NGX_HTTP_UPSTREAM_FT_HTTP_404) + { + u->peer_state = NGX_PEER_NEXT; + + } else { + u->peer_state = NGX_PEER_FAILED; + } } #if (NGX_HTTP_CACHE) @@ -4623,7 +4642,7 @@ ngx_http_upstream_finalize_request(ngx_h u->finalize_request(r, rc); if (u->peer.free && u->peer.sockaddr) { - u->peer.free(&u->peer, u->peer.data, 0); + u->peer.free(&u->peer, u->peer.data, u->peer_state); u->peer.sockaddr = NULL; } diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h --- a/src/http/ngx_http_upstream.h +++ b/src/http/ngx_http_upstream.h @@ -404,6 +404,8 @@ struct ngx_http_upstream_s { unsigned request_body_sent:1; unsigned request_body_blocked:1; unsigned header_sent:1; + + unsigned peer_state:3; }; From mdounin at mdounin.ru Sat Jan 31 06:36:55 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Sat, 31 Jan 2026 09:36:55 +0300 Subject: [nginx] Upstream: improved stale-if-error handling. Message-ID: details: http://freenginx.org/hg/nginx/rev/d6723d4c4b81 branches: changeset: 9462:d6723d4c4b81 user: Maxim Dounin date: Sat Jan 31 09:27:12 2026 +0300 description: Upstream: improved stale-if-error handling. Following 7702:7015f26aef90, "Cache-Control: stale-if-error=..." is ignored for HTTP 4xx and 5xx responses, even if these are expected to be treated as errors per u->conf->next_upstream configuration, notably when switching to the next upstream server wasn't possible. This makes it non-trivial to configure things with stale response usage for a limited time per stale-if-error as long as an actual error is detected not by the cache itself, but by an intermediate proxy, even if the particular status code is configured to be mostly equivalent to network errors, such as with "proxy_next_upstream error timeout http_502". With this change, as long as the particular error is considered to be a failure per u->conf->next_upstream, stale-if-error information from the cached response is now used. diffstat: src/http/ngx_http_upstream.c | 4 +++- 1 files changed, 3 insertions(+), 1 deletions(-) diffs (14 lines): diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -2626,7 +2626,9 @@ ngx_http_upstream_test_next(ngx_http_req #if (NGX_HTTP_CACHE) if (u->cache_status == NGX_HTTP_CACHE_EXPIRED - && (u->conf->cache_use_stale & un->mask)) + && ((u->conf->cache_use_stale & un->mask) + || ((u->peer_state & NGX_PEER_FAILED) + && r->cache->stale_error))) { ngx_int_t rc; From mdounin at mdounin.ru Sat Jan 31 06:37:23 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Sat, 31 Jan 2026 09:37:23 +0300 Subject: [nginx-tests] Tests: added proxy_next_upstream test with no next... Message-ID: details: http://freenginx.org/hg/nginx-tests/rev/dd9752c3750a branches: changeset: 2040:dd9752c3750a user: Maxim Dounin date: Sat Jan 31 09:27:23 2026 +0300 description: Tests: added proxy_next_upstream test with no next upstream. Previously, peer failures were not reported to the balancer if an error response listed in proxy_next_upstream was received, but switching to the next upstream server was not possible. diffstat: proxy_next_upstream.t | 26 +++++++++++++++++++++++++- 1 files changed, 25 insertions(+), 1 deletions(-) diffs (55 lines): diff --git a/proxy_next_upstream.t b/proxy_next_upstream.t --- a/proxy_next_upstream.t +++ b/proxy_next_upstream.t @@ -21,7 +21,7 @@ use Test::Nginx; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http proxy rewrite/)->plan(8); +my $t = Test::Nginx->new()->has(qw/http proxy rewrite/)->plan(11); $t->write_file_expand('nginx.conf', <<'EOF'); @@ -50,6 +50,11 @@ http { server 127.0.0.1:8082 down; } + upstream u4 { + server 127.0.0.1:8081; + server 127.0.0.1:8082; + } + server { listen 127.0.0.1:8080; server_name localhost; @@ -74,6 +79,12 @@ http { proxy_pass http://u3; proxy_next_upstream http_404; } + + location /nonext { + proxy_pass http://u4/500; + proxy_next_upstream http_500; + proxy_next_upstream_tries 1; + } } server { @@ -148,4 +159,17 @@ like(http_get('/all/rr'), like(http_get('/down/'), qr/Not Found/, 'all tried with down'); +# make sure backend is switched off with http_500 +# if switching to next upstream is not possible + +like(http_get('/nonext'), qr/500 Internal|SEE-THIS/, 'request nonext'); +like(http_get('/nonext'), qr/500 Internal|SEE-THIS/, 'request nonext second'); + +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.29.5'); + +like(http_get('/nonext'), qr/SEE-THIS/, 'down after nonext'); + +} + ############################################################################### From mdounin at mdounin.ru Sat Jan 31 06:37:24 2026 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Sat, 31 Jan 2026 09:37:24 +0300 Subject: [nginx-tests] Tests: stale-if-error with "proxy_next_upstream ht... Message-ID: details: http://freenginx.org/hg/nginx-tests/rev/cccd744b9159 branches: changeset: 2041:cccd744b9159 user: Maxim Dounin date: Sat Jan 31 09:27:25 2026 +0300 description: Tests: stale-if-error with "proxy_next_upstream http_500". diffstat: proxy_cache_use_stale.t | 22 +++++++++++++++++++--- 1 files changed, 19 insertions(+), 3 deletions(-) diffs (58 lines): diff --git a/proxy_cache_use_stale.t b/proxy_cache_use_stale.t --- a/proxy_cache_use_stale.t +++ b/proxy_cache_use_stale.t @@ -94,6 +94,12 @@ http { proxy_cache_use_stale updating; } + location /next/ { + proxy_pass http://127.0.0.1:8081/; + + proxy_next_upstream http_500; + } + location /t7.html { proxy_pass http://127.0.0.1:8081; @@ -150,7 +156,7 @@ EOF $t->write_file('escape.html', 'SEE-THIS'); $t->write_file('regexp.html', 'SEE-THIS'); -$t->run()->plan(34); +$t->run()->plan(35); ############################################################################### @@ -171,20 +177,30 @@ http_get('/ssi.html'); get('/updating/t.html', 'max-age=1'); get('/updating/t2.html', 'max-age=1, stale-while-revalidate=2'); get('/updating/tt.html', 'max-age=1, stale-if-error=5'); +get('/next/tt.html', 'max-age=1, stale-if-error=5'); get('/t8.html', 'stale-while-revalidate=20'); get('/escape.htm%6C', 'max-age=1, stale-while-revalidate=20'); get('/regexp.html', 'max-age=1, stale-while-revalidate=20'); sleep 2; -# stale 5xx response is ignored since 1.19.3, -# "proxy_cache_use_stale updating;" allows to get it still +# if an upstream returns a valid HTTP response, stale cached response +# with "stale-if-error=..." is only used if the status code is listed +# in proxy_next_upstream (1.29.5) or using stale responses is enabled +# with proxy_cache_use_stale like(http_get('/t.html?e=1'), qr/ 500 /, 's-i-e - stale 5xx ignore'); like(http_get('/tt.html?e=1'), qr/ 500 /, 's-i-e - stale 5xx ignore 2'); like(http_get('/updating/tt.html'), qr/STALE/, 's-i-e - stale 5xx updating'); like(http_get('/t.html'), qr/REVALIDATED/, 's-i-e - revalidated'); +TODO: { +local $TODO = 'not yet' unless $t->has_version('1.29.5'); + +like(http_get('/next/tt.html?e=1'), qr/STALE/, 's-i-e - stale 5xx next'); + +} + like(http_get('/t2.html?e=1'), qr/STALE/, 's-w-r - revalidate error'); like(http_get('/t2.html'), qr/STALE/, 's-w-r - stale while revalidate'); like(http_get('/t2.html'), qr/HIT/, 's-w-r - revalidated');