diff --git a/src/include/microhttpd2.h b/src/include/microhttpd2.h index 5e13fa8a..48392a37 100644 --- a/src/include/microhttpd2.h +++ b/src/include/microhttpd2.h @@ -4728,7 +4728,7 @@ MHD_FN_PAR_NONNULL_ALL_; * error code otherwise */ #define MHD_daemon_set_option(daemon, option_ptr) \ - MHD_daemon_set_options (daemon, options_ptr, 1) + MHD_daemon_set_options (daemon, option_ptr, 1) /* *INDENT-OFF* */ @@ -6098,10 +6098,10 @@ MHD_response_from_callback (enum MHD_HTTP_StatusCode sc, * needs to be valid while the response is used * @param free_cb the callback to free any allocated data, called * when response is being destroyed, can be NULL - * to skip the free/cleanup callback + * to skip the free/cleanup callback; * @param free_cb_cls the parameter for @a free_cb * @return NULL on error (i.e. invalid arguments, out of memory) - * FIXME: Call free callback on error? + * on error, @a free_cb is NOT called * @ingroup response */ MHD_EXTERN_ struct MHD_Response * @@ -6975,7 +6975,7 @@ MHD_upgraded_recv (struct MHD_UpgradedHandle *MHD_RESTRICT urh, void *MHD_RESTRICT recv_buf, size_t *MHD_RESTRICT received_size, uint_fast64_t max_wait_millisec) -MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_SIZE_(3,2) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_SIZE_ (3,2) MHD_FN_PAR_OUT_ (4); @@ -7030,7 +7030,7 @@ MHD_upgraded_send (struct MHD_UpgradedHandle *MHD_RESTRICT urh, size_t *MHD_RESTRICT sent_size, uint_fast64_t max_wait_millisec, enum MHD_Bool more_data_to_come) -MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_(3,2) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_OUT_ (4); @@ -8365,7 +8365,7 @@ MHD_response_add_auth_basic_challenge ( struct MHD_Response *MHD_RESTRICT response, const char *MHD_RESTRICT realm, enum MHD_Bool prefer_utf8) -MHD_FN_PAR_NONNULL_(2) MHD_FN_PAR_CSTR_ (2); +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2); #ifndef MHD_NO_STATIC_INLINE diff --git a/src/tests/client_server/libtest.h b/src/tests/client_server/libtest.h index b443688b..854bb2a3 100644 --- a/src/tests/client_server/libtest.h +++ b/src/tests/client_server/libtest.h @@ -741,6 +741,19 @@ MHDT_server_setup_minimal (const void *cls, struct MHD_Daemon *d); +/** + * 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. * @@ -824,6 +837,20 @@ MHDT_server_run_blocking (void *cls, struct MHD_Daemon *d); +/** + * 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 index ed7e8eb7..476b1671 100644 --- a/src/tests/client_server/libtest_convenience.c +++ b/src/tests/client_server/libtest_convenience.c @@ -30,6 +30,7 @@ #include #include #include +#include const char * @@ -290,3 +291,200 @@ 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 |= EPOLLRDHUP | EPOLLHUP | EPOLLERR; + 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_usec = next_wait; + + 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 | EPOLLRDHUP)) ) + state |= MHD_FD_STATE_EXCEPT; + MHD_daemon_event_update (d, + events[i].data.ptr, + state); + } + + if (MHD_SC_OK != + MHD_daemon_process_blocking (d, + 1000)) + { + fprintf (stderr, + "Failure running MHD_daemon_process_blocking()\n"); + break; + } + } + } + + { + 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 index d922d311..4c2f6a8a 100644 --- a/src/tests/client_server/test_client_server.c +++ b/src/tests/client_server/test_client_server.c @@ -138,6 +138,12 @@ main (int argc, char *argv[]) .server_setup_cls = external0auto, .server_runner = &MHDT_server_run_blocking, }, + { + .label = "auto-selected external event loop mode, no threads", + .server_setup = &MHDT_server_setup_external, + .server_setup_cls = NULL, + .server_runner = &MHDT_server_run_external, + }, { .label = "END" }