Implemented new API function MHD_run_wait().

This commit is contained in:
Evgeny Grin (Karlson2k)
2021-04-01 17:52:08 +03:00
parent fcca12c270
commit bf25ee3a32
6 changed files with 439 additions and 62 deletions
+5
View File
@@ -1,3 +1,8 @@
Thu 01 Apr 2021 17:46:00 MSK
Added new function MHD_run_wait() useful for single-threaded applications
without other network activity.
Added tests for the new function. -EG
Wed 17 Mar 2021 20:53:33 MSK
Re-factored startup log parameters processing. Warn user if wrong logger
could be used potentially.
+34 -1
View File
@@ -135,7 +135,7 @@ typedef intptr_t ssize_t;
* they are parsed as decimal numbers.
* Example: 0x01093001 = 1.9.30-1.
*/
#define MHD_VERSION 0x00097205
#define MHD_VERSION 0x00097206
/**
* Operational results from MHD calls.
@@ -2698,6 +2698,39 @@ _MHD_EXTERN enum MHD_Result
MHD_run (struct MHD_Daemon *daemon);
/**
* Run websever operation with possible blocking.
* This function do the following: waits for any network event not more than
* specified number of milliseconds, processes all incoming and outgoing
* data, processes new connections, processes any timed-out connection, and
* do other things required to run webserver.
* Once all connections are processed, function returns.
* This function is useful for quick and simple webserver implementation if
* application needs to run a single thread only and does not have any other
* network activity.
* @param daemon the daemon to run
* @param millisec the maximum time in milliseconds to wait for network and
* other events. Note: there is no guarantee that function
* blocks for specified amount of time. The real processing
* time can be shorter (if some data comes earlier) or
* longer (if data processing requires more time, especially
* in the user callbacks).
* If set to '0' then function does not block and processes
* only already available data (if any).
* If set to '-1' then function waits for events
* indefinitely (blocks until next network activity).
* @return #MHD_YES on success, #MHD_NO if this
* daemon was not started with the right
* options for this call or some serious
* unrecoverable error occurs.
* @note Available since #MHD_VERSION 0x00097206
* @ingroup event
*/
enum MHD_Result
MHD_run_wait (struct MHD_Daemon *daemon,
int32_t millisec);
/**
* Run webserver operations. This method should be called by clients
* in combination with #MHD_get_fdset and #MHD_get_timeout() if the
+140 -61
View File
@@ -101,16 +101,17 @@ close_all_connections (struct MHD_Daemon *daemon);
#ifdef EPOLL_SUPPORT
/**
* Do epoll()-based processing (this function is allowed to
* block if @a may_block is set to #MHD_YES).
* Do epoll()-based processing.
*
* @param daemon daemon to run poll loop for
* @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking
* @param millisec the maximum time in milliseconds to wait for events,
* set to '0' for non-blocking processing,
* set to '-1' to wait indefinitely.
* @return #MHD_NO on serious errors, #MHD_YES on success
*/
static enum MHD_Result
MHD_epoll (struct MHD_Daemon *daemon,
int may_block);
int32_t millisec);
#endif /* EPOLL_SUPPORT */
@@ -3834,6 +3835,35 @@ MHD_get_timeout (struct MHD_Daemon *daemon,
}
/**
* Obtain timeout value for polling function for this daemon.
* @remark To be called only from the thread that processes
* daemon's select()/poll()/etc.
*
* @param daemon the daemon to query for timeout
* @param max_timeout the maximum return value (in milliseconds),
* ignored if set to '-1'
* @return timeout value in milliseconds or -1 if no timeout is expected.
*/
static int
get_timeout_millisec_ (struct MHD_Daemon *daemon,
int32_t max_timeout)
{
MHD_UNSIGNED_LONG_LONG ulltimeout;
if (0 == max_timeout)
return 0;
if (MHD_NO == MHD_get_timeout (daemon, &ulltimeout))
return (INT_MAX < max_timeout) ? INT_MAX : (int) max_timeout;
if ( (0 > max_timeout) ||
((uint32_t) max_timeout > ulltimeout) )
return (INT_MAX < ulltimeout) ? INT_MAX : (int) ulltimeout;
return (INT_MAX < max_timeout) ? INT_MAX : (int) max_timeout;
}
/**
* Internal version of #MHD_run_from_select().
*
@@ -3978,7 +4008,7 @@ MHD_run_from_select (struct MHD_Daemon *daemon,
{
#ifdef EPOLL_SUPPORT
enum MHD_Result ret = MHD_epoll (daemon,
MHD_NO);
0);
MHD_cleanup_connections (daemon);
return ret;
@@ -4003,12 +4033,14 @@ MHD_run_from_select (struct MHD_Daemon *daemon,
* and then #internal_run_from_select with the result.
*
* @param daemon daemon to run select() loop for
* @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking
* @param millisec the maximum time in milliseconds to wait for events,
* set to '0' for non-blocking processing,
* set to '-1' to wait indefinitely.
* @return #MHD_NO on serious errors, #MHD_YES on success
*/
static enum MHD_Result
MHD_select (struct MHD_Daemon *daemon,
int may_block)
int32_t millisec)
{
int num_ready;
fd_set rs;
@@ -4017,7 +4049,6 @@ MHD_select (struct MHD_Daemon *daemon,
MHD_socket maxsock;
struct timeval timeout;
struct timeval *tv;
MHD_UNSIGNED_LONG_LONG ltimeout;
int err_state;
MHD_socket ls;
@@ -4033,7 +4064,7 @@ MHD_select (struct MHD_Daemon *daemon,
if ( (0 != (daemon->options & MHD_TEST_ALLOW_SUSPEND_RESUME)) &&
(MHD_NO != resume_suspended_connections (daemon)) &&
(0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) )
may_block = MHD_NO;
millisec = 0;
if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION))
{
@@ -4118,25 +4149,47 @@ MHD_select (struct MHD_Daemon *daemon,
FD_CLR (ls,
&rs);
}
tv = NULL;
if (MHD_NO != err_state)
may_block = MHD_NO;
if (MHD_NO == may_block)
millisec = 0;
tv = NULL;
if (0 == millisec)
{
timeout.tv_usec = 0;
timeout.tv_sec = 0;
tv = &timeout;
}
else if ( (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
(MHD_NO != MHD_get_timeout (daemon, &ltimeout)) )
else
{
/* ltimeout is in ms */
timeout.tv_usec = (ltimeout % 1000) * 1000;
if (ltimeout / 1000 > TIMEVAL_TV_SEC_MAX)
timeout.tv_sec = TIMEVAL_TV_SEC_MAX;
else
timeout.tv_sec = (_MHD_TIMEVAL_TV_SEC_TYPE) (ltimeout / 1000);
tv = &timeout;
MHD_UNSIGNED_LONG_LONG ltimeout;
if ( (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
(MHD_NO != MHD_get_timeout (daemon, &ltimeout)) )
{
tv = &timeout; /* have timeout value */
if ( (0 < millisec) &&
(ltimeout > (MHD_UNSIGNED_LONG_LONG) millisec) )
ltimeout = (MHD_UNSIGNED_LONG_LONG) millisec;
}
else if (0 < millisec)
{
tv = &timeout; /* have timeout value */
ltimeout = (MHD_UNSIGNED_LONG_LONG) millisec;
}
if (NULL != tv)
{ /* have timeout value */
if (ltimeout / 1000 > TIMEVAL_TV_SEC_MAX)
{
timeout.tv_sec = TIMEVAL_TV_SEC_MAX;
timeout.tv_usec = 0;
}
else
{
timeout.tv_sec = (_MHD_TIMEVAL_TV_SEC_TYPE) (ltimeout / 1000);
timeout.tv_usec = (ltimeout % 1000) * 1000;
}
}
}
num_ready = MHD_SYS_select_ (maxsock + 1,
&rs,
@@ -4172,12 +4225,14 @@ MHD_select (struct MHD_Daemon *daemon,
* socket using poll().
*
* @param daemon daemon to run poll loop for
* @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking
* @param millisec the maximum time in milliseconds to wait for events,
* set to '0' for non-blocking processing,
* set to '-1' to wait indefinitely.
* @return #MHD_NO on serious errors, #MHD_YES on success
*/
static enum MHD_Result
MHD_poll_all (struct MHD_Daemon *daemon,
int may_block)
int32_t millisec)
{
unsigned int num_connections;
struct MHD_Connection *pos;
@@ -4189,7 +4244,7 @@ MHD_poll_all (struct MHD_Daemon *daemon,
if ( (0 != (daemon->options & MHD_TEST_ALLOW_SUSPEND_RESUME)) &&
(MHD_NO != resume_suspended_connections (daemon)) )
may_block = MHD_NO;
millisec = 0;
/* count number of connections and thus determine poll set size */
num_connections = 0;
@@ -4200,7 +4255,6 @@ MHD_poll_all (struct MHD_Daemon *daemon,
num_connections += 2;
#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
{
MHD_UNSIGNED_LONG_LONG ltimeout;
unsigned int i;
int timeout;
unsigned int poll_server;
@@ -4243,14 +4297,8 @@ MHD_poll_all (struct MHD_Daemon *daemon,
poll_itc_idx = (int) poll_server;
poll_server++;
}
if (may_block == MHD_NO)
timeout = 0;
else if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) ||
(MHD_NO == MHD_get_timeout (daemon,
&ltimeout)) )
timeout = -1;
else
timeout = (ltimeout > INT_MAX) ? INT_MAX : (int) ltimeout;
timeout = get_timeout_millisec_ (daemon, millisec);
i = 0;
for (pos = daemon->connections_tail; NULL != pos; pos = pos->prev)
@@ -4494,7 +4542,7 @@ MHD_poll (struct MHD_Daemon *daemon,
return MHD_NO;
if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION))
return MHD_poll_all (daemon,
may_block);
may_block ? -1 : 0);
return MHD_poll_listen_socket (daemon,
may_block);
#else
@@ -4685,16 +4733,17 @@ static const char *const epoll_itc_marker = "itc_marker";
/**
* Do epoll()-based processing (this function is allowed to
* block if @a may_block is set to #MHD_YES).
* Do epoll()-based processing.
*
* @param daemon daemon to run poll loop for
* @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking
* @param millisec the maximum time in milliseconds to wait for events,
* set to '0' for non-blocking processing,
* set to '-1' to wait indefinitely.
* @return #MHD_NO on serious errors, #MHD_YES on success
*/
static enum MHD_Result
MHD_epoll (struct MHD_Daemon *daemon,
int may_block)
int32_t millisec)
{
#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
static const char *const upgrade_marker = "upgrade_ptr";
@@ -4704,7 +4753,6 @@ MHD_epoll (struct MHD_Daemon *daemon,
struct epoll_event events[MAX_EVENTS];
struct epoll_event event;
int timeout_ms;
MHD_UNSIGNED_LONG_LONG timeout_ll;
int num_events;
unsigned int i;
MHD_socket ls;
@@ -4790,23 +4838,9 @@ MHD_epoll (struct MHD_Daemon *daemon,
if ( (0 != (daemon->options & MHD_TEST_ALLOW_SUSPEND_RESUME)) &&
(MHD_NO != resume_suspended_connections (daemon)) )
may_block = MHD_NO;
millisec = 0;
if (MHD_NO != may_block)
{
if (MHD_NO != MHD_get_timeout (daemon,
&timeout_ll))
{
if (timeout_ll >= (MHD_UNSIGNED_LONG_LONG) INT_MAX)
timeout_ms = INT_MAX;
else
timeout_ms = (int) timeout_ll;
}
else
timeout_ms = -1;
}
else
timeout_ms = 0;
timeout_ms = get_timeout_millisec_ (daemon, millisec);
/* Reset. New value will be set when connections are processed. */
/* Note: Used mostly for uniformity here as same situation is
@@ -5026,24 +5060,69 @@ MHD_run (struct MHD_Daemon *daemon)
if ( (daemon->shutdown) ||
(0 != (daemon->options & MHD_USE_INTERNAL_POLLING_THREAD)) )
return MHD_NO;
(void) MHD_run_wait (daemon, 0);
return MHD_YES;
}
/**
* Run websever operation with possible blocking.
* This function do the following: waits for any network event not more than
* specified number of milliseconds, processes all incoming and outgoing
* data, processes new connections, processes any timed-out connection, and
* do other things required to run webserver.
* Once all connections are processed, function returns.
* This function is useful for quick and simple webserver implementation if
* application needs to run a single thread only and does not have any other
* network activity.
* @param daemon the daemon to run
* @param millisec the maximum time in milliseconds to wait for network and
* other events. Note: there is no guarantee that function
* blocks for specified amount of time. The real processing
* time can be shorter (if some data comes earlier) or
* longer (if data processing requires more time, especially
* in the user callbacks).
* If set to '0' then function does not block and processes
* only already available data (if any).
* If set to '-1' then function waits for events
* indefinitely (blocks until next network activity).
* @return #MHD_YES on success, #MHD_NO if this
* daemon was not started with the right
* options for this call or some serious
* unrecoverable error occurs.
* @note Available since #MHD_VERSION 0x00097206
* @ingroup event
*/
enum MHD_Result
MHD_run_wait (struct MHD_Daemon *daemon,
int32_t millisec)
{
enum MHD_Result res;
if ( (daemon->shutdown) ||
(0 != (daemon->options & MHD_USE_INTERNAL_POLLING_THREAD)) )
return MHD_NO;
if (0 > millisec)
millisec = -1;
if (0 != (daemon->options & MHD_USE_POLL))
{
MHD_poll (daemon, MHD_NO);
res = MHD_poll_all (daemon, millisec);
MHD_cleanup_connections (daemon);
}
#ifdef EPOLL_SUPPORT
else if (0 != (daemon->options & MHD_USE_EPOLL))
{
MHD_epoll (daemon, MHD_NO);
res = MHD_epoll (daemon, millisec);
MHD_cleanup_connections (daemon);
}
#endif
else
{
MHD_select (daemon, MHD_NO);
res = MHD_select (daemon, millisec);
/* MHD_select does MHD_cleanup_connections already */
}
return MHD_YES;
return res;
}
@@ -5139,10 +5218,10 @@ MHD_polling_thread (void *cls)
MHD_poll (daemon, MHD_YES);
#ifdef EPOLL_SUPPORT
else if (0 != (daemon->options & MHD_USE_EPOLL))
MHD_epoll (daemon, MHD_YES);
MHD_epoll (daemon, -1);
#endif
else
MHD_select (daemon, MHD_YES);
MHD_select (daemon, -1);
MHD_cleanup_connections (daemon);
}
+2
View File
@@ -118,3 +118,5 @@ test_patch11
core
/test_get_iovec
/test_get_iovec11
/test_get_wait
/test_get_wait11
+20
View File
@@ -46,6 +46,8 @@ THREAD_ONLY_TESTS += \
endif
THREAD_ONLY_TESTS += \
test_get_wait \
test_get_wait11 \
test_quiesce \
$(EMPTY_ITEM)
@@ -232,6 +234,24 @@ test_get_sendfile_LDADD = \
$(top_builddir)/src/microhttpd/libmicrohttpd.la \
@LIBCURL@
test_get_wait_SOURCES = \
test_get_wait.c \
mhd_has_in_name.h
test_get_wait_CFLAGS = \
$(PTHREAD_CFLAGS) $(AM_CFLAGS)
test_get_wait_LDADD = \
$(top_builddir)/src/microhttpd/libmicrohttpd.la \
$(PTHREAD_LIBS) @LIBCURL@
test_get_wait11_SOURCES = \
test_get_wait.c \
mhd_has_in_name.h
test_get_wait11_CFLAGS = \
$(PTHREAD_CFLAGS) $(AM_CFLAGS)
test_get_wait11_LDADD = \
$(top_builddir)/src/microhttpd/libmicrohttpd.la \
$(PTHREAD_LIBS) @LIBCURL@
test_urlparse_SOURCES = \
test_urlparse.c mhd_has_in_name.h
test_urlparse_LDADD = \
+238
View File
@@ -0,0 +1,238 @@
/*
This file is part of libmicrohttpd
Copyright (C) 2007, 2009, 2011 Christian Grothoff
Copyright (C) 2016-2021 Karlson2k (Evgeny Grin)
libmicrohttpd is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation; either version 2, or (at your
option) any later version.
libmicrohttpd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with libmicrohttpd; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
/**
* @file test_get_wait.c
* @brief Test 'MHD_run_wait()' function.
* @author Christian Grothoff
* @author Karlson2k (Evgeny Grin)
*/
#include "MHD_config.h"
#include "platform.h"
#include <curl/curl.h>
#include <microhttpd.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include "mhd_has_in_name.h"
#if defined(MHD_CPU_COUNT) && (MHD_CPU_COUNT + 0) < 2
#undef MHD_CPU_COUNT
#endif
#if ! defined(MHD_CPU_COUNT)
#define MHD_CPU_COUNT 2
#endif
/**
* How many rounds of operations do we do for each
* test.
* Check all three types of requests for HTTP/1.1:
* * first request, new connection;
* * "middle" request, existing connection with stay-alive;
* * final request, no data processed after.
*/
#define ROUNDS 3
/**
* Do we use HTTP 1.1?
*/
static int oneone;
/**
* Response to return (re-used).
*/
static struct MHD_Response *response;
/**
* Set to 1 if the worker threads are done.
*/
static volatile int signal_done;
static size_t
copyBuffer (void *ptr,
size_t size, size_t nmemb,
void *ctx)
{
(void) ptr; (void) ctx; /* Unused. Silent compiler warning. */
return size * nmemb;
}
static enum MHD_Result
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 **unused)
{
static int ptr;
const char *me = cls;
enum MHD_Result ret;
(void) url; (void) version; /* Unused. Silent compiler warning. */
(void) upload_data; (void) upload_data_size; /* Unused. Silent compiler warning. */
if (0 != strcmp (me, method))
return MHD_NO; /* unexpected method */
if (&ptr != *unused)
{
*unused = &ptr;
return MHD_YES;
}
*unused = NULL;
ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
if (ret == MHD_NO)
abort ();
return ret;
}
static void *
thread_gets (void *param)
{
CURL *c;
CURLcode errornum;
unsigned int i;
char url[64];
int port = (int) (intptr_t) param;
snprintf (url,
sizeof (url),
"http://127.0.0.1:%d/hello_world",
port);
c = curl_easy_init ();
if (NULL == c)
_exit (99);
curl_easy_setopt (c, CURLOPT_URL, url);
curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
curl_easy_setopt (c, CURLOPT_WRITEDATA, NULL);
curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L);
curl_easy_setopt (c, CURLOPT_TIMEOUT, 15L);
if (oneone)
curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
else
curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 15L);
curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L);
for (i = 0; i < ROUNDS; i++)
{
if (CURLE_OK != (errornum = curl_easy_perform (c)))
{
signal_done = 1;
fprintf (stderr,
"curl_easy_perform failed: `%s'\n",
curl_easy_strerror (errornum));
curl_easy_cleanup (c);
abort ();
}
}
curl_easy_cleanup (c);
signal_done = 1;
return NULL;
}
static int
testRunWaitGet (int port, int poll_flag)
{
pthread_t get_tid;
struct MHD_Daemon *d;
const char *const test_desc = ((poll_flag & MHD_USE_AUTO) ?
"MHD_USE_AUTO" :
(poll_flag & MHD_USE_POLL) ?
"MHD_USE_POLL" :
(poll_flag & MHD_USE_EPOLL) ?
"MHD_USE_EPOLL" :
"select()");
if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT))
port = 0;
printf ("Starting MHD_run_wait() test with MHD in %s polling mode.\n",
test_desc);
signal_done = 0;
d = MHD_start_daemon (MHD_USE_ERROR_LOG | poll_flag,
port, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
if (d == NULL)
abort ();
if (0 == port)
{
const union MHD_DaemonInfo *dinfo;
dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
if ((NULL == dinfo) || (0 == dinfo->port) )
abort ();
port = (int) dinfo->port;
}
if (0 != pthread_create (&get_tid, NULL,
&thread_gets, (void*) (intptr_t) port))
_exit (99);
/* As another thread sets "done" flag after ending of network
* activity, it's required to set positive timeout value for MHD_run_wait().
* Alternatively, to use timeout value "-1" here, another thread should start
* additional connection to wake MHD after setting "done" flag. */
do
{
if (MHD_NO == MHD_run_wait (d, 50))
abort ();
} while (0 == signal_done);
if (0 != pthread_join (get_tid, NULL))
_exit (99);
MHD_stop_daemon (d);
printf ("Test succeeded.\n");
return 0;
}
int
main (int argc, char *const *argv)
{
int port = 1675;
(void) argc; /* Unused. Silent compiler warning. */
if ((NULL == argv) || (0 == argv[0]))
return 99;
oneone = has_in_name (argv[0], "11");
if (oneone)
port += 5;
if (0 != curl_global_init (CURL_GLOBAL_WIN32))
return 2;
response = MHD_create_response_from_buffer (strlen ("/hello_world"),
"/hello_world",
MHD_RESPMEM_MUST_COPY);
testRunWaitGet (port++, 0);
if (MHD_YES == MHD_is_feature_supported (MHD_FEATURE_EPOLL))
testRunWaitGet (port++, MHD_USE_EPOLL);
testRunWaitGet (port++, MHD_USE_AUTO);
MHD_destroy_response (response);
curl_global_cleanup ();
return 0; /* Errors produce abort() or _exit() */
}