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;
}