[nginx] Mail: lingering close.

Maxim Dounin mdounin at mdounin.ru
Mon Jun 30 22:41:00 UTC 2025


details:   http://freenginx.org/hg/nginx/rev/1746cc8754af
branches:  
changeset: 9383:1746cc8754af
user:      Maxim Dounin <mdounin at mdounin.ru>
date:      Tue Jul 01 01:09:56 2025 +0300
description:
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.

diffstat:

 src/mail/ngx_mail.h              |   10 ++
 src/mail/ngx_mail_core_module.c  |   31 ++++++++
 src/mail/ngx_mail_handler.c      |  149 ++++++++++++++++++++++++++++++++++++++-
 src/mail/ngx_mail_imap_handler.c |    1 +
 src/mail/ngx_mail_pop3_handler.c |    2 +
 src/mail/ngx_mail_smtp_handler.c |    1 +
 6 files changed, 193 insertions(+), 1 deletions(-)

diffs (322 lines):

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;
 


More information about the nginx-devel mailing list