Hello everyone. I've tried to use an external select, the suspend/resume feature and a detached thread for some specific (slow) requests. The main idea around this, is: the common requests (database CRUDs, html/js/css sending etc.) are processed in the main loop within main thread (application), but, slow requests (large report generation, remote backups etc.) are suspended to be processed in a detached thread, which resumes the connection as soon as the request processing ends. However, I have had some difficulty to solve it because the MHD_queue_response() must be called by the main thread, not by detached thread(s).
After some googling I found two good related topics: 1. https://lists.gnu.org/archive/html/libmicrohttpd/2016-09/msg00000.html 2. https://lists.endsoftwarepatents.org/archive/html/libmicrohttpd/2016-10/msg00011.html The 1. is the same problem I'm trying to solve, but I wouldn't like to use global variables. The 2. is exactly I'm looking for, but I couldn't find any code/example showing how the problem was solved, so I suspect he used the *con_cls instead of global variables to share some list reference to the detached thread(s). After reading these topics I got two doubts. It is a good practice to use a shared vector (or any list + binary search) plus a mutex to manage each detached thread? If so, could the same list be used to be iterated to call the functions MHD_create_response_*() + MHD_queue_response() + MHD_resume_connection() to dispatch their respective responses? I need to take some care not to lose performance by choosing a bad design and, since some members tried to solve the same problem, I decided to ask. I have a draft (in attachment) which I'll use to start/try the design using an external select + suspend/resume + pthread. It works fine if you uncomment the macro INTERNAL_SELECT to use the internal MHD loop or the macro TIMEOUT to use the external one. In this draft, the MHD_queue_response() is called in the detached thread, it is wrong, but it is just to understand how MHD works using external threads + suspend/resume. If you keep both INTERNAL_SELECT and TIMEOUT commented, the first request will never end unless another request arrives, I think a good design should solve it. I would appreciate any good idea about this. If solved, it would be nice to convert it to a MHD example to share the solution for other members. Thank you! P.S.1: to test the draft above, use: $ curl http://localhost:<PORT> # simulates a common request. $ curl http://localhost:<PORT>/sleep # simulates a slow (about 10s) request. P.S.2: I'm studying select()/epoll() and doing some exercises to fully understand them. -- Silvio Clécio
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <pthread.h> #include <microhttpd.h> /*#define INTERNAL_SELECT 1*/ /*#define TIMEOUT 1*/ struct Request { struct MHD_Connection *con; char *url; }; static void * process_cb (void *cls) { struct Request *req = cls; struct MHD_Response *res; const union MHD_ConnectionInfo *info; const char *msg = "hello"; if (0 == strcmp (req->url, "/sleep")) { usleep (1000 * 1000 * 10); /* Simulates a slow processing. */ msg = "sleep"; } res = MHD_create_response_from_buffer (strlen (msg), (void *) msg, MHD_RESPMEM_PERSISTENT); MHD_queue_response (req->con, MHD_HTTP_OK, res); MHD_resume_connection (req->con); info = MHD_get_connection_info (req->con, MHD_CONNECTION_INFO_DAEMON); if (NULL != info) MHD_run (info->daemon); MHD_destroy_response (res); free (req->url); free (req); pthread_exit (NULL); } static int ahc_cb (void *cls, struct MHD_Connection *con, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **ptr) { struct Request *req; pthread_t thrd; (void) cls; /* Unused. Silence compiler warning. */ (void) method; /* Unused. Silence compiler warning. */ (void) version; /* Unused. Silence compiler warning. */ (void) upload_data; /* Unused. Silence compiler warning. */ (void) upload_data_size; /* Unused. Silence compiler warning. */ if (NULL == *ptr) { *ptr = (void *) 1; return MHD_YES; } *ptr = NULL; MHD_suspend_connection (con); req = malloc (sizeof (struct Request)); if (NULL == req) return MHD_NO; req->con = con; req->url = strdup (url); if (NULL == req->url) { free (req); return MHD_NO; } if (0 != pthread_create (&thrd, NULL, process_cb, req)) { free (req->url); free (req); return MHD_NO; } pthread_detach (thrd); return MHD_YES; } int main (int argc, char *const *argv) { struct MHD_Daemon *d; unsigned int flags = MHD_USE_SUSPEND_RESUME | MHD_USE_ERROR_LOG; #ifndef INTERNAL_SELECT struct timeval tv; struct timeval *tvp; fd_set rs; fd_set ws; fd_set es; MHD_socket max; MHD_UNSIGNED_LONG_LONG timeout; int errnum; if (argc != 2) { printf ("%s PORT\n", argv[0]); return 1; } #else flags += MHD_USE_AUTO_INTERNAL_THREAD; #endif d = MHD_start_daemon (flags, 8080, NULL, NULL, &ahc_cb, NULL, MHD_OPTION_END); if (NULL == d) return 1; #ifdef INTERNAL_SELECT (void) getc (stdin); #else while (1) { max = 0; FD_ZERO(&rs); FD_ZERO(&ws); FD_ZERO(&es); timeout = (MHD_UNSIGNED_LONG_LONG) -1; if (MHD_YES != MHD_get_fdset(d, &rs, &ws, &es, &max)) break; if (MHD_YES == MHD_get_timeout (d, &timeout)) { tv.tv_sec = (time_t) (timeout / 1000L); tv.tv_usec = (suseconds_t) ((timeout % 1000L) * 1000L); tvp = &tv; } else { #ifdef TIMEOUT tv.tv_sec = 0; tv.tv_usec = 1000; tvp = &tv; #else tvp = NULL; #endif } if (-1 == select (max + 1, &rs, &ws, &es, tvp)) { errnum = errno; if (EINTR != errnum) { fprintf (stderr, "Aborting due to error during select: %s\n", strerror (errnum)); fflush (stderr); } break; } if (!MHD_run_from_select (d, &rs, &ws, &es)) return 1; } #endif MHD_stop_daemon (d); return 0; }