From mdounin at mdounin.ru Wed Jun 4 23:29:11 2025 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 05 Jun 2025 02:29:11 +0300 Subject: [nginx-tests] Tests: fixed Valgrind errors filtering. Message-ID: details: http://freenginx.org/hg/nginx-tests/rev/7bfe91f27c94 branches: changeset: 2009:7bfe91f27c94 user: Maxim Dounin date: Sun Jun 01 01:12:21 2025 +0300 description: Tests: fixed Valgrind errors filtering. Broken in 1995:d329b05e20fa during $t->grep_file() introduction and resulted in warnings in output (and no filtering) when testing with TEST_NGINX_VALGRIND set. diffstat: lib/Test/Nginx.pm | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff --git a/lib/Test/Nginx.pm b/lib/Test/Nginx.pm --- a/lib/Test/Nginx.pm +++ b/lib/Test/Nginx.pm @@ -89,7 +89,7 @@ sub DESTROY { } if (Test::More->builder->expected_tests && $ENV{TEST_NGINX_VALGRIND}) { - my $errors = $self->grep_file('valgrind.log', /^==\d+== .+/m); + my $errors = $self->grep_file('valgrind.log', qr/^==\d+== .+/m); Test::More::is($errors, '', 'no valgrind errors'); } From mdounin at mdounin.ru Wed Jun 4 23:37:03 2025 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 05 Jun 2025 02:37:03 +0300 Subject: [nginx] Disabled open_file_cache on platforms without pread(). Message-ID: details: http://freenginx.org/hg/nginx/rev/5249bce09d1f branches: changeset: 9363:5249bce09d1f user: Maxim Dounin date: Thu Jun 05 02:16:42 2025 +0300 description: Disabled open_file_cache on platforms without pread(). Current open file cache code cannot properly work on platforms without pread(), since file->sys_offset is not shared across files. Further, it is not set on file initialization after ngx_open_cached_file(), leading to incorrect value 0 instead of non-zero current offset for cached file descriptors. Since platforms without pread() are rather exotic nowadays, fix is to disable open_file_cache for them. diffstat: src/http/ngx_http_core_module.c | 18 +++++++++++++----- 1 files changed, 13 insertions(+), 5 deletions(-) diffs (35 lines): diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -4981,6 +4981,8 @@ ngx_http_core_error_page(ngx_conf_t *cf, static char * ngx_http_core_open_file_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { +#if (NGX_HAVE_PREAD || NGX_WIN32) + ngx_http_core_loc_conf_t *clcf = conf; time_t inactive; @@ -5048,11 +5050,17 @@ ngx_http_core_open_file_cache(ngx_conf_t } clcf->open_file_cache = ngx_open_file_cache_init(cf->pool, max, inactive); - if (clcf->open_file_cache) { - return NGX_CONF_OK; - } - - return NGX_CONF_ERROR; + if (clcf->open_file_cache == NULL) { + return NGX_CONF_ERROR; + } + +#else + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "\"open_file_cache\" is not supported " + "on this platform, ignored"); +#endif + + return NGX_CONF_OK; } From mdounin at mdounin.ru Wed Jun 4 23:37:03 2025 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 05 Jun 2025 02:37:03 +0300 Subject: [nginx] Open file cache: style. Message-ID: details: http://freenginx.org/hg/nginx/rev/a5f8fb848439 branches: changeset: 9364:a5f8fb848439 user: Maxim Dounin date: Thu Jun 05 02:17:12 2025 +0300 description: Open file cache: style. diffstat: src/core/ngx_open_file_cache.c | 2 +- src/core/ngx_open_file_cache.h | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diffs (35 lines): diff --git a/src/core/ngx_open_file_cache.c b/src/core/ngx_open_file_cache.c --- a/src/core/ngx_open_file_cache.c +++ b/src/core/ngx_open_file_cache.c @@ -974,7 +974,7 @@ ngx_open_file_add_event(ngx_open_file_ca file->use_event = 0; file->event = ngx_calloc(sizeof(ngx_event_t), log); - if (file->event== NULL) { + if (file->event == NULL) { return; } diff --git a/src/core/ngx_open_file_cache.h b/src/core/ngx_open_file_cache.h --- a/src/core/ngx_open_file_cache.h +++ b/src/core/ngx_open_file_cache.h @@ -53,9 +53,7 @@ typedef struct { } ngx_open_file_info_t; -typedef struct ngx_cached_open_file_s ngx_cached_open_file_t; - -struct ngx_cached_open_file_s { +typedef struct { ngx_rbtree_node_t node; ngx_queue_t queue; @@ -87,7 +85,7 @@ struct ngx_cached_open_file_s { unsigned is_directio:1; ngx_event_t *event; -}; +} ngx_cached_open_file_t; typedef struct { From mdounin at mdounin.ru Wed Jun 4 23:37:04 2025 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 05 Jun 2025 02:37:04 +0300 Subject: [nginx] Open file cache: disable_symlinks with open_file_cache_e... Message-ID: details: http://freenginx.org/hg/nginx/rev/116453ebb900 branches: changeset: 9365:116453ebb900 user: Maxim Dounin date: Thu Jun 05 02:17:23 2025 +0300 description: Open file cache: disable_symlinks with open_file_cache_events. Previously, different disable_symlinks settings were respected when retrieving a file from open file cache when using periodic retest, but were ignored with using open_file_cache_events. Fix is to test disable_symlinks settings in all cases, and retest the file if requested settings are different from what we already have in the cache. diffstat: src/core/ngx_open_file_cache.c | 14 +++++++------- 1 files changed, 7 insertions(+), 7 deletions(-) diffs (26 lines): diff --git a/src/core/ngx_open_file_cache.c b/src/core/ngx_open_file_cache.c --- a/src/core/ngx_open_file_cache.c +++ b/src/core/ngx_open_file_cache.c @@ -227,15 +227,15 @@ ngx_open_cached_file(ngx_open_file_cache goto add_event; } - if (file->use_event - || (file->event == NULL - && (of->uniq == 0 || of->uniq == file->uniq) - && now - file->created < of->valid + if ((file->use_event + || (file->event == NULL + && (of->uniq == 0 || of->uniq == file->uniq) + && now - file->created < of->valid)) #if (NGX_HAVE_OPENAT) - && of->disable_symlinks == file->disable_symlinks - && of->disable_symlinks_from == file->disable_symlinks_from + && of->disable_symlinks == file->disable_symlinks + && of->disable_symlinks_from == file->disable_symlinks_from #endif - )) + ) { if (file->err == 0) { From mdounin at mdounin.ru Wed Jun 4 23:37:04 2025 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 05 Jun 2025 02:37:04 +0300 Subject: [nginx] Open file cache: fixed file->uses loss on retest. Message-ID: details: http://freenginx.org/hg/nginx/rev/4604f3fe8f83 branches: changeset: 9366:4604f3fe8f83 user: Maxim Dounin date: Thu Jun 05 02:18:00 2025 +0300 description: Open file cache: fixed file->uses loss on retest. If an open file was reopened during a retest, but was in use by another request, the cache entry was re-created with file->uses set to 1. Fix is to preserve existing file->uses. diffstat: src/core/ngx_open_file_cache.c | 7 ++++++- 1 files changed, 6 insertions(+), 1 deletions(-) diffs (38 lines): diff --git a/src/core/ngx_open_file_cache.c b/src/core/ngx_open_file_cache.c --- a/src/core/ngx_open_file_cache.c +++ b/src/core/ngx_open_file_cache.c @@ -147,6 +147,7 @@ ngx_open_cached_file(ngx_open_file_cache time_t now; uint32_t hash; ngx_int_t rc; + ngx_uint_t uses; ngx_file_info_t fi; ngx_pool_cleanup_t *cln; ngx_cached_open_file_t *file; @@ -348,6 +349,8 @@ ngx_open_cached_file(ngx_open_file_cache file->close = 1; + uses = file->uses; + goto create; } @@ -359,6 +362,8 @@ ngx_open_cached_file(ngx_open_file_cache goto failed; } + uses = 1; + create: if (cache->current >= cache->max) { @@ -387,7 +392,7 @@ create: cache->current++; - file->uses = 1; + file->uses = uses; file->count = 0; file->use_event = 0; file->event = NULL; From mdounin at mdounin.ru Wed Jun 4 23:37:04 2025 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 05 Jun 2025 02:37:04 +0300 Subject: [nginx] Open file cache: correct directio handling with threads. Message-ID: details: http://freenginx.org/hg/nginx/rev/e0b6f94156c8 branches: changeset: 9367:e0b6f94156c8 user: Maxim Dounin date: Thu Jun 05 02:18:25 2025 +0300 description: Open file cache: correct directio handling with threads. On Linux with open_file_cache and directio enabled along with threaded IO, if a file descriptor is shared among multiple requests, one request might unexpectedly switch directio on while another request does an unaligned read in a thread, leading to EINVAL from pread(). To fix this, now all directio changes are done with reference counting when open file cache is used, and directio is only re-enabled on a file descriptor when there are no outstanding requests to switch it off. diffstat: src/core/ngx_open_file_cache.c | 113 +++++++++++++++++++++++++++++++- src/core/ngx_open_file_cache.h | 6 + src/core/ngx_output_chain.c | 8 +- src/http/modules/ngx_http_mp4_module.c | 4 +- 4 files changed, 121 insertions(+), 10 deletions(-) diffs (262 lines): diff --git a/src/core/ngx_open_file_cache.c b/src/core/ngx_open_file_cache.c --- a/src/core/ngx_open_file_cache.c +++ b/src/core/ngx_open_file_cache.c @@ -41,7 +41,8 @@ static void ngx_open_file_add_event(ngx_ ngx_cached_open_file_t *file, ngx_open_file_info_t *of, ngx_log_t *log); static void ngx_open_file_cleanup(void *data); static void ngx_close_cached_file(ngx_open_file_cache_t *cache, - ngx_cached_open_file_t *file, ngx_uint_t min_uses, ngx_log_t *log); + ngx_cached_open_file_t *file, ngx_uint_t min_uses, ngx_uint_t directio_off, + ngx_log_t *log); static void ngx_open_file_del_event(ngx_cached_open_file_t *file); static void ngx_expire_old_cached_files(ngx_open_file_cache_t *cache, ngx_uint_t n, ngx_log_t *log); @@ -51,6 +52,8 @@ static ngx_cached_open_file_t * ngx_open_file_lookup(ngx_open_file_cache_t *cache, ngx_str_t *name, uint32_t hash); static void ngx_open_file_cache_remove(ngx_event_t *ev); +static ngx_open_file_cache_cleanup_t * + ngx_open_file_cache_get_cleanup(ngx_pool_t *p, ngx_fd_t fd); ngx_open_file_cache_t * @@ -118,7 +121,7 @@ ngx_open_file_cache_cleanup(void *data) if (!file->err && !file->is_dir) { file->close = 1; file->count = 0; - ngx_close_cached_file(cache, file, 0, ngx_cycle->log); + ngx_close_cached_file(cache, file, 0, 0, ngx_cycle->log); } else { ngx_free(file->name); @@ -396,6 +399,7 @@ create: file->count = 0; file->use_event = 0; file->event = NULL; + file->directio_off = of->is_directio_off; add_event: @@ -449,6 +453,7 @@ found: ofcln->cache = cache; ofcln->file = file; ofcln->min_uses = of->min_uses; + ofcln->directio_off = of->is_directio_off; ofcln->log = pool->log; } @@ -1032,7 +1037,8 @@ ngx_open_file_cleanup(void *data) c->file->count--; - ngx_close_cached_file(c->cache, c->file, c->min_uses, c->log); + ngx_close_cached_file(c->cache, c->file, c->min_uses, c->directio_off, + c->log); /* drop one or two expired open files */ ngx_expire_old_cached_files(c->cache, 1, c->log); @@ -1041,12 +1047,25 @@ ngx_open_file_cleanup(void *data) static void ngx_close_cached_file(ngx_open_file_cache_t *cache, - ngx_cached_open_file_t *file, ngx_uint_t min_uses, ngx_log_t *log) + ngx_cached_open_file_t *file, ngx_uint_t min_uses, ngx_uint_t directio_off, + ngx_log_t *log) { ngx_log_debug5(NGX_LOG_DEBUG_CORE, log, 0, "close cached open file: %s, fd:%d, c:%d, u:%d, %d", file->name, file->fd, file->count, file->uses, file->close); + if (directio_off) { + file->directio_off--; + + if (file->directio_off == 0) { + if (ngx_directio_on(file->fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + ngx_directio_on_n " \"%s\" failed", + file->name); + } + } + } + if (!file->close) { file->accessed = ngx_time(); @@ -1143,7 +1162,7 @@ ngx_expire_old_cached_files(ngx_open_fil if (!file->err && !file->is_dir) { file->close = 1; - ngx_close_cached_file(cache, file, 0, log); + ngx_close_cached_file(cache, file, 0, 0, log); } else { ngx_free(file->name); @@ -1255,10 +1274,92 @@ ngx_open_file_cache_remove(ngx_event_t * file->close = 1; - ngx_close_cached_file(fev->cache, file, 0, ev->log); + ngx_close_cached_file(fev->cache, file, 0, 0, ev->log); /* free memory only when fev->cache and fev->file are already not needed */ ngx_free(ev->data); ngx_free(ev); } + + +ngx_int_t +ngx_open_file_directio_on(ngx_fd_t fd, ngx_pool_t *pool) +{ + ngx_open_file_cache_cleanup_t *c; + + /* + * DIRECTIO is only re-enabled on a file descriptor + * when there are no outstanding requests to switch it off + */ + + c = ngx_open_file_cache_get_cleanup(pool, fd); + + if (c) { + if (!c->directio_off) { + return NGX_OK; + } + + c->directio_off = 0; + c->file->directio_off--; + + if (c->file->directio_off > 0) { + return NGX_OK; + } + } + + if (ngx_directio_on(fd) == NGX_FILE_ERROR) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_open_file_directio_off(ngx_fd_t fd, ngx_pool_t *pool) +{ + ngx_open_file_cache_cleanup_t *c; + + c = ngx_open_file_cache_get_cleanup(pool, fd); + + if (c) { + if (c->directio_off) { + return NGX_OK; + } + + c->directio_off = 1; + c->file->directio_off++; + + if (c->file->directio_off > 1) { + return NGX_OK; + } + } + + if (ngx_directio_off(fd) == NGX_FILE_ERROR) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_open_file_cache_cleanup_t * +ngx_open_file_cache_get_cleanup(ngx_pool_t *p, ngx_fd_t fd) +{ + ngx_pool_cleanup_t *cln; + ngx_open_file_cache_cleanup_t *c; + + for (cln = p->cleanup; cln; cln = cln->next) { + if (cln->handler == ngx_open_file_cleanup) { + + c = cln->data; + + if (c->file->fd == fd) { + return c; + } + } + } + + return NULL; +} diff --git a/src/core/ngx_open_file_cache.h b/src/core/ngx_open_file_cache.h --- a/src/core/ngx_open_file_cache.h +++ b/src/core/ngx_open_file_cache.h @@ -67,6 +67,8 @@ typedef struct { off_t size; ngx_err_t err; + ngx_uint_t directio_off; + uint32_t uses; #if (NGX_HAVE_OPENAT) @@ -103,6 +105,7 @@ typedef struct { ngx_open_file_cache_t *cache; ngx_cached_open_file_t *file; ngx_uint_t min_uses; + ngx_uint_t directio_off; ngx_log_t *log; } ngx_open_file_cache_cleanup_t; @@ -125,5 +128,8 @@ ngx_open_file_cache_t *ngx_open_file_cac ngx_int_t ngx_open_cached_file(ngx_open_file_cache_t *cache, ngx_str_t *name, ngx_open_file_info_t *of, ngx_pool_t *pool); +ngx_int_t ngx_open_file_directio_on(ngx_fd_t fd, ngx_pool_t *pool); +ngx_int_t ngx_open_file_directio_off(ngx_fd_t fd, ngx_pool_t *pool); + #endif /* _NGX_OPEN_FILE_CACHE_H_INCLUDED_ */ diff --git a/src/core/ngx_output_chain.c b/src/core/ngx_output_chain.c --- a/src/core/ngx_output_chain.c +++ b/src/core/ngx_output_chain.c @@ -553,7 +553,9 @@ ngx_output_chain_copy_buf(ngx_output_cha #if (NGX_HAVE_ALIGNED_DIRECTIO) if (ctx->unaligned) { - if (ngx_directio_off(src->file->fd) == NGX_FILE_ERROR) { + if (ngx_open_file_directio_off(src->file->fd, ctx->pool) + != NGX_OK) + { ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, ngx_errno, ngx_directio_off_n " \"%s\" failed", src->file->name.data); @@ -600,7 +602,9 @@ ngx_output_chain_copy_buf(ngx_output_cha err = ngx_errno; - if (ngx_directio_on(src->file->fd) == NGX_FILE_ERROR) { + if (ngx_open_file_directio_on(src->file->fd, ctx->pool) + != NGX_OK) + { ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, ngx_errno, ngx_directio_on_n " \"%s\" failed", src->file->name.data); diff --git a/src/http/modules/ngx_http_mp4_module.c b/src/http/modules/ngx_http_mp4_module.c --- a/src/http/modules/ngx_http_mp4_module.c +++ b/src/http/modules/ngx_http_mp4_module.c @@ -622,7 +622,7 @@ ngx_http_mp4_handler(ngx_http_request_t * to allow kernel to cache "moov" atom */ - if (ngx_directio_off(of.fd) == NGX_FILE_ERROR) { + if (ngx_open_file_directio_off(of.fd, r->pool) != NGX_OK) { ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ngx_directio_off_n " \"%s\" failed", path.data); } @@ -677,7 +677,7 @@ ngx_http_mp4_handler(ngx_http_request_t /* DIRECTIO was switched off, restore it */ - if (ngx_directio_on(of.fd) == NGX_FILE_ERROR) { + if (ngx_open_file_directio_on(of.fd, r->pool) != NGX_OK) { ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, ngx_directio_on_n " \"%s\" failed", path.data); } From mdounin at mdounin.ru Wed Jun 4 23:45:29 2025 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 05 Jun 2025 02:45:29 +0300 Subject: [nginx] Configure: fixed nobody/nogroup detection stderr redirec... Message-ID: details: http://freenginx.org/hg/nginx/rev/808c90eb3a8d branches: changeset: 9368:808c90eb3a8d user: Maxim Dounin date: Thu Jun 05 02:37:20 2025 +0300 description: Configure: fixed nobody/nogroup detection stderr redirection. Previously, "grep: /etc/group: No such file or directory" errors were printed on systems without the /etc/group file along with the configure output. Fix is to use correct stderr redirection. diffstat: auto/unix | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diffs (19 lines): diff --git a/auto/unix b/auto/unix --- a/auto/unix +++ b/auto/unix @@ -7,13 +7,13 @@ NGX_USER=${NGX_USER:-nobody} if [ -z "$NGX_GROUP" ]; then if [ $NGX_USER = nobody ]; then - if grep nobody /etc/group 2>&1 >/dev/null; then + if grep nobody /etc/group >/dev/null 2>&1; then echo "checking for nobody group ... found" NGX_GROUP=nobody else echo "checking for nobody group ... not found" - if grep nogroup /etc/group 2>&1 >/dev/null; then + if grep nogroup /etc/group >/dev/null 2>&1; then echo "checking for nogroup group ... found" NGX_GROUP=nogroup else From mdounin at mdounin.ru Wed Jun 4 23:45:29 2025 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 05 Jun 2025 02:45:29 +0300 Subject: [nginx] Configure: added uint8_t and uint16_t detection. Message-ID: details: http://freenginx.org/hg/nginx/rev/863870a96371 branches: changeset: 9369:863870a96371 user: Maxim Dounin date: Thu Jun 05 02:37:23 2025 +0300 description: Configure: added uint8_t and uint16_t detection. This fixes compilation on some old systems (though mostly for consistency with uint32_t and uint64_t). diffstat: auto/unix | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-) diffs (12 lines): diff --git a/auto/unix b/auto/unix --- a/auto/unix +++ b/auto/unix @@ -645,6 +645,8 @@ ngx_param=NGX_PTR_SIZE; ngx_value=$ngx_s NGX_INCLUDE_AUTO_CONFIG_H="#include \"ngx_auto_config.h\"" +ngx_type="uint8_t"; ngx_types="u_int8_t"; . auto/types/typedef +ngx_type="uint16_t"; ngx_types="u_int16_t"; . auto/types/typedef ngx_type="uint32_t"; ngx_types="u_int32_t"; . auto/types/typedef ngx_type="uint64_t"; ngx_types="u_int64_t"; . auto/types/typedef From mdounin at mdounin.ru Wed Jun 4 23:45:29 2025 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 05 Jun 2025 02:45:29 +0300 Subject: [nginx] Cleaned up iov_base, msg_name, msg_control type casts. Message-ID: details: http://freenginx.org/hg/nginx/rev/c106f096c950 branches: changeset: 9370:c106f096c950 user: Maxim Dounin date: Thu Jun 05 02:37:30 2025 +0300 description: Cleaned up iov_base, msg_name, msg_control type casts. In old OSes iov_base, msg_name, and msg_control fields were caddr_t ("char *" in case of iov_base), though later were changed to "void *". In particular, in FreeBSD this change happened around FreeBSD 4.0. With newer "void *" type no type casts are needed, but with caddr_t type casts were required in most cases. Previously, casts were used inconsistently across the code: to caddr_t, to "void *", or no casts at all in some recent code. With this change, casts to "void *" are consistently used in all relevant places. diffstat: src/event/ngx_event_udp.c | 6 +++--- src/event/quic/ngx_event_quic_output.c | 12 ++++++------ src/event/quic/ngx_event_quic_udp.c | 6 +++--- src/os/unix/ngx_channel.c | 8 ++++---- src/os/unix/ngx_udp_sendmsg_chain.c | 4 ++-- 5 files changed, 18 insertions(+), 18 deletions(-) diffs (164 lines): diff --git a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c --- a/src/event/ngx_event_udp.c +++ b/src/event/ngx_event_udp.c @@ -70,14 +70,14 @@ ngx_event_recvmsg(ngx_event_t *ev) iov[0].iov_base = (void *) buffer; iov[0].iov_len = sizeof(buffer); - msg.msg_name = &sa; + msg.msg_name = (void *) &sa; msg.msg_namelen = sizeof(ngx_sockaddr_t); msg.msg_iov = iov; msg.msg_iovlen = 1; #if (NGX_HAVE_ADDRINFO_CMSG) if (ls->wildcard) { - msg.msg_control = &msg_control; + msg.msg_control = (void *) &msg_control; msg.msg_controllen = sizeof(msg_control); ngx_memzero(&msg_control, sizeof(msg_control)); @@ -108,7 +108,7 @@ ngx_event_recvmsg(ngx_event_t *ev) } #endif - sockaddr = msg.msg_name; + sockaddr = (void *) msg.msg_name; socklen = msg.msg_namelen; if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) { diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c --- a/src/event/quic/ngx_event_quic_output.c +++ b/src/event/quic/ngx_event_quic_output.c @@ -410,16 +410,16 @@ ngx_quic_send_segments(ngx_connection_t ngx_memzero(&msg, sizeof(struct msghdr)); ngx_memzero(msg_control, sizeof(msg_control)); + iov.iov_base = (void *) buf; iov.iov_len = len; - iov.iov_base = buf; msg.msg_iov = &iov; msg.msg_iovlen = 1; - msg.msg_name = sockaddr; + msg.msg_name = (void *) sockaddr; msg.msg_namelen = socklen; - msg.msg_control = msg_control; + msg.msg_control = (void *) msg_control; msg.msg_controllen = sizeof(msg_control); cmsg = CMSG_FIRSTHDR(&msg); @@ -698,19 +698,19 @@ ngx_quic_send(ngx_connection_t *c, u_cha ngx_memzero(&msg, sizeof(struct msghdr)); + iov.iov_base = (void *) buf; iov.iov_len = len; - iov.iov_base = buf; msg.msg_iov = &iov; msg.msg_iovlen = 1; - msg.msg_name = sockaddr; + msg.msg_name = (void *) sockaddr; msg.msg_namelen = socklen; #if (NGX_HAVE_ADDRINFO_CMSG) if (c->listening && c->listening->wildcard && c->local_sockaddr) { - msg.msg_control = msg_control; + msg.msg_control = (void *) msg_control; msg.msg_controllen = sizeof(msg_control); ngx_memzero(msg_control, sizeof(msg_control)); diff --git a/src/event/quic/ngx_event_quic_udp.c b/src/event/quic/ngx_event_quic_udp.c --- a/src/event/quic/ngx_event_quic_udp.c +++ b/src/event/quic/ngx_event_quic_udp.c @@ -68,14 +68,14 @@ ngx_quic_recvmsg(ngx_event_t *ev) iov[0].iov_base = (void *) buffer; iov[0].iov_len = sizeof(buffer); - msg.msg_name = &sa; + msg.msg_name = (void *) &sa; msg.msg_namelen = sizeof(ngx_sockaddr_t); msg.msg_iov = iov; msg.msg_iovlen = 1; #if (NGX_HAVE_ADDRINFO_CMSG) if (ls->wildcard) { - msg.msg_control = &msg_control; + msg.msg_control = (void *) &msg_control; msg.msg_controllen = sizeof(msg_control); ngx_memzero(&msg_control, sizeof(msg_control)); @@ -106,7 +106,7 @@ ngx_quic_recvmsg(ngx_event_t *ev) } #endif - sockaddr = msg.msg_name; + sockaddr = (void *) msg.msg_name; socklen = msg.msg_namelen; if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) { diff --git a/src/os/unix/ngx_channel.c b/src/os/unix/ngx_channel.c --- a/src/os/unix/ngx_channel.c +++ b/src/os/unix/ngx_channel.c @@ -31,7 +31,7 @@ ngx_write_channel(ngx_socket_t s, ngx_ch msg.msg_controllen = 0; } else { - msg.msg_control = (caddr_t) &cmsg; + msg.msg_control = (void *) &cmsg; msg.msg_controllen = sizeof(cmsg); ngx_memzero(&cmsg, sizeof(cmsg)); @@ -68,7 +68,7 @@ ngx_write_channel(ngx_socket_t s, ngx_ch #endif - iov[0].iov_base = (char *) ch; + iov[0].iov_base = (void *) ch; iov[0].iov_len = size; msg.msg_name = NULL; @@ -109,7 +109,7 @@ ngx_read_channel(ngx_socket_t s, ngx_cha int fd; #endif - iov[0].iov_base = (char *) ch; + iov[0].iov_base = (void *) ch; iov[0].iov_len = size; msg.msg_name = NULL; @@ -118,7 +118,7 @@ ngx_read_channel(ngx_socket_t s, ngx_cha msg.msg_iovlen = 1; #if (NGX_HAVE_MSGHDR_MSG_CONTROL) - msg.msg_control = (caddr_t) &cmsg; + msg.msg_control = (void *) &cmsg; msg.msg_controllen = sizeof(cmsg); #else msg.msg_accrights = (caddr_t) &fd; diff --git a/src/os/unix/ngx_udp_sendmsg_chain.c b/src/os/unix/ngx_udp_sendmsg_chain.c --- a/src/os/unix/ngx_udp_sendmsg_chain.c +++ b/src/os/unix/ngx_udp_sendmsg_chain.c @@ -216,7 +216,7 @@ ngx_sendmsg_vec(ngx_connection_t *c, ngx ngx_memzero(&msg, sizeof(struct msghdr)); if (c->socklen) { - msg.msg_name = c->sockaddr; + msg.msg_name = (void *) c->sockaddr; msg.msg_namelen = c->socklen; } @@ -226,7 +226,7 @@ ngx_sendmsg_vec(ngx_connection_t *c, ngx #if (NGX_HAVE_ADDRINFO_CMSG) if (c->listening && c->listening->wildcard && c->local_sockaddr) { - msg.msg_control = msg_control; + msg.msg_control = (void *) msg_control; msg.msg_controllen = sizeof(msg_control); ngx_memzero(msg_control, sizeof(msg_control)); From mdounin at mdounin.ru Wed Jun 4 23:45:29 2025 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 05 Jun 2025 02:45:29 +0300 Subject: [nginx] Adjusted mmap() type casts. Message-ID: details: http://freenginx.org/hg/nginx/rev/fc202c264ad7 branches: changeset: 9371:fc202c264ad7 user: Maxim Dounin date: Thu Jun 05 02:38:21 2025 +0300 description: Adjusted mmap() type casts. Type casting mmap() result explicitly to (u_char *) is only needed on systems where mmap() returns caddr_t, but on these systems MAP_FAILED also needs to be casted. Added appropriate type casts. diffstat: src/os/unix/ngx_shmem.c | 6 +++--- 1 files changed, 3 insertions(+), 3 deletions(-) diffs (30 lines): diff --git a/src/os/unix/ngx_shmem.c b/src/os/unix/ngx_shmem.c --- a/src/os/unix/ngx_shmem.c +++ b/src/os/unix/ngx_shmem.c @@ -18,7 +18,7 @@ ngx_shm_alloc(ngx_shm_t *shm) PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0); - if (shm->addr == MAP_FAILED) { + if (shm->addr == (u_char *) MAP_FAILED) { ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "mmap(MAP_ANON|MAP_SHARED, %uz) failed", shm->size); return NGX_ERROR; @@ -55,7 +55,7 @@ ngx_shm_alloc(ngx_shm_t *shm) shm->addr = (u_char *) mmap(NULL, shm->size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); - if (shm->addr == MAP_FAILED) { + if (shm->addr == (u_char *) MAP_FAILED) { ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "mmap(/dev/zero, MAP_SHARED, %uz) failed", shm->size); } @@ -65,7 +65,7 @@ ngx_shm_alloc(ngx_shm_t *shm) "close(\"/dev/zero\") failed"); } - return (shm->addr == MAP_FAILED) ? NGX_ERROR : NGX_OK; + return (shm->addr == (u_char *) MAP_FAILED) ? NGX_ERROR : NGX_OK; } From mdounin at mdounin.ru Wed Jun 4 23:45:29 2025 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 05 Jun 2025 02:45:29 +0300 Subject: [nginx] Fixed unused variable in ngx_set_srcaddr_cmsg() on MINIX. Message-ID: details: http://freenginx.org/hg/nginx/rev/05b5d8c4cfd9 branches: changeset: 9372:05b5d8c4cfd9 user: Maxim Dounin date: Thu Jun 05 02:38:31 2025 +0300 description: Fixed unused variable in ngx_set_srcaddr_cmsg() on MINIX. diffstat: src/os/unix/ngx_udp_sendmsg_chain.c | 3 +++ 1 files changed, 3 insertions(+), 0 deletions(-) diffs (14 lines): diff --git a/src/os/unix/ngx_udp_sendmsg_chain.c b/src/os/unix/ngx_udp_sendmsg_chain.c --- a/src/os/unix/ngx_udp_sendmsg_chain.c +++ b/src/os/unix/ngx_udp_sendmsg_chain.c @@ -245,7 +245,10 @@ ngx_sendmsg_vec(ngx_connection_t *c, ngx size_t ngx_set_srcaddr_cmsg(struct cmsghdr *cmsg, struct sockaddr *local_sockaddr) { +#if (NGX_HAVE_IP_SENDSRCADDR || NGX_HAVE_IP_PKTINFO \ + || (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO)) size_t len; +#endif #if (NGX_HAVE_IP_SENDSRCADDR) struct in_addr *addr; struct sockaddr_in *sin; From mdounin at mdounin.ru Wed Jun 4 23:45:29 2025 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 05 Jun 2025 02:45:29 +0300 Subject: [nginx] Compatibility with systems without SA_SIGINFO in sigacti... Message-ID: details: http://freenginx.org/hg/nginx/rev/9715dd369bf2 branches: changeset: 9373:9715dd369bf2 user: Maxim Dounin date: Thu Jun 05 02:38:40 2025 +0300 description: Compatibility with systems without SA_SIGINFO in sigaction(). This change restores compatibility with some older systems without SA_SIGINFO support, broken by 6985:23ecffd5bcfe. Notably, this fixes compilation on MINIX. diffstat: auto/unix | 13 +++++++++++++ src/os/unix/ngx_process.c | 22 +++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletions(-) diffs (89 lines): diff --git a/auto/unix b/auto/unix --- a/auto/unix +++ b/auto/unix @@ -1043,3 +1043,16 @@ ngx_feature_test='struct addrinfo *res; if (getaddrinfo("localhost", NULL, NULL, &res) != 0) return 1; freeaddrinfo(res)' . auto/feature + + +ngx_feature="sigaction(SA_SIGINFO)" +ngx_feature_name="NGX_HAVE_SIGINFO" +ngx_feature_run=no +ngx_feature_incs="#include " +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="struct sigaction sa; + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = NULL; + if (sigaction(0, &sa, NULL) == -1) return 1;" +. auto/feature diff --git a/src/os/unix/ngx_process.c b/src/os/unix/ngx_process.c --- a/src/os/unix/ngx_process.c +++ b/src/os/unix/ngx_process.c @@ -15,13 +15,21 @@ typedef struct { int signo; char *signame; char *name; +#if (NGX_HAVE_SIGINFO) void (*handler)(int signo, siginfo_t *siginfo, void *ucontext); +#else + void (*handler)(int signo); +#endif } ngx_signal_t; static void ngx_execute_proc(ngx_cycle_t *cycle, void *data); +#if (NGX_HAVE_SIGINFO) static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext); +#else +static void ngx_signal_handler(int signo); +#endif static void ngx_process_get_status(void); static void ngx_unlock_mutexes(ngx_pid_t pid); @@ -291,8 +299,12 @@ ngx_init_signals(ngx_log_t *log) ngx_memzero(&sa, sizeof(struct sigaction)); if (sig->handler) { +#if (NGX_HAVE_SIGINFO) sa.sa_sigaction = sig->handler; sa.sa_flags = SA_SIGINFO; +#else + sa.sa_handler = sig->handler; +#endif } else { sa.sa_handler = SIG_IGN; @@ -315,8 +327,13 @@ ngx_init_signals(ngx_log_t *log) } +#if (NGX_HAVE_SIGINFO) static void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext) +#else +static void +ngx_signal_handler(int signo) +#endif { char *action; ngx_int_t ignore; @@ -441,12 +458,15 @@ ngx_signal_handler(int signo, siginfo_t break; } +#if (NGX_HAVE_SIGINFO) if (siginfo && siginfo->si_pid) { ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0, "signal %d (%s) received from %P%s", signo, sig->signame, siginfo->si_pid, action); - } else { + } else +#endif + { ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0, "signal %d (%s) received%s", signo, sig->signame, action); From mdounin at mdounin.ru Thu Jun 5 00:35:57 2025 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 05 Jun 2025 03:35:57 +0300 Subject: [nginx] Win32: fixed 64-bit compilation with MSVC after 9333:199... Message-ID: details: http://freenginx.org/hg/nginx/rev/ccd6209f65ca branches: changeset: 9374:ccd6209f65ca user: Maxim Dounin date: Thu Jun 05 02:52:07 2025 +0300 description: Win32: fixed 64-bit compilation with MSVC after 9333:1996ea0bc55d. Compatibility with zlib in LIT_MEM mode added in 9333:1996ea0bc55d triggered MSVC warning C4334 (result of 32-bit shift implicitly converted to 64 bits), fixed by adding explicit conversion. diffstat: src/http/modules/ngx_http_gzip_filter_module.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff --git a/src/http/modules/ngx_http_gzip_filter_module.c b/src/http/modules/ngx_http_gzip_filter_module.c --- a/src/http/modules/ngx_http_gzip_filter_module.c +++ b/src/http/modules/ngx_http_gzip_filter_module.c @@ -520,7 +520,7 @@ ngx_http_gzip_filter_memory(ngx_http_req * buffer for literals/lengths. */ - ctx->allocated += (1 << (memlevel + 6)); + ctx->allocated += ((ngx_uint_t) 1 << (memlevel + 6)); ctx->zlib_lit_mem = 1; } From mdounin at mdounin.ru Thu Jun 5 00:35:57 2025 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 05 Jun 2025 03:35:57 +0300 Subject: [nginx] Win32: cleaned up most of C4244 warnings with 32-bit com... Message-ID: details: http://freenginx.org/hg/nginx/rev/1c595b208741 branches: changeset: 9375:1c595b208741 user: Maxim Dounin date: Thu Jun 05 02:52:15 2025 +0300 description: Win32: cleaned up most of C4244 warnings with 32-bit compilation. C4244 warnings (conversion from 'type1' to 'type2', possible loss of data) were disabled in 6861:e4590dfd97ff as part of introducing support for 64-bit compilation with MSVC. Still, these warnings remain useful in some cases. Additionally, they explain multiple otherwise unneeded type casts in the code. This change resolves most of the warnings as observed during 32-bit compilation with C4244 warnings enabled by introducing additional type casts, in most cases already used in other parts of the code in similar constructs. diffstat: src/core/ngx_inet.c | 2 +- src/core/ngx_times.c | 2 +- src/http/ngx_http_file_cache.c | 5 +++-- src/http/ngx_http_request_body.c | 3 ++- 4 files changed, 7 insertions(+), 5 deletions(-) diffs (59 lines): diff --git a/src/core/ngx_inet.c b/src/core/ngx_inet.c --- a/src/core/ngx_inet.c +++ b/src/core/ngx_inet.c @@ -1286,7 +1286,7 @@ ngx_inet_add_addr(ngx_pool_t *pool, ngx_ ngx_memcpy(sa, sockaddr, socklen); - ngx_inet_set_port(sa, u->port + i); + ngx_inet_set_port(sa, u->port + (in_port_t) i); switch (sa->sa_family) { diff --git a/src/core/ngx_times.c b/src/core/ngx_times.c --- a/src/core/ngx_times.c +++ b/src/core/ngx_times.c @@ -332,7 +332,7 @@ ngx_gmtime(time_t t, ngx_tm_t *tp) t = 0; } - days = t / 86400; + days = (ngx_uint_t) t / 86400; sec = t % 86400; /* diff --git a/src/http/ngx_http_file_cache.c b/src/http/ngx_http_file_cache.c --- a/src/http/ngx_http_file_cache.c +++ b/src/http/ngx_http_file_cache.c @@ -387,7 +387,8 @@ ngx_http_file_cache_open(ngx_http_reques if (of.is_directio) { - c->body_start = ngx_align(c->body_start, clcf->directio_alignment); + c->body_start = ngx_align(c->body_start, + (size_t) clcf->directio_alignment); c->buf = ngx_calloc_buf(r->pool); if (c->buf == NULL) { @@ -395,7 +396,7 @@ ngx_http_file_cache_open(ngx_http_reques } c->buf->start = ngx_pmemalign(r->pool, c->body_start, - clcf->directio_alignment); + (size_t) clcf->directio_alignment); if (c->buf->start == NULL) { return NGX_ERROR; } diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c --- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -1220,7 +1220,8 @@ ngx_http_request_body_chunked_filter(ngx } } else { - ngx_memmove(b->last, cl->buf->pos, rb->chunked->size); + ngx_memmove(b->last, cl->buf->pos, + (size_t) rb->chunked->size); b->last += rb->chunked->size; cl->buf->pos += rb->chunked->size; rb->chunked->size = 0; From mdounin at mdounin.ru Thu Jun 5 00:35:57 2025 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 05 Jun 2025 03:35:57 +0300 Subject: [nginx] Postpone filter: fixed incorrect content length check. Message-ID: details: http://freenginx.org/hg/nginx/rev/f5928c2e47c5 branches: changeset: 9376:f5928c2e47c5 user: Maxim Dounin date: Thu Jun 05 02:52:31 2025 +0300 description: Postpone filter: fixed incorrect content length check. The code in ngx_http_postpone_filter_in_memory() used to assign r->headers_out.content_length_n to a size_t variable before comparison, which can lead to incorrect results on 32-bit platforms. Fix is to compare r->headers_out.content_length_n before conversion to size_t. Found with MSVC with C4244 warnings (conversion from 'type1' to 'type2', possible loss of data) enabled. diffstat: src/http/ngx_http_postpone_filter_module.c | 10 +++++++--- 1 files changed, 7 insertions(+), 3 deletions(-) diffs (25 lines): diff --git a/src/http/ngx_http_postpone_filter_module.c b/src/http/ngx_http_postpone_filter_module.c --- a/src/http/ngx_http_postpone_filter_module.c +++ b/src/http/ngx_http_postpone_filter_module.c @@ -194,14 +194,18 @@ ngx_http_postpone_filter_in_memory(ngx_h clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (r->headers_out.content_length_n != -1) { - len = r->headers_out.content_length_n; - if (len > clcf->subrequest_output_buffer_size) { + if (r->headers_out.content_length_n + > (off_t) clcf->subrequest_output_buffer_size) + { ngx_log_error(NGX_LOG_ERR, c->log, 0, - "too big subrequest response: %uz", len); + "too big subrequest response: %O", + r->headers_out.content_length_n); return NGX_ERROR; } + len = (size_t) r->headers_out.content_length_n; + } else { len = clcf->subrequest_output_buffer_size; } From mdounin at mdounin.ru Thu Jun 5 00:35:57 2025 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 05 Jun 2025 03:35:57 +0300 Subject: [nginx] SSL: usage of SSL_SESSION_get_time_ex() with OpenSSL 3.3+. Message-ID: details: http://freenginx.org/hg/nginx/rev/ab7fedd48bfe branches: changeset: 9377:ab7fedd48bfe user: Maxim Dounin date: Thu Jun 05 02:52:54 2025 +0300 description: SSL: usage of SSL_SESSION_get_time_ex() with OpenSSL 3.3+. In OpenSSL, SSL_SESSION_get_time() and SSL_SESSION_set_time() functions use "long" to store seconds since the Epoch, which makes these functions problematic after Y2038 on 32-bit platforms, and, more importantly, on 64-bit platforms with 32-bit long (notably Windows). Note that there is no such problem in BoringSSL, which uses uint64_t instead of "long". LibreSSL also uses "long", but it does not support TLSv1.3 session resumption anyway, hence this is not an issue. Fix is to use SSL_SESSION_get_time_ex() and SSL_SESSION_set_time_ex() functions introduced in OpenSSL 3.3 when these are available. Prodded by MSVC with C4244 warnings (conversion from 'type1' to 'type2', possible loss of data) enabled. diffstat: src/event/ngx_event_openssl.c | 2 +- src/event/ngx_event_openssl.h | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletions(-) diffs (28 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 @@ -1190,7 +1190,7 @@ ngx_ssl_info_callback(const ngx_ssl_conn } else { SSL_SESSION_set_time(sess, now); - SSL_SESSION_set_timeout(sess, timeout - (now - time)); + SSL_SESSION_set_timeout(sess, (long) (timeout - (now - time))); } } } diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -83,6 +83,12 @@ #endif +#if (OPENSSL_VERSION_NUMBER > 0x30300000L) +#define SSL_SESSION_get_time(s) SSL_SESSION_get_time_ex(s) +#define SSL_SESSION_set_time(s, t) SSL_SESSION_set_time_ex(s, t) +#endif + + typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; From nytosene at gmail.com Thu Jun 5 17:15:43 2025 From: nytosene at gmail.com (=?UTF-8?Q?Antoine_S=C3=A9ne?=) Date: Thu, 5 Jun 2025 17:15:43 +0000 Subject: [nginx] Postpone filter: fixed incorrect content length check. In-Reply-To: References: Message-ID: Hello, can you please send me the unsubscribe to the mailing list link? I want to connect with my other email Thank you On Thu, Jun 5, 2025 at 12:35?AM Maxim Dounin wrote: > details: http://freenginx.org/hg/nginx/rev/f5928c2e47c5 > branches: > changeset: 9376:f5928c2e47c5 > user: Maxim Dounin > date: Thu Jun 05 02:52:31 2025 +0300 > description: > Postpone filter: fixed incorrect content length check. > > The code in ngx_http_postpone_filter_in_memory() used to assign > r->headers_out.content_length_n to a size_t variable before comparison, > which can lead to incorrect results on 32-bit platforms. > > Fix is to compare r->headers_out.content_length_n before conversion > to size_t. > > Found with MSVC with C4244 warnings (conversion from 'type1' to 'type2', > possible loss of data) enabled. > > diffstat: > > src/http/ngx_http_postpone_filter_module.c | 10 +++++++--- > 1 files changed, 7 insertions(+), 3 deletions(-) > > diffs (25 lines): > > diff --git a/src/http/ngx_http_postpone_filter_module.c > b/src/http/ngx_http_postpone_filter_module.c > --- a/src/http/ngx_http_postpone_filter_module.c > +++ b/src/http/ngx_http_postpone_filter_module.c > @@ -194,14 +194,18 @@ ngx_http_postpone_filter_in_memory(ngx_h > clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); > > if (r->headers_out.content_length_n != -1) { > - len = r->headers_out.content_length_n; > > - if (len > clcf->subrequest_output_buffer_size) { > + if (r->headers_out.content_length_n > + > (off_t) clcf->subrequest_output_buffer_size) > + { > ngx_log_error(NGX_LOG_ERR, c->log, 0, > - "too big subrequest response: %uz", len); > + "too big subrequest response: %O", > + r->headers_out.content_length_n); > return NGX_ERROR; > } > > + len = (size_t) r->headers_out.content_length_n; > + > } else { > len = clcf->subrequest_output_buffer_size; > } > -------------- next part -------------- An HTML attachment was scrubbed... URL: From mdounin at mdounin.ru Fri Jun 6 00:58:06 2025 From: mdounin at mdounin.ru (Maxim Dounin) Date: Fri, 6 Jun 2025 03:58:06 +0300 Subject: [nginx] Postpone filter: fixed incorrect content length check. In-Reply-To: References: Message-ID: Hello! On Thu, Jun 05, 2025 at 05:15:43PM +0000, Antoine S?ne wrote: > Hello, can you please send me the unsubscribe to the mailing list link? I > want to connect with my other email Relevant links are in the corresponding headers of all messages sent by the list: List-Unsubscribe: , It is also linked from the site, here: https://freenginx.org/en/support.html Hope that helps. -- Maxim Dounin http://mdounin.ru/ From mdounin at mdounin.ru Wed Jun 18 12:37:32 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 18 Jun 2025 15:37:32 +0300 Subject: [PATCH 0 of 7] limit_rate improvements Message-ID: Hello! The following patch series implements various limit_rate and related improvements, notably: 1. The limit_rate directive was modified to use leaky bucket algorithm, with limit_rate_after being burst size limit. Such approach ensures more precise limiting and better handling of complex traffic patterns. 2. The limit_rate and limit_rate_after directives are now supported in the mail proxy module. 3. The send_min_rate and client_body_min_rate directives, which configure minimum transfer rate which needs to be maintained by the client. Review and testing appreciated. --- Maxim Dounin From mdounin at mdounin.ru Wed Jun 18 12:37:33 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 18 Jun 2025 15:37:33 +0300 Subject: [PATCH 1 of 7] Changed limit_rate algorithm to leaky bucket In-Reply-To: References: Message-ID: # HG changeset patch # User Maxim Dounin # Date 1750204960 -10800 # Wed Jun 18 03:02:40 2025 +0300 # Node ID a63efb0d2b7576f003e5c97d18a4eccbe0600e50 # Parent ab7fedd48bfed512180a8f9c3ea39a0624a9e64c Changed limit_rate algorithm to leaky bucket. The bucket size (burst size) is set to the size expected to be sent in 1 second (that is, the rate configured), so the first operation will be able to send corresponding number of bytes, matching the previous behaviour. To ensure that inaccurate timers won't affect rate limiting accuracy, delays are set to trigger next sending when the bucket is half-empty. The limit_rate_after directive makes the bucket size larger, allowing for larger traffic bursts (it won't affect delays though). This is mostly equivalent to the previous behaviour in simple cases, like downloading a static file by a fast client, but expected to work better in complex cases, such as when actual response traffic might stop for a while. Similar changes are made to proxy_limit_rate (and friends) in the http module, as well as proxy_upload_rate and proxy_download_rate in the stream module. Immediate benefits include more accurate limiting, notably at rates which resulted in 1 or 2 millisecond delays after sending (and very inaccurate limiting) with the old algorithm. Further, there will be less unneeded delays, notably no delays at all in most cases as long as the client is not using allowed bandwidth. Note that due to the new algorithm there are minor changes in the limiting, mostly visible with very small limits: typically 1.5x of the limit will be sent in the first second (one immediately, and half after 0.5 seconds delay), and additional data will be sent once per 0.5 seconds (instead of once per second previously). Some tests needs to be adapted for this. Note that r->limit_last is not initialized, and will be set to an actual value only after first sending. As a result, "ms" can be large during initial sending, and to ensure that there will be no signed integer overflows, calculations of "excess" are performed in (uint64_t) (and the result is type casted into (off_t) to clarify that the code intentionally relies on implementation-defined behaviour, similarly to how we handle calculations of "ms"). diff --git a/src/event/ngx_event_pipe.c b/src/event/ngx_event_pipe.c --- a/src/event/ngx_event_pipe.c +++ b/src/event/ngx_event_pipe.c @@ -108,12 +108,13 @@ ngx_event_pipe(ngx_event_pipe_t *p, ngx_ static ngx_int_t ngx_event_pipe_read_upstream(ngx_event_pipe_t *p) { - off_t limit; - ssize_t n, size; - ngx_int_t rc; - ngx_buf_t *b; - ngx_msec_t delay; - ngx_chain_t *chain, *cl, *ln; + off_t limit, excess; + ssize_t n, size, sent; + ngx_int_t rc; + ngx_buf_t *b; + ngx_msec_t delay; + ngx_chain_t *chain, *cl, *ln; + ngx_msec_int_t ms; if (p->upstream_eof || p->upstream_error || p->upstream_done || p->upstream == NULL) @@ -145,6 +146,8 @@ ngx_event_pipe_read_upstream(ngx_event_p ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0, "pipe read upstream: %d", p->upstream->read->ready); + excess = 0; + for ( ;; ) { if (p->upstream_eof || p->upstream_error || p->upstream_done) { @@ -212,12 +215,19 @@ ngx_event_pipe_read_upstream(ngx_event_p break; } - limit = (off_t) p->limit_rate * (ngx_time() - p->start_sec + 1) - - p->read_length; + ms = (ngx_msec_int_t) (ngx_current_msec - p->limit_last); + ms = ngx_max(ms, 0); + + excess = (off_t) (p->limit_excess + - (uint64_t) p->limit_rate * ms / 1000); + excess = ngx_max(excess, 0); + + limit = (off_t) p->limit_rate - excess; if (limit <= 0) { p->upstream->read->delayed = 1; - delay = (ngx_msec_t) (- limit * 1000 / p->limit_rate + 1); + excess -= (off_t) p->limit_rate / 2; + delay = (ngx_msec_t) (excess * 1000 / p->limit_rate + 1); ngx_add_timer(p->upstream->read, delay); break; } @@ -345,8 +355,7 @@ ngx_event_pipe_read_upstream(ngx_event_p } } - delay = p->limit_rate ? (ngx_msec_t) n * 1000 / p->limit_rate : 0; - + sent = n; p->read_length += n; cl = chain; p->free_raw_bufs = NULL; @@ -384,10 +393,22 @@ ngx_event_pipe_read_upstream(ngx_event_p p->free_raw_bufs = cl; } - if (delay > 0) { - p->upstream->read->delayed = 1; - ngx_add_timer(p->upstream->read, delay); - break; + if (p->limit_rate) { + excess += sent; + + p->limit_last = ngx_current_msec; + p->limit_excess = excess; + + excess -= (off_t) p->limit_rate / 2; + excess = ngx_max(excess, 0); + + delay = (ngx_msec_t) (excess * 1000 / p->limit_rate); + + if (delay > 0) { + p->upstream->read->delayed = 1; + ngx_add_timer(p->upstream->read, delay); + break; + } } } diff --git a/src/event/ngx_event_pipe.h b/src/event/ngx_event_pipe.h --- a/src/event/ngx_event_pipe.h +++ b/src/event/ngx_event_pipe.h @@ -91,7 +91,8 @@ struct ngx_event_pipe_s { ngx_buf_t *buf_to_file; size_t limit_rate; - time_t start_sec; + ngx_msec_t limit_last; + off_t limit_excess; ngx_temp_file_t *temp_file; diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -448,6 +448,9 @@ struct ngx_http_request_s { size_t limit_rate; size_t limit_rate_after; + ngx_msec_t limit_last; + off_t limit_excess; + /* used to learn the Apache compatible response length without a header */ size_t header_size; 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 @@ -3271,7 +3271,6 @@ ngx_http_upstream_send_response(ngx_http p->pool = r->pool; p->log = c->log; p->limit_rate = u->conf->limit_rate; - p->start_sec = ngx_time(); p->cacheable = u->cacheable || u->store; diff --git a/src/http/ngx_http_write_filter_module.c b/src/http/ngx_http_write_filter_module.c --- a/src/http/ngx_http_write_filter_module.c +++ b/src/http/ngx_http_write_filter_module.c @@ -47,10 +47,11 @@ ngx_module_t ngx_http_write_filter_modu ngx_int_t ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in) { - off_t size, sent, nsent, limit; + off_t size, sent, excess, limit; ngx_uint_t last, flush, sync; ngx_msec_t delay; ngx_chain_t *cl, *ln, **ll, *chain; + ngx_msec_int_t ms; ngx_connection_t *c; ngx_http_core_loc_conf_t *clcf; @@ -66,6 +67,10 @@ ngx_http_write_filter(ngx_http_request_t last = 0; ll = &r->out; +#if (NGX_SUPPRESS_WARN) + excess = 0; +#endif + /* find the size, the flush point and the last link of the saved chain */ for (cl = r->out; cl; cl = cl->next) { @@ -268,12 +273,19 @@ ngx_http_write_filter(ngx_http_request_t r->limit_rate_after_set = 1; } - limit = (off_t) r->limit_rate * (ngx_time() - r->start_sec + 1) - - (c->sent - r->limit_rate_after); + ms = (ngx_msec_int_t) (ngx_current_msec - r->limit_last); + ms = ngx_max(ms, 0); + + excess = (off_t) (r->limit_excess + - (uint64_t) r->limit_rate * ms / 1000); + excess = ngx_max(excess, 0); + + limit = (off_t) r->limit_rate + (off_t) r->limit_rate_after - excess; if (limit <= 0) { c->write->delayed = 1; - delay = (ngx_msec_t) (- limit * 1000 / r->limit_rate + 1); + excess -= (off_t) r->limit_rate_after + (off_t) r->limit_rate / 2; + delay = (ngx_msec_t) (excess * 1000 / r->limit_rate + 1); ngx_add_timer(c->write, delay); c->buffered |= NGX_HTTP_WRITE_BUFFERED; @@ -308,22 +320,15 @@ ngx_http_write_filter(ngx_http_request_t if (r->limit_rate) { - nsent = c->sent; + excess += (c->sent - sent); - if (r->limit_rate_after) { + r->limit_last = ngx_current_msec; + r->limit_excess = excess; - sent -= r->limit_rate_after; - if (sent < 0) { - sent = 0; - } + excess -= (off_t) r->limit_rate_after + (off_t) r->limit_rate / 2; + excess = ngx_max(excess, 0); - nsent -= r->limit_rate_after; - if (nsent < 0) { - nsent = 0; - } - } - - delay = (ngx_msec_t) ((nsent - sent) * 1000 / r->limit_rate); + delay = (ngx_msec_t) (excess * 1000 / r->limit_rate); if (delay > 0) { c->write->delayed = 1; diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c --- a/src/stream/ngx_stream_proxy_module.c +++ b/src/stream/ngx_stream_proxy_module.c @@ -435,7 +435,6 @@ ngx_stream_proxy_handler(ngx_stream_sess } u->peer.type = c->type; - u->start_sec = ngx_time(); c->write->handler = ngx_stream_proxy_downstream_handler; c->read->handler = ngx_stream_proxy_downstream_handler; @@ -924,6 +923,9 @@ ngx_stream_proxy_init_upstream(ngx_strea u->upload_rate = ngx_stream_complex_value_size(s, pscf->upload_rate, 0); u->download_rate = ngx_stream_complex_value_size(s, pscf->download_rate, 0); + u->upload_last = ngx_current_msec; + u->upload_excess = s->received; + u->connected = 1; pc->read->handler = ngx_stream_proxy_upstream_handler; @@ -1555,14 +1557,15 @@ ngx_stream_proxy_process(ngx_stream_sess ngx_uint_t do_write) { char *recv_action, *send_action; - off_t *received, limit, sent; + off_t *received, *limit_excess, limit, excess, sent; size_t size, limit_rate; ssize_t n; ngx_buf_t *b; ngx_int_t rc; ngx_uint_t flags, *packets; - ngx_msec_t delay; + ngx_msec_t *limit_last, delay; ngx_chain_t *cl, **ll, **out, **busy; + ngx_msec_int_t ms; ngx_connection_t *c, *pc, *src, *dst; ngx_log_handler_pt handler; ngx_stream_upstream_t *u; @@ -1595,6 +1598,8 @@ ngx_stream_proxy_process(ngx_stream_sess dst = c; b = &u->upstream_buf; limit_rate = u->download_rate; + limit_last = &u->download_last; + limit_excess = &u->download_excess; received = &u->received; packets = &u->responses; out = &u->downstream_out; @@ -1607,6 +1612,8 @@ ngx_stream_proxy_process(ngx_stream_sess dst = pc; b = &u->downstream_buf; limit_rate = u->upload_rate; + limit_last = &u->upload_last; + limit_excess = &u->upload_excess; received = &s->received; packets = &u->requests; out = &u->upstream_out; @@ -1616,6 +1623,7 @@ ngx_stream_proxy_process(ngx_stream_sess } #if (NGX_SUPPRESS_WARN) + excess = 0; sent = 0; #endif @@ -1652,12 +1660,19 @@ ngx_stream_proxy_process(ngx_stream_sess if (size && src->read->ready && !src->read->delayed) { if (limit_rate) { - limit = (off_t) limit_rate * (ngx_time() - u->start_sec + 1) - - *received; + ms = (ngx_msec_int_t) (ngx_current_msec - *limit_last); + ms = ngx_max(ms, 0); + + excess = (off_t) (*limit_excess + - (uint64_t) limit_rate * ms / 1000); + excess = ngx_max(excess, 0); + + limit = (off_t) limit_rate - excess; if (limit <= 0) { src->read->delayed = 1; - delay = (ngx_msec_t) (- limit * 1000 / limit_rate + 1); + excess -= (off_t) limit_rate / 2; + delay = (ngx_msec_t) (excess * 1000 / limit_rate + 1); ngx_add_timer(src->read, delay); break; } @@ -1682,7 +1697,15 @@ ngx_stream_proxy_process(ngx_stream_sess if (n >= 0) { if (limit_rate) { - delay = (ngx_msec_t) (n * 1000 / limit_rate); + excess += n; + + *limit_last = ngx_current_msec; + *limit_excess = excess; + + excess -= (off_t) limit_rate / 2; + excess = ngx_max(excess, 0); + + delay = (ngx_msec_t) (excess * 1000 / limit_rate); if (delay > 0) { src->read->delayed = 1; diff --git a/src/stream/ngx_stream_upstream.h b/src/stream/ngx_stream_upstream.h --- a/src/stream/ngx_stream_upstream.h +++ b/src/stream/ngx_stream_upstream.h @@ -127,7 +127,6 @@ typedef struct { ngx_chain_t *downstream_busy; off_t received; - time_t start_sec; ngx_uint_t requests; ngx_uint_t responses; ngx_msec_t start_time; @@ -135,6 +134,11 @@ typedef struct { size_t upload_rate; size_t download_rate; + ngx_msec_t upload_last; + ngx_msec_t download_last; + off_t upload_excess; + off_t download_excess; + ngx_str_t ssl_name; ngx_stream_upstream_srv_conf_t *upstream; From mdounin at mdounin.ru Wed Jun 18 12:37:34 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 18 Jun 2025 15:37:34 +0300 Subject: [PATCH 2 of 7] Switched to ngx_current_msec for $request_time tracking In-Reply-To: References: Message-ID: <1a6f707ab976e825a00d.1750250254@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1750204964 -10800 # Wed Jun 18 03:02:44 2025 +0300 # Node ID 1a6f707ab976e825a00d32d8eaba6fb6402fd7a8 # Parent a63efb0d2b7576f003e5c97d18a4eccbe0600e50 Switched to ngx_current_msec for $request_time tracking. This ensures that $request_time (and $session_time in stream) is not affected by system time changes on platforms with monotonic time available. Also, it is now tracked similarly to $upstream_response_time, and there should be no unexpected differences as previously observed with clock_gettime(CLOCK_MONOTONIC_COARSE) (see 7939:9e7de0547f09). diff --git a/src/http/modules/ngx_http_log_module.c b/src/http/modules/ngx_http_log_module.c --- a/src/http/modules/ngx_http_log_module.c +++ b/src/http/modules/ngx_http_log_module.c @@ -825,13 +825,9 @@ static u_char * ngx_http_log_request_time(ngx_http_request_t *r, u_char *buf, ngx_http_log_op_t *op) { - ngx_time_t *tp; - ngx_msec_int_t ms; + ngx_msec_int_t ms; - tp = ngx_timeofday(); - - ms = (ngx_msec_int_t) - ((tp->sec - r->start_sec) * 1000 + (tp->msec - r->start_msec)); + ms = (ngx_msec_int_t) (ngx_current_msec - r->start_time); ms = ngx_max(ms, 0); return ngx_sprintf(buf, "%T.%03M", (time_t) ms / 1000, ms % 1000); diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -2323,7 +2323,6 @@ ngx_http_subrequest(ngx_http_request_t * ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **psr, ngx_http_post_subrequest_t *ps, ngx_uint_t flags) { - ngx_time_t *tp; ngx_connection_t *c; ngx_http_request_t *sr; ngx_http_core_srv_conf_t *cscf; @@ -2470,9 +2469,7 @@ ngx_http_subrequest(ngx_http_request_t * sr->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1; sr->subrequests = r->subrequests - 1; - tp = ngx_timeofday(); - sr->start_sec = tp->sec; - sr->start_msec = tp->msec; + sr->start_time = ngx_current_msec; r->main->count++; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -560,7 +560,6 @@ static ngx_http_request_t * ngx_http_alloc_request(ngx_connection_t *c) { ngx_pool_t *pool; - ngx_time_t *tp; ngx_http_request_t *r; ngx_http_connection_t *hc; ngx_http_core_srv_conf_t *cscf; @@ -635,9 +634,7 @@ ngx_http_alloc_request(ngx_connection_t r->main = r; r->count = 1; - tp = ngx_timeofday(); - r->start_sec = tp->sec; - r->start_msec = tp->msec; + r->start_time = ngx_current_msec; r->method = NGX_HTTP_UNKNOWN; r->http_version = NGX_HTTP_VERSION_10; diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -410,8 +410,7 @@ struct ngx_http_request_s { ngx_http_request_body_t *request_body; time_t lingering_time; - time_t start_sec; - ngx_msec_t start_msec; + ngx_msec_t start_time; ngx_uint_t method; ngx_uint_t http_version; diff --git a/src/http/ngx_http_variables.c b/src/http/ngx_http_variables.c --- a/src/http/ngx_http_variables.c +++ b/src/http/ngx_http_variables.c @@ -2216,7 +2216,6 @@ ngx_http_variable_request_time(ngx_http_ ngx_http_variable_value_t *v, uintptr_t data) { u_char *p; - ngx_time_t *tp; ngx_msec_int_t ms; p = ngx_pnalloc(r->pool, NGX_TIME_T_LEN + 4); @@ -2224,10 +2223,7 @@ ngx_http_variable_request_time(ngx_http_ return NGX_ERROR; } - tp = ngx_timeofday(); - - ms = (ngx_msec_int_t) - ((tp->sec - r->start_sec) * 1000 + (tp->msec - r->start_msec)); + ms = (ngx_msec_int_t) (ngx_current_msec - r->start_time); ms = ngx_max(ms, 0); v->len = ngx_sprintf(p, "%T.%03M", (time_t) ms / 1000, ms % 1000) - p; diff --git a/src/stream/ngx_stream.h b/src/stream/ngx_stream.h --- a/src/stream/ngx_stream.h +++ b/src/stream/ngx_stream.h @@ -202,8 +202,7 @@ struct ngx_stream_session_s { ngx_connection_t *connection; off_t received; - time_t start_sec; - ngx_msec_t start_msec; + ngx_msec_t start_time; ngx_log_handler_pt log_handler; diff --git a/src/stream/ngx_stream_handler.c b/src/stream/ngx_stream_handler.c --- a/src/stream/ngx_stream_handler.c +++ b/src/stream/ngx_stream_handler.c @@ -23,7 +23,6 @@ ngx_stream_init_connection(ngx_connectio u_char text[NGX_SOCKADDR_STRLEN]; size_t len; ngx_uint_t i; - ngx_time_t *tp; ngx_event_t *rev; struct sockaddr *sa; ngx_stream_port_t *port; @@ -169,9 +168,7 @@ ngx_stream_init_connection(ngx_connectio return; } - tp = ngx_timeofday(); - s->start_sec = tp->sec; - s->start_msec = tp->msec; + s->start_time = ngx_current_msec; rev = c->read; rev->handler = ngx_stream_session_handler; diff --git a/src/stream/ngx_stream_variables.c b/src/stream/ngx_stream_variables.c --- a/src/stream/ngx_stream_variables.c +++ b/src/stream/ngx_stream_variables.c @@ -753,7 +753,6 @@ ngx_stream_variable_session_time(ngx_str ngx_stream_variable_value_t *v, uintptr_t data) { u_char *p; - ngx_time_t *tp; ngx_msec_int_t ms; p = ngx_pnalloc(s->connection->pool, NGX_TIME_T_LEN + 4); @@ -761,10 +760,7 @@ ngx_stream_variable_session_time(ngx_str return NGX_ERROR; } - tp = ngx_timeofday(); - - ms = (ngx_msec_int_t) - ((tp->sec - s->start_sec) * 1000 + (tp->msec - s->start_msec)); + ms = (ngx_msec_int_t) (ngx_current_msec - s->start_time); ms = ngx_max(ms, 0); v->len = ngx_sprintf(p, "%T.%03M", (time_t) ms / 1000, ms % 1000) - p; From mdounin at mdounin.ru Wed Jun 18 12:37:35 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 18 Jun 2025 15:37:35 +0300 Subject: [PATCH 3 of 7] Mail: simplified ngx_mail_proxy_handler() In-Reply-To: References: Message-ID: # HG changeset patch # User Maxim Dounin # Date 1750204968 -10800 # Wed Jun 18 03:02:48 2025 +0300 # Node ID cd85e94f52db98778bac968957277a41bc944927 # Parent 1a6f707ab976e825a00d32d8eaba6fb6402fd7a8 Mail: simplified ngx_mail_proxy_handler(). diff --git a/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c --- a/src/mail/ngx_mail_proxy_module.c +++ b/src/mail/ngx_mail_proxy_module.c @@ -1148,37 +1148,21 @@ ngx_mail_proxy_handler(ngx_event_t *ev) return; } - if (c == s->connection) { - if (ev->write) { - recv_action = "proxying and reading from upstream"; - send_action = "proxying and sending to client"; - src = s->proxy->upstream.connection; - dst = c; - b = s->proxy->buffer; - - } else { - recv_action = "proxying and reading from client"; - send_action = "proxying and sending to upstream"; - src = c; - dst = s->proxy->upstream.connection; - b = s->buffer; - } + if ((c == s->connection && ev->write) + || (c != s->connection && !ev->write)) + { + recv_action = "proxying and reading from upstream"; + send_action = "proxying and sending to client"; + src = s->proxy->upstream.connection; + dst = s->connection; + b = s->proxy->buffer; } else { - if (ev->write) { - recv_action = "proxying and reading from client"; - send_action = "proxying and sending to upstream"; - src = s->connection; - dst = c; - b = s->buffer; - - } else { - recv_action = "proxying and reading from upstream"; - send_action = "proxying and sending to client"; - src = c; - dst = s->connection; - b = s->proxy->buffer; - } + recv_action = "proxying and reading from client"; + send_action = "proxying and sending to upstream"; + src = s->connection; + dst = s->proxy->upstream.connection; + b = s->buffer; } do_write = ev->write ? 1 : 0; From mdounin at mdounin.ru Wed Jun 18 12:37:36 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 18 Jun 2025 15:37:36 +0300 Subject: [PATCH 4 of 7] Mail: added "limit_rate" and "limit_rate_after" directives In-Reply-To: References: Message-ID: <6c8bdd0c04eda08d0c8f.1750250256@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1750204972 -10800 # Wed Jun 18 03:02:52 2025 +0300 # Node ID 6c8bdd0c04eda08d0c8ffb8335c6e6b352dc6a28 # Parent cd85e94f52db98778bac968957277a41bc944927 Mail: added "limit_rate" and "limit_rate_after" directives. diff --git a/src/mail/ngx_mail.h b/src/mail/ngx_mail.h --- a/src/mail/ngx_mail.h +++ b/src/mail/ngx_mail.h @@ -119,6 +119,9 @@ typedef struct { ngx_uint_t max_errors; ngx_uint_t max_commands; + size_t limit_rate; + size_t limit_rate_after; + ngx_str_t server_name; u_char *file_name; @@ -248,6 +251,9 @@ typedef struct { ngx_uint_t commands; ngx_uint_t login_attempt; + ngx_msec_t limit_last; + off_t limit_excess; + /* used to parse POP3/IMAP/SMTP command */ ngx_uint_t state; diff --git a/src/mail/ngx_mail_core_module.c b/src/mail/ngx_mail_core_module.c --- a/src/mail/ngx_mail_core_module.c +++ b/src/mail/ngx_mail_core_module.c @@ -99,6 +99,20 @@ static ngx_command_t ngx_mail_core_comm offsetof(ngx_mail_core_srv_conf_t, max_commands), NULL }, + { ngx_string("limit_rate"), + NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_MAIL_SRV_CONF_OFFSET, + offsetof(ngx_mail_core_srv_conf_t, limit_rate), + NULL }, + + { ngx_string("limit_rate_after"), + NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_MAIL_SRV_CONF_OFFSET, + offsetof(ngx_mail_core_srv_conf_t, limit_rate_after), + NULL }, + ngx_null_command }; @@ -180,6 +194,9 @@ ngx_mail_core_create_srv_conf(ngx_conf_t cscf->max_errors = NGX_CONF_UNSET_UINT; cscf->max_commands = NGX_CONF_UNSET_UINT; + cscf->limit_rate = NGX_CONF_UNSET_SIZE; + cscf->limit_rate_after = NGX_CONF_UNSET_SIZE; + cscf->resolver = NGX_CONF_UNSET_PTR; cscf->file_name = cf->conf_file->file.name.data; @@ -202,6 +219,10 @@ ngx_mail_core_merge_srv_conf(ngx_conf_t ngx_conf_merge_uint_value(conf->max_errors, prev->max_errors, 5); ngx_conf_merge_uint_value(conf->max_commands, prev->max_commands, 1000); + ngx_conf_merge_size_value(conf->limit_rate, prev->limit_rate, 0); + ngx_conf_merge_size_value(conf->limit_rate_after, prev->limit_rate_after, + 0); + ngx_conf_merge_str_value(conf->server_name, prev->server_name, ""); if (conf->server_name.len == 0) { diff --git a/src/mail/ngx_mail_handler.c b/src/mail/ngx_mail_handler.c --- a/src/mail/ngx_mail_handler.c +++ b/src/mail/ngx_mail_handler.c @@ -1026,7 +1026,10 @@ ngx_mail_auth_oauthbearer(ngx_mail_sessi void ngx_mail_send(ngx_event_t *wev) { + off_t excess; ngx_int_t n; + ngx_msec_t delay; + ngx_msec_int_t ms; ngx_connection_t *c; ngx_mail_session_t *s; ngx_mail_core_srv_conf_t *cscf; @@ -1034,6 +1037,11 @@ ngx_mail_send(ngx_event_t *wev) c = wev->data; s = c->data; + if (wev->delayed && wev->timedout) { + wev->delayed = 0; + wev->timedout = 0; + } + if (wev->timedout) { ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); c->timedout = 1; @@ -1041,7 +1049,7 @@ ngx_mail_send(ngx_event_t *wev) return; } - if (s->out.len == 0) { + if (s->out.len == 0 || wev->delayed) { if (ngx_handle_write_event(wev, 0) != NGX_OK) { ngx_mail_close_connection(c); } @@ -1049,12 +1057,48 @@ ngx_mail_send(ngx_event_t *wev) return; } + cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module); + +#if (NGX_SUPPRESS_WARN) + excess = 0; +#endif + + if (cscf->limit_rate) { + ms = (ngx_msec_int_t) (ngx_current_msec - s->limit_last); + ms = ngx_max(ms, 0); + + excess = (off_t) (s->limit_excess + - (uint64_t) cscf->limit_rate * ms / 1000); + excess = ngx_max(excess, 0); + + if (excess > (off_t) cscf->limit_rate + + (off_t) cscf->limit_rate_after) + { + wev->delayed = 1; + excess -= (off_t) cscf->limit_rate_after; + excess -= (off_t) cscf->limit_rate / 2; + delay = (ngx_msec_t) (excess * 1000 / cscf->limit_rate + 1); + ngx_add_timer(wev, delay); + + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_mail_close_connection(c); + } + + return; + } + } + n = c->send(c, s->out.data, s->out.len); if (n > 0) { s->out.data += n; s->out.len -= n; + if (cscf->limit_rate) { + s->limit_last = ngx_current_msec; + s->limit_excess = excess + n; + } + if (s->out.len != 0) { goto again; } diff --git a/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c --- a/src/mail/ngx_mail_proxy_module.c +++ b/src/mail/ngx_mail_proxy_module.c @@ -1115,19 +1115,27 @@ ngx_mail_proxy_read_response(ngx_mail_se static void ngx_mail_proxy_handler(ngx_event_t *ev) { - char *action, *recv_action, *send_action; - off_t sent; - size_t size; - ssize_t n; - ngx_buf_t *b; - ngx_uint_t do_write; - ngx_connection_t *c, *src, *dst; - ngx_mail_session_t *s; - ngx_mail_proxy_conf_t *pcf; + char *action, *recv_action, *send_action; + off_t sent, excess, limit; + size_t size, limit_rate, limit_rate_after; + ssize_t n; + ngx_buf_t *b; + ngx_uint_t do_write; + ngx_msec_t delay; + ngx_msec_int_t ms; + ngx_connection_t *c, *src, *dst; + ngx_mail_session_t *s; + ngx_mail_proxy_conf_t *pcf; + ngx_mail_core_srv_conf_t *cscf; c = ev->data; s = c->data; + if (ev->delayed && ev->timedout) { + ev->delayed = 0; + ev->timedout = 0; + } + if (ev->timedout || c->close) { c->log->action = "proxying"; @@ -1157,17 +1165,27 @@ ngx_mail_proxy_handler(ngx_event_t *ev) dst = s->connection; b = s->proxy->buffer; + cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module); + limit_rate = cscf->limit_rate; + limit_rate_after = cscf->limit_rate_after; + } else { recv_action = "proxying and reading from client"; send_action = "proxying and sending to upstream"; src = s->connection; dst = s->proxy->upstream.connection; b = s->buffer; + limit_rate = 0; + limit_rate_after = 0; } do_write = ev->write ? 1 : 0; sent = dst->sent; +#if (NGX_SUPPRESS_WARN) + excess = 0; +#endif + ngx_log_debug3(NGX_LOG_DEBUG_MAIL, ev->log, 0, "mail proxy handler: %ui, #%d > #%d", do_write, src->fd, dst->fd); @@ -1178,9 +1196,34 @@ ngx_mail_proxy_handler(ngx_event_t *ev) size = b->last - b->pos; - if (size && dst->write->ready) { + if (size && dst->write->ready && !dst->write->delayed) { c->log->action = send_action; + if (limit_rate) { + ms = (ngx_msec_int_t) (ngx_current_msec - s->limit_last); + ms = ngx_max(ms, 0); + + excess = (off_t) (s->limit_excess + - (uint64_t) limit_rate * ms / 1000); + excess = ngx_max(excess, 0); + + limit = (off_t) limit_rate + (off_t) limit_rate_after + - excess; + + if (limit <= 0) { + dst->write->delayed = 1; + excess -= (off_t) limit_rate_after; + excess -= (off_t) limit_rate / 2; + delay = (ngx_msec_t) (excess * 1000 / limit_rate + 1); + ngx_add_timer(dst->write, delay); + break; + } + + if ((off_t) size > limit) { + size = (size_t) limit; + } + } + n = dst->send(dst, b->pos, size); if (n == NGX_ERROR) { @@ -1195,6 +1238,24 @@ ngx_mail_proxy_handler(ngx_event_t *ev) b->pos = b->start; b->last = b->start; } + + if (limit_rate) { + excess += n; + + s->limit_last = ngx_current_msec; + s->limit_excess = excess; + + excess -= (off_t) limit_rate_after; + excess -= (off_t) limit_rate / 2; + excess = ngx_max(excess, 0); + + delay = (ngx_msec_t) (excess * 1000 / limit_rate); + + if (delay > 0) { + dst->write->delayed = 1; + ngx_add_timer(dst->write, delay); + } + } } } } From mdounin at mdounin.ru Wed Jun 18 12:37:37 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 18 Jun 2025 15:37:37 +0300 Subject: [PATCH 5 of 7] HTTP/2: fixed timeout logging In-Reply-To: References: Message-ID: <4838a0361d54d7b3ed9d.1750250257@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1750204976 -10800 # Wed Jun 18 03:02:56 2025 +0300 # Node ID 4838a0361d54d7b3ed9de5c64baa5e9cd9439032 # Parent 6c8bdd0c04eda08d0c8ffb8335c6e6b352dc6a28 HTTP/2: fixed timeout logging. diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -460,8 +460,8 @@ ngx_http_v2_write_handler(ngx_event_t *w h2c = c->data; if (wev->timedout) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http2 write event timed out"); + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, + "client timed out"); c->error = 1; c->timedout = 1; ngx_http_v2_finalize_connection(h2c, 0); From mdounin at mdounin.ru Wed Jun 18 12:37:38 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 18 Jun 2025 15:37:38 +0300 Subject: [PATCH 6 of 7] Added the "send_min_rate" configuration directive In-Reply-To: References: Message-ID: <422bc6906b928c937e05.1750250258@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1750204980 -10800 # Wed Jun 18 03:03:00 2025 +0300 # Node ID 422bc6906b928c937e053a44c9854815223ff609 # Parent 4838a0361d54d7b3ed9de5c64baa5e9cd9439032 Added the "send_min_rate" configuration directive. Specifies the minimum required data transmission rate to the client. If the average transmission rate is below this value for send_timeout period, the connection is closed. The default value is 0, which implies that any data transfer results in a new timeout (that is, the existing behaviour). Algorithm used is as follows: the send_timeout timer is only rearmed if average sending rate after the timer was last rearmed is above the specified minimum. Note that this directive might interfere with limit_rate if it is used: minimum rates below ((limit_rate / 2 + limit_rate_after) / send_timeout) might not work, due to delay timers used by limit_rate on c->write. To avoid interference, using limit_rate_after at least as large as (send_min_rate * send_timeout) is recommended. diff --git a/src/event/ngx_event_pipe.c b/src/event/ngx_event_pipe.c --- a/src/event/ngx_event_pipe.c +++ b/src/event/ngx_event_pipe.c @@ -17,6 +17,7 @@ static ngx_int_t ngx_event_pipe_write_to static ngx_int_t ngx_event_pipe_write_chain_to_temp_file(ngx_event_pipe_t *p); static ngx_inline void ngx_event_pipe_remove_shadow_links(ngx_buf_t *buf); static ngx_int_t ngx_event_pipe_drain_chains(ngx_event_pipe_t *p); +static ngx_int_t ngx_event_pipe_min_rate(ngx_event_pipe_t *p, off_t sent); ngx_int_t @@ -91,7 +92,7 @@ ngx_event_pipe(ngx_event_pipe_t *p, ngx_ if (!wev->delayed) { if (wev->active && !wev->ready) { - if (p->downstream->sent != sent || !wev->timer_set) { + if (ngx_event_pipe_min_rate(p, p->downstream->sent - sent)) { ngx_add_timer(wev, p->send_timeout); } @@ -1170,3 +1171,42 @@ ngx_event_pipe_drain_chains(ngx_event_pi } } } + + +static ngx_int_t +ngx_event_pipe_min_rate(ngx_event_pipe_t *p, off_t sent) +{ + ngx_msec_t now; + ngx_msec_int_t ms; + + if (p->send_min_rate == 0) { + return (sent > 0 || !p->downstream->write->timer_set); + } + + now = ngx_current_msec; + + if (p->send_min_last == 0 || !p->downstream->write->timer_set) { + p->send_min_last = now; + p->send_min_excess = 0; + return 1; + } + + ms = (ngx_msec_int_t) (now - p->send_min_last); + ms = ngx_max(ms, 0); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, p->log, 0, + "pipe min rate: %O, %O, %M", + sent, p->send_min_excess, ms); + + if (p->send_min_excess + sent + > (off_t) p->send_min_rate * ms / 1000) + { + p->send_min_last = now; + p->send_min_excess = 0; + return 1; + } + + p->send_min_excess += sent; + + return 0; +} diff --git a/src/event/ngx_event_pipe.h b/src/event/ngx_event_pipe.h --- a/src/event/ngx_event_pipe.h +++ b/src/event/ngx_event_pipe.h @@ -94,6 +94,10 @@ struct ngx_event_pipe_s { ngx_msec_t limit_last; off_t limit_excess; + size_t send_min_rate; + ngx_msec_t send_min_last; + off_t send_min_excess; + ngx_temp_file_t *temp_file; /* STUB */ int num; diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -166,6 +166,9 @@ void ngx_http_block_reading(ngx_http_req void ngx_http_test_reading(ngx_http_request_t *r); +void ngx_http_send_timeout(ngx_http_request_t *r, off_t sent); + + char *ngx_http_types_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); char *ngx_http_merge_types(ngx_conf_t *cf, ngx_array_t **keys, ngx_hash_t *types_hash, ngx_array_t **prev_keys, diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -472,6 +472,13 @@ static ngx_command_t ngx_http_core_comm offsetof(ngx_http_core_loc_conf_t, send_timeout), NULL }, + { ngx_string("send_min_rate"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_core_loc_conf_t, send_min_rate), + NULL }, + { ngx_string("send_lowat"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, @@ -3609,6 +3616,7 @@ ngx_http_core_create_loc_conf(ngx_conf_t clcf->tcp_nopush = NGX_CONF_UNSET; clcf->tcp_nodelay = NGX_CONF_UNSET; clcf->send_timeout = NGX_CONF_UNSET_MSEC; + clcf->send_min_rate = NGX_CONF_UNSET_SIZE; clcf->send_lowat = NGX_CONF_UNSET_SIZE; clcf->postpone_output = NGX_CONF_UNSET_SIZE; clcf->limit_rate = NGX_CONF_UNSET_PTR; @@ -3840,6 +3848,7 @@ ngx_http_core_merge_loc_conf(ngx_conf_t ngx_conf_merge_value(conf->tcp_nodelay, prev->tcp_nodelay, 1); ngx_conf_merge_msec_value(conf->send_timeout, prev->send_timeout, 60000); + ngx_conf_merge_size_value(conf->send_min_rate, prev->send_min_rate, 0); ngx_conf_merge_size_value(conf->send_lowat, prev->send_lowat, 0); ngx_conf_merge_size_value(conf->postpone_output, prev->postpone_output, 1460); diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -359,6 +359,7 @@ struct ngx_http_core_loc_conf_s { off_t directio_alignment; /* directio_alignment */ size_t client_body_buffer_size; /* client_body_buffer_size */ + size_t send_min_rate; /* send_min_rate */ size_t send_lowat; /* send_lowat */ size_t postpone_output; /* postpone_output */ size_t sendfile_max_chunk; /* sendfile_max_chunk */ diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -39,6 +39,7 @@ static void ngx_http_terminate_handler(n static void ngx_http_finalize_connection(ngx_http_request_t *r); static ngx_int_t ngx_http_set_write_handler(ngx_http_request_t *r); static void ngx_http_writer(ngx_http_request_t *r); +static ngx_int_t ngx_http_send_min_rate(ngx_http_request_t *r, off_t sent); static void ngx_http_request_finalizer(ngx_http_request_t *r); static void ngx_http_set_keepalive(ngx_http_request_t *r); @@ -2835,11 +2836,11 @@ ngx_http_set_write_handler(ngx_http_requ return NGX_OK; } + if (!wev->delayed) { + ngx_http_send_timeout(r, 0); + } + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - if (!wev->delayed) { - ngx_add_timer(wev, clcf->send_timeout); - } - if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) { ngx_http_close_request(r, 0); return NGX_ERROR; @@ -2880,7 +2881,7 @@ ngx_http_writer(ngx_http_request_t *r) "http writer delayed"); if (!wev->delayed) { - ngx_add_timer(wev, clcf->send_timeout); + ngx_http_send_timeout(r, 0); } if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) { @@ -2905,8 +2906,8 @@ ngx_http_writer(ngx_http_request_t *r) if (r->buffered || r->postponed || (r == r->main && c->buffered)) { - if (!wev->delayed && (c->sent != sent || !wev->timer_set)) { - ngx_add_timer(wev, clcf->send_timeout); + if (!wev->delayed) { + ngx_http_send_timeout(r, c->sent - sent); } if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) { @@ -2925,6 +2926,66 @@ ngx_http_writer(ngx_http_request_t *r) } +void +ngx_http_send_timeout(ngx_http_request_t *r, off_t sent) +{ + ngx_http_core_loc_conf_t *clcf; + + if (!ngx_http_send_min_rate(r, sent)) { + return; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + ngx_add_timer(r->connection->write, clcf->send_timeout); +} + + +static ngx_int_t +ngx_http_send_min_rate(ngx_http_request_t *r, off_t sent) +{ + ngx_msec_t now; + ngx_msec_int_t ms; + ngx_connection_t *c; + ngx_http_core_loc_conf_t *clcf; + + r = r->main; + c = r->connection; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->send_min_rate == 0) { + return (sent > 0 || !c->write->timer_set); + } + + now = ngx_current_msec; + + if (r->send_min_last == 0 || !c->write->timer_set) { + r->send_min_last = now; + r->send_min_excess = 0; + return 1; + } + + ms = (ngx_msec_int_t) (now - r->send_min_last); + ms = ngx_max(ms, 0); + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http min rate: %O, %O, %M", + sent, r->send_min_excess, ms); + + if (r->send_min_excess + sent + > (off_t) clcf->send_min_rate * ms / 1000) + { + r->send_min_last = now; + r->send_min_excess = 0; + return 1; + } + + r->send_min_excess += sent; + + return 0; +} + + static void ngx_http_request_finalizer(ngx_http_request_t *r) { diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -450,6 +450,9 @@ struct ngx_http_request_s { ngx_msec_t limit_last; off_t limit_excess; + ngx_msec_t send_min_last; + off_t send_min_excess; + /* used to learn the Apache compatible response length without a header */ size_t header_size; 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 @@ -3368,6 +3368,7 @@ ngx_http_upstream_send_response(ngx_http p->read_timeout = u->conf->read_timeout; p->send_timeout = clcf->send_timeout; + p->send_min_rate = clcf->send_min_rate; p->send_lowat = clcf->send_lowat; p->length = -1; @@ -3707,9 +3708,7 @@ ngx_http_upstream_process_upgraded(ngx_h } if (downstream->write->active && !downstream->write->ready) { - if (downstream->sent != dsent || !downstream->write->timer_set) { - ngx_add_timer(downstream->write, clcf->send_timeout); - } + ngx_http_send_timeout(r, downstream->sent - dsent); } else if (downstream->write->timer_set) { ngx_del_timer(downstream->write); @@ -3876,9 +3875,7 @@ ngx_http_upstream_process_non_buffered_r } if (downstream->write->active && !downstream->write->ready) { - if (downstream->sent != sent || !downstream->write->timer_set) { - ngx_add_timer(downstream->write, clcf->send_timeout); - } + ngx_http_send_timeout(r, downstream->sent - sent); } else if (downstream->write->timer_set) { ngx_del_timer(downstream->write); diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -51,6 +51,8 @@ static void ngx_http_v2_read_handler(ngx_event_t *rev); static void ngx_http_v2_write_handler(ngx_event_t *wev); +static ngx_int_t ngx_http_v2_send_min_rate(ngx_http_v2_connection_t *h2c, + off_t sent); static void ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c); static void ngx_http_v2_lingering_close(ngx_connection_t *c); static void ngx_http_v2_lingering_close_handler(ngx_event_t *rev); @@ -595,7 +597,7 @@ ngx_http_v2_send_output_queue(ngx_http_v h2c->last_out = frame; if (!wev->ready) { - if (c->sent != sent || !wev->timer_set) { + if (ngx_http_v2_send_min_rate(h2c, c->sent - sent)) { ngx_add_timer(wev, clcf->send_timeout); } @@ -620,6 +622,52 @@ error: } +static ngx_int_t +ngx_http_v2_send_min_rate(ngx_http_v2_connection_t *h2c, off_t sent) +{ + ngx_msec_t now; + ngx_msec_int_t ms; + ngx_connection_t *c; + ngx_http_core_loc_conf_t *clcf; + + c = h2c->connection; + + clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx, + ngx_http_core_module); + + if (clcf->send_min_rate == 0) { + return (sent > 0 || !c->write->timer_set); + } + + now = ngx_current_msec; + + if (h2c->send_min_last == 0 || !c->write->timer_set) { + h2c->send_min_last = now; + h2c->send_min_excess = 0; + return 1; + } + + ms = (ngx_msec_int_t) (now - h2c->send_min_last); + ms = ngx_max(ms, 0); + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http2 min rate: %O, %O, %M", + sent, h2c->send_min_excess, ms); + + if (h2c->send_min_excess + sent + > (off_t) clcf->send_min_rate * ms / 1000) + { + h2c->send_min_last = now; + h2c->send_min_excess = 0; + return 1; + } + + h2c->send_min_excess += sent; + + return 0; +} + + static void ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c) { diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h --- a/src/http/v2/ngx_http_v2.h +++ b/src/http/v2/ngx_http_v2.h @@ -164,6 +164,9 @@ struct ngx_http_v2_connection_s { time_t lingering_time; + ngx_msec_t send_min_last; + off_t send_min_excess; + unsigned settings_ack:1; unsigned table_update:1; unsigned blocked:1; From mdounin at mdounin.ru Wed Jun 18 12:37:39 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Wed, 18 Jun 2025 15:37:39 +0300 Subject: [PATCH 7 of 7] Added the "client_body_min_rate" configuration directive In-Reply-To: References: Message-ID: <121d6685362076da8b26.1750250259@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1750204984 -10800 # Wed Jun 18 03:03:04 2025 +0300 # Node ID 121d6685362076da8b261e92674b17c2a7ca145e # Parent 422bc6906b928c937e053a44c9854815223ff609 Added the "client_body_min_rate" configuration directive. Similarly to send_min_rate, this directive limits minimum allowed data rate when reading the request body. diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -167,6 +167,7 @@ void ngx_http_test_reading(ngx_http_requ void ngx_http_send_timeout(ngx_http_request_t *r, off_t sent); +void ngx_http_request_body_timeout(ngx_http_request_t *r, off_t bytes); char *ngx_http_types_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -373,6 +373,13 @@ static ngx_command_t ngx_http_core_comm offsetof(ngx_http_core_loc_conf_t, client_body_timeout), NULL }, + { ngx_string("client_body_min_rate"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_core_loc_conf_t, client_body_min_rate), + NULL }, + { ngx_string("client_body_temp_path"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1234, ngx_conf_set_path_slot, @@ -3594,6 +3601,7 @@ ngx_http_core_create_loc_conf(ngx_conf_t clcf->client_max_body_size = NGX_CONF_UNSET; clcf->client_body_buffer_size = NGX_CONF_UNSET_SIZE; clcf->client_body_timeout = NGX_CONF_UNSET_MSEC; + clcf->client_body_min_rate = NGX_CONF_UNSET_SIZE; clcf->satisfy = NGX_CONF_UNSET_UINT; clcf->auth_delay = NGX_CONF_UNSET_MSEC; clcf->if_modified_since = NGX_CONF_UNSET_UINT; diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -359,6 +359,7 @@ struct ngx_http_core_loc_conf_s { off_t directio_alignment; /* directio_alignment */ size_t client_body_buffer_size; /* client_body_buffer_size */ + size_t client_body_min_rate; /* client_body_min_rate */ size_t send_min_rate; /* send_min_rate */ size_t send_lowat; /* send_lowat */ size_t postpone_output; /* postpone_output */ diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -303,6 +303,8 @@ typedef struct { ngx_buf_t *buf; off_t rest; off_t received; + ngx_msec_t rate_last; + off_t rate_excess; ngx_chain_t *free; ngx_chain_t *busy; ngx_http_chunked_t *chunked; diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c --- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -12,6 +12,8 @@ static void ngx_http_read_client_request_body_handler(ngx_http_request_t *r); static ngx_int_t ngx_http_do_read_client_request_body(ngx_http_request_t *r); +static ngx_int_t ngx_http_request_body_min_rate(ngx_http_request_t *r, + off_t bytes); static ngx_int_t ngx_http_copy_pipelined_header(ngx_http_request_t *r, ngx_buf_t *buf); static ngx_int_t ngx_http_write_request_body(ngx_http_request_t *r); @@ -84,6 +86,7 @@ ngx_http_read_client_request_body(ngx_ht * rb->busy = NULL; * rb->chunked = NULL; * rb->received = 0; + * rb->rate_last = 0; * rb->no_buffering = 0; * rb->filter_need_buffering = 0; * rb->last_sent = 0; @@ -334,19 +337,19 @@ ngx_http_read_client_request_body_handle static ngx_int_t ngx_http_do_read_client_request_body(ngx_http_request_t *r) { - off_t rest; - size_t size; - ssize_t n; - ngx_int_t rc; - ngx_uint_t flush; - ngx_chain_t out; - ngx_connection_t *c; - ngx_http_request_body_t *rb; - ngx_http_core_loc_conf_t *clcf; + off_t rest, bytes; + size_t size; + ssize_t n; + ngx_int_t rc; + ngx_uint_t flush; + ngx_chain_t out; + ngx_connection_t *c; + ngx_http_request_body_t *rb; c = r->connection; rb = r->request_body; flush = 1; + bytes = 0; n = NGX_AGAIN; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -382,9 +385,7 @@ ngx_http_do_read_client_request_body(ngx } if (rb->filter_need_buffering) { - clcf = ngx_http_get_module_loc_conf(r, - ngx_http_core_module); - ngx_add_timer(c->read, clcf->client_body_timeout); + ngx_http_request_body_timeout(r, bytes); if (ngx_handle_read_event(c->read, 0) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; @@ -436,6 +437,7 @@ ngx_http_do_read_client_request_body(ngx rb->buf->last += n; r->request_length += n; + bytes += n; /* pass buffer to request body filter chain */ @@ -475,8 +477,7 @@ ngx_http_do_read_client_request_body(ngx if (n == NGX_AGAIN || !c->read->ready || rb->rest == 0) { - clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - ngx_add_timer(c->read, clcf->client_body_timeout); + ngx_http_request_body_timeout(r, bytes); if (ngx_handle_read_event(c->read, 0) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; @@ -504,6 +505,67 @@ ngx_http_do_read_client_request_body(ngx } +void +ngx_http_request_body_timeout(ngx_http_request_t *r, off_t bytes) +{ + ngx_http_core_loc_conf_t *clcf; + + if (!ngx_http_request_body_min_rate(r, bytes)) { + return; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + ngx_add_timer(r->connection->read, clcf->client_body_timeout); +} + + +static ngx_int_t +ngx_http_request_body_min_rate(ngx_http_request_t *r, off_t bytes) +{ + ngx_msec_t now; + ngx_msec_int_t ms; + ngx_connection_t *c; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + c = r->connection; + rb = r->request_body; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->client_body_min_rate == 0) { + return (bytes > 0 || !c->read->timer_set); + } + + now = ngx_current_msec; + + if (rb->rate_last == 0 || !c->read->timer_set) { + rb->rate_last = now; + rb->rate_excess = 0; + return 1; + } + + ms = (ngx_msec_int_t) (now - rb->rate_last); + ms = ngx_max(ms, 0); + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http body min rate: %O, %O, %M", + bytes, rb->rate_excess, ms); + + if (rb->rate_excess + bytes + > (off_t) clcf->client_body_min_rate * ms / 1000) + { + rb->rate_last = now; + rb->rate_excess = 0; + return 1; + } + + rb->rate_excess += bytes; + + return 0; +} + + static ngx_int_t ngx_http_copy_pipelined_header(ngx_http_request_t *r, ngx_buf_t *buf) { diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -3988,7 +3988,7 @@ ngx_http_v2_read_request_body(ngx_http_r } if (!buf) { - ngx_add_timer(r->connection->read, clcf->client_body_timeout); + ngx_http_request_body_timeout(r, 0); } r->read_event_handler = ngx_http_v2_read_client_request_body_handler; @@ -4002,11 +4002,11 @@ static ngx_int_t ngx_http_v2_process_request_body(ngx_http_request_t *r, u_char *pos, size_t size, ngx_uint_t last, ngx_uint_t flush) { - size_t n; - ngx_int_t rc; - ngx_connection_t *fc; - ngx_http_request_body_t *rb; - ngx_http_core_loc_conf_t *clcf; + off_t bytes; + size_t n; + ngx_int_t rc; + ngx_connection_t *fc; + ngx_http_request_body_t *rb; fc = r->connection; rb = r->request_body; @@ -4018,6 +4018,8 @@ ngx_http_v2_process_request_body(ngx_htt return NGX_AGAIN; } + bytes = 0; + for ( ;; ) { for ( ;; ) { if (rb->buf->last == rb->buf->end && size) { @@ -4070,6 +4072,7 @@ ngx_http_v2_process_request_body(ngx_htt pos += n; size -= n; + bytes += n; if (size == 0 && last) { rb->rest = 0; @@ -4096,8 +4099,7 @@ ngx_http_v2_process_request_body(ngx_htt } if (size == 0) { - clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - ngx_add_timer(fc->read, clcf->client_body_timeout); + ngx_http_request_body_timeout(r, bytes); if (!flush) { ngx_post_event(fc->read, &ngx_posted_events); diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -1336,19 +1336,19 @@ ngx_http_v3_read_unbuffered_request_body static ngx_int_t ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) { - off_t rest; - size_t size; - ssize_t n; - ngx_int_t rc; - ngx_uint_t flush; - ngx_chain_t out; - ngx_connection_t *c; - ngx_http_request_body_t *rb; - ngx_http_core_loc_conf_t *clcf; + off_t rest, bytes; + size_t size; + ssize_t n; + ngx_int_t rc; + ngx_uint_t flush; + ngx_chain_t out; + ngx_connection_t *c; + ngx_http_request_body_t *rb; c = r->connection; rb = r->request_body; flush = 1; + bytes = 0; n = NGX_AGAIN; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -1384,9 +1384,7 @@ ngx_http_v3_do_read_client_request_body( } if (rb->filter_need_buffering) { - clcf = ngx_http_get_module_loc_conf(r, - ngx_http_core_module); - ngx_add_timer(c->read, clcf->client_body_timeout); + ngx_http_request_body_timeout(r, bytes); if (ngx_handle_read_event(c->read, 0) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; @@ -1436,6 +1434,7 @@ ngx_http_v3_do_read_client_request_body( } rb->buf->last += n; + bytes += n; /* pass buffer to request body filter chain */ @@ -1475,8 +1474,7 @@ ngx_http_v3_do_read_client_request_body( if (n == NGX_AGAIN || !c->read->ready || rb->rest == 0) { - clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - ngx_add_timer(c->read, clcf->client_body_timeout); + ngx_http_request_body_timeout(r, bytes); if (ngx_handle_read_event(c->read, 0) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; From mdounin at mdounin.ru Thu Jun 19 02:40:18 2025 From: mdounin at mdounin.ru (=?iso-8859-1?q?Maxim_Dounin?=) Date: Thu, 19 Jun 2025 05:40:18 +0300 Subject: [nginx-tests] Tests: adjusted limit_rate tests for leaky bucket ... Message-ID: details: http://freenginx.org/hg/nginx-tests/rev/25cf08cb2bf8 branches: changeset: 2010:25cf08cb2bf8 user: Maxim Dounin date: Thu Jun 12 17:50:38 2025 +0300 description: Tests: adjusted limit_rate tests for leaky bucket implementation. Previously, stream limit_rate tests were quite dependant on the particular limit_rate implementation. Relaxed testing so it would also succeed with the upcoming leaky bucket implementation, and described high-level requirements in the comment instead of the behaviour of the old implementation. Similarly, comment in proxy_limit_rate.t was replaced with a high-level description instead of an (incorrect) description of the behaviour of the old implementation. diffstat: proxy_limit_rate.t | 3 ++- stream_limit_rate.t | 9 ++++----- stream_limit_rate2.t | 9 ++++----- 3 files changed, 10 insertions(+), 11 deletions(-) diffs (67 lines): diff --git a/proxy_limit_rate.t b/proxy_limit_rate.t --- a/proxy_limit_rate.t +++ b/proxy_limit_rate.t @@ -78,7 +78,8 @@ my $r = http_get('/'); my ($t1) = $r =~ /X-Msec: (\d+)/; my $diff = time() - $t1; -# four chunks are split with three 1s delays +# reading 40000 byte response with proxy_limit_rate 20000 +# should take at least 1 second cmp_ok($diff, '>=', 1, 'proxy_limit_rate'); like($r, qr/^(XXXXXXXXXX){4000}\x0d?\x0a?$/m, 'response body'); diff --git a/stream_limit_rate.t b/stream_limit_rate.t --- a/stream_limit_rate.t +++ b/stream_limit_rate.t @@ -120,19 +120,18 @@ is($r{'data'}, '1', 'upload - one byte') } -# Five chunks are split with four 1s delays: -# the first four chunks are quarters of test string -# and the fifth one is some extra data from backend. +# reading 1000+ byte response with proxy_download_rate 250 +# should take at least 3 seconds %r = response($str, peer => '127.0.0.1:' . port(8085)); my $diff = time() - $r{'time'}; -cmp_ok($diff, '>=', 4, 'download - time'); +cmp_ok($diff, '>=', 3, 'download - time'); is($r{'data'}, $str, 'download - data'); my $time = time(); %r = response($str . 'close', peer => '127.0.0.1:' . port(8086)); $diff = time() - $time; -cmp_ok($diff, '>=', 4, 'upload - time'); +cmp_ok($diff, '>=', 3, 'upload - time'); is($r{'data'}, $str . 'close', 'upload - data'); ############################################################################### diff --git a/stream_limit_rate2.t b/stream_limit_rate2.t --- a/stream_limit_rate2.t +++ b/stream_limit_rate2.t @@ -134,19 +134,18 @@ is($r{'data'}, '1', 'upload - one byte') } -# Five chunks are split with four 1s delays: -# the first four chunks are quarters of test string -# and the fifth one is some extra data from backend. +# reading 1000+ byte response with proxy_download_rate 250 +# should take at least 3 seconds %r = response($str, peer => '127.0.0.1:' . port(8085)); my $diff = time() - $r{'time'}; -cmp_ok($diff, '>=', 4, 'download - time'); +cmp_ok($diff, '>=', 3, 'download - time'); is($r{'data'}, $str, 'download - data'); my $time = time(); %r = response($str . 'close', peer => '127.0.0.1:' . port(8086)); $diff = time() - $time; -cmp_ok($diff, '>=', 4, 'upload - time'); +cmp_ok($diff, '>=', 3, 'upload - time'); is($r{'data'}, $str . 'close', 'upload - data'); ############################################################################### From mdounin at mdounin.ru Thu Jun 26 20:53:07 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Thu, 26 Jun 2025 23:53:07 +0300 Subject: [PATCH 1 of 2] Switched to ngx_current_msec for lingering_time tracking Message-ID: <38d84f18b138ae4989d0.1750971187@vm-bsd.mdounin.ru> # HG changeset patch # User Maxim Dounin # Date 1750962077 -10800 # Thu Jun 26 21:21:17 2025 +0300 # Node ID 38d84f18b138ae4989d071fcb5cabda2f846b219 # Parent 121d6685362076da8b261e92674b17c2a7ca145e Switched to ngx_current_msec for lingering_time tracking. This ensures that lingering time is not affected by system time changes on platforms with monotonic time available. diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -2772,8 +2772,7 @@ ngx_http_finalize_connection(ngx_http_re ngx_add_timer(r->connection->read, clcf->lingering_timeout); if (r->lingering_time == 0) { - r->lingering_time = ngx_time() - + (time_t) (clcf->lingering_time / 1000); + r->lingering_time = ngx_current_msec + clcf->lingering_time; } } @@ -3538,7 +3537,7 @@ ngx_http_set_lingering_close(ngx_connect clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (r->lingering_time == 0) { - r->lingering_time = ngx_time() + (time_t) (clcf->lingering_time / 1000); + r->lingering_time = ngx_current_msec + clcf->lingering_time; } #if (NGX_HTTP_SSL) @@ -3618,7 +3617,7 @@ ngx_http_lingering_close_handler(ngx_eve return; } - timer = (ngx_msec_t) r->lingering_time - (ngx_msec_t) ngx_time(); + timer = r->lingering_time - ngx_current_msec; if ((ngx_msec_int_t) timer <= 0) { ngx_http_close_request(r, 0); return; @@ -3647,8 +3646,6 @@ ngx_http_lingering_close_handler(ngx_eve clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - timer *= 1000; - if (timer > clcf->lingering_timeout) { timer = clcf->lingering_timeout; } diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -411,7 +411,7 @@ struct ngx_http_request_s { ngx_http_request_body_t *request_body; - time_t lingering_time; + ngx_msec_t lingering_time; ngx_msec_t start_time; ngx_uint_t method; diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c --- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -833,7 +833,7 @@ ngx_http_discarded_request_body_handler( } if (r->lingering_time) { - timer = (ngx_msec_t) r->lingering_time - (ngx_msec_t) ngx_time(); + timer = r->lingering_time - ngx_current_msec; if ((ngx_msec_int_t) timer <= 0) { r->discarding_body = 0; @@ -874,8 +874,6 @@ ngx_http_discarded_request_body_handler( clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - timer *= 1000; - if (timer > clcf->lingering_timeout) { timer = clcf->lingering_timeout; } diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -765,8 +765,7 @@ ngx_http_v2_lingering_close(ngx_connecti } if (h2c->lingering_time == 0) { - h2c->lingering_time = ngx_time() - + (time_t) (clcf->lingering_time / 1000); + h2c->lingering_time = ngx_current_msec + clcf->lingering_time; } #if (NGX_HTTP_SSL) @@ -844,7 +843,7 @@ ngx_http_v2_lingering_close_handler(ngx_ return; } - timer = (ngx_msec_t) h2c->lingering_time - (ngx_msec_t) ngx_time(); + timer = h2c->lingering_time - ngx_current_msec; if ((ngx_msec_int_t) timer <= 0) { ngx_http_close_connection(c); return; @@ -873,7 +872,6 @@ ngx_http_v2_lingering_close_handler(ngx_ clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx, ngx_http_core_module); - timer *= 1000; if (timer > clcf->lingering_timeout) { timer = clcf->lingering_timeout; diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h --- a/src/http/v2/ngx_http_v2.h +++ b/src/http/v2/ngx_http_v2.h @@ -162,7 +162,7 @@ struct ngx_http_v2_connection_s { ngx_uint_t closed_nodes; ngx_uint_t last_sid; - time_t lingering_time; + ngx_msec_t lingering_time; ngx_msec_t send_min_last; off_t send_min_excess; From mdounin at mdounin.ru Thu Jun 26 20:53:08 2025 From: mdounin at mdounin.ru (=?utf-8?q?Maxim_Dounin?=) Date: Thu, 26 Jun 2025 23:53:08 +0300 Subject: [PATCH 2 of 2] Mail: lingering close In-Reply-To: <38d84f18b138ae4989d0.1750971187@vm-bsd.mdounin.ru> References: <38d84f18b138ae4989d0.1750971187@vm-bsd.mdounin.ru> Message-ID: # HG changeset patch # User Maxim Dounin # Date 1750964100 -10800 # Thu Jun 26 21:55:00 2025 +0300 # Node ID f7e9eb427a1457102b2c9a43371622924361026d # Parent 38d84f18b138ae4989d071fcb5cabda2f846b219 Mail: lingering close. In mail, lingering close is not normally needed, since connections are usually closed at well known states, and further commands from clients are not expected. Still, it can be useful, for example, when closing a connection due to errors. diff --git a/src/mail/ngx_mail.h b/src/mail/ngx_mail.h --- a/src/mail/ngx_mail.h +++ b/src/mail/ngx_mail.h @@ -115,10 +115,14 @@ typedef struct { ngx_msec_t timeout; ngx_msec_t resolver_timeout; + ngx_msec_t lingering_time; + ngx_msec_t lingering_timeout; ngx_uint_t max_errors; ngx_uint_t max_commands; + ngx_flag_t lingering_close; + size_t limit_rate; size_t limit_rate_after; @@ -217,6 +221,7 @@ typedef struct { unsigned protocol:3; unsigned blocked:1; unsigned quit:1; + unsigned no_lingering_close:1; unsigned quoted:1; unsigned backslash:1; unsigned no_sync_literal:1; @@ -254,6 +259,8 @@ typedef struct { ngx_msec_t limit_last; off_t limit_excess; + ngx_msec_t lingering_time; + /* used to parse POP3/IMAP/SMTP command */ ngx_uint_t state; @@ -337,6 +344,9 @@ typedef struct { #define NGX_MAIL_PARSE_INVALID_COMMAND 20 +#define NGX_MAIL_LINGERING_BUFFER_SIZE 4096 + + typedef void (*ngx_mail_init_session_pt)(ngx_mail_session_t *s, ngx_connection_t *c); typedef void (*ngx_mail_init_protocol_pt)(ngx_event_t *rev); diff --git a/src/mail/ngx_mail_core_module.c b/src/mail/ngx_mail_core_module.c --- a/src/mail/ngx_mail_core_module.c +++ b/src/mail/ngx_mail_core_module.c @@ -113,6 +113,27 @@ static ngx_command_t ngx_mail_core_comm offsetof(ngx_mail_core_srv_conf_t, limit_rate_after), NULL }, + { ngx_string("lingering_close"), + NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_MAIL_SRV_CONF_OFFSET, + offsetof(ngx_mail_core_srv_conf_t, lingering_close), + NULL }, + + { ngx_string("lingering_time"), + NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_MAIL_SRV_CONF_OFFSET, + offsetof(ngx_mail_core_srv_conf_t, lingering_time), + NULL }, + + { ngx_string("lingering_timeout"), + NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_MAIL_SRV_CONF_OFFSET, + offsetof(ngx_mail_core_srv_conf_t, lingering_timeout), + NULL }, + ngx_null_command }; @@ -190,10 +211,14 @@ ngx_mail_core_create_srv_conf(ngx_conf_t cscf->timeout = NGX_CONF_UNSET_MSEC; cscf->resolver_timeout = NGX_CONF_UNSET_MSEC; + cscf->lingering_time = NGX_CONF_UNSET_MSEC; + cscf->lingering_timeout = NGX_CONF_UNSET_MSEC; cscf->max_errors = NGX_CONF_UNSET_UINT; cscf->max_commands = NGX_CONF_UNSET_UINT; + cscf->lingering_close = NGX_CONF_UNSET; + cscf->limit_rate = NGX_CONF_UNSET_SIZE; cscf->limit_rate_after = NGX_CONF_UNSET_SIZE; @@ -215,10 +240,16 @@ ngx_mail_core_merge_srv_conf(ngx_conf_t ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000); ngx_conf_merge_msec_value(conf->resolver_timeout, prev->resolver_timeout, 30000); + ngx_conf_merge_msec_value(conf->lingering_time, prev->lingering_time, + 30000); + ngx_conf_merge_msec_value(conf->lingering_timeout, prev->lingering_timeout, + 5000); ngx_conf_merge_uint_value(conf->max_errors, prev->max_errors, 5); ngx_conf_merge_uint_value(conf->max_commands, prev->max_commands, 1000); + ngx_conf_merge_value(conf->lingering_close, prev->lingering_close, 1); + ngx_conf_merge_size_value(conf->limit_rate, prev->limit_rate, 0); ngx_conf_merge_size_value(conf->limit_rate_after, prev->limit_rate_after, 0); diff --git a/src/mail/ngx_mail_handler.c b/src/mail/ngx_mail_handler.c --- a/src/mail/ngx_mail_handler.c +++ b/src/mail/ngx_mail_handler.c @@ -22,6 +22,10 @@ static ngx_int_t ngx_mail_verify_cert(ng ngx_connection_t *c); #endif +static void ngx_mail_lingering_close(ngx_connection_t *c); +static void ngx_mail_lingering_close_handler(ngx_event_t *rev); +static void ngx_mail_empty_handler(ngx_event_t *wev); + void ngx_mail_init_connection(ngx_connection_t *c) @@ -1108,7 +1112,7 @@ ngx_mail_send(ngx_event_t *wev) } if (s->quit) { - ngx_mail_close_connection(c); + ngx_mail_lingering_close(c); return; } @@ -1267,6 +1271,149 @@ ngx_mail_session_internal_server_error(n } +static void +ngx_mail_lingering_close(ngx_connection_t *c) +{ + ngx_event_t *rev, *wev; + ngx_mail_session_t *s; + ngx_mail_core_srv_conf_t *cscf; + + s = c->data; + + cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module); + + if (s->no_lingering_close || !cscf->lingering_close) { + ngx_mail_close_connection(c); + return; + } + + if (s->lingering_time == 0) { + s->lingering_time = ngx_current_msec + cscf->lingering_time; + } + +#if (NGX_MAIL_SSL) + if (c->ssl) { + ngx_int_t rc; + + c->ssl->shutdown_without_free = 1; + + rc = ngx_ssl_shutdown(c); + + if (rc == NGX_ERROR) { + ngx_mail_close_connection(c); + return; + } + + if (rc == NGX_AGAIN) { + c->ssl->handler = ngx_mail_lingering_close; + return; + } + } +#endif + + rev = c->read; + rev->handler = ngx_mail_lingering_close_handler; + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_mail_close_connection(c); + return; + } + + wev = c->write; + wev->handler = ngx_mail_empty_handler; + + if (wev->active && (ngx_event_flags & NGX_USE_LEVEL_EVENT)) { + if (ngx_del_event(wev, NGX_WRITE_EVENT, 0) != NGX_OK) { + ngx_mail_close_connection(c); + return; + } + } + + if (ngx_shutdown_socket(c->fd, NGX_WRITE_SHUTDOWN) == -1) { + ngx_connection_error(c, ngx_socket_errno, + ngx_shutdown_socket_n " failed"); + ngx_mail_close_connection(c); + return; + } + + c->close = 0; + ngx_reusable_connection(c, 1); + + ngx_add_timer(rev, cscf->lingering_timeout); + + if (rev->ready) { + ngx_mail_lingering_close_handler(rev); + } +} + + +static void +ngx_mail_lingering_close_handler(ngx_event_t *rev) +{ + ssize_t n; + ngx_msec_t timer; + ngx_connection_t *c; + ngx_mail_session_t *s; + ngx_mail_core_srv_conf_t *cscf; + u_char buffer[NGX_MAIL_LINGERING_BUFFER_SIZE]; + + c = rev->data; + s = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, + "mail lingering close handler"); + + if (rev->timedout || c->close) { + ngx_mail_close_connection(c); + return; + } + + timer = s->lingering_time - ngx_current_msec; + if ((ngx_msec_int_t) timer <= 0) { + ngx_mail_close_connection(c); + return; + } + + do { + n = c->recv(c, buffer, NGX_MAIL_LINGERING_BUFFER_SIZE); + + ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0, "lingering read: %z", n); + + if (n == NGX_AGAIN) { + break; + } + + if (n == NGX_ERROR || n == 0) { + ngx_mail_close_connection(c); + return; + } + + } while (rev->ready); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_mail_close_connection(c); + return; + } + + cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module); + + if (timer > cscf->lingering_timeout) { + timer = cscf->lingering_timeout; + } + + ngx_add_timer(rev, timer); +} + + +static void +ngx_mail_empty_handler(ngx_event_t *wev) +{ + ngx_log_debug0(NGX_LOG_DEBUG_MAIL, wev->log, 0, "mail empty handler"); + + return; +} + + void ngx_mail_close_connection(ngx_connection_t *c) { diff --git a/src/mail/ngx_mail_imap_handler.c b/src/mail/ngx_mail_imap_handler.c --- a/src/mail/ngx_mail_imap_handler.c +++ b/src/mail/ngx_mail_imap_handler.c @@ -179,6 +179,7 @@ ngx_mail_imap_auth_state(ngx_event_t *re case NGX_IMAP_LOGOUT: s->quit = 1; + s->no_lingering_close = 1; ngx_str_set(&s->text, imap_bye); break; diff --git a/src/mail/ngx_mail_pop3_handler.c b/src/mail/ngx_mail_pop3_handler.c --- a/src/mail/ngx_mail_pop3_handler.c +++ b/src/mail/ngx_mail_pop3_handler.c @@ -192,6 +192,7 @@ ngx_mail_pop3_auth_state(ngx_event_t *re case NGX_POP3_QUIT: s->quit = 1; + s->no_lingering_close = 1; break; case NGX_POP3_NOOP: @@ -222,6 +223,7 @@ ngx_mail_pop3_auth_state(ngx_event_t *re case NGX_POP3_QUIT: s->quit = 1; + s->no_lingering_close = 1; break; case NGX_POP3_NOOP: diff --git a/src/mail/ngx_mail_smtp_handler.c b/src/mail/ngx_mail_smtp_handler.c --- a/src/mail/ngx_mail_smtp_handler.c +++ b/src/mail/ngx_mail_smtp_handler.c @@ -496,6 +496,7 @@ ngx_mail_smtp_auth_state(ngx_event_t *re case NGX_SMTP_QUIT: s->quit = 1; + s->no_lingering_close = 1; ngx_str_set(&s->out, smtp_bye); break;