View Issue Details

IDProjectCategoryView StatusLast Update
0003924libmicrohttpdexternal even looppublic2021-09-02 17:54
Reporterslimp Assigned ToChristian Grothoff  
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionfixed 
Platformx86_64OSCentOS LinuxOS Version7.1.1503
Product Version0.9.42 
Target Version0.9.43Fixed in Version0.9.43 
Summary0003924: MHD_get_timeout began always return 0
DescriptionIf you will not call MHD_add_connection libmicrohttpd the previous complete response will not drop and after MHD_OPTION_CONNECTION_TIMEOUT seconds you will get MHD_get_timeout() == 0
No one future API call can stop it

It doesn't reproduced on debian 8 with default library set. I can see that response callback destroyed normally on it
Steps To ReproduceBuild with default library set
0. I'm using external accept and use MHD_add_connection to push it to daemon
1. Process single connection with response:
MHD_create_response_from_callback( -1, 4096,
        &ConnectionReader::Read,
        (void *)new ConnectionReader(m_self),
        &ConnectionReader::Free);

Then I have see that connection was destroyed (MHD_OPTION_NOTIFY_COMPLETED called) but ConnectionReader::Free was not called.

2. Wait for timeout given by MHD_OPTION_CONNECTION_TIMEOUT (i have 600 seconds). You should not do MHD_add_connection calls

Now you will always recieve MHD_get_timeout() == 0
TagsNo tags attached.

Activities

slimp

2015-07-29 12:15

reporter   ~0009507

I have used ssl with ipv4:

    opts.push_back(Option(MHD_OPTION_HTTPS_MEM_KEY, 0, key.c_str()));
    opts.push_back(Option(MHD_OPTION_HTTPS_MEM_CERT, 0, cert.c_str()));
    opts.push_back(Option(MHD_OPTION_HTTPS_PRIORITIES, 0, "NORMAL:-VERS-SSL3.0"));
    opts.push_back(Option(MHD_OPTION_NOTIFY_COMPLETED, (intptr_t)&ConnectionData::Free));
    opts.push_back(Option(MHD_OPTION_CONNECTION_TIMEOUT, binding.connection_timeout));
    //opts.push_back(Option(MHD_OPTION_LISTEN_SOCKET, 0));
    //opts.push_back(Option(MHD_OPTION_CONNECTION_LIMIT, 256 + 128));
    opts.push_back(Option(MHD_OPTION_CONNECTION_MEMORY_LIMIT, 1048576));
    opts.push_back(Option(MHD_OPTION_END, 0, nullptr));
    m_daemon = MHD_start_daemon(
            MHD_USE_NO_LISTEN_SOCKET | MHD_USE_SUSPEND_RESUME |
            (ssl ? MHD_USE_SSL : MHD_NO_FLAG) |
            (binding.ip.find(':') == string::npos ? MHD_NO_FLAG : MHD_USE_IPv6),
        binding.port, nullptr, nullptr,
        (!ssl && binding.redirect) ? &Daemon::HTTP_Redirect : &Daemon::HTTP_Main, this,
        MHD_OPTION_ARRAY, &opts[0],
        MHD_OPTION_END);

slimp

2015-07-30 04:05

reporter   ~0009508

Reproduced only inside openvz container ...

Christian Grothoff

2015-08-02 22:00

manager   ~0009517

Why do you think this is incorrect behavior?

MHD_get_timeout() returns MHD_NO (0) if there is no timeout. If there is no active connection (which is correct, as you said, you got the completion callback), then no timeout is the correct response.

Note that the reason why your ::Free handler was not called is probably that you failed to call MHD_response_destroy() *after* MHD_queue_response(), so the reference counter is still > 0 (as you are free to queue the same response object with multiple connections!).

Finally, you should not pass '-1' to create_response_from_callback, but use MHD_SIZE_UNKNOWN.

I'll change this bug to 'feedback': please either clarify the report or confirm that you just didn't quite use (or understand) the API correctly.

slimp

2015-08-04 04:10

reporter   ~0009527

I have managed to reproduce it. Simple example (it was written only to investigate the problem):

#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <microhttpd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define PORT 8888

ssize_t Reader(void *cls, uint64_t pos, char *buf, size_t max) {
  printf("Reader(%llu)\n", pos);
ssize_t res;
  switch (*(int *)cls) {
    case 0:
        printf("call reader\n");
        strcpy(buf, "test");
        res = 4;
        break;
    case 1:
        printf("call reader. No result\n");
        res = 0;
        break;
    case 2:
        printf("call reader\n");
        strcpy(buf, "test");
        res = 4;
        break;
    case 3:
    case 4:
        printf("delay before done\n");
        res = 0;
        break;
    default:
        printf("call reader. finish\n");
        res = MHD_CONTENT_READER_END_OF_STREAM;
  }
  ++(*((int *)cls));
  return res;
}

void Deleter(void *cls) {
    printf("~DROP READER\n");
    free(cls);
}

void DropConnection(void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) {
    printf("~DROP CONNECTION\n");
}

int answer_to_connection (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 **con_cls)
{
  const char *page = "<html><body>Hello, browser!</body></html>";
  struct MHD_Response *response;
  int ret;

    printf("request\n");
  void *arg = malloc(sizeof(int));
  *((int *)arg) = 0;
  response = MHD_create_response_from_callback (-1, 4096, Reader, arg, Deleter);
  ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
  MHD_destroy_response (response);
  return ret;
}

void Logger(void * arg, const char * fmt, va_list ap) {
  vprintf(fmt, ap);
}

int main () {
  struct MHD_Daemon *daemon;

  daemon = MHD_start_daemon (MHD_USE_NO_LISTEN_SOCKET|MHD_USE_DEBUG, PORT, NULL, NULL,
                             &answer_to_connection, NULL,
                MHD_OPTION_NOTIFY_COMPLETED, (intptr_t)&DropConnection, (void *)0,
                MHD_OPTION_EXTERNAL_LOGGER, (intptr_t)&Logger, (void *)0,
                MHD_OPTION_CONNECTION_TIMEOUT, 30, (void *)0,
                MHD_OPTION_END);

  if (NULL == daemon)
    err(1, "daemon error");
  struct sockaddr_in addr;
  bzero(&addr, sizeof(addr));
  addr.sin_family = AF_INET;
  addr.sin_port = htons(PORT);
  addr.sin_addr.s_addr = inet_addr("0.0.0.0");
  int sock = socket(AF_INET, SOCK_STREAM|SOCK_NONBLOCK, 0);
  int opt = 1;
  if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)))
    err(1, "sockopt error");
  if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)))
    err(1, "bind error");
  if (listen(sock, 5))
    err(1, "listen error");
  do {
    int m_max = 0;
    fd_set m_read, m_write, m_except;
    FD_ZERO(&m_read);
    FD_ZERO(&m_write);
    FD_ZERO(&m_except);
    if (MHD_YES != MHD_get_fdset(daemon, &m_read, &m_write, &m_except, &m_max))
    err(1, "fdset");
    if (m_max)
      ++m_max;
    printf("daemon max %d\n", m_max);
    unsigned long long mhd_timeout;
    struct timeval timeout;
    if (MHD_YES == MHD_get_timeout(daemon, &mhd_timeout)) {
        timeout.tv_sec = mhd_timeout / 1000;
        timeout.tv_usec = mhd_timeout % 1000;
        printf("select with timeout %llu\n", mhd_timeout);
    } else {
    timeout.tv_sec = 30;
        timeout.tv_usec = 0;
        printf("select with default timeout 30\n");
    }
    FD_SET(sock, &m_read);
    if (sock + 1 > m_max)
    m_max = sock + 1;
    printf("select max = %d\n", m_max);
    select(m_max, &m_read, &m_write, &m_except, &timeout);
    printf("done\n");
    socklen_t addr_len = sizeof(addr);
    int client = accept(sock, (struct sockaddr *)&addr, &addr_len);
    if (client != -1) {
    printf("new client\n");
    MHD_add_connection(daemon, client, (struct sockaddr *)&addr, addr_len);
    } else
    printf("new data\n");
  } while ( MHD_YES == MHD_run(daemon));

  MHD_stop_daemon (daemon);
  return 0;
}

start it and do request. than you should wait 30 seconds - it will hang. I know that I should use MHD_suspend_connection. But it bring me another problems - i had to control timeout for this connections manually...

slimp

2015-08-04 04:15

reporter   ~0009528

Problem is that you check suspend (was it suspend when I have return 0 in reader callback ?) connections in MHD_get_timeout. But doesn't drop it if timeout happens

Christian Grothoff

2015-08-04 13:52

manager   ~0009529

Ah, I finally even understand what the issue is and when it occurs. Should be fixed in SVN 36201. Thanks for persisting & writing a test.

Christian Grothoff

2021-09-02 17:54

manager   ~0018192

Fix committed to master branch.

Related Changesets

libmicrohttpd: master d5048c22

2015-08-04 15:52

Christian Grothoff


Details Diff
fix 0003924 Affected Issues
0003924
mod - ChangeLog Diff File
mod - src/include/microhttpd.h Diff File
mod - src/microhttpd/daemon.c Diff File

Issue History

Date Modified Username Field Change
2015-07-29 12:12 slimp New Issue
2015-07-29 12:15 slimp Note Added: 0009507
2015-07-30 04:05 slimp Note Added: 0009508
2015-08-02 22:00 Christian Grothoff Note Added: 0009517
2015-08-02 22:00 Christian Grothoff Assigned To => Christian Grothoff
2015-08-02 22:00 Christian Grothoff Status new => feedback
2015-08-04 04:10 slimp Note Added: 0009527
2015-08-04 04:10 slimp Status feedback => assigned
2015-08-04 04:15 slimp Note Added: 0009528
2015-08-04 13:52 Christian Grothoff Note Added: 0009529
2015-08-04 13:52 Christian Grothoff Status assigned => resolved
2015-08-04 13:52 Christian Grothoff Fixed in Version => 0.9.43
2015-08-04 13:52 Christian Grothoff Resolution open => fixed
2015-08-04 13:53 Christian Grothoff Target Version => 0.9.43
2015-09-16 11:00 Christian Grothoff Status resolved => closed
2021-09-02 17:54 Christian Grothoff Changeset attached => libmicrohttpd master d5048c22
2021-09-02 17:54 Christian Grothoff Note Added: 0018192
2024-01-21 13:24 Christian Grothoff Category libmicrohttpd external select => external even loop