View Issue Details

IDProjectCategoryView StatusLast Update
0005661libmicrohttpdlibmicrohttpd external selectpublic2019-04-10 13:26
ReportersilvioprogAssigned ToChristian Grothoff 
PrioritynormalSeverityblockReproducibilityalways
Status resolvedResolutionno change required 
PlatformGNU/LinuxOSXubuntuOS Version18.10
Product Versioncurrent SVN 
Target Version0.9.64Fixed in Version0.9.64 
Summary0005661: Request is never finished when using suspend/resume combined with external select and pthreads
DescriptionHello.

I've tried to process a slow request in a detached thread combined with suspend/resume and external select, but the request is never finished.
Steps To Reproduce1. Build and run the example below.
2. In a terminal tab, perform:
$ curl -w '\n' http://localhost:8080/sleep

3. In other terminal tab, perform (multiple times):
$ curl -w '\n' http://localhost:8080


This first test will work fine, because it is using the internal MHD select. Now, enabled the external select by changing the line 7 from "#define INTERNAL_SELECT 1" to "/* #define INTERNAL_SELECT 1 */" and build/run the example again; repeat the steps 2. and 3. and notice the 2. will stall.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <microhttpd.h>

#define INTERNAL_SELECT 1

#ifndef INTERNAL_SELECT
#include <errno.h>
#endif

#define HELLO_CONTENT "<html><body>hello</body></html>"

#define SLEEP_CONTENT "<html><body>sleep</body></html>"

struct Request {
    struct MHD_Connection *con;
    size_t written;
    size_t total;
};

static void *thr_cb (void *arg)
{
  usleep (1000 * 100);  /* Just to simulate a slow process ... */
  MHD_resume_connection (arg);
  pthread_exit (NULL);
}

static ssize_t crc_cb (void *cls,
                       uint64_t pos,
                       char *buf,
                       size_t max)
{
  struct Request *req = cls;
  pthread_t thr;
  (void) pos;  /* Unused. Silence compiler warning. */
  (void) max;  /* Unused. Silence compiler warning. */
  if (req->written >= req->total)
    {
      return MHD_CONTENT_READER_END_OF_STREAM;
    }
  *buf = SLEEP_CONTENT[req->written++];
  MHD_suspend_connection (req->con);
  if (0 != pthread_create (&thr, NULL, thr_cb, req->con))
    return MHD_NO;
  pthread_detach (thr);
  return MHD_YES;
}

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;
  struct MHD_Response *res;
  (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 *) MHD_YES;
      return MHD_YES;
    }
  *ptr = NULL;
  if (0 == strcmp (url, "/sleep"))
    {
      req = malloc (sizeof (struct Request));
      if (NULL == req)
        return MHD_NO;
      req->con = con;
      req->written = 0;
      req->total = strlen (SLEEP_CONTENT);
      res = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN, 4096,
                                               &crc_cb,
                                               req,
                                               &free);
    }
  else
    {
      res = MHD_create_response_from_buffer (strlen (HELLO_CONTENT),
                                             (void *) HELLO_CONTENT,
                                             MHD_RESPMEM_PERSISTENT);
    }
  MHD_queue_response (con, MHD_HTTP_OK, res);
  MHD_destroy_response (res);
  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,
                        (uint16_t) atoi (argv[1]),
                        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
        {
          tvp = NULL;
        }
      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;
}
Additional InformationNot sure if it is indeed a bug, but I've tried to fix it around a week but no success.

Related to (but using other example): http://lists.gnu.org/archive/html/libmicrohttpd/2019-03/msg00004.html.
TagsNo tags attached.

Activities

Christian Grothoff

2019-03-22 10:01

manager   ~0014234

If you are using the 'external' select, you are responsible for 'kicking' the external select loop back into action when you call MHD_connection_resume(). So you should (for example) add an additional eventfd() file descriptor to the &rs, drain it inside select() and write to it at the time where you call MHD_connection_resume(). Otherwise your main() function will be blocked in select() and MHD has no chance of executing the resume.

silvioprog

2019-03-22 23:16

developer   ~0014235

This is exactly I'm looking for. Thanks a lot for explaining and showing the way to follow. After your explanation I took a look at MHD sources and found an entry for "sys/eventfd.h". Now that I know where to go I'm going to study it to return some feedback. :-)

I've noticed some members looking for it too, so I'll convert it to a MHD example as soon as it is done. I've used IPC before inside NodeJS(JS) and Delphi(Pascal), it is the first time I'll use it in C in a more raw way. Having success I will close this issue followed by the commit with the example.

Thanks again for the help you have given me all these years. :-D

Issue History

Date Modified Username Field Change
2019-03-22 07:52 silvioprog New Issue
2019-03-22 07:57 silvioprog Steps to Reproduce Updated View Revisions
2019-03-22 10:01 Christian Grothoff Note Added: 0014234
2019-03-22 23:16 silvioprog Note Added: 0014235
2019-04-10 13:26 Christian Grothoff Assigned To => Christian Grothoff
2019-04-10 13:26 Christian Grothoff Status new => resolved
2019-04-10 13:26 Christian Grothoff Resolution open => no change required
2019-04-10 13:26 Christian Grothoff Fixed in Version => 0.9.64
2019-04-10 13:26 Christian Grothoff Target Version current SVN => 0.9.64