Hi Damon,

The problem is that your logic queues a response during the *first*
callback to 'access_handler', and your 'count' protection there is not
working as intended by you.

You need to be a bit careful with the API, the
MHD_OPTION_NOTIF_CONNECTIONCATION is per socket, while what you need for
_that_ counter is MHD_URI_LOG_CALLBACK and MHD_OPTION_NOTIFY_COMPLETED.
We _know_ that some of our names are confusing for historic reasons, and
I hope that if we ever find funding for a "MHD v2.0" we can make the API
_much_ more intuitive.

Anyway, I've attached a revision of your code that fixes the bad
behavior. Basically: make sure not to queue a response "early" (as per
line connection.c:4014), and MHD will be happy to do pipelining. (This
is not really a regression, more of a bugfix, as before this could
result in really bad unwarranted behaviors --- and we always said that
you should ONLY queue 'early' *if* the client requested 100 CONTINUE and
you want to _abort_ the upload. And in that case, we _must_ force a
"Connection: close").

Happy hacking!

Christian

On 1/5/21 6:32 PM, Earp, Damon N. (GSFC-619.0)[SCIENCE SYSTEMS AND
APPLICATIONS INC] via libmicrohttpd wrote:
> Hey all,
> We're witnessing an increase in one time use connections when testing against 
> 0.9.71 and 0.9.72. It appears to be related to suspending a connection 
> immediately then resuming it in a different thread. We are using Linux 
> (Debian 9.13 amd64). I wrote up a minimal test program and attempted to test 
> connection reuse and the connection idle timeout. Below are the results of 
> each test when running with the old and new versions of the library and the 
> test source code.
> 
> For the tests:
> Version 0.9.62 is provided via aptitude and installed under /usr/lib/.
> Version 0.9.71 is custom compiled without providing any arguments to 
> `./configure` and is located in /usr/local/lib. 
> 
> Thanks for all your hard work. Hopefully there is a small fix for this.
> Damon Earp
> 
> --------------------------------------------------------------------------------
> TEST 1 - Connection Reuse
> Client Command: `$ curl -v localhost:8080{,,,,,,,,,,,,,,}`
> 
> libmicrohttpd 0.9.62
> ```
> $ ./idle
> close after 0.0046 seconds, completed 15 requests
> ```
> 
> libmicrohttpd 0.9.71
> ```
> $ LD_LIBRARY_PATH=/usr/local/lib ./idle 
> close after 0.0004 seconds, completed 1 requests
> close after 0.0002 seconds, completed 1 requests
> close after 0.0002 seconds, completed 1 requests
> close after 0.0002 seconds, completed 1 requests
> close after 0.0002 seconds, completed 1 requests
> close after 0.0002 seconds, completed 1 requests
> close after 0.0002 seconds, completed 1 requests
> close after 0.0002 seconds, completed 1 requests
> close after 0.0002 seconds, completed 1 requests
> close after 0.0002 seconds, completed 1 requests
> close after 0.0002 seconds, completed 1 requests
> close after 0.0002 seconds, completed 1 requests
> close after 0.0002 seconds, completed 1 requests
> close after 0.0001 seconds, completed 1 requests
> close after 0.0002 seconds, completed 1 requests
> ```
> 
> --------------------------------------------------------------------------------
> TEST 2 - Idle Connection Timeout of 5 Seconds.
> Client Command: `$ perl -e '$|=1;print "GET /index.html 
> HTTP/1.1\r\n\r\n";while(<>){}' | nc localhost 8080`
> 
> libmicrohttpd 0.9.62
> ```
> $ ./idle 
> close after 5.6223 seconds, completed 1 requests
> ```
> 
> libmicrohttpd 0.9.71
> ```
> $ LD_LIBRARY_PATH=/usr/local/lib ./idle 
> close after 0.0027 seconds, completed 1 requests
> ```
> 
> --------------------------------------------------------------------------------
> Test Program
> Build command: `gcc -Wall -Werror -o idle main.c -lmicrohttpd -pthread`
> 
> ```
> #include <microhttpd.h>
> #include <pthread.h>
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <arpa/inet.h>
> #include <sys/socket.h>
> #include <time.h>
> #include <unistd.h>
> 
> #if MHD_VERSION < 0x00097000 
> #define MHD_RESULT_TYPE int
> #else
> #define MHD_RESULT_TYPE enum MHD_Result
> #endif
> 
> struct connection {
>     struct timespec begin;
>     unsigned count;  
> };
> 
> static void *worker(void *userdata)
> {
>     struct MHD_Connection *con = userdata;
>     MHD_resume_connection(con); 
>     return NULL;
> }
> 
> static void connection_notify(void* cls, struct MHD_Connection* con,
>     void** ctx, enum MHD_ConnectionNotificationCode code)
> {
>     if (code == MHD_CONNECTION_NOTIFY_STARTED)
>     {
>         struct connection *c = calloc(sizeof(*c), 1);
>         clock_gettime(CLOCK_MONOTONIC, &c->begin);
>         *ctx = c; 
>     }
>     else if (code == MHD_CONNECTION_NOTIFY_CLOSED)
>     {
>         struct connection *c = *ctx;
>         struct timespec now, diff;
>         clock_gettime(CLOCK_MONOTONIC, &now);
>         diff.tv_sec = now.tv_sec - c->begin.tv_sec;
>         if ((diff.tv_nsec = now.tv_nsec - c->begin.tv_nsec) < 0)
>         {
>             --diff.tv_sec;
>             diff.tv_nsec += 1000000000;
>         }
>         double secs = (diff.tv_sec * 1000000000 + diff.tv_nsec) / 
> 1000000000.0; 
>         printf("close after %.4lf seconds, completed %u requests\n", secs, 
> c->count);
>         free(c);
>     }
> }
> 
> static MHD_RESULT_TYPE access_handler(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 **con_cls)
> {
>     /*
>      * hackish trick to detect if this is the first or second time the 
> handler 
>      * has been called for a request. 
>      */
>     uintptr_t *count = (void*) con_cls;
>     if ((*count)++ == 0)
>     {
>         ((struct connection*) MHD_get_connection_info(con, 
> MHD_CONNECTION_INFO_SOCKET_CONTEXT)->socket_context)->count++;
>         MHD_suspend_connection(con);
>         pthread_t t;
>         pthread_create(&t, NULL, &worker, con);
>         pthread_detach(t);
>     }
>     else
>     {
>         struct MHD_Response *resp = MHD_create_response_from_buffer(0, NULL, 
> MHD_RESPMEM_PERSISTENT);
>         MHD_queue_response(con, 200, resp);
>         MHD_destroy_response(resp);
>     }
>     return MHD_YES;
> }
> 
> int main(int argc, const char *argv[])
> {
>     int flags = MHD_USE_EPOLL_INTERNAL_THREAD | MHD_USE_ITC | 
> MHD_ALLOW_SUSPEND_RESUME;
>     struct MHD_Daemon *d = MHD_start_daemon(flags, 8080, NULL, NULL, 
>         &access_handler, NULL, 
>         MHD_OPTION_NOTIFY_CONNECTION, &connection_notify, NULL,
>         MHD_OPTION_CONNECTION_TIMEOUT, 5u,
>         MHD_OPTION_END);
>     pause();
>     MHD_stop_daemon(d);
>     return 0;
> }
> ```
> 
#include <microhttpd.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>

#if MHD_VERSION < 0x00097000
#define MHD_RESULT_TYPE int
#else
#define MHD_RESULT_TYPE enum MHD_Result
#endif

struct connection
{
  struct timespec begin;
  unsigned count;
};

struct request
{
  struct timespec begin;
  unsigned count;
};

static void *
worker (void *userdata)
{
  struct MHD_Connection *con = userdata;
  MHD_resume_connection (con);
  return NULL;
}



static void connection_notify(void* cls, struct MHD_Connection* con,
    void** ctx, enum MHD_ConnectionNotificationCode code)
{
    if (code == MHD_CONNECTION_NOTIFY_STARTED)
    {
        struct connection *c = calloc(sizeof(*c), 1);
        clock_gettime(CLOCK_MONOTONIC, &c->begin);
        *ctx = c; 
    }
    else if (code == MHD_CONNECTION_NOTIFY_CLOSED)
    {
        struct connection *c = *ctx;
        struct timespec now, diff;
        clock_gettime(CLOCK_MONOTONIC, &now);
        diff.tv_sec = now.tv_sec - c->begin.tv_sec;
        if ((diff.tv_nsec = now.tv_nsec - c->begin.tv_nsec) < 0)
        {
            --diff.tv_sec;
            diff.tv_nsec += 1000000000;
        }
        double secs = (diff.tv_sec * 1000000000 + diff.tv_nsec) / 1000000000.0; 
        printf("close after %.4lf seconds, completed %u requests\n", secs, c->count);
        free(c);
    }
}


static void *
started_callback (void*cls, const char *uri,
                  struct MHD_Connection*con)
{
  struct request *c = calloc (sizeof(*c), 1);
  clock_gettime (CLOCK_MONOTONIC, &c->begin);
  return c;
}

static void
completed_callback (void *cls,
  struct MHD_Connection *connection,
  void **con_cls,
  enum MHD_RequestTerminationCode toe)
{
  struct request *c = *con_cls;
  struct timespec now, diff;

    clock_gettime (CLOCK_MONOTONIC, &now);
    diff.tv_sec = now.tv_sec - c->begin.tv_sec;
    if ((diff.tv_nsec = now.tv_nsec - c->begin.tv_nsec) < 0)
    {
      --diff.tv_sec;
      diff.tv_nsec += 1000000000;
    }
    double secs = (diff.tv_sec * 1000000000 + diff.tv_nsec) / 1000000000.0;
    printf ("request done after %.4lf seconds\n", secs);
    free (c);
}


static MHD_RESULT_TYPE
access_handler (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 **con_cls)
{
  /*
   * hackish trick to detect if this is the first or second time the handler
   * has been called for a request.
   */
  struct request *ic = (struct request*) *con_cls;

  if (0 == ic->count)
  {
    ic->count++;
    return MHD_YES;
  }
  if (1 == ic->count)
  {
    ic->count++;
    MHD_suspend_connection (con);
    pthread_t t;
    pthread_create (&t, NULL, &worker, con);
    pthread_detach (t);
    return MHD_YES;
  }

  {
    enum MHD_Result res;
    struct MHD_Response *resp = MHD_create_response_from_buffer (0, NULL,
                                                                 MHD_RESPMEM_PERSISTENT);
    res = MHD_queue_response (con, 200, resp);
    MHD_destroy_response (resp);

    ((struct connection*) MHD_get_connection_info(con, MHD_CONNECTION_INFO_SOCKET_CONTEXT)->socket_context)->count++;
    return res;
  }
}


int
main (int argc, const char *argv[])
{
  int flags = MHD_USE_EPOLL_INTERNAL_THREAD | MHD_USE_ITC
              | MHD_ALLOW_SUSPEND_RESUME;
  struct MHD_Daemon *d = MHD_start_daemon (flags, 8080, NULL, NULL,
                                           &access_handler, NULL,
                                           MHD_OPTION_NOTIFY_COMPLETED,
                                           &completed_callback, NULL,
				           MHD_OPTION_URI_LOG_CALLBACK,
                                           &started_callback, NULL,
				           MHD_OPTION_NOTIFY_CONNECTION, &connection_notify, NULL,
                                           MHD_OPTION_CONNECTION_TIMEOUT, 5u,
                                           MHD_OPTION_END);
  pause ();
  MHD_stop_daemon (d);
  return 0;
}

Attachment: signature.asc
Description: OpenPGP digital signature

  • ... Earp, Damon N. (GSFC-619.0)[SCIENCE SYSTEMS AND APPLICATIONS INC] via libmicrohttpd
    • ... Christian Grothoff

Reply via email to