commit 9605c318522d4432b2ce55029f82ea74c8bb91a6
parent 4cc464be2ea4c61a05129208ffef55df81d02fb8
Author: Christian Grothoff <grothoff@gnunet.org>
Date: Tue, 1 Apr 2025 04:23:37 +0300
test case for external event loop
Diffstat:
3 files changed, 223 insertions(+), 0 deletions(-)
diff --git a/src/tests/client_server/libtest.h b/src/tests/client_server/libtest.h
@@ -742,6 +742,19 @@ MHDT_server_setup_minimal (const void *cls,
/**
+ * Initialize MHD daemon for an external event loop.
+ * Must be used together with #MHDT_server_run_external().
+ *
+ * @param cls closure (use NULL)
+ * @param[in,out] d daemon to initialize
+ * @return error message, NULL on success
+ */
+const char *
+MHDT_server_setup_external (const void *cls,
+ struct MHD_Daemon *d);
+
+
+/**
* Initialize MHD daemon with TLS support, binding to any free port.
*
* @param cls closure
@@ -825,6 +838,20 @@ MHDT_server_run_blocking (void *cls,
/**
+ * Function that runs an MHD daemon with an external event loop until
+ * a read() against @a finsig succeeds.
+ *
+ * @param cls closure
+ * @param finsig fd to read from to detect termination request
+ * @param[in,out] d daemon to run
+ */
+void
+MHDT_server_run_external (void *cls,
+ int finsig,
+ struct MHD_Daemon *d);
+
+
+/**
* Run test suite with @a phases for a daemon initialized
* using @a ss_cb on the local machine.
*
diff --git a/src/tests/client_server/libtest_convenience.c b/src/tests/client_server/libtest_convenience.c
@@ -30,6 +30,7 @@
#include <unistd.h>
#include <errno.h>
#include <curl/curl.h>
+#include <sys/epoll.h>
const char *
@@ -290,3 +291,192 @@ MHDT_server_run_blocking (void *cls,
"Failed to drain termination signal\n");
}
}
+
+
+static int my_epoll_fd = -1;
+
+
+/**
+ * The callback for registration/de-registration of the sockets to watch.
+ *
+ * This callback must not call #MHD_daemon_destroy(), #MHD_daemon_quiesce(),
+ * #MHD_daemon_add_connection().
+ *
+ * @param cls the closure
+ * @param fd the socket to watch
+ * @param watch_for the states of the @a fd to watch, if set to
+ * #MHD_FD_STATE_NONE the socket must be de-registred
+ * @param app_cntx_old the old application defined context for the socket,
+ * NULL if @a fd socket was not registered before
+ * @param ecb_cntx the context handle to be used
+ * with #MHD_daemon_event_update()
+ * @return NULL if error (to connection will be aborted),
+ * or the new socket context
+ * @ingroup event
+ */
+static void *
+update_fd (
+ void *cls,
+ MHD_Socket fd,
+ enum MHD_FdState watch_for,
+ MHD_APP_SOCKET_CNTX_TYPE *app_cntx_old,
+ struct MHD_EventUpdateContext *ecb_cntx)
+{
+ struct epoll_event ev;
+
+ if (watch_for == MHD_FD_STATE_NONE)
+ {
+ epoll_ctl (my_epoll_fd,
+ EPOLL_CTL_DEL,
+ fd,
+ &ev /* for Linux 2.6.9-compatibility */);
+ return NULL;
+ }
+ ev.data.ptr = ecb_cntx;
+ ev.events = 0;
+ if (0 != (watch_for & MHD_FD_STATE_RECV))
+ ev.events |= EPOLLIN;
+ if (0 != (watch_for & MHD_FD_STATE_SEND))
+ ev.events |= EPOLLOUT;
+ if (0 != (watch_for & MHD_FD_STATE_EXCEPT))
+ ev.events |= EPOLLHUP;
+ if (0 !=
+ epoll_ctl (my_epoll_fd,
+ NULL == app_cntx_old
+ ? EPOLL_CTL_ADD
+ : EPOLL_CTL_MOD,
+ fd,
+ &ev))
+ {
+ fprintf (stderr,
+ "epoll_ctl failed: %s\n",
+ strerror (errno));
+ return NULL;
+ }
+ return ecb_cntx;
+}
+
+
+const char *
+MHDT_server_setup_external (const void *cls,
+ struct MHD_Daemon *d)
+{
+ (void) cls;
+ if (MHD_SC_OK !=
+ MHD_DAEMON_SET_OPTIONS (
+ d,
+ MHD_D_OPTION_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL (&update_fd,
+ NULL)))
+ return "Failed to configure external mode!";
+ if (MHD_SC_OK !=
+ MHD_DAEMON_SET_OPTIONS (
+ d,
+ MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO,
+ 0)))
+ return "Failed to bind to port 0!";
+ my_epoll_fd = epoll_create1 (0);
+
+ return NULL;
+}
+
+
+void
+MHDT_server_run_external (void *cls,
+ int finsig,
+ struct MHD_Daemon *d)
+{
+ fd_set r;
+
+ (void) cls; /* Unused */
+ if (-1 == my_epoll_fd)
+ abort ();
+ while (1)
+ {
+ uint_fast64_t next_wait;
+ struct timeval timeout;
+
+ if (MHD_SC_OK !=
+ MHD_daemon_process_reg_events (d,
+ &next_wait))
+ {
+ fprintf (stderr,
+ "MHD_daemon_process_reg_events() failed\n");
+ break;
+ }
+ timeout.tv_sec = next_wait / 1000000;
+ timeout.tv_usec = next_wait % 1000000;
+
+ FD_ZERO (&r);
+ FD_SET (finsig,
+ &r);
+ FD_SET (my_epoll_fd,
+ &r);
+ if ( (-1 ==
+ select ((my_epoll_fd > finsig ? my_epoll_fd : finsig) + 1,
+ &r,
+ NULL,
+ NULL,
+ &timeout)) &&
+ (EAGAIN != errno) )
+ {
+ fprintf (stderr,
+ "Failure in select(): %s\n",
+ strerror (errno));
+ break;
+ }
+ if (FD_ISSET (finsig,
+ &r))
+ break;
+ if (FD_ISSET (my_epoll_fd,
+ &r))
+ {
+ int maxevents = 40;
+ struct epoll_event events[maxevents];
+ int n;
+ int i;
+
+ n = epoll_wait (my_epoll_fd,
+ events,
+ maxevents,
+ 0);
+ if (-1 == n)
+ {
+ fprintf (stderr,
+ "epoll_wait() failed: %s\n",
+ strerror (errno));
+ break;
+ }
+ for (i = 0; i < n; i++)
+ {
+ enum MHD_FdState state = MHD_FD_STATE_NONE;
+
+ if (0 != (events[i].events & EPOLLIN))
+ state |= MHD_FD_STATE_RECV;
+ if (0 != (events[i].events & EPOLLOUT))
+ state |= MHD_FD_STATE_SEND;
+ if (0 != (events[i].events & (EPOLLERR | EPOLLHUP)) )
+ state |= MHD_FD_STATE_EXCEPT;
+ MHD_daemon_event_update (d,
+ events[i].data.ptr,
+ state);
+ }
+ }
+ }
+
+ {
+ char c;
+
+ if ( (FD_ISSET (finsig,
+ &r)) &&
+ (1 != read (finsig,
+ &c,
+ 1)) )
+ {
+ fprintf (stderr,
+ "Failed to drain termination signal\n");
+ }
+ }
+
+ close (my_epoll_fd);
+ my_epoll_fd = -1;
+}
diff --git a/src/tests/client_server/test_client_server.c b/src/tests/client_server/test_client_server.c
@@ -125,6 +125,12 @@ main (int argc, char *argv[])
.server_setup_cls = thread1auto,
.server_runner = &MHDT_server_run_minimal,
},
+ {
+ .label = "external events loop mode, no internal threads",
+ .server_setup = &MHDT_server_setup_external,
+ .server_setup_cls = NULL,
+ .server_runner = &MHDT_server_run_external,
+ },
#if 1
/* FIXME: remove once MHD_daemon_process_blocking
has been implemented */