[PATCH 2 of 2] Mail: lingering close

Maxim Dounin mdounin at mdounin.ru
Thu Jun 26 20:53:08 UTC 2025


# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# 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;
 



More information about the nginx-devel mailing list