From 6b7eccfec4f76b7d9d1f865caf741ff3214b5964 Mon Sep 17 00:00:00 2001 From: Pavlo Lavrenenko Date: Thu, 2 Jun 2016 08:18:51 +0300 Subject: [PATCH 07/33] Disconnect IMAP clients if only few free FDs left (#37) After network connection to DB server goes down the processing of IMAP session stalls: DB connection pool becomes exhausted as active connections do not close till TCP timeout kicks in (true at least for Oracle). While DBMail still accepts incoming connections it quickly reaches the RLIMIT_NOFILE and becomes unresponsive. Send BYE response if the number of opened FDs reaches the RLIMIT_NOFILE value. --- src/dbmail.h.in | 5 +++++ src/dm_misc.c | 20 ++++++++++++++++++++ src/dm_misc.h | 8 ++++++++ src/imap4.c | 23 ++++++++++++++++++++++- 4 files changed, 55 insertions(+), 1 deletion(-) diff --git src/dbmail.h.in src/dbmail.h.in index d826dc3..17215ef 100644 --- src/dbmail.h.in +++ src/dbmail.h.in @@ -69,12 +69,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -252,6 +254,9 @@ /* input reading linelimit */ #define MAX_LINESIZE (64*1024) +/* minumun number of free file descriptors required to run the daemon */ +#define FREE_DF_THRESHOLD 16 + /* string length for query */ #define DEF_QUERYSIZE (32*1024) #define DEF_FRAGSIZE 256 diff --git src/dm_misc.c src/dm_misc.c index e27ef34..e795de1 100644 --- src/dm_misc.c +++ src/dm_misc.c @@ -104,6 +104,26 @@ int drop_privileges(char *newuser, char *newgroup) return 0; } +int get_opened_fd_count(void) +{ + DIR* dir = NULL; + struct dirent* entry = NULL; + char buf[32]; + int fd_count = 0; + + snprintf(buf, 32, "/proc/%i/fd/", getpid()); + + dir = opendir(buf); + if (dir == NULL) + return -1; + + while ((entry = readdir(dir)) != NULL) + fd_count++; + closedir(dir); + + return fd_count - 2; /* exclude '.' and '..' entries */ +} + void create_unique_id(char *target, uint64_t message_idnr) { char md5_str[FIELDSIZE]; diff --git src/dm_misc.h src/dm_misc.h index 9660dfa..b6cf24f 100644 --- src/dm_misc.h +++ src/dm_misc.h @@ -45,6 +45,14 @@ void g_string_maybe_shrink(GString *s); int drop_privileges(char *newuser, char *newgroup); /** + \brief get the number of opened files (requires /proc mounted) + \return + - -1 on error + - number of opened files +*/ +int get_opened_fd_count(void); + +/** * \brief create a unique id for a message (used for pop, stored per message) * \param target target string. Length should be UID_SIZE * \param message_idnr message_idnr of message diff --git src/imap4.c src/imap4.c index 0532f2e..e523edc 100644 --- src/imap4.c +++ src/imap4.c @@ -351,6 +351,12 @@ static void send_greeting(ImapSession *session) dbmail_imap_session_set_state(session, CLIENTSTATE_NON_AUTHENTICATED); } +static void disconnect_user(ImapSession *session) +{ + imap_session_printf(session, "* BYE [Service unavailable.]\r\n"); + imap_handle_abort(session); +} + /* * the default timeout callback */ @@ -601,6 +607,8 @@ int imap_handle_connection(client_sock *c) { ImapSession *session; ClientBase_T *ci; + struct rlimit fd_limit; + int fd_count; ci = client_init(c); @@ -617,7 +625,20 @@ int imap_handle_connection(client_sock *c) Capa_remove(session->capa, "LOGINDISABLED"); } - send_greeting(session); + fd_count = get_opened_fd_count(); + if (fd_count < 0 || getrlimit(RLIMIT_NPROC, &fd_limit) < 0) { + TRACE(TRACE_ERR, + "[%p] failed to retrieve fd limits, dropping client connection", + session); + disconnect_user(session); + } else if (fd_limit.rlim_cur - fd_count < FREE_DF_THRESHOLD) { + TRACE(TRACE_WARNING, + "[%p] fd count [%d], fd limit [%d], fd threshold [%d]: dropping client connection", + session, fd_count, fd_limit.rlim_cur, FREE_DF_THRESHOLD); + disconnect_user(session); + } else { + send_greeting(session); + } reset_callbacks(session); -- 2.10.1 (Apple Git-78)