First, thanks for your amazing work on MHD!

This question is a near duplicate of the 2014 message thread from Tom
Cornell entitled "Trouble getting a response sent from a separate worker
thread (with external select)".  However, I am not using separate worker
threads--everything is in one thread and so I don't think the
recommendations found in that thread apply to my scenario.

Basically after receiving a request from an HTTP client, I want to be able
to do some asynchronous 'work' which is really just waiting on another
process such as a database engine to calculate and return the result which,
when complete, I will forward back to the client.  This is all done in one
thread using epoll, so I don't want any blocking and I don't want any busy
loops.  MHD's external epoll support combined with suspend/resume fits into
this architecture perfectly, but there's a problem: after resuming the
connection and queueing the data, the headers are sent to the client
immediately, but the body of the response does not get sent until another
client request arrives.

Anyway, to make this all concrete, I've put together a small working
example (below) which shows the problem.  This is built against the latest
dev rev (7f1dbb2) on elementaryOS (which is Ubuntu 16.04).  Every time a
request comes in it suspends the connection and starts a 1 second timer
which, when it expires, resumes the connection.  When the connection is
resumed the response is queued (simply echos the request url).  I realize
this example leaks timer fds and doesn't clean up properly but it
successfully demonstrates the problem.

I have experimented with calling MHD_run() twice after
MHD_resume_connection() rather than the once required by the docs, and that
does seem to work, but that seems extremely hacky and I'm not sure if twice
is enough (why twice and not three times?).  I've skimmed the source code
looking for obvious answers but none are readily apparent to me.

At this point I'm pretty sure that this is a bug with MHD but am I missing
something?

Thanks!
Bob Kocisko

-------------------------

#include "platform.h"
#include <microhttpd.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>

#define TIMEOUT_INFINITE -1

struct Request {
  struct MHD_Connection *connection;
  int timerfd;
};

int epfd;
struct epoll_event evt;

static int
ahc_echo (void *cls,
          struct MHD_Connection *connection,
          const char *url,
          const char *method,
          const char *version,
          const char *upload_data, size_t *upload_data_size, void **ptr)
{
  struct MHD_Response *response;
  int ret;
  struct Request* req;
  struct itimerspec ts;
  (void)url;               /* Unused. Silent compiler warning. */
  (void)version;           /* Unused. Silent compiler warning. */
  (void)upload_data;       /* Unused. Silent compiler warning. */
  (void)upload_data_size;  /* Unused. Silent compiler warning. */

  req = *ptr;
  if (!req)
  {

    req = malloc(sizeof(struct Request));
    req->connection = connection;
    req->timerfd = 0;
    *ptr = req;
    return MHD_YES;
  }

  if (req->timerfd)
  {
    // send response (echo request url)
    response = MHD_create_response_from_buffer (strlen (url),
                                                (void *) url,
                                                MHD_RESPMEM_MUST_COPY);
    ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
    MHD_destroy_response (response);
    return ret;
  }
  else
  {
    // create timer and suspend connection
    req->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
    if (-1 == req->timerfd)
    {
      printf("timerfd_create: %s", strerror(errno));
      return MHD_NO;
    }
    evt.events = EPOLLIN;
    evt.data.ptr = req;
    if (-1 == epoll_ctl(epfd, EPOLL_CTL_ADD, req->timerfd, &evt))
    {
      printf("epoll_ctl: %s", strerror(errno));
      return MHD_NO;
    }
    ts.it_value.tv_sec = 1;
    ts.it_value.tv_nsec = 0;
    ts.it_interval.tv_sec = 0;
    ts.it_interval.tv_nsec = 0;
    if (-1 == timerfd_settime(req->timerfd, 0, &ts, NULL))
    {
      printf("timerfd_settime: %s", strerror(errno));
      return MHD_NO;
    }

    MHD_suspend_connection(connection);
    return MHD_YES;
  }
}

static int
connection_done(struct MHD_Connection *connection,
                void **con_cls,
                enum MHD_RequestTerminationCode toe)
{
  free(*con_cls);
}

int
main (int argc, char *const *argv)
{
  struct MHD_Daemon *d;
  const union MHD_DaemonInfo * info;
  int current_event_count;
  struct epoll_event events_list[1];
  struct Request *req;
  uint64_t timer_expirations;

  if (argc != 2)
    {
      printf ("%s PORT\n", argv[0]);
      return 1;
    }
  d = MHD_start_daemon (MHD_USE_EPOLL | MHD_ALLOW_SUSPEND_RESUME,
                        atoi (argv[1]),
                        NULL, NULL, &ahc_echo, NULL,
                        MHD_OPTION_NOTIFY_COMPLETED, &connection_done, NULL,
MHD_OPTION_END);
  if (d == NULL)
    return 1;

  info = MHD_get_daemon_info(d, MHD_DAEMON_INFO_EPOLL_FD);
  if (info == NULL)
    return 1;

  epfd = epoll_create1(EPOLL_CLOEXEC);
  if (-1 == epfd)
    return 1;

  evt.events = EPOLLIN;
  evt.data.ptr = NULL;
  if (-1 == epoll_ctl(epfd, EPOLL_CTL_ADD, info->epoll_fd, &evt))
    return 1;

  while (1)
  {
    current_event_count = epoll_wait(epfd, events_list, 1,
TIMEOUT_INFINITE);

    if (1 == current_event_count)
    {
      if (events_list[0].data.ptr)
      {
        // A timer has timed out
        req = events_list[0].data.ptr;
        // read from the fd so the system knows we heard the notice
        if (-1 == read(req->timerfd, &timer_expirations,
sizeof(timer_expirations)))
        {
          return 1;
        }
        // Now resume the connection
        MHD_resume_connection(req->connection);
        if (!MHD_run(d))
          return 1;
      }
      else
      {
        // MHD is ready
        if (!MHD_run(d))
          return 1;
      }
    }
    else if (0 == current_event_count)
    {
      // no events: continue
    }
    else
    {
      // error
      return 1;
    }
  }

  return 0;
}

Reply via email to