exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

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:
Msrc/auditor/taler-auditor-httpd.c | 11+++++++++++
Msrc/exchange/taler-exchange-httpd.c | 15+++++++++++++++
Msrc/include/taler/taler_mhd_lib.h | 6+++++-
Msrc/mhd/mhd_run.c | 40++++++++++++++++++++++++++++++++++++++++
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,