commit 607ba0aec449eb3a04236330c5244df057922b76
parent 9e9941a182d046afe3c7ce7ae459981dc757ca40
Author: Christian Grothoff <christian@grothoff.org>
Date: Sat, 10 Oct 2026 22:39:32 +0200
introduce hard limit on FDs at 1024 to avoid running into select() problems, even if system-wide ulimit is higher
Diffstat:
4 files changed, 71 insertions(+), 1 deletion(-)
diff --git a/src/auditor/taler-auditor-httpd.c b/src/auditor/taler-auditor-httpd.c
@@ -98,6 +98,15 @@
#define UNIX_BACKLOG 500
/**
+ * Maximum number of concurrent connections per HTTP daemon.
+ * TALER_MHD_daemon_start() keeps our descriptors below FD_SETSIZE;
+ * this leaves ample room for the database connection and our other
+ * descriptors. At the limit, MHD stops accepting until a connection
+ * closes.
+ */
+#define CONNECTION_LIMIT 300
+
+/**
* Should we return "Connection: close" in each response?
*/
static int auditor_connection_close;
@@ -1458,6 +1467,8 @@ start_daemon (void *cls,
NULL,
MHD_OPTION_CONNECTION_TIMEOUT,
connection_timeout,
+ MHD_OPTION_CONNECTION_LIMIT,
+ (unsigned int) CONNECTION_LIMIT,
MHD_OPTION_END);
if (NULL == mhd)
{
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c
@@ -92,6 +92,19 @@
#define UNIX_BACKLOG 50
/**
+ * Maximum number of concurrent connections per HTTP daemon.
+ * TALER_MHD_daemon_start() keeps our descriptors below FD_SETSIZE
+ * (1024). 300 connections plus two pipe descriptors per request for
+ * running helpers (we never use curl while doing so) leave 124
+ * descriptors for the database, the secmod helpers, the brief extra
+ * descriptors while creating pipes and everything else. At the
+ * limit, MHD stops accepting, so that other taler-exchange-httpd
+ * processes sharing the listen socket (as high-performance setups do
+ * via systemd) take the connection.
+ */
+#define CONNECTION_LIMIT 300
+
+/**
* How often will we try to connect to the database before giving up?
*/
#define MAX_DB_RETRIES 5
@@ -2912,6 +2925,8 @@ start_daemon (void *cls,
NULL,
MHD_OPTION_CONNECTION_TIMEOUT,
connection_timeout,
+ MHD_OPTION_CONNECTION_LIMIT,
+ (unsigned int) CONNECTION_LIMIT,
(0 == allow_address_reuse)
? MHD_OPTION_END
: MHD_OPTION_LISTENING_ADDRESS_REUSE,
diff --git a/src/include/taler/taler_mhd_lib.h b/src/include/taler/taler_mhd_lib.h
@@ -1066,7 +1066,11 @@ TALER_MHD_listen_bind (const struct GNUNET_CONFIGURATION_Handle *cfg,
/**
- * Start to run an event loop for @a daemon.
+ * Start to run an event loop for @a daemon. As the event loop uses
+ * select(), this also lowers the soft limit on open file descriptors
+ * (RLIMIT_NOFILE) of the process to FD_SETSIZE. Callers should set
+ * an MHD_OPTION_CONNECTION_LIMIT that leaves room below FD_SETSIZE
+ * for the descriptors they use besides those of MHD's connections.
*
* @param daemon the MHD service to run
*/
diff --git a/src/mhd/mhd_run.c b/src/mhd/mhd_run.c
@@ -23,6 +23,8 @@
#include <gnunet/gnunet_json_lib.h>
#include <jansson.h>
#include <microhttpd.h>
+#include <errno.h>
+#include <sys/resource.h>
#include "taler/taler_util.h"
#include "taler/taler_mhd_lib.h"
@@ -152,11 +154,49 @@ prepare_daemon (struct DaemonEntry *de)
}
+/**
+ * Lower the soft limit on open file descriptors to FD_SETSIZE.
+ *
+ * Our event loop is select()-based: neither MHD (as we run it) nor
+ * the GNUnet scheduler can wait on a descriptor numbered FD_SETSIZE
+ * or higher. MHD closes connections it accepts on such descriptors,
+ * the scheduler aborts on an assertion, and libcurl silently stops
+ * waiting on them. With the soft limit at FD_SETSIZE, creating such
+ * a descriptor fails with EMFILE instead: MHD then stops accepting
+ * until a connection closes, and elsewhere just that operation fails.
+ *
+ * FIXME: omit this on platforms with epoll() support once we have
+ * migrated to MHD2 -- provided that by then the GNUnet scheduler no
+ * longer select()s on our other descriptors either.
+ */
+static void
+limit_descriptors (void)
+{
+ struct rlimit rl;
+
+ if (0 != getrlimit (RLIMIT_NOFILE,
+ &rl))
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "getrlimit");
+ return;
+ }
+ if (rl.rlim_cur <= FD_SETSIZE)
+ return;
+ rl.rlim_cur = FD_SETSIZE;
+ if (0 != setrlimit (RLIMIT_NOFILE,
+ &rl))
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+ "setrlimit");
+}
+
+
void
TALER_MHD_daemon_start (struct MHD_Daemon *daemon)
{
struct DaemonEntry *de;
+ limit_descriptors ();
de = GNUNET_new (struct DaemonEntry);
de->mhd = daemon;
GNUNET_CONTAINER_DLL_insert (mhd_head,