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

Reply via email to