libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

commit 4cc464be2ea4c61a05129208ffef55df81d02fb8
parent 4facc093e133e66bc9aa49311e522badfb688164
Author: Evgeny Grin (Karlson2k) <k2k@drgrin.dev>
Date:   Wed,  9 Apr 2025 13:00:05 +0300

Implemented external events, suspend/resume + some fixes and debug prints

Diffstat:
Msrc/incl_priv/mhd_sys_options.h | 15+++++++++++++++
Msrc/include/d_options.rec | 15+++++++++++++++
Msrc/include/microhttpd2.h | 167+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/include/microhttpd2_generated_daemon_options.h | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/microhttpd2_main.h.in | 16++++++----------
Msrc/include/microhttpd2_preamble.h.in | 132++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Msrc/mhd2/Makefile.am | 4++++
Msrc/mhd2/conn_data_process.c | 13++++++++++++-
Msrc/mhd2/conn_mark_ready.h | 2+-
Msrc/mhd2/daemon_add_conn.c | 195+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Asrc/mhd2/daemon_event_update.c | 174+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/daemon_funcs.c | 10----------
Msrc/mhd2/daemon_funcs.h | 10----------
Msrc/mhd2/daemon_get_info.c | 19+++++++++++++------
Msrc/mhd2/daemon_options.h | 6++++++
Msrc/mhd2/daemon_set_options.c | 3+++
Msrc/mhd2/daemon_start.c | 239++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/mhd2/events_process.c | 847+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/mhd2/events_process.h | 9+++++++++
Asrc/mhd2/extr_events_funcs.c | 44++++++++++++++++++++++++++++++++++++++++++++
Asrc/mhd2/extr_events_funcs.h | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/mhd_conn_socket.h | 2+-
Msrc/mhd2/mhd_connection.h | 30+++++++++++++++++++++++++++---
Msrc/mhd2/mhd_daemon.h | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Asrc/mhd2/mhd_dbg_print.h | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/mhd_dlinked_list.h | 32+++++++++++++++++---------------
Msrc/mhd2/mhd_recv.c | 8+++++++-
Msrc/mhd2/mhd_send.c | 20+++++++++++++++++---
Asrc/mhd2/request_resume.c | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/stream_funcs.c | 116++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/mhd2/stream_funcs.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mhd2/stream_process_request.c | 16++++++++++++++++
Msrc/mhd2/stream_process_states.c | 49+++++++++++++++++++++++++++++++++++++++----------
Msrc/mhd2/sys_sockets_headers.h | 12+++++++++---
34 files changed, 2347 insertions(+), 346 deletions(-)

diff --git a/src/incl_priv/mhd_sys_options.h b/src/incl_priv/mhd_sys_options.h @@ -516,6 +516,21 @@ # endif #endif +#ifndef mhd_DEBUG_POLLING_FDS +/* Use debug-print for FDs polling */ +/* # define mhd_DEBUG_POLLING_FDS 1 */ +#endif + +#ifndef mhd_DEBUG_SUSPEND_RESUME +/* Use debug-print for suspending and resuming of the requests */ +/* # define mhd_DEBUG_SUSPEND_RESUME 1 */ +#endif + +#ifndef mhd_DEBUG_CONN_ADD_CLOSE +/* Use debug-print for adding and closing of the connections */ +/* # define mhd_DEBUG_CONN_ADD_CLOSE 1 */ +#endif + #ifndef MHD_AUTH_DIGEST_DEF_TIMEOUT # define MHD_AUTH_DIGEST_DEF_TIMEOUT 90 #endif /* ! MHD_AUTH_DIGEST_DEF_TIMEOUT */ diff --git a/src/include/d_options.rec b/src/include/d_options.rec @@ -30,6 +30,21 @@ Comment: Select a sockets watch system call used for internal polling. Argument1: enum MHD_SockPollSyscall els Description1: FIXME +Name: reregister_all +Value: 45 +Type: enum MHD_Bool +Comment: Instruct MHD to register all sockets every processing round. ++ ++ By default (this options is not enabled) every processing round (every time ++ when #MHD_daemon_event_update() is called) MHD calls ++ #MHD_SocketRegistrationUpdateCallback only for the new sockets, for ++ the removed sockets and for the updated sockets. ++ Some sockets are registered when #MHD_daemon_start() is called. ++ ++ If this options is enabled, then #MHD_SocketRegistrationUpdateCallback is ++ called for every socket each processing round. No sockets are registered when ++ the daemon is being started. + Name: log_callback Value: 60 Comment: Set a callback to use for logging diff --git a/src/include/microhttpd2.h b/src/include/microhttpd2.h @@ -973,6 +973,12 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_EPOLL_ADD_DAEMON_FDS_FAILURE = 50065 , /** + * Failed to register daemon's FDs (ITC or listening) in the application + * (external event) monitoring + */ + MHD_SC_EXT_EVENT_REG_DAEMON_FDS_FAILURE = 50066 + , + /** * The select() syscall is not available on this platform or in this MHD * build. */ @@ -1049,7 +1055,7 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_ITC_CHECK_FAILED = 500102 , /** - * System reported error conditions on the ITC FD.. + * System reported error conditions on the ITC FD. */ MHD_SC_ITC_STATUS_ERROR = 500104 , @@ -1288,7 +1294,15 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode /** * Unexpected reasons for thread stop */ - MHD_SC_DAEMON_THREAD_STOP_UNEXPECTED = 50350 + MHD_SC_DAEMON_THREAD_STOP_UNEXPECTED = 50351 + , + /** + * Daemon system data is broken (like listen socket was unexpectedly closed). + * The daemon needs to be closed. + * A new daemon can be started as a replacement after closing the current + * daemon. + */ + MHD_SC_DAEMON_SYS_DATA_BROKEN = 50370 , /** * Failed to acquire response mutex lock @@ -1429,6 +1443,12 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_SYSCALL_QUIESCE_REQUIRES_ITC = 60011 , /** + * The option provided or function called can be used only with "external + * events" modes. + */ + MHD_SC_EXTERNAL_EVENT_ONLY = 60012 + , + /** * MHD is closing a connection because the application * logic to generate the response data failed. */ @@ -1654,6 +1674,27 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode * The Digest Auth is not supported due to configuration */ MHD_SC_AUTH_DIGEST_UNSUPPORTED = 60242 + , + /** + * The application failed to register FD for the external events monitoring + */ + MHD_SC_EXTR_EVENT_REG_FAILED = 60243 + , + /** + * The application failed to de-register FD for the external events monitoring + */ + MHD_SC_EXTR_EVENT_DEREG_FAILED = 60244 + , + /** + * The application called #MHD_daemon_event_update() with broken data + */ + MHD_SC_EXTR_EVENT_BROKEN_DATA = 60250 + , + /** + * The application called #MHD_daemon_event_update() with status that + * has not been requested + */ + MHD_SC_EXTR_EVENT_UNEXPECTED_STATUS = 60251 }; /** @@ -2574,10 +2615,10 @@ MHD_FN_PAR_NONNULL_ALL_; /** * The network status of the socket. - * When set by MHD (by #MHD_SocketRegistrationUpdateCallback and + * When set by MHD (by #MHD_SocketRegistrationUpdateCallback or * similar) it indicates a request to watch for specific socket state: - * readiness for receiving the data, readiness for sending the data and/or - * exception state of the socket. + * watch for readiness for receiving the data, watch for readiness for sending + * the data and/or watch for exception state of the socket. * When set by application (and provided for #MHD_daemon_event_update() and * similar) it must indicate the actual status of the socket. * @@ -2656,65 +2697,65 @@ enum MHD_FIXED_ENUM_ MHD_FdState }; /** - * Checks whether specific @a state is enabled in @a var + * Checks whether specific @a state is enabled/set in the @a var */ -#define MHD_FD_STATE_IS_SET(var,state) \ - (MHD_FD_STATE_NONE != \ +#define MHD_FD_STATE_IS_SET(var,state) \ + (MHD_FD_STATE_NONE != \ (((enum MHD_FdState) (var)) & ((enum MHD_FdState) (state)))) /** - * Checks whether RECV is enabled in @a var + * Checks whether RECV is enabled/set in the @a var */ #define MHD_FD_STATE_IS_SET_RECV(var) \ MHD_FD_STATE_IS_SET ((var),MHD_FD_STATE_RECV) /** - * Checks whether SEND is enabled in @a var + * Checks whether SEND is enabled/set in the @a var */ #define MHD_FD_STATE_IS_SET_SEND(var) \ MHD_FD_STATE_IS_SET ((var),MHD_FD_STATE_SEND) /** - * Checks whether EXCEPT is enabled in @a var + * Checks whether EXCEPT is enabled/set in the @a var */ #define MHD_FD_STATE_IS_SET_EXCEPT(var) \ MHD_FD_STATE_IS_SET ((var),MHD_FD_STATE_EXCEPT) /** - * Enable specific @a state in @a var + * Set/enable specific @a state in the @a var */ #define MHD_FD_STATE_SET(var,state) \ (var) = (enum MHD_FdState) ((var) | (state)) /** - * Enable RECV state in @a var + * Set/enable RECV state in the @a var */ #define MHD_FD_STATE_SET_RECV(var) MHD_FD_STATE_SET ((var),MHD_FD_STATE_RECV) /** - * Enable SEND state in @a var + * Set/enable SEND state in the @a var */ #define MHD_FD_STATE_SET_SEND(var) MHD_FD_STATE_SET ((var),MHD_FD_STATE_SEND) /** - * Enable EXCEPT state in @a var + * Set/enable EXCEPT state in the @a var */ #define MHD_FD_STATE_SET_EXCEPT(var) \ MHD_FD_STATE_SET ((var),MHD_FD_STATE_EXCEPT) /** - * Clear/disable specific @a state in @a var + * Clear/disable specific @a state in the @a var */ #define MHD_FD_STATE_CLEAR(var,state) \ (var) = (enum MHD_FdState) ((var) & (((enum MHD_FdState))(~state))) /** - * Clear/disable RECV state in @a var + * Clear/disable RECV state in the @a var */ #define MHD_FD_STATE_CLEAR_RECV(var) \ MHD_FD_STATE_CLEAR ((var),MHD_FD_STATE_RECV) /** - * Clear/disable SEND state in @a var + * Clear/disable SEND state in the @a var */ #define MHD_FD_STATE_CLEAR_SEND(var) \ MHD_FD_STATE_CLEAR ((var),MHD_FD_STATE_SEND) /** - * Clear/disable EXCEPT state in @a var + * Clear/disable EXCEPT state in the @a var */ #define MHD_FD_STATE_CLEAR_EXCEPT(var) \ MHD_FD_STATE_CLEAR ((var),MHD_FD_STATE_EXCEPT) @@ -2750,8 +2791,12 @@ struct MHD_EventUpdateContext; * NULL if @a fd socket was not registered before * @param ecb_cntx the context handle to be used * with #MHD_daemon_event_update() - * @return NULL if error (to connection will be aborted), - * or the new socket context + * @return must be NULL for the removed (de-registred) sockets, + * for new and updated sockets: NULL in case of error (the connection + * will be aborted or daemon failed to start if FD does not belong to + * connection) + * or the new socket context (opaque for MHD, must be non-NULL) + * @sa #MHD_D_OPTION_REREGISTER_ALL * @ingroup event */ typedef MHD_APP_SOCKET_CNTX_TYPE * @@ -2768,8 +2813,9 @@ typedef MHD_APP_SOCKET_CNTX_TYPE * * Update the sockets state. * Must be called for every socket that got state updated. * For #MHD_D_OPTION_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL() mode - * should be called for each socket. - * Available only for daemons stated in + * this function must be called for each socket between any two calls of + * #MHD_daemon_process_reg_events() function. + * Available only for daemons started in * #MHD_D_OPTION_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL or * #MHD_D_OPTION_WM_EXTERNAL_EVENT_LOOP_CB_EDGE modes. * @param daemon the daemon handle @@ -2787,21 +2833,28 @@ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2); /** - * Perform sockets registration, process registered network events. + * Perform all daemon activities based on FDs events provided earlier by + * application via #MHD_daemon_event_update(). * - * This function first processes all registered (by MHD_daemon_event_update()) - * network events (if any) and then calls #MHD_SocketRegistrationUpdateCallback + * This function accepts new connections (if any), performs HTTP communications + * on all active connections, closes connections as needed and performs FDs + * registration updates by calling #MHD_SocketRegistrationUpdateCallback * callback for every socket that needs to be added/updated/removed. * - * Available only for daemons stated in #MHD_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL or + * Available only for daemons started in #MHD_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL or * #MHD_WM_EXTERNAL_EVENT_LOOP_CB_EDGE modes. * + * When used in #MHD_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL mode, application must + * provide all updates by calling #MHD_daemon_event_update() for every + * registered FD between any two calls of this function. + * * @param daemon the daemon handle * @param[out] next_max_wait the optional pointer to receive the next maximum * wait time in microseconds to be used for sockets * polling function, can be NULL * @return #MHD_SC_OK on success, * error code otherwise + * @sa #MHD_D_OPTION_REREGISTER_ALL * @ingroup event */ MHD_EXTERN_ enum MHD_StatusCode @@ -2829,19 +2882,27 @@ enum MHD_FIXED_ENUM_APP_SET_ MHD_WorkMode , /** * Work mode with an external event loop with level triggers. - * Application uses #MHD_SocketRegistrationUpdateCallback, level triggered - * sockets polling (like select() or poll()) and #MHD_daemon_event_update(). + * MHD provides registration of all FDs to be monitored by using + * #MHD_SocketRegistrationUpdateCallback, application performs level triggered + * FDs polling (like select() or poll()), calls function + * #MHD_daemon_event_update() for every registered FD and then calls main + * function MHD_daemon_process_reg_events() to process the data. * Use helper macro #MHD_D_OPTION_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL() to enable * this mode. + * @sa #MHD_D_OPTION_REREGISTER_ALL */ MHD_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL = 8 , /** * Work mode with an external event loop with edge triggers. - * Application uses #MHD_SocketRegistrationUpdateCallback, edge triggered - * sockets polling (like epoll with EPOLLET) and #MHD_daemon_event_update(). + * MHD provides registration of all FDs to be monitored by using + * #MHD_SocketRegistrationUpdateCallback, application performs edge triggered + * sockets polling (like epoll with EPOLLET), calls function + * #MHD_daemon_event_update() for FDs with updated states and then calls main + * function MHD_daemon_process_reg_events() to process the data. * Use helper macro #MHD_D_OPTION_WM_EXTERNAL_EVENT_LOOP_CB_EDGE() to enable * this mode. + * @sa #MHD_D_OPTION_REREGISTER_ALL */ MHD_WM_EXTERNAL_EVENT_LOOP_CB_EDGE = 9 , @@ -3898,12 +3959,19 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_RequestEndedCode , /** * The request was aborted due to the application failed to provide a valid - * resonse. + * response. * @ingroup request */ MHD_REQUEST_ENDED_BY_APP_ERROR = 41 , /** + * The request was aborted due to the application failed to register external + * event monitoring for the connection. + * @ingroup request + */ + MHD_REQUEST_ENDED_BY_EXT_EVENT_ERROR = 42 + , + /** * Error handling the connection due to resources exhausted. * @ingroup request */ @@ -4011,6 +4079,25 @@ MHD_D_OPTION_POLL_SYSCALL ( ); /** + * Instruct MHD to register all sockets every processing round. + * +By default (this options is not enabled) every processing round (every time + * when #MHD_daemon_event_update() is called) MHD calls + * #MHD_SocketRegistrationUpdateCallback only for the new sockets, for + * the removed sockets and for the updated sockets. + * Some sockets are registered when #MHD_daemon_start() is called. + * +If this options is enabled, then #MHD_SocketRegistrationUpdateCallback is + * called for every socket each processing round. No sockets are registered when + * the daemon is being started. + * @param value the value of the parameter * @return structure with the requested setting + */ +struct MHD_DaemonOptionAndValue +MHD_D_OPTION_REREGISTER_ALL ( + enum MHD_Bool value + ); + +/** * Set a callback to use for logging * @param log_cb the callback to use for logging, * NULL to disable logging. @@ -5309,7 +5396,9 @@ MHD_FN_PAR_NONNULL_ (4) MHD_FN_PAR_OUT_SIZE_ (4,3); * @param kind what kind of value are we looking for * @param key the name of the value looking for (used for case-insensetive match), empty to lookup 'trailing' value without a key - * @return NULL if no such item was found + * @return NULL if no such item was found, + * non-NULL if item found (the inner pointer to string can be NULL + * if item found, but has no value) * @ingroup request */ MHD_EXTERN_ const struct MHD_StringNullable * @@ -5626,17 +5715,11 @@ MHD_FN_CONST_; /** - * Resume handling of network data for suspended request. It is - * safe to resume a suspended request at any time. Calling this - * function on a request that was not previously suspended will + * Resume handling of network data for suspended request. + * It is safe to resume a suspended request at any time. + * Calling this function on a request that was not previously suspended will * result in undefined behaviour. * - * If you are using this function in ``external'' select mode, you must make - * sure to run #MHD_daemon_process_blocking() afterwards (as otherwise the - * change may not be reflected in the set returned to your - * MHD_SocketRegistrationUpdateCallback and you may end up with a request - * that is stuck until the next network activity. - * * @param[in,out] request the request to resume */ MHD_EXTERN_ void diff --git a/src/include/microhttpd2_generated_daemon_options.h b/src/include/microhttpd2_generated_daemon_options.h @@ -24,6 +24,22 @@ enum MHD_FIXED_ENUM_APP_SET_ MHD_DaemonOption , /** + * Instruct MHD to register all sockets every processing round. + * +By default (this options is not enabled) every processing round (every time + * when #MHD_daemon_event_update() is called) MHD calls + * #MHD_SocketRegistrationUpdateCallback only for the new sockets, for + * the removed sockets and for the updated sockets. + * Some sockets are registered when #MHD_daemon_start() is called. + * +If this options is enabled, then #MHD_SocketRegistrationUpdateCallback is + * called for every socket each processing round. No sockets are registered when + * the daemon is being started. + */ + MHD_D_O_REREGISTER_ALL = 45 + , + + /** * Set a callback to use for logging */ MHD_D_O_LOG_CALLBACK = 60 @@ -604,6 +620,11 @@ union MHD_DaemonOptionValue enum MHD_SockPollSyscall poll_syscall; /** + * Value for #MHD_D_O_REREGISTER_ALL. + */ + enum MHD_Bool reregister_all; + + /** * Value for #MHD_D_O_LOG_CALLBACK. * the callback to use for logging, * NULL to disable logging. @@ -869,6 +890,28 @@ struct MHD_DaemonOptionAndValue } \ MHD_RESTORE_WARN_COMPOUND_LITERALS_ /** + * Instruct MHD to register all sockets every processing round. + * +By default (this options is not enabled) every processing round (every time + * when #MHD_daemon_event_update() is called) MHD calls + * #MHD_SocketRegistrationUpdateCallback only for the new sockets, for + * the removed sockets and for the updated sockets. + * Some sockets are registered when #MHD_daemon_start() is called. + * +If this options is enabled, then #MHD_SocketRegistrationUpdateCallback is + * called for every socket each processing round. No sockets are registered when + * the daemon is being started. + * @param value the value of the parameter * @return structure with the requested setting + */ +# define MHD_D_OPTION_REREGISTER_ALL(value) \ + MHD_NOWARN_COMPOUND_LITERALS_ \ + (const struct MHD_DaemonOptionAndValue) \ + { \ + .opt = MHD_D_O_REREGISTER_ALL, \ + .val.reregister_all = (value) \ + } \ + MHD_RESTORE_WARN_COMPOUND_LITERALS_ +/** * Set a callback to use for logging * @param log_cb the callback to use for logging, * NULL to disable logging. @@ -1530,6 +1573,34 @@ MHD_D_OPTION_POLL_SYSCALL ( /** + * Instruct MHD to register all sockets every processing round. + * +By default (this options is not enabled) every processing round (every time + * when #MHD_daemon_event_update() is called) MHD calls + * #MHD_SocketRegistrationUpdateCallback only for the new sockets, for + * the removed sockets and for the updated sockets. + * Some sockets are registered when #MHD_daemon_start() is called. + * +If this options is enabled, then #MHD_SocketRegistrationUpdateCallback is + * called for every socket each processing round. No sockets are registered when + * the daemon is being started. + * @param value the value of the parameter * @return structure with the requested setting + */ +static MHD_INLINE struct MHD_DaemonOptionAndValue +MHD_D_OPTION_REREGISTER_ALL ( + enum MHD_Bool value + ) +{ + struct MHD_DaemonOptionAndValue opt_val; + + opt_val.opt = MHD_D_O_REREGISTER_ALL; + opt_val.val.reregister_all = (value); \ + + return opt_val; +} + + +/** * Set a callback to use for logging * @param log_cb the callback to use for logging, * NULL to disable logging. diff --git a/src/include/microhttpd2_main.h.in b/src/include/microhttpd2_main.h.in @@ -687,7 +687,9 @@ MHD_FN_PAR_NONNULL_ (4) MHD_FN_PAR_OUT_SIZE_ (4,3); * @param kind what kind of value are we looking for * @param key the name of the value looking for (used for case-insensetive match), empty to lookup 'trailing' value without a key - * @return NULL if no such item was found + * @return NULL if no such item was found, + * non-NULL if item found (the inner pointer to string can be NULL + * if item found, but has no value) * @ingroup request */ MHD_EXTERN_ const struct MHD_StringNullable * @@ -1004,17 +1006,11 @@ MHD_FN_CONST_; /** - * Resume handling of network data for suspended request. It is - * safe to resume a suspended request at any time. Calling this - * function on a request that was not previously suspended will + * Resume handling of network data for suspended request. + * It is safe to resume a suspended request at any time. + * Calling this function on a request that was not previously suspended will * result in undefined behaviour. * - * If you are using this function in ``external'' select mode, you must make - * sure to run #MHD_daemon_process_blocking() afterwards (as otherwise the - * change may not be reflected in the set returned to your - * MHD_SocketRegistrationUpdateCallback and you may end up with a request - * that is stuck until the next network activity. - * * @param[in,out] request the request to resume */ MHD_EXTERN_ void diff --git a/src/include/microhttpd2_preamble.h.in b/src/include/microhttpd2_preamble.h.in @@ -973,6 +973,12 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_EPOLL_ADD_DAEMON_FDS_FAILURE = 50065 , /** + * Failed to register daemon's FDs (ITC or listening) in the application + * (external event) monitoring + */ + MHD_SC_EXT_EVENT_REG_DAEMON_FDS_FAILURE = 50066 + , + /** * The select() syscall is not available on this platform or in this MHD * build. */ @@ -1049,7 +1055,7 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_ITC_CHECK_FAILED = 500102 , /** - * System reported error conditions on the ITC FD.. + * System reported error conditions on the ITC FD. */ MHD_SC_ITC_STATUS_ERROR = 500104 , @@ -1288,7 +1294,15 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode /** * Unexpected reasons for thread stop */ - MHD_SC_DAEMON_THREAD_STOP_UNEXPECTED = 50350 + MHD_SC_DAEMON_THREAD_STOP_UNEXPECTED = 50351 + , + /** + * Daemon system data is broken (like listen socket was unexpectedly closed). + * The daemon needs to be closed. + * A new daemon can be started as a replacement after closing the current + * daemon. + */ + MHD_SC_DAEMON_SYS_DATA_BROKEN = 50370 , /** * Failed to acquire response mutex lock @@ -1429,6 +1443,12 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode MHD_SC_SYSCALL_QUIESCE_REQUIRES_ITC = 60011 , /** + * The option provided or function called can be used only with "external + * events" modes. + */ + MHD_SC_EXTERNAL_EVENT_ONLY = 60012 + , + /** * MHD is closing a connection because the application * logic to generate the response data failed. */ @@ -1654,6 +1674,27 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode * The Digest Auth is not supported due to configuration */ MHD_SC_AUTH_DIGEST_UNSUPPORTED = 60242 + , + /** + * The application failed to register FD for the external events monitoring + */ + MHD_SC_EXTR_EVENT_REG_FAILED = 60243 + , + /** + * The application failed to de-register FD for the external events monitoring + */ + MHD_SC_EXTR_EVENT_DEREG_FAILED = 60244 + , + /** + * The application called #MHD_daemon_event_update() with broken data + */ + MHD_SC_EXTR_EVENT_BROKEN_DATA = 60250 + , + /** + * The application called #MHD_daemon_event_update() with status that + * has not been requested + */ + MHD_SC_EXTR_EVENT_UNEXPECTED_STATUS = 60251 }; /** @@ -2574,10 +2615,10 @@ MHD_FN_PAR_NONNULL_ALL_; /** * The network status of the socket. - * When set by MHD (by #MHD_SocketRegistrationUpdateCallback and + * When set by MHD (by #MHD_SocketRegistrationUpdateCallback or * similar) it indicates a request to watch for specific socket state: - * readiness for receiving the data, readiness for sending the data and/or - * exception state of the socket. + * watch for readiness for receiving the data, watch for readiness for sending + * the data and/or watch for exception state of the socket. * When set by application (and provided for #MHD_daemon_event_update() and * similar) it must indicate the actual status of the socket. * @@ -2656,65 +2697,65 @@ enum MHD_FIXED_ENUM_ MHD_FdState }; /** - * Checks whether specific @a state is enabled in @a var + * Checks whether specific @a state is enabled/set in the @a var */ -#define MHD_FD_STATE_IS_SET(var,state) \ - (MHD_FD_STATE_NONE != \ +#define MHD_FD_STATE_IS_SET(var,state) \ + (MHD_FD_STATE_NONE != \ (((enum MHD_FdState) (var)) & ((enum MHD_FdState) (state)))) /** - * Checks whether RECV is enabled in @a var + * Checks whether RECV is enabled/set in the @a var */ #define MHD_FD_STATE_IS_SET_RECV(var) \ MHD_FD_STATE_IS_SET ((var),MHD_FD_STATE_RECV) /** - * Checks whether SEND is enabled in @a var + * Checks whether SEND is enabled/set in the @a var */ #define MHD_FD_STATE_IS_SET_SEND(var) \ MHD_FD_STATE_IS_SET ((var),MHD_FD_STATE_SEND) /** - * Checks whether EXCEPT is enabled in @a var + * Checks whether EXCEPT is enabled/set in the @a var */ #define MHD_FD_STATE_IS_SET_EXCEPT(var) \ MHD_FD_STATE_IS_SET ((var),MHD_FD_STATE_EXCEPT) /** - * Enable specific @a state in @a var + * Set/enable specific @a state in the @a var */ #define MHD_FD_STATE_SET(var,state) \ (var) = (enum MHD_FdState) ((var) | (state)) /** - * Enable RECV state in @a var + * Set/enable RECV state in the @a var */ #define MHD_FD_STATE_SET_RECV(var) MHD_FD_STATE_SET ((var),MHD_FD_STATE_RECV) /** - * Enable SEND state in @a var + * Set/enable SEND state in the @a var */ #define MHD_FD_STATE_SET_SEND(var) MHD_FD_STATE_SET ((var),MHD_FD_STATE_SEND) /** - * Enable EXCEPT state in @a var + * Set/enable EXCEPT state in the @a var */ #define MHD_FD_STATE_SET_EXCEPT(var) \ MHD_FD_STATE_SET ((var),MHD_FD_STATE_EXCEPT) /** - * Clear/disable specific @a state in @a var + * Clear/disable specific @a state in the @a var */ #define MHD_FD_STATE_CLEAR(var,state) \ (var) = (enum MHD_FdState) ((var) & (((enum MHD_FdState))(~state))) /** - * Clear/disable RECV state in @a var + * Clear/disable RECV state in the @a var */ #define MHD_FD_STATE_CLEAR_RECV(var) \ MHD_FD_STATE_CLEAR ((var),MHD_FD_STATE_RECV) /** - * Clear/disable SEND state in @a var + * Clear/disable SEND state in the @a var */ #define MHD_FD_STATE_CLEAR_SEND(var) \ MHD_FD_STATE_CLEAR ((var),MHD_FD_STATE_SEND) /** - * Clear/disable EXCEPT state in @a var + * Clear/disable EXCEPT state in the @a var */ #define MHD_FD_STATE_CLEAR_EXCEPT(var) \ MHD_FD_STATE_CLEAR ((var),MHD_FD_STATE_EXCEPT) @@ -2750,8 +2791,12 @@ struct MHD_EventUpdateContext; * NULL if @a fd socket was not registered before * @param ecb_cntx the context handle to be used * with #MHD_daemon_event_update() - * @return NULL if error (to connection will be aborted), - * or the new socket context + * @return must be NULL for the removed (de-registred) sockets, + * for new and updated sockets: NULL in case of error (the connection + * will be aborted or daemon failed to start if FD does not belong to + * connection) + * or the new socket context (opaque for MHD, must be non-NULL) + * @sa #MHD_D_OPTION_REREGISTER_ALL * @ingroup event */ typedef MHD_APP_SOCKET_CNTX_TYPE * @@ -2768,8 +2813,9 @@ typedef MHD_APP_SOCKET_CNTX_TYPE * * Update the sockets state. * Must be called for every socket that got state updated. * For #MHD_D_OPTION_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL() mode - * should be called for each socket. - * Available only for daemons stated in + * this function must be called for each socket between any two calls of + * #MHD_daemon_process_reg_events() function. + * Available only for daemons started in * #MHD_D_OPTION_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL or * #MHD_D_OPTION_WM_EXTERNAL_EVENT_LOOP_CB_EDGE modes. * @param daemon the daemon handle @@ -2787,21 +2833,28 @@ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2); /** - * Perform sockets registration, process registered network events. + * Perform all daemon activities based on FDs events provided earlier by + * application via #MHD_daemon_event_update(). * - * This function first processes all registered (by MHD_daemon_event_update()) - * network events (if any) and then calls #MHD_SocketRegistrationUpdateCallback + * This function accepts new connections (if any), performs HTTP communications + * on all active connections, closes connections as needed and performs FDs + * registration updates by calling #MHD_SocketRegistrationUpdateCallback * callback for every socket that needs to be added/updated/removed. * - * Available only for daemons stated in #MHD_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL or + * Available only for daemons started in #MHD_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL or * #MHD_WM_EXTERNAL_EVENT_LOOP_CB_EDGE modes. * + * When used in #MHD_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL mode, application must + * provide all updates by calling #MHD_daemon_event_update() for every + * registered FD between any two calls of this function. + * * @param daemon the daemon handle * @param[out] next_max_wait the optional pointer to receive the next maximum * wait time in microseconds to be used for sockets * polling function, can be NULL * @return #MHD_SC_OK on success, * error code otherwise + * @sa #MHD_D_OPTION_REREGISTER_ALL * @ingroup event */ MHD_EXTERN_ enum MHD_StatusCode @@ -2829,19 +2882,27 @@ enum MHD_FIXED_ENUM_APP_SET_ MHD_WorkMode , /** * Work mode with an external event loop with level triggers. - * Application uses #MHD_SocketRegistrationUpdateCallback, level triggered - * sockets polling (like select() or poll()) and #MHD_daemon_event_update(). + * MHD provides registration of all FDs to be monitored by using + * #MHD_SocketRegistrationUpdateCallback, application performs level triggered + * FDs polling (like select() or poll()), calls function + * #MHD_daemon_event_update() for every registered FD and then calls main + * function MHD_daemon_process_reg_events() to process the data. * Use helper macro #MHD_D_OPTION_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL() to enable * this mode. + * @sa #MHD_D_OPTION_REREGISTER_ALL */ MHD_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL = 8 , /** * Work mode with an external event loop with edge triggers. - * Application uses #MHD_SocketRegistrationUpdateCallback, edge triggered - * sockets polling (like epoll with EPOLLET) and #MHD_daemon_event_update(). + * MHD provides registration of all FDs to be monitored by using + * #MHD_SocketRegistrationUpdateCallback, application performs edge triggered + * sockets polling (like epoll with EPOLLET), calls function + * #MHD_daemon_event_update() for FDs with updated states and then calls main + * function MHD_daemon_process_reg_events() to process the data. * Use helper macro #MHD_D_OPTION_WM_EXTERNAL_EVENT_LOOP_CB_EDGE() to enable * this mode. + * @sa #MHD_D_OPTION_REREGISTER_ALL */ MHD_WM_EXTERNAL_EVENT_LOOP_CB_EDGE = 9 , @@ -3898,12 +3959,19 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_RequestEndedCode , /** * The request was aborted due to the application failed to provide a valid - * resonse. + * response. * @ingroup request */ MHD_REQUEST_ENDED_BY_APP_ERROR = 41 , /** + * The request was aborted due to the application failed to register external + * event monitoring for the connection. + * @ingroup request + */ + MHD_REQUEST_ENDED_BY_EXT_EVENT_ERROR = 42 + , + /** * Error handling the connection due to resources exhausted. * @ingroup request */ diff --git a/src/mhd2/Makefile.am b/src/mhd2/Makefile.am @@ -46,12 +46,14 @@ libmicrohttpd2_la_SOURCES = \ mhd_buffer.h \ mhd_limits.h \ mhd_iovec.h \ + mhd_dbg_print.h \ mhd_panic.c mhd_panic.h \ mhd_lib_init.c mhd_lib_init_impl.h mhd_lib_init.h \ mhd_lib_init_auto.h \ lib_get_info.c \ mhd_dlinked_list.h \ mhd_conn_socket.h mhd_connection.h \ + mhd_stream.h \ mhd_locks.h \ mhd_itc.c mhd_itc.h mhd_itc_types.h \ mhd_threads.c mhd_threads.h sys_thread_entry_type.h \ @@ -76,6 +78,7 @@ libmicrohttpd2_la_SOURCES = \ daemon_get_info.c \ daemon_add_conn.c daemon_add_conn.h \ daemon_funcs.c daemon_funcs.h \ + daemon_event_update.c extr_events_funcs.c extr_events_funcs.h \ conn_data_process.c conn_data_process.h \ conn_data_recv.c conn_data_recv.h \ conn_data_send.c conn_data_send.h \ @@ -83,6 +86,7 @@ libmicrohttpd2_la_SOURCES = \ conn_get_info.c \ request_funcs.c request_funcs.h \ request_get_value.c request_get_value.h \ + request_resume.c \ respond_with_error.c respond_with_error.h \ request_get_info.c \ response_from.c response_from.h \ diff --git a/src/mhd2/conn_data_process.c b/src/mhd2/conn_data_process.c @@ -58,6 +58,18 @@ mhd_conn_process_recv_send_data (struct MHD_Connection *restrict c) bool has_sock_err; bool data_processed; + data_processed = false; + + mhd_assert (! c->suspended); + if (c->resuming) + { + /* Fully resume the connection + call app callbacks for the data */ + if (! mhd_conn_process_data (c)) + return false; + + data_processed = true; + } + #ifdef MHD_SUPPORT_HTTPS if (mhd_C_HAS_TLS (c)) { @@ -86,7 +98,6 @@ mhd_conn_process_recv_send_data (struct MHD_Connection *restrict c) (0 != (MHD_EVENT_LOOP_INFO_SEND & c->event_loop_info))); has_sock_err = (0 != (mhd_SOCKET_NET_STATE_ERROR_READY & c->sk.ready)); - data_processed = false; if (0 != (MHD_EVENT_LOOP_INFO_RECV & c->event_loop_info)) { diff --git a/src/mhd2/conn_mark_ready.h b/src/mhd2/conn_mark_ready.h @@ -103,7 +103,7 @@ mhd_conn_mark_unready (struct MHD_Connection *restrict c, */ MHD_static_inline_ MHD_FN_PAR_NONNULL_ALL_ void mhd_conn_mark_ready_update3 (struct MHD_Connection *restrict c, - unsigned int force_ready, + bool force_ready, struct MHD_Daemon *restrict d) { if (force_ready || diff --git a/src/mhd2/daemon_add_conn.c b/src/mhd2/daemon_add_conn.c @@ -43,6 +43,9 @@ #include "sys_sockets_headers.h" #include "sys_ip_headers.h" +#ifdef mhd_DEBUG_CONN_ADD_CLOSE +# include <stdio.h> +#endif /* mhd_DEBUG_CONN_ADD_CLOSE */ #include <string.h> #ifdef MHD_SUPPORT_EPOLL # include <sys/epoll.h> @@ -54,6 +57,7 @@ #include "mhd_sockets_funcs.h" #include "mhd_panic.h" +#include "mhd_dbg_print.h" #include "mhd_daemon.h" #include "mhd_connection.h" @@ -77,6 +81,11 @@ /** * Set initial internal states for the connection to start reading and * processing incoming data. + * This sets: + * + data processing stage + * + stream request and reply initial data + * + connection read and write buffers + * * @param c the connection to process */ static void @@ -89,14 +98,28 @@ connection_set_initial_state (struct MHD_Connection *restrict c) c->conn_reuse = mhd_CONN_KEEPALIVE_POSSIBLE; c->event_loop_info = MHD_EVENT_LOOP_INFO_RECV; - memset (&c->rq, 0, sizeof(c->rq)); - memset (&c->rp, 0, sizeof(c->rp)); + // TODO: move request reset to special function + memset (&(c->rq), 0, sizeof(c->rq)); + // TODO: move reply reset to special function + memset (&(c->rp), 0, sizeof(c->rp)); #ifndef HAVE_NULL_PTR_ALL_ZEROS - mhd_DLINKEDL_INIT_LINKS (c, all_conn); - mhd_DLINKEDL_INIT_LINKS (c, proc_ready); - mhd_DLINKEDL_INIT_LINKS (c, by_timeout); - // TODO: set all other pointers manually + // TODO: move request reset to special function + mhd_DLINKEDL_INIT_LIST (&(c->rq), fields); +#ifdef MHD_SUPPORT_POST_PARSER + mhd_DLINKEDL_INIT_LIST (&(c->rq), post_fields); +#endif /* MHD_SUPPORT_POST_PARSER */ + c->rq.version = NULL; + c->rq.url = NULL; + c->rq.field_lines.start = NULL; + c->rq.app_context = NULL; + c->rq.hdrs.rq_line.rq_tgt = NULL; + c->rq.hdrs.rq_line.rq_tgt_qmark = NULL; + + // TODO: move reply reset to special function + c->rp.app_act_ctx.connection = NULL; + c->rp.response = NULL; + c->rp.resp_iov.iov = NULL; #endif /* ! HAVE_NULL_PTR_ALL_ZEROS */ c->write_buffer = NULL; @@ -184,6 +207,17 @@ new_connection_prepare_ (struct MHD_Daemon *restrict daemon, } else { +#ifndef HAVE_NULL_PTR_ALL_ZEROS + mhd_DLINKEDL_INIT_LINKS (c, all_conn); + c->extr_event.app_cntx = NULL; + mhd_DLINKEDL_INIT_LINKS (c, proc_ready); + mhd_DLINKEDL_INIT_LINKS (c, by_timeout); +# ifdef MHD_SUPPORT_UPGRADE + c->upgr.c = NULL; + mhd_DLINKEDL_INIT_LINKS (c, upgr_cleanup); +# endif /* MHD_SUPPORT_UPGRADE */ + c->socket_context = NULL; +#endif /* ! HAVE_NULL_PTR_ALL_ZEROS */ #ifdef MHD_SUPPORT_HTTPS if (0 != tls_data_size) c->tls = (struct mhd_TlsConnData *) (c + 1); @@ -280,6 +314,7 @@ new_connection_prepare_ (struct MHD_Daemon *restrict daemon, /** + * Internal (inner) function. * Finally insert the new connection to the list of connections * served by the daemon and start processing. * @remark To be called only from thread that process @@ -287,11 +322,12 @@ new_connection_prepare_ (struct MHD_Daemon *restrict daemon, * * @param daemon daemon that manages the connection * @param connection the newly created connection - * @return #MHD_YES on success, #MHD_NO on error + * @return #MHD_SC_OK on success, + * error code otherwise */ static enum MHD_StatusCode -new_connection_process_ (struct MHD_Daemon *restrict daemon, - struct MHD_Connection *restrict connection) +new_connection_process_inner (struct MHD_Daemon *restrict daemon, + struct MHD_Connection *restrict connection) { enum MHD_StatusCode res; mhd_assert (connection->daemon == daemon); @@ -325,7 +361,7 @@ new_connection_process_ (struct MHD_Daemon *restrict daemon, daemon->conns.block_new = (daemon->conns.count >= daemon->conns.cfg.count_limit); mhd_DLINKEDL_INS_LAST (&(daemon->conns), connection, all_conn); - if (mhd_WM_INT_INTERNAL_EVENTS_THREAD_PER_CONNECTION != daemon->wmode_int) + if (! mhd_D_HAS_THR_PER_CONN (daemon)) mhd_DLINKEDL_INS_FIRST_D (&(daemon->conns.def_timeout), \ connection, by_timeout); @@ -391,6 +427,11 @@ new_connection_process_ (struct MHD_Daemon *restrict daemon, } else { + mhd_dbg_print_fd_mon_req ("conn", \ + connection->sk.fd, \ + true, \ + true, \ + false); if (0) // TODO: implement turbo { connection->sk.ready = mhd_SOCKET_NET_STATE_RECV_READY @@ -409,7 +450,7 @@ new_connection_process_ (struct MHD_Daemon *restrict daemon, mhd_assert (MHD_SC_OK != res); notify_app_conn (daemon, connection, true); - if (mhd_WM_INT_INTERNAL_EVENTS_THREAD_PER_CONNECTION != daemon->wmode_int) + if (! mhd_D_HAS_THR_PER_CONN (daemon)) mhd_DLINKEDL_DEL_D (&(daemon->conns.def_timeout), \ connection, by_timeout); @@ -438,6 +479,41 @@ new_connection_process_ (struct MHD_Daemon *restrict daemon, /** + * Finally insert the new connection to the list of connections + * served by the daemon and start processing. + * @remark To be called only from thread that process + * daemon's select()/poll()/etc. + * + * @param daemon daemon that manages the connection + * @param connection the newly created connection + * @return #MHD_SC_OK on success, + * error code otherwise + */ +static enum MHD_StatusCode +new_connection_process_ (struct MHD_Daemon *restrict daemon, + struct MHD_Connection *restrict connection) +{ + enum MHD_StatusCode res; + + res = new_connection_process_inner (daemon, + connection); +#ifdef mhd_DEBUG_CONN_ADD_CLOSE + if (MHD_SC_OK == res) + fprintf (stderr, + "&&& Added new connection, FD: %2llu\n", + (unsigned long long) connection->sk.fd); + else + fprintf (stderr, + "&&& Failed add connection, FD: %2llu -> %u\n", + (unsigned long long) connection->sk.fd, + (unsigned int) res); +#endif /* mhd_DEBUG_CONN_ADD_CLOSE */ + + return res; +} + + +/** * The given client socket will be managed (and closed!) by MHD after * this call and must no longer be used directly by the application * afterwards. @@ -777,9 +853,9 @@ mhd_daemon_accept_connection (struct MHD_Daemon *restrict daemon) bool sk_spipe_supprs; bool sk_cloexec; enum mhd_Tristate sk_non_ip; -#if defined(_DEBUG) && defined (USE_ACCEPT4) +#if defined(_DEBUG) && defined (mhd_USE_ACCEPT4) const bool use_accept4 = ! daemon->dbg.avoid_accept4; -#elif defined (USE_ACCEPT4) +#elif defined (mhd_USE_ACCEPT4) static const bool use_accept4 = true; #else /* ! USE_ACCEPT4 && ! _DEBUG */ static const bool use_accept4 = false; @@ -793,6 +869,7 @@ mhd_daemon_accept_connection (struct MHD_Daemon *restrict daemon) fd = daemon->net.listen.fd; mhd_assert (MHD_INVALID_SOCKET != fd); + mhd_assert (! daemon->net.listen.is_broken); addrlen = (socklen_t) sizeof (addrstorage); memset (&addrstorage, @@ -808,44 +885,47 @@ mhd_daemon_accept_connection (struct MHD_Daemon *restrict daemon) sk_cloexec = false; s = MHD_INVALID_SOCKET; -#ifdef USE_ACCEPT4 - if (use_accept4 && - (MHD_INVALID_SOCKET != - (s = accept4 (fd, - (struct sockaddr *) &addrstorage, - &addrlen, - SOCK_CLOEXEC_OR_ZERO | SOCK_NONBLOCK_OR_ZERO - | SOCK_NOSIGPIPE_OR_ZERO)))) +#ifdef mhd_USE_ACCEPT4 + if (use_accept4) { - sk_nonbl = (SOCK_NONBLOCK_OR_ZERO != 0); + s = accept4 (fd, + (struct sockaddr *) &addrstorage, + &addrlen, + mhd_SOCK_CLOEXEC | mhd_SOCK_NONBLOCK | mhd_SOCK_NOSIGPIPE); + if (MHD_INVALID_SOCKET != s) + { + sk_nonbl = (mhd_SOCK_NONBLOCK != 0); #ifndef MHD_SOCKETS_KIND_WINSOCK - sk_spipe_supprs = (SOCK_NOSIGPIPE_OR_ZERO != 0); + sk_spipe_supprs = (mhd_SOCK_NOSIGPIPE != 0); #else /* MHD_SOCKETS_KIND_WINSOCK */ - sk_spipe_supprs = true; /* Nothing to suppress on W32 */ + sk_spipe_supprs = true; /* Nothing to suppress on W32 */ #endif /* MHD_SOCKETS_KIND_WINSOCK */ - sk_cloexec = (SOCK_CLOEXEC_OR_ZERO != 0); + sk_cloexec = (mhd_SOCK_CLOEXEC != 0); + } } -#endif /* USE_ACCEPT4 */ -#if defined(_DEBUG) || ! defined(USE_ACCEPT4) - if (! use_accept4 && - (MHD_INVALID_SOCKET != - (s = accept (fd, - (struct sockaddr *) &addrstorage, - &addrlen)))) +#endif /* mhd_USE_ACCEPT4 */ +#if ! defined(mhd_USE_ACCEPT4) || defined(_DEBUG) + if (! use_accept4) { -#ifdef MHD_ACCEPT_INHERIT_NONBLOCK - sk_nonbl = daemon->listen_nonblk; -#else /* ! MHD_ACCEPT_INHERIT_NONBLOCK */ - sk_nonbl = false; -#endif /* ! MHD_ACCEPT_INHERIT_NONBLOCK */ + s = accept (fd, + (struct sockaddr *) &addrstorage, + &addrlen); + if (MHD_INVALID_SOCKET != s) + { +#ifdef MHD_ACCEPTED_INHERITS_NONBLOCK + sk_nonbl = daemon->net.listen.non_block; +#else /* ! MHD_ACCEPTED_INHERITS_NONBLOCK */ + sk_nonbl = false; +#endif /* ! MHD_ACCEPTED_INHERITS_NONBLOCK */ #ifndef MHD_SOCKETS_KIND_WINSOCK - sk_spipe_supprs = false; + sk_spipe_supprs = false; #else /* MHD_SOCKETS_KIND_WINSOCK */ - sk_spipe_supprs = true; /* Nothing to suppress on W32 */ + sk_spipe_supprs = true; /* Nothing to suppress on W32 */ #endif /* MHD_SOCKETS_KIND_WINSOCK */ - sk_cloexec = false; + sk_cloexec = false; + } } -#endif /* _DEBUG || !USE_ACCEPT4 */ +#endif /* !mhd_USE_ACCEPT4 || _DEBUG */ if (MHD_INVALID_SOCKET == s) { /* This could be a common occurrence with multiple worker threads */ @@ -934,20 +1014,22 @@ mhd_daemon_accept_connection (struct MHD_Daemon *restrict daemon) #endif /* HAVE_INET6 */ } - if (! sk_nonbl && ! mhd_socket_nonblocking (s)) - { - mhd_LOG_MSG (daemon, MHD_SC_ACCEPT_CONFIGURE_NONBLOCKING_FAILED, \ - "Failed to set nonblocking mode on incoming connection " \ - "socket."); + if (! sk_nonbl) + { /* Was not set automatically */ + sk_nonbl = mhd_socket_nonblocking (s); + if (! sk_nonbl) + mhd_LOG_MSG (daemon, MHD_SC_ACCEPT_CONFIGURE_NONBLOCKING_FAILED, \ + "Failed to set nonblocking mode on " + "new connection socket."); } - else - sk_nonbl = true; - if (! sk_cloexec && ! mhd_socket_noninheritable (s)) - { - mhd_LOG_MSG (daemon, MHD_SC_ACCEPT_CONFIGURE_NOINHERIT_FAILED, \ - "Failed to set non-inheritable mode on incoming connection " \ - "socket."); + if (! sk_cloexec) + { /* Was not set automatically */ + sk_cloexec = mhd_socket_noninheritable (s); + if (! sk_cloexec) + mhd_LOG_MSG (daemon, MHD_SC_ACCEPT_CONFIGURE_NOINHERIT_FAILED, \ + "Failed to set non-inheritable mode on " + "new connection socket."); } #if defined(mhd_socket_nosignal) @@ -962,7 +1044,7 @@ mhd_daemon_accept_connection (struct MHD_Daemon *restrict daemon) if (! daemon->sigpipe_blocked) { (void) MHD_socket_close_ (s); - return MHD_NO; + return mhd_DAEMON_ACCEPT_FAILED; } #endif /* HAVE_DCLR_MSG_NOSIGNAL */ } @@ -1060,6 +1142,11 @@ mhd_conn_close_final (struct MHD_Connection *restrict c) if (NULL != c->sk.addr.data) free (c->sk.addr.data); mhd_socket_close (c->sk.fd); +#ifdef mhd_DEBUG_CONN_ADD_CLOSE + fprintf (stderr, + "&&& Closed connection, FD: %2llu\n", + (unsigned long long) c->sk.fd); +#endif /* mhd_DEBUG_CONN_ADD_CLOSE */ free (c); } diff --git a/src/mhd2/daemon_event_update.c b/src/mhd2/daemon_event_update.c @@ -0,0 +1,174 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2025 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/daemon_event_update.c + * @brief The implementation of MHD_daemon_event_update() function for external + * events updates + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +#include "mhd_assert.h" + +#include "mhd_daemon.h" +#include "mhd_connection.h" + +#include "daemon_logger.h" + +#ifdef mhd_DEBUG_POLLING_FDS +# include "mhd_itc.h" +# include <stdio.h> +#endif /* mhd_DEBUG_POLLING_FDS */ + +#include "mhd_public_api.h" + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) void +MHD_daemon_event_update ( + struct MHD_Daemon *MHD_RESTRICT daemon, + struct MHD_EventUpdateContext *MHD_RESTRICT ecb_cntx, + enum MHD_FdState fd_current_state) +{ + bool broken_app_data; + bool unneeded_event; + + if (mhd_DAEMON_STATE_STARTED > daemon->state) + return; + if (! mhd_WM_INT_HAS_EXT_EVENTS (daemon->wmode_int)) + return; /* FIXME: log error? */ + if (mhd_DAEMON_STATE_STARTED < daemon->state) + return; + +#ifdef mhd_DEBUG_POLLING_FDS + if (1) + { + char state_str[] = "x:x:x"; + state_str[0] = MHD_FD_STATE_IS_SET_RECV (fd_current_state) ? 'R' : '-'; + state_str[2] = MHD_FD_STATE_IS_SET_SEND (fd_current_state) ? 'W' : '-'; + state_str[4] = MHD_FD_STATE_IS_SET_EXCEPT (fd_current_state) ? 'E' : '-'; + + switch ((mhd_SockRelMarker) ecb_cntx) + { + case mhd_SOCKET_REL_MARKER_EMPTY: + fprintf (stderr, + "### MHD_daemon_event_update(daemon, [unknown], %s)\n", + state_str); + break; + case mhd_SOCKET_REL_MARKER_ITC: + fprintf (stderr, + "### MHD_daemon_event_update(daemon, [ITC: %2llu], %s)\n", + (unsigned long long) mhd_itc_r_fd (daemon->threading.itc), + state_str); + break; + case mhd_SOCKET_REL_MARKER_LISTEN: + fprintf (stderr, + "### MHD_daemon_event_update(daemon, [lstn: %2llu], %s)\n", + (unsigned long long) daemon->net.listen.fd, + state_str); + break; + default: + fprintf (stderr, + "### MHD_daemon_event_update(daemon, [conn: %2llu], %s)\n", + (unsigned long long) + (((struct MHD_Connection *) ecb_cntx)->sk.fd), + state_str); + break; + } + } +#endif /* mhd_DEBUG_POLLING_FDS */ + + broken_app_data = false; + unneeded_event = false; + + switch ((mhd_SockRelMarker) ecb_cntx) + { + case mhd_SOCKET_REL_MARKER_EMPTY: + broken_app_data = true; + break; + case mhd_SOCKET_REL_MARKER_ITC: +#ifdef MHD_SUPPORT_THREADS + if (MHD_FD_STATE_IS_SET_EXCEPT (fd_current_state)) + daemon->events.data.extr.itc_data.is_broken = true; + else + { + daemon->events.data.extr.itc_data.is_active = + MHD_FD_STATE_IS_SET_RECV (fd_current_state); + unneeded_event = MHD_FD_STATE_IS_SET_SEND (fd_current_state); + } +#else /* ! MHD_SUPPORT_THREADS */ + broken_app_data = true; +#endif /* ! MHD_SUPPORT_THREADS */ + break; + case mhd_SOCKET_REL_MARKER_LISTEN: + if (MHD_INVALID_SOCKET == daemon->net.listen.fd) + broken_app_data = true; + else if (MHD_FD_STATE_IS_SET_EXCEPT (fd_current_state)) + daemon->net.listen.is_broken = true; + else + { + daemon->events.accept_pending = + MHD_FD_STATE_IS_SET_RECV (fd_current_state); + unneeded_event = MHD_FD_STATE_IS_SET_SEND (fd_current_state); + } + break; + default: + if (((struct MHD_Connection *) ecb_cntx)->daemon != daemon) + broken_app_data = true; + else + { + struct MHD_Connection *const c = ((struct MHD_Connection *) ecb_cntx); + unsigned int err_flag; + + mhd_assert (MHD_FD_STATE_NONE != c->extr_event.reg_for); + + unneeded_event = (0 != ((~((unsigned int) c->extr_event.reg_for)) + & ((unsigned int) fd_current_state))); + + /* Preserve connection's "error flag" */ + err_flag = (((unsigned int) c->sk.ready) + & (unsigned int) mhd_SOCKET_NET_STATE_ERROR_READY); + + c->sk.ready = + (enum mhd_SocketNetState) + (err_flag | (((unsigned int) fd_current_state) + & ((unsigned int) c->extr_event.reg_for))); + } + break; + } + + if (broken_app_data) + { + mhd_LOG_MSG (daemon, \ + MHD_SC_EXTR_EVENT_BROKEN_DATA, \ + "MHD_daemon_event_update() is called with broken content " \ + "data"); + } + else if (unneeded_event) + { + mhd_LOG_MSG (daemon, \ + MHD_SC_EXTR_EVENT_BROKEN_DATA, \ + "MHD_daemon_event_update() is called with status that has " \ + "not been requested"); + } +} diff --git a/src/mhd2/daemon_funcs.c b/src/mhd2/daemon_funcs.c @@ -68,16 +68,6 @@ mhd_daemon_trigger_itc (struct MHD_Daemon *restrict d) #endif /* MHD_SUPPORT_THREADS */ -MHD_NORETURN_ // TODO: implement -MHD_INTERNAL -MHD_FN_PAR_NONNULL_ALL_ void -mhd_daemon_resume_conns (struct MHD_Daemon *restrict d) -{ - (void) d; - mhd_assert (0 && "Not implemented yet"); -} - - MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_MUST_CHECK_RESULT_ bool mhd_daemon_claim_lbuf (struct MHD_Daemon *d, diff --git a/src/mhd2/daemon_funcs.h b/src/mhd2/daemon_funcs.h @@ -64,16 +64,6 @@ mhd_daemon_trigger_itc (struct MHD_Daemon *restrict d); /** - * Check whether any resuming connections are pending and resume them - * @param d the daemon to use - */ -MHD_NORETURN_ // TODO: implement -MHD_INTERNAL void -mhd_daemon_resume_conns (struct MHD_Daemon *restrict d) -MHD_FN_PAR_NONNULL_ALL_; - - -/** * Request allocation of the large buffer * @param d the daemon to use * @param requested_size the requested size of allocation diff --git a/src/mhd2/daemon_get_info.c b/src/mhd2/daemon_get_info.c @@ -60,7 +60,8 @@ MHD_daemon_get_info_fixed_sz ( switch (info_type) { case MHD_DAEMON_INFO_FIXED_BIND_PORT: - if (MHD_INVALID_SOCKET == daemon->net.listen.fd) + if ((MHD_INVALID_SOCKET == daemon->net.listen.fd) + && ! daemon->net.listen.is_broken) return MHD_SC_INFO_GET_TYPE_NOT_APPLICABLE; if (mhd_SOCKET_TYPE_UNKNOWN > daemon->net.listen.type) return MHD_SC_INFO_GET_TYPE_NOT_APPLICABLE; @@ -75,11 +76,17 @@ MHD_daemon_get_info_fixed_sz ( output_buf->v_bind_port_uint16 = daemon->net.listen.port; return MHD_SC_OK; case MHD_DAEMON_INFO_FIXED_LISTEN_SOCKET: - if (MHD_INVALID_SOCKET == daemon->net.listen.fd) - return MHD_SC_INFO_GET_TYPE_NOT_APPLICABLE; - if (sizeof(output_buf->v_listen_socket) > output_buf_size) - return MHD_SC_INFO_GET_BUFF_TOO_SMALL; - output_buf->v_listen_socket = daemon->net.listen.fd; + if (1) + { + MHD_Socket listen_fd = daemon->net.listen.fd; + if (MHD_INVALID_SOCKET == listen_fd) + return daemon->net.listen.is_broken ? + MHD_SC_INFO_GET_TYPE_UNOBTAINABLE : + MHD_SC_INFO_GET_TYPE_NOT_APPLICABLE; + if (sizeof(output_buf->v_listen_socket) > output_buf_size) + return MHD_SC_INFO_GET_BUFF_TOO_SMALL; + output_buf->v_listen_socket = listen_fd; + } return MHD_SC_OK; case MHD_DAEMON_INFO_FIXED_AGGREAGATE_FD: #ifdef MHD_SUPPORT_EPOLL diff --git a/src/mhd2/daemon_options.h b/src/mhd2/daemon_options.h @@ -29,6 +29,12 @@ struct DaemonOptions /** + * Value for #MHD_D_O_REREGISTER_ALL. + */ + enum MHD_Bool reregister_all; + + + /** * Value for #MHD_D_O_LOG_CALLBACK. * the callback to use for logging, * NULL to disable logging. diff --git a/src/mhd2/daemon_set_options.c b/src/mhd2/daemon_set_options.c @@ -44,6 +44,9 @@ MHD_daemon_set_options ( case MHD_D_O_POLL_SYSCALL: settings->poll_syscall = option->val.poll_syscall; continue; + case MHD_D_O_REREGISTER_ALL: + settings->reregister_all = option->val.reregister_all; + continue; case MHD_D_O_LOG_CALLBACK: /* Note: set directly to the daemon */ daemon->log_params = option->val.log_callback; diff --git a/src/mhd2/daemon_start.c b/src/mhd2/daemon_start.c @@ -62,6 +62,10 @@ # endif #endif +#include "extr_events_funcs.h" + +#include "mhd_dbg_print.h" + #include "mhd_limits.h" #include "mhd_daemon.h" @@ -79,10 +83,11 @@ # include "mhd_tls_funcs.h" #endif +#include "events_process.h" + #ifdef MHD_SUPPORT_THREADS # include "mhd_itc.h" # include "mhd_threads.h" -# include "events_process.h" # include "daemon_funcs.h" #endif @@ -223,6 +228,18 @@ daemon_set_work_mode (struct MHD_Daemon *restrict d, return MHD_SC_CONFIGURATION_UNEXPECTED_WM; } + if ((mhd_WM_INT_EXTERNAL_EVENTS_LEVEL != d->wmode_int) && + (mhd_WM_INT_EXTERNAL_EVENTS_EDGE != d->wmode_int) && + (MHD_NO != s->reregister_all)) + { + mhd_LOG_MSG ( \ + d, \ + MHD_SC_EXTERNAL_EVENT_ONLY, \ + "The MHD_D_O_REREGISTER_ALL option can be used only with external " \ + "events work modes."); + return MHD_SC_EXTERNAL_EVENT_ONLY; + } + return MHD_SC_OK; } @@ -633,6 +650,7 @@ create_bind_listen_stream_socket (struct MHD_Daemon *restrict d, { /* No listen socket */ d->net.listen.fd = MHD_INVALID_SOCKET; + d->net.listen.is_broken = false; d->net.listen.type = mhd_SOCKET_TYPE_UNKNOWN; d->net.listen.non_block = false; d->net.listen.port = 0; @@ -976,6 +994,7 @@ create_bind_listen_stream_socket (struct MHD_Daemon *restrict d, /* Set to the daemon only when the listening socket is fully ready */ d->net.listen.fd = sk; + d->net.listen.is_broken = false; switch (sk_type) { case mhd_SKT_UNKNOWN: @@ -1322,19 +1341,26 @@ daemon_choose_and_preinit_events (struct MHD_Daemon *restrict d, { case mhd_POLL_TYPE_EXT: mhd_assert ((MHD_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL == s->work_mode.mode) || \ - (MHD_WM_EXTERNAL_EVENT_LOOP_CB_EDGE != s->work_mode.mode)); + (MHD_WM_EXTERNAL_EVENT_LOOP_CB_EDGE == s->work_mode.mode)); mhd_assert (mhd_WM_INT_HAS_EXT_EVENTS (d->wmode_int)); - mhd_assert (MHD_WM_EXTERNAL_SINGLE_FD_WATCH != s->work_mode.mode); d->events.poll_type = mhd_POLL_TYPE_EXT; - d->events.data.ext.cb = + d->events.data.extr.cb_data.cb = s->work_mode.params.v_external_event_loop_cb.reg_cb; - d->events.data.ext.cls = + d->events.data.extr.cb_data.cls = s->work_mode.params.v_external_event_loop_cb.reg_cb_cls; + d->events.data.extr.reg_all = (MHD_NO != s->reregister_all); +#ifdef MHD_SUPPORT_THREADS + d->events.data.extr.itc_data.app_cntx = NULL; +#endif /* MHD_SUPPORT_THREADS */ + d->events.data.extr.listen_data.app_cntx = NULL; break; #ifdef MHD_SUPPORT_SELECT case mhd_POLL_TYPE_SELECT: mhd_assert (! mhd_WM_INT_HAS_EXT_EVENTS (d->wmode_int)); mhd_assert (MHD_WM_EXTERNAL_SINGLE_FD_WATCH != s->work_mode.mode); + mhd_assert (MHD_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL != s->work_mode.mode); + mhd_assert (MHD_WM_EXTERNAL_EVENT_LOOP_CB_EDGE != s->work_mode.mode); + mhd_assert (MHD_NO == s->reregister_all); d->events.poll_type = mhd_POLL_TYPE_SELECT; d->events.data.select.rfds = NULL; /* Memory allocated during event and threads init */ d->events.data.select.wfds = NULL; /* Memory allocated during event and threads init */ @@ -1345,6 +1371,9 @@ daemon_choose_and_preinit_events (struct MHD_Daemon *restrict d, case mhd_POLL_TYPE_POLL: mhd_assert (! mhd_WM_INT_HAS_EXT_EVENTS (d->wmode_int)); mhd_assert (MHD_WM_EXTERNAL_SINGLE_FD_WATCH != s->work_mode.mode); + mhd_assert (MHD_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL != s->work_mode.mode); + mhd_assert (MHD_WM_EXTERNAL_EVENT_LOOP_CB_EDGE != s->work_mode.mode); + mhd_assert (MHD_NO == s->reregister_all); d->events.poll_type = mhd_POLL_TYPE_POLL; d->events.data.poll.fds = NULL; /* Memory allocated during event and threads init */ d->events.data.poll.rel = NULL; /* Memory allocated during event and threads init */ @@ -1353,6 +1382,9 @@ daemon_choose_and_preinit_events (struct MHD_Daemon *restrict d, #ifdef MHD_SUPPORT_EPOLL case mhd_POLL_TYPE_EPOLL: mhd_assert (! mhd_WM_INT_HAS_EXT_EVENTS (d->wmode_int)); + mhd_assert (MHD_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL != s->work_mode.mode); + mhd_assert (MHD_WM_EXTERNAL_EVENT_LOOP_CB_EDGE != s->work_mode.mode); + mhd_assert (MHD_NO == s->reregister_all); /* Pre-initialised by init_epoll() */ mhd_assert (mhd_POLL_TYPE_EPOLL == d->events.poll_type); mhd_assert (0 <= d->events.data.epoll.e_fd); @@ -1424,7 +1456,7 @@ daemon_init_net (struct MHD_Daemon *restrict d, { if ((MHD_INVALID_SOCKET != d->net.listen.fd) && ! d->net.listen.non_block - && ((mhd_WM_INT_EXTERNAL_EVENTS_EDGE == d->wmode_int) || + && (mhd_D_IS_USING_EDGE_TRIG (d) || (mhd_WM_INT_INTERNAL_EVENTS_THREAD_POOL == d->wmode_int))) { mhd_LOG_MSG (d, MHD_SC_LISTEN_SOCKET_NONBLOCKING_FAILURE, \ @@ -1772,7 +1804,8 @@ allocate_events (struct MHD_Daemon *restrict d) switch (d->events.poll_type) { case mhd_POLL_TYPE_EXT: - mhd_assert (NULL != d->events.data.ext.cb); + mhd_assert (NULL != d->events.data.extr.cb_data.cb); + /* Nothing to do: allocation is not needed */ #ifndef NDEBUG d->dbg.events_allocated = true; #endif @@ -2025,7 +2058,7 @@ deinit_itc (struct MHD_Daemon *restrict d) */ static MHD_FN_PAR_NONNULL_ (1) MHD_FN_MUST_CHECK_RESULT_ enum MHD_StatusCode -add_itc_and_listen_to_monitoring (struct MHD_Daemon *restrict d) +init_daemon_fds_monitoring (struct MHD_Daemon *restrict d) { mhd_assert (d->dbg.net_inited); mhd_assert (! d->dbg.net_deinited); @@ -2036,13 +2069,79 @@ add_itc_and_listen_to_monitoring (struct MHD_Daemon *restrict d) mhd_assert (mhd_ITC_IS_VALID (d->threading.itc)); #endif + d->events.accept_pending = false; + switch (d->events.poll_type) { case mhd_POLL_TYPE_EXT: - mhd_assert (NULL != d->events.data.ext.cb); - /* Nothing to do with the external events */ - // FIXME: Register the ITC and the listening NOW? - return MHD_SC_OK; + mhd_assert (NULL != d->events.data.extr.cb_data.cb); +#ifdef MHD_SUPPORT_THREADS + d->events.data.extr.itc_data.is_active = false; + d->events.data.extr.itc_data.is_broken = false; +#endif /* MHD_SUPPORT_THREADS */ + if (! d->events.data.extr.reg_all) + { + bool itc_reg_succeed; + + /* Register daemon's FDs now */ +#ifdef MHD_SUPPORT_THREADS + d->events.data.extr.itc_data.app_cntx = + mhd_daemon_extr_event_reg (d, + mhd_itc_r_fd (d->threading.itc), + MHD_FD_STATE_RECV_EXCEPT, + NULL, + (struct MHD_EventUpdateContext *) + mhd_SOCKET_REL_MARKER_ITC); + itc_reg_succeed = (NULL != d->events.data.extr.itc_data.app_cntx); +#else /* ! MHD_SUPPORT_THREADS */ + itc_reg_succeed = true; +#endif /* ! MHD_SUPPORT_THREADS */ + if (itc_reg_succeed) + { + if (MHD_INVALID_SOCKET == d->net.listen.fd) + { + d->events.data.extr.listen_data.app_cntx = NULL; + return MHD_SC_OK; /* Success exit point */ + } + + /* Need to register the listen FD */ + d->events.data.extr.listen_data.app_cntx = + mhd_daemon_extr_event_reg (d, + d->net.listen.fd, + MHD_FD_STATE_RECV_EXCEPT, + NULL, + (struct MHD_EventUpdateContext *) + mhd_SOCKET_REL_MARKER_LISTEN); + if (NULL != d->events.data.extr.listen_data.app_cntx) + return MHD_SC_OK; /* Success exit point */ + + /* Below is a clean-up path for 'case mhd_POLL_TYPE_EXT:' */ +#ifdef MHD_SUPPORT_THREADS + /* De-register ITC FD */ + (void) mhd_daemon_extr_event_reg (d, + mhd_itc_r_fd (d->threading.itc), + MHD_FD_STATE_NONE, + d->events.data.extr.itc_data.app_cntx, + (struct MHD_EventUpdateContext *) + mhd_SOCKET_REL_MARKER_ITC); + d->events.data.extr.itc_data.app_cntx = NULL; +#endif /* MHD_SUPPORT_THREADS */ + } + + mhd_LOG_MSG (d, MHD_SC_EXT_EVENT_REG_DAEMON_FDS_FAILURE, \ + "Failed to register daemon FDs in the application " + "(external events) monitoring."); + return MHD_SC_EXT_EVENT_REG_DAEMON_FDS_FAILURE; + } + else + { + /* Daemons FDs are repeatedly registered every processing cycle */ +#ifdef MHD_SUPPORT_THREADS + d->events.data.extr.itc_data.app_cntx = NULL; +#endif /* MHD_SUPPORT_THREADS */ + d->events.data.extr.listen_data.app_cntx = NULL; + return MHD_SC_OK; + } break; #ifdef MHD_SUPPORT_SELECT case mhd_POLL_TYPE_SELECT: @@ -2086,15 +2185,20 @@ add_itc_and_listen_to_monitoring (struct MHD_Daemon *restrict d) { struct epoll_event reg_event; #ifdef MHD_SUPPORT_THREADS - reg_event.events = EPOLLIN; + reg_event.events = EPOLLIN | EPOLLET; reg_event.data.u64 = (uint64_t) mhd_SOCKET_REL_MARKER_ITC; /* uint64_t is used in the epoll header */ if (0 != epoll_ctl (d->events.data.epoll.e_fd, EPOLL_CTL_ADD, mhd_itc_r_fd (d->threading.itc), &reg_event)) { mhd_LOG_MSG (d, MHD_SC_EPOLL_ADD_DAEMON_FDS_FAILURE, \ - "Failed to add ITC fd to the epoll monitoring."); + "Failed to add ITC FD to the epoll monitoring."); return MHD_SC_EPOLL_ADD_DAEMON_FDS_FAILURE; } + mhd_dbg_print_fd_mon_req ("ITC", \ + mhd_itc_r_fd (d->threading.itc), \ + true, \ + false, \ + false); #endif if (MHD_INVALID_SOCKET != d->net.listen.fd) { @@ -2104,9 +2208,14 @@ add_itc_and_listen_to_monitoring (struct MHD_Daemon *restrict d) d->net.listen.fd, &reg_event)) { mhd_LOG_MSG (d, MHD_SC_EPOLL_ADD_DAEMON_FDS_FAILURE, \ - "Failed to add listening fd to the epoll monitoring."); + "Failed to add listening FD to the epoll monitoring."); return MHD_SC_EPOLL_ADD_DAEMON_FDS_FAILURE; } + mhd_dbg_print_fd_mon_req ("lstn", \ + d->net.listen.fd, \ + true, \ + false, \ + false); } } return MHD_SC_OK; @@ -2132,6 +2241,73 @@ add_itc_and_listen_to_monitoring (struct MHD_Daemon *restrict d) /** + * The initial part of events de-initialisation: remove ITC and listening FD + * from the monitored items (if supported by monitoring syscall). + * @param d the daemon object + */ +static MHD_FN_PAR_NONNULL_ (1) void +deinit_daemon_fds_monitoring (struct MHD_Daemon *restrict d) +{ + mhd_assert (d->dbg.events_fully_inited); + + switch (d->events.poll_type) + { + case mhd_POLL_TYPE_EXT: + if (NULL != d->events.data.extr.listen_data.app_cntx) + (void) mhd_daemon_extr_event_reg ( + d, + d->net.listen.fd, + MHD_FD_STATE_NONE, + d->events.data.extr.listen_data.app_cntx, + (struct MHD_EventUpdateContext *) mhd_SOCKET_REL_MARKER_LISTEN); +#ifdef MHD_SUPPORT_THREADS + if (NULL != d->events.data.extr.itc_data.app_cntx) + (void) mhd_daemon_extr_event_reg (d, + mhd_itc_r_fd (d->threading.itc), + MHD_FD_STATE_NONE, + d->events.data.extr.itc_data.app_cntx, + (struct MHD_EventUpdateContext *) + mhd_SOCKET_REL_MARKER_ITC); +#endif /* MHD_SUPPORT_THREADS */ + return; +#ifdef MHD_SUPPORT_SELECT + case mhd_POLL_TYPE_SELECT: + /* Nothing to do when using 'select()' */ + return; + break; +#endif /* MHD_SUPPORT_SELECT */ +#ifdef MHD_SUPPORT_POLL + case mhd_POLL_TYPE_POLL: + /* Nothing to do when using 'poll()' */ + return; + break; +#endif /* MHD_SUPPORT_POLL */ +#ifdef MHD_SUPPORT_EPOLL + case mhd_POLL_TYPE_EPOLL: + /* Nothing to do when using epoll. + Monitoring stopped by closing epoll FD. */ + return; + break; +#endif /* MHD_SUPPORT_EPOLL */ +#ifndef MHD_SUPPORT_SELECT + case mhd_POLL_TYPE_SELECT: +#endif /* ! MHD_SUPPORT_SELECT */ +#ifndef MHD_SUPPORT_POLL + case mhd_POLL_TYPE_POLL: +#endif /* ! MHD_SUPPORT_POLL */ +#ifndef MHD_SUPPORT_EPOLL + case mhd_POLL_TYPE_EPOLL: +#endif /* ! MHD_SUPPORT_EPOLL */ + case mhd_POLL_TYPE_NOT_SET_YET: + default: + mhd_UNREACHABLE (); + break; + } + mhd_UNREACHABLE (); +} + + +/** * Initialise daemon connections' data. * @param d the daemon object * @param s the user settings @@ -2228,7 +2404,7 @@ init_individual_thread_data_events_conns (struct MHD_Daemon *restrict d, res = init_itc (d); if (MHD_SC_OK == res) { - res = add_itc_and_listen_to_monitoring (d); + res = init_daemon_fds_monitoring (d); if (MHD_SC_OK == res) { @@ -2246,6 +2422,10 @@ init_individual_thread_data_events_conns (struct MHD_Daemon *restrict d, res = init_individual_conns (d, s); if (MHD_SC_OK == res) return MHD_SC_OK; + + /* Below is a clean-up path */ + + deinit_daemon_fds_monitoring (d); } deinit_itc (d); } @@ -2268,6 +2448,7 @@ static MHD_FN_PAR_NONNULL_ (1) void deinit_individual_thread_data_events_conns (struct MHD_Daemon *restrict d) { deinit_individual_conns (d); + deinit_daemon_fds_monitoring (d); deinit_itc (d); deallocate_events (d); mhd_assert (NULL == mhd_DLINKEDL_GET_FIRST (&(d->conns),all_conn)); @@ -2467,7 +2648,7 @@ set_d_threading_type (struct MHD_Daemon *restrict d) case mhd_WM_INT_EXTERNAL_EVENTS_LEVEL: mhd_assert (! mhd_WM_INT_HAS_THREADS (d->wmode_int)); mhd_assert (mhd_POLL_TYPE_EXT == d->events.poll_type); - mhd_assert (NULL != d->events.data.ext.cb); + mhd_assert (NULL != d->events.data.extr.cb_data.cb); #ifdef MHD_SUPPORT_THREADS d->threading.d_type = mhd_DAEMON_TYPE_SINGLE; #endif /* MHD_SUPPORT_THREADS */ @@ -3026,9 +3207,7 @@ daemon_start_threads (struct MHD_Daemon *restrict d) /** * Stop the daemon internal threads, if the daemon configured to use them. - * @param d the daemon object, the threads (if any) must be started - * @return #MHD_SC_OK on success, - * the error code otherwise + * @param d the daemon object to stop threads */ static MHD_FN_PAR_NONNULL_ (1) void daemon_stop_threads (struct MHD_Daemon *restrict d) @@ -3063,6 +3242,24 @@ daemon_stop_threads (struct MHD_Daemon *restrict d) /** + * Close all daemon connections for modes without internal threads + * @param d the daemon object + */ +static MHD_FN_PAR_NONNULL_ (1) void +daemon_close_connections (struct MHD_Daemon *restrict d) +{ + if (mhd_WM_INT_HAS_THREADS (d->wmode_int)) + { + /* In these modes connections must be closed in the daemon thread */ + mhd_assert (NULL == mhd_DLINKEDL_GET_LAST (&(d->conns),all_conn)); + return; + } + + mhd_daemon_close_all_conns (d); +} + + +/** * Internal daemon initialisation function. * This function calls all required initialisation stages one-by-one. * @param d the daemon object @@ -3176,6 +3373,8 @@ MHD_daemon_destroy (struct MHD_Daemon *daemon) daemon_stop_threads (daemon); + daemon_close_connections (daemon); + daemon_deinit_threading_and_conn (daemon); daemon_deinit_large_buf (daemon); diff --git a/src/mhd2/events_process.c b/src/mhd2/events_process.c @@ -30,6 +30,10 @@ #include "mhd_assert.h" #include "mhd_unreachable.h" +#if defined(mhd_DEBUG_SUSPEND_RESUME) || defined(mhd_DEBUG_POLLING_FDS) +# include <stdio.h> +#endif /* mhd_DEBUG_SUSPEND_RESUME */ + #include "mhd_locks.h" #include "mhd_socket_type.h" @@ -45,6 +49,7 @@ #include "mhd_itc.h" #include "mhd_panic.h" +#include "mhd_dbg_print.h" #include "mhd_sockets_macros.h" @@ -57,6 +62,7 @@ #include "daemon_funcs.h" #include "conn_data_process.h" #include "stream_funcs.h" +#include "extr_events_funcs.h" #ifdef MHD_SUPPORT_UPGRADE # include "upgrade_proc.h" @@ -64,27 +70,176 @@ #include "mhd_public_api.h" +#ifdef mhd_DEBUG_POLLING_FDS +/** + * Debug-printf request of FD polling/monitoring + * @param fd_name the name of FD ("ITC", "lstn" or "conn") + * @param fd the FD value + * @param r_ready the request for read (or receive) readiness + * @param w_ready the request for write (or send) readiness + * @param e_ready the request for exception (or error) readiness + */ +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_dbg_print_fd_mon_req (const char *fd_name, + MHD_Socket fd, + bool r_ready, + bool w_ready, + bool e_ready) +{ + char state_str[] = "x:x:x"; + state_str[0] = r_ready ? 'R' : '-'; + state_str[2] = w_ready ? 'W' : '-'; + state_str[4] = e_ready ? 'E' : '-'; + + fprintf (stderr, + "### Set FD watching: %4s [%2llu] for %s\n", + fd_name, + (unsigned long long) fd, + state_str); +} + + +/** + * Debug-printf reported (by polling) status of FD + * @param fd_name the name of FD ("ITC", "lstn" or "conn") + * @param fd the FD value + * @param r_ready the read (or receive) readiness + * @param w_ready the write (or send) readiness + * @param e_ready the exception (or error) readiness + */ +static MHD_FN_PAR_NONNULL_ALL_ void +dbg_print_fd_state_update (const char *fd_name, + MHD_Socket fd, + bool r_ready, + bool w_ready, + bool e_ready) +{ + char state_str[] = "x:x:x"; + state_str[0] = r_ready ? 'R' : '-'; + state_str[2] = w_ready ? 'W' : '-'; + state_str[4] = e_ready ? 'E' : '-'; + + fprintf (stderr, + "### FD state update: %4s [%2llu] -> %s\n", + fd_name, + (unsigned long long) fd, + state_str); +} + + +#else /* ! mhd_DEBUG_POLLING_FDS */ +# define dbg_print_fd_state_update(fd_n,fd,r_ready,w_ready,e_ready) \ + ((void) 0) +#endif /* ! mhd_DEBUG_POLLING_FDS */ + +/** + * Log error message about broken ITC + * @param d the daemon to use + */ +static MHD_FN_PAR_NONNULL_ALL_ void +log_itc_broken (struct MHD_Daemon *restrict d) +{ + mhd_LOG_MSG (d, \ + MHD_SC_ITC_STATUS_ERROR, \ + "System reported that ITC has an error status or broken."); +} + + +/** + * Log error message about broken listen socket + * @param d the daemon to use + */ +static MHD_FN_PAR_NONNULL_ALL_ void +log_listen_broken (struct MHD_Daemon *restrict d) +{ + mhd_LOG_MSG (d, MHD_SC_LISTEN_STATUS_ERROR, \ + "System reported that the listening socket has an error " \ + "status or broken. The daemon will not listen any more."); +} + + MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ uint_fast64_t mhd_daemon_get_wait_max (struct MHD_Daemon *restrict d) { - bool zero_wait = d->events.zero_wait; mhd_assert (! mhd_D_HAS_WORKERS (d)); - if (d->events.act_req.accept && d->conns.block_new) - d->events.act_req.accept = false; - - if (d->events.act_req.accept) + if (d->events.accept_pending && ! d->conns.block_new) + { +#ifdef mhd_DEBUG_POLLING_FDS + fprintf (stderr, + "### mhd_daemon_get_wait_max(daemon) -> zero " + "(accept new conn pending)\n"); +#endif return 0; - if (zero_wait) + } + if (d->threading.resume_requested) /* Remove? It is triggered by ITC anyway */ + { +#ifdef mhd_DEBUG_POLLING_FDS + fprintf (stderr, + "### mhd_daemon_get_wait_max(daemon) -> zero " + "(resume connection pending)\n"); +#endif return 0; + } if (NULL != mhd_DLINKEDL_GET_FIRST (&(d->events), proc_ready)) + { +#ifdef mhd_DEBUG_POLLING_FDS + fprintf (stderr, + "### mhd_daemon_get_wait_max(daemon) -> zero " + "(connection(s) is already ready)\n"); +#endif return 0; + } +#ifdef mhd_DEBUG_POLLING_FDS + fprintf (stderr, + "### mhd_daemon_get_wait_max(daemon) -> MHD_WAIT_INDEFINITELY\n"); +#endif return MHD_WAIT_INDEFINITELY; // TODO: calculate correct timeout value } +static MHD_FN_PAR_NONNULL_ALL_ void +start_resuming_connection (struct MHD_Connection *restrict c, + struct MHD_Daemon *restrict d) +{ + mhd_assert (c->suspended); +#ifdef mhd_DEBUG_SUSPEND_RESUME + fprintf (stderr, + "%%%%%% Resuming connection, FD: %2llu\n", + (unsigned long long) c->sk.fd); +#endif /* mhd_DEBUG_SUSPEND_RESUME */ + c->suspended = false; + mhd_stream_resumed_activity_mark (c); + mhd_conn_mark_ready (c, d); /* Force processing connection in this round */ +} + + +/** + * Check whether any resuming connections are pending and resume them + * @param d the daemon to use + */ +static MHD_FN_PAR_NONNULL_ALL_ void +daemon_resume_conns_if_needed (struct MHD_Daemon *restrict d) +{ + struct MHD_Connection *c; + + if (! d->threading.resume_requested) + return; + + d->threading.resume_requested = false; /* Reset flag before processing data */ + + for (c = mhd_DLINKEDL_GET_FIRST (&(d->conns),all_conn); + NULL != c; + c = mhd_DLINKEDL_GET_NEXT (c,all_conn)) + { + if (c->resuming) + start_resuming_connection (c, d); + } +} + + mhd_DATA_TRUNCATION_RUNTIME_CHECK_DISABLE static MHD_FN_PAR_NONNULL_ALL_ int @@ -93,9 +248,6 @@ get_max_wait (struct MHD_Daemon *restrict d) uint_fast64_t ui64_wait = mhd_daemon_get_wait_max (d); int i_wait = (int) ui64_wait; - // TODO: move reset to the processing loop - d->events.zero_wait = false; /* Reset as this pending data will be processed */ - if ((0 > i_wait) || (ui64_wait != (uint_fast64_t) i_wait)) return INT_MAX; @@ -118,6 +270,14 @@ update_conn_net_status (struct MHD_Daemon *restrict d, enum mhd_SocketNetState sk_state; mhd_assert (d == c->daemon); + /* "resuming" must be not processed yet */ + mhd_assert (! c->resuming || c->suspended); + + dbg_print_fd_state_update ("conn", \ + c->sk.fd, \ + recv_ready, \ + send_ready, \ + err_state); sk_state = mhd_SOCKET_NET_STATE_NOTHING; if (recv_ready) @@ -131,7 +291,10 @@ update_conn_net_status (struct MHD_Daemon *restrict d, (sk_state | (unsigned int) mhd_SOCKET_NET_STATE_ERROR_READY); c->sk.ready = sk_state; - mhd_conn_mark_ready_update3 (c, err_state, d); + if (! c->suspended) + mhd_conn_mark_ready_update3 (c, err_state, d); + else + mhd_assert (! c->in_proc_ready); } @@ -146,6 +309,7 @@ daemon_accept_new_conns (struct MHD_Daemon *restrict d) { unsigned int num_to_accept; mhd_assert (MHD_INVALID_SOCKET != d->net.listen.fd); + mhd_assert (! d->net.listen.is_broken); mhd_assert (! d->conns.block_new); mhd_assert (d->conns.count < d->conns.cfg.count_limit); mhd_assert (! mhd_D_HAS_WORKERS (d)); @@ -256,9 +420,10 @@ daemon_accept_new_conns (struct MHD_Daemon *restrict d) if (mhd_DAEMON_ACCEPT_NO_MORE_PENDING == res) return true; if (mhd_DAEMON_ACCEPT_FAILED == res) - break; + return false; /* This is probably "no system resources" error. + To do try to accept more connections now. */ } - return false; + return false; /* More connections may need to be accepted */ } @@ -281,9 +446,7 @@ is_conn_excluded_from_http_comm (struct MHD_Connection *restrict c) } #endif /* MHD_SUPPORT_UPGRADE */ - // TODO: Support suspended connection - - return false; + return c->suspended; } @@ -297,13 +460,19 @@ daemon_process_all_active_conns (struct MHD_Daemon *restrict d) while (NULL != c) { struct MHD_Connection *next; - next = mhd_DLINKEDL_GET_NEXT (c, proc_ready); /* The current connection can be closed */ + /* The current connection can be closed or removed from + "ready" list */ + next = mhd_DLINKEDL_GET_NEXT (c, proc_ready); if (! mhd_conn_process_recv_send_data (c)) { mhd_conn_pre_clean (c); mhd_conn_remove_from_daemon (c); mhd_conn_close_final (c); } + else + { + mhd_assert (! c->resuming || c->suspended); + } c = next; } @@ -317,10 +486,14 @@ daemon_process_all_active_conns (struct MHD_Daemon *restrict d) * @param d the daemon to process */ static MHD_FN_PAR_NONNULL_ALL_ void -daemon_cleanup_upgraded_conns (struct MHD_Daemon *restrict d) +daemon_cleanup_upgraded_conns (struct MHD_Daemon *d) { + volatile struct MHD_Daemon *voltl_d = d; mhd_assert (! mhd_D_HAS_WORKERS (d)); + if (NULL == mhd_DLINKEDL_GET_FIRST (&(voltl_d->conns.upgr), upgr_cleanup)) + return; + while (true) { struct MHD_Connection *c; @@ -347,8 +520,8 @@ daemon_cleanup_upgraded_conns (struct MHD_Daemon *restrict d) #define daemon_cleanup_upgraded_conns(d) ((void) d) #endif /* ! MHD_SUPPORT_UPGRADE */ -static void -close_all_daemon_conns (struct MHD_Daemon *d) +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_daemon_close_all_conns (struct MHD_Daemon *d) { struct MHD_Connection *c; bool has_upgraded_unclosed; @@ -389,6 +562,209 @@ close_all_daemon_conns (struct MHD_Daemon *d) } +/** + * Process all external events updated of existing connections, information + * about new connections pending to be accept()'ed, presence of the events on + * the daemon's ITC; resume connections. + * @return 'true' if processed successfully, + * 'false' is unrecoverable error occurs and the daemon must be + * closed + */ +static MHD_FN_PAR_NONNULL_ (1) bool +ext_events_process_net_updates_and_resume_conn (struct MHD_Daemon *restrict d) +{ + struct MHD_Connection *restrict c; + + mhd_assert (mhd_WM_INT_HAS_EXT_EVENTS (d->wmode_int)); + mhd_assert (mhd_POLL_TYPE_EXT == d->events.poll_type); + + d->threading.resume_requested = false; /* Reset flag before processing data */ + +#ifdef MHD_SUPPORT_THREADS + if (d->events.data.extr.itc_data.is_active) + { + d->events.data.extr.itc_data.is_active = false; + /* Clear ITC here, before other data processing. + * Any external events will activate ITC again if additional data to + * process is added externally. Clearing ITC early ensures that new data + * (with additional ITC activation) will not be missed. */ + mhd_itc_clear (d->threading.itc); + } +#endif /* MHD_SUPPORT_THREADS */ + + for (c = mhd_DLINKEDL_GET_FIRST (&(d->conns),all_conn); + NULL != c; + c = mhd_DLINKEDL_GET_NEXT (c,all_conn)) + { + bool has_err_state; + + if (c->resuming) + start_resuming_connection (c, d); + else + { + if (is_conn_excluded_from_http_comm (c)) + { + mhd_assert (! c->in_proc_ready); + continue; + } + + has_err_state = (0 != (((unsigned int) c->sk.ready) + & mhd_SOCKET_NET_STATE_ERROR_READY)); + + mhd_conn_mark_ready_update3 (c, + has_err_state, + d); + } + } + + return true; +} + + +/** + * Update all registrations of FDs for external monitoring. + * @return #MHD_SC_OK on success, + * error code otherwise + */ +static MHD_FN_PAR_NONNULL_ (1) enum MHD_StatusCode +ext_events_update_registrations (struct MHD_Daemon *restrict d) +{ + const bool rereg_all = d->events.data.extr.reg_all; + const bool edge_trigg = (mhd_WM_INT_EXTERNAL_EVENTS_EDGE == d->wmode_int); + bool daemon_fds_succeed; + struct MHD_Connection *c; + struct MHD_Connection *c_next; + + mhd_assert (mhd_WM_INT_HAS_EXT_EVENTS (d->wmode_int)); + mhd_assert (mhd_POLL_TYPE_EXT == d->events.poll_type); + + /* (Re-)register daemon's FDs */ + +#ifdef MHD_SUPPORT_THREADS + if (rereg_all || + (NULL == d->events.data.extr.itc_data.app_cntx)) + { + /* (Re-)register ITC FD */ + d->events.data.extr.itc_data.app_cntx = + mhd_daemon_extr_event_reg (d, + mhd_itc_r_fd (d->threading.itc), + MHD_FD_STATE_RECV_EXCEPT, + d->events.data.extr.itc_data.app_cntx, + (struct MHD_EventUpdateContext *) + mhd_SOCKET_REL_MARKER_ITC); + } + daemon_fds_succeed = (NULL != d->events.data.extr.itc_data.app_cntx); +#else /* ! MHD_SUPPORT_THREADS */ + daemon_fds_succeed = true; +#endif /* ! MHD_SUPPORT_THREADS */ + + if (daemon_fds_succeed) + { + if ((MHD_INVALID_SOCKET == d->net.listen.fd) && + (NULL != d->events.data.extr.listen_data.app_cntx)) + { + /* De-register the listen FD */ + d->events.data.extr.listen_data.app_cntx = + mhd_daemon_extr_event_reg (d, + d->net.listen.fd, + MHD_FD_STATE_NONE, + d->events.data.extr.listen_data.app_cntx, + (struct MHD_EventUpdateContext *) + mhd_SOCKET_REL_MARKER_LISTEN); + if (NULL != d->events.data.extr.listen_data.app_cntx) + mhd_log_extr_event_dereg_failed (d); + } + else if ((MHD_INVALID_SOCKET != d->net.listen.fd) && + (rereg_all || (NULL == d->events.data.extr.listen_data.app_cntx))) + { + /* (Re-)register listen FD */ + d->events.data.extr.listen_data.app_cntx = + mhd_daemon_extr_event_reg (d, + d->net.listen.fd, + MHD_FD_STATE_RECV_EXCEPT, + d->events.data.extr.listen_data.app_cntx, + (struct MHD_EventUpdateContext *) + mhd_SOCKET_REL_MARKER_LISTEN); + + daemon_fds_succeed = (NULL != d->events.data.extr.listen_data.app_cntx); + } + } + + if (! daemon_fds_succeed) + { + mhd_LOG_MSG (d, MHD_SC_EXT_EVENT_REG_DAEMON_FDS_FAILURE, \ + "Failed to register daemon FDs in the application " + "(external events) monitoring."); + return MHD_SC_EXT_EVENT_REG_DAEMON_FDS_FAILURE; + } + + for (c = mhd_DLINKEDL_GET_FIRST (&(d->conns),all_conn); + NULL != c; + c = c_next) + { + enum MHD_FdState watch_for; + + /* Get the next connection now, as the current connection could be removed + from the daemon. */ + c_next = mhd_DLINKEDL_GET_NEXT (c,all_conn); + + mhd_assert (! c->resuming || c->suspended); + + if (is_conn_excluded_from_http_comm (c)) + { + if (NULL != c->extr_event.app_cntx) + { + /* De-register the connection socket FD */ + c->extr_event.app_cntx = + mhd_daemon_extr_event_reg (d, + c->sk.fd, + MHD_FD_STATE_NONE, + c->extr_event.app_cntx, + (struct MHD_EventUpdateContext *) c); + if (NULL != c->extr_event.app_cntx) + mhd_log_extr_event_dereg_failed (d); + } + continue; + } + + watch_for = + edge_trigg ? + MHD_FD_STATE_RECV_SEND_EXCEPT : + (enum MHD_FdState) (MHD_FD_STATE_EXCEPT + | (((unsigned int) c->event_loop_info) + & (MHD_EVENT_LOOP_INFO_RECV + | MHD_EVENT_LOOP_INFO_SEND))); + + mhd_assert ((! edge_trigg) || \ + (MHD_FD_STATE_RECV_SEND_EXCEPT == c->extr_event.reg_for) || \ + (NULL == c->extr_event.app_cntx)); + + if ((NULL == c->extr_event.app_cntx) || + rereg_all || + (! edge_trigg && (watch_for != c->extr_event.reg_for))) + { + /* (Re-)register the connection socket FD */ + c->extr_event.app_cntx = + mhd_daemon_extr_event_reg (d, + c->sk.fd, + watch_for, + c->extr_event.app_cntx, + (struct MHD_EventUpdateContext *) c); + if (NULL == c->extr_event.app_cntx) + { + mhd_conn_start_closing_ext_event_failed (c); + mhd_conn_pre_clean (c); + mhd_conn_remove_from_daemon (c); + mhd_conn_close_final (c); + } + c->extr_event.reg_for = watch_for; + } + } + + return MHD_SC_OK; +} + + #ifdef MHD_SUPPORT_SELECT /** @@ -469,9 +845,17 @@ select_update_fdsets (struct MHD_Daemon *restrict d, efds, &ret, d); + mhd_dbg_print_fd_mon_req ("ITC", \ + mhd_itc_r_fd (d->threading.itc), \ + true, \ + false, \ + true); #endif - if (MHD_INVALID_SOCKET != d->net.listen.fd) + if ((MHD_INVALID_SOCKET != d->net.listen.fd) + && ! d->conns.block_new) { + mhd_assert (! d->net.listen.is_broken); + fd_set_wrap (d->net.listen.fd, rfds, &ret, @@ -480,6 +864,11 @@ select_update_fdsets (struct MHD_Daemon *restrict d, efds, &ret, d); + mhd_dbg_print_fd_mon_req ("lstn", \ + d->net.listen.fd, \ + true, \ + false, \ + true); } if (listen_only) return ret; @@ -505,6 +894,11 @@ select_update_fdsets (struct MHD_Daemon *restrict d, efds, &ret, d); + mhd_dbg_print_fd_mon_req ("conn", \ + c->sk.fd, \ + FD_ISSET (c->sk.fd, rfds), \ + FD_ISSET (c->sk.fd, wfds), \ + true); } return ret; @@ -512,18 +906,27 @@ select_update_fdsets (struct MHD_Daemon *restrict d, static MHD_FN_PAR_NONNULL_ (1) bool -select_update_statuses_from_fdsets (struct MHD_Daemon *d, - int num_events) +select_update_statuses_from_fdsets_and_resume_conn (struct MHD_Daemon *d, + int num_events) { struct MHD_Connection *c; fd_set *const restrict rfds = d->events.data.select.rfds; fd_set *const restrict wfds = d->events.data.select.wfds; fd_set *const restrict efds = d->events.data.select.efds; + bool resuming_conn; mhd_assert (mhd_POLL_TYPE_SELECT == d->events.poll_type); mhd_assert (0 <= num_events); mhd_assert (((unsigned int) num_events) <= d->dbg.num_events_elements); + resuming_conn = d->threading.resume_requested; + if (resuming_conn) + { + mhd_assert (mhd_DAEMON_TYPE_LISTEN_ONLY != d->threading.d_type); + num_events = (int) -1; /* Force process all connections */ + d->threading.resume_requested = false; + } + #ifndef MHD_FAVOR_SMALL_CODE if (0 == num_events) return true; @@ -531,10 +934,14 @@ select_update_statuses_from_fdsets (struct MHD_Daemon *d, #ifdef MHD_SUPPORT_THREADS mhd_assert (mhd_ITC_IS_VALID (d->threading.itc)); + dbg_print_fd_state_update ("ITC", \ + mhd_itc_r_fd (d->threading.itc), \ + FD_ISSET (mhd_itc_r_fd (d->threading.itc), rfds), \ + FD_ISSET (mhd_itc_r_fd (d->threading.itc), wfds), \ + FD_ISSET (mhd_itc_r_fd (d->threading.itc), efds)); if (FD_ISSET (mhd_itc_r_fd (d->threading.itc), efds)) { - mhd_LOG_MSG (d, MHD_SC_ITC_STATUS_ERROR, \ - "System reported that ITC has an error status."); + log_itc_broken (d); /* ITC is broken, need to stop the daemon thread now as otherwise application will not be able to stop the thread. */ return false; @@ -542,10 +949,10 @@ select_update_statuses_from_fdsets (struct MHD_Daemon *d, if (FD_ISSET (mhd_itc_r_fd (d->threading.itc), rfds)) { --num_events; - /* Clear ITC here, as before any other data processing. - * Any external events may activate ITC again if any data to process is - * added externally. Cleaning ITC early ensures guaranteed that new data - * will not be missed. */ + /* Clear ITC here, before other data processing. + * Any external events will activate ITC again if additional data to + * process is added externally. Clearing ITC early ensures that new data + * (with additional ITC activation) will not be missed. */ mhd_itc_clear (d->threading.itc); } @@ -557,23 +964,30 @@ select_update_statuses_from_fdsets (struct MHD_Daemon *d, if (MHD_INVALID_SOCKET != d->net.listen.fd) { + mhd_assert (! d->net.listen.is_broken); + dbg_print_fd_state_update ("lstn", \ + d->net.listen.fd, \ + FD_ISSET (d->net.listen.fd, rfds), \ + FD_ISSET (d->net.listen.fd, wfds), \ + FD_ISSET (d->net.listen.fd, efds)); if (FD_ISSET (d->net.listen.fd, efds)) { --num_events; - mhd_LOG_MSG (d, MHD_SC_ITC_STATUS_ERROR, \ - "System reported that the listening socket has an error " \ - "status. The daemon will not listen any more."); + log_listen_broken (d); /* Close the listening socket unless the master daemon should close it */ if (! mhd_D_HAS_MASTER (d)) mhd_socket_close (d->net.listen.fd); + d->events.accept_pending = false; + d->net.listen.is_broken = true; /* Stop monitoring socket to avoid spinning with busy-waiting */ d->net.listen.fd = MHD_INVALID_SOCKET; } - else if (FD_ISSET (d->net.listen.fd, rfds)) + else { - --num_events; - d->events.act_req.accept = true; + d->events.accept_pending = FD_ISSET (d->net.listen.fd, rfds); + if (d->events.accept_pending) + --num_events; } } @@ -582,39 +996,44 @@ select_update_statuses_from_fdsets (struct MHD_Daemon *d, #ifdef MHD_FAVOR_SMALL_CODE (void) num_events; - num_events = 1; /* Use static value to optimise out the next look */ + num_events = 1; /* Use static value to minimise the binary size of the next loop */ #endif /* ! MHD_FAVOR_SMALL_CODE */ for (c = mhd_DLINKEDL_GET_FIRST (&(d->conns), all_conn); (NULL != c) && (0 != num_events); c = mhd_DLINKEDL_GET_NEXT (c, all_conn)) { - MHD_Socket sk; - bool recv_ready; - bool send_ready; - bool err_state; + if (c->resuming) + start_resuming_connection (c, d); + else + { + MHD_Socket sk; + bool recv_ready; + bool send_ready; + bool err_state; - if (is_conn_excluded_from_http_comm (c)) - continue; + if (is_conn_excluded_from_http_comm (c)) + continue; - sk = c->sk.fd; - recv_ready = FD_ISSET (sk, rfds); - send_ready = FD_ISSET (sk, wfds); - err_state = FD_ISSET (sk, efds); + sk = c->sk.fd; + recv_ready = FD_ISSET (sk, rfds); + send_ready = FD_ISSET (sk, wfds); + err_state = FD_ISSET (sk, efds); - update_conn_net_status (d, - c, - recv_ready, - send_ready, - err_state); + update_conn_net_status (d, + c, + recv_ready, + send_ready, + err_state); #ifndef MHD_FAVOR_SMALL_CODE - if (recv_ready || send_ready || err_state) - --num_events; + if (recv_ready || send_ready || err_state) + --num_events; #endif /* MHD_FAVOR_SMALL_CODE */ + } } - #ifndef MHD_FAVOR_SMALL_CODE - mhd_assert (0 == num_events); +#ifndef MHD_FAVOR_SMALL_CODE + mhd_assert ((0 == num_events) || resuming_conn); #endif /* MHD_FAVOR_SMALL_CODE */ return true; } @@ -622,7 +1041,7 @@ select_update_statuses_from_fdsets (struct MHD_Daemon *d, /** * Update states of all connections, check for connection pending - * to be accept()'ed, check for the events on ITC. + * to be accept()'ed, check for the events on ITC; resume connections * @param listen_only set to 'true' if connections's sockets should NOT * be monitored * @return 'true' if processed successfully, @@ -630,8 +1049,8 @@ select_update_statuses_from_fdsets (struct MHD_Daemon *d, * closed */ static MHD_FN_PAR_NONNULL_ (1) bool -get_all_net_updates_by_select (struct MHD_Daemon *restrict d, - bool listen_only) +get_all_net_updates_by_select_and_resume_conn (struct MHD_Daemon *restrict d, + bool listen_only) { int max_socket; int max_wait; @@ -659,11 +1078,27 @@ get_all_net_updates_by_select (struct MHD_Daemon *restrict d, tmvl.tv_usec = (int) ((max_wait % 1000) * 1000); #endif +#ifdef mhd_DEBUG_POLLING_FDS + fprintf (stderr, + "### (Starting) select(%d, rfds, wfds, efds, [%llu, %llu])...\n", + max_socket + 1, + (unsigned long long) tmvl.tv_sec, + (unsigned long long) tmvl.tv_usec); +#endif /* mhd_DEBUG_POLLING_FDS */ num_events = select (max_socket + 1, d->events.data.select.rfds, d->events.data.select.wfds, d->events.data.select.efds, &tmvl); +#ifdef mhd_DEBUG_POLLING_FDS + fprintf (stderr, + "### (Finished) select(%d, rfds, wfds, efds, ->[%llu, %llu]) -> " + "%d\n", + max_socket + 1, + (unsigned long long) tmvl.tv_sec, + (unsigned long long) tmvl.tv_usec, + num_events); +#endif /* mhd_DEBUG_POLLING_FDS */ if (0 > num_events) { @@ -700,7 +1135,7 @@ get_all_net_updates_by_select (struct MHD_Daemon *restrict d, } } - return select_update_statuses_from_fdsets (d, num_events); + return select_update_statuses_from_fdsets_and_resume_conn (d, num_events); } @@ -727,15 +1162,28 @@ poll_update_fds (struct MHD_Daemon *restrict d, mhd_assert (mhd_ITC_IS_VALID (d->threading.itc)); mhd_assert (d->events.data.poll.fds[i_s].fd == \ mhd_itc_r_fd (d->threading.itc)); + mhd_assert (POLLIN == d->events.data.poll.fds[i_s].events); mhd_assert (mhd_SOCKET_REL_MARKER_ITC == \ d->events.data.poll.rel[i_s].fd_id); + mhd_dbg_print_fd_mon_req ("ITC", \ + mhd_itc_r_fd (d->threading.itc), \ + true, \ + false, \ + false); ++i_s; #endif if (MHD_INVALID_SOCKET != d->net.listen.fd) { + mhd_assert (! d->net.listen.is_broken); mhd_assert (d->events.data.poll.fds[i_s].fd == d->net.listen.fd); mhd_assert (mhd_SOCKET_REL_MARKER_LISTEN == \ d->events.data.poll.rel[i_s].fd_id); + d->events.data.poll.fds[i_s].events = d->conns.block_new ? 0 : POLLIN; + mhd_dbg_print_fd_mon_req ("lstn", \ + d->net.listen.fd, \ + POLLIN == d->events.data.poll.fds[i_s].events, \ + false, \ + false); ++i_s; } if (listen_only) @@ -768,6 +1216,11 @@ poll_update_fds (struct MHD_Daemon *restrict d, events |= MHD_POLL_OUT; d->events.data.poll.fds[i_c].events = (short) events; + mhd_dbg_print_fd_mon_req ("conn", \ + c->sk.fd, \ + MHD_POLL_IN == (MHD_POLL_IN & events), \ + MHD_POLL_OUT == (MHD_POLL_OUT & events), \ + false); ++i_c; } mhd_assert ((d->conns.count - num_skipped) == (i_c - i_s)); @@ -796,10 +1249,16 @@ poll_update_statuses_from_fds (struct MHD_Daemon *restrict d, mhd_itc_r_fd (d->threading.itc)); mhd_assert (mhd_SOCKET_REL_MARKER_ITC == \ d->events.data.poll.rel[i_s].fd_id); + dbg_print_fd_state_update ( \ + "ITC", \ + d->events.data.poll.fds[i_s].fd, \ + 0 != (d->events.data.poll.fds[i_s].revents & (MHD_POLL_IN | POLLIN)), \ + 0 != (d->events.data.poll.fds[i_s].revents & (MHD_POLL_OUT | POLLOUT)), \ + 0 != (d->events.data.poll.fds[i_s].revents & (POLLERR | POLLNVAL))); + if (0 != (d->events.data.poll.fds[i_s].revents & (POLLERR | POLLNVAL))) { - mhd_LOG_MSG (d, MHD_SC_ITC_STATUS_ERROR, \ - "System reported that ITC has an error status."); + log_itc_broken (d); /* ITC is broken, need to stop the daemon thread now as otherwise application will not be able to stop the thread. */ return false; @@ -807,10 +1266,10 @@ poll_update_statuses_from_fds (struct MHD_Daemon *restrict d, if (0 != (d->events.data.poll.fds[i_s].revents & (MHD_POLL_IN | POLLIN))) { --num_events; - /* Clear ITC here, as before any other data processing. - * Any external events may activate ITC again if any data to process is - * added externally. Cleaning ITC early ensures guaranteed that new data - * will not be missed. */ + /* Clear ITC here, before other data processing. + * Any external events will activate ITC again if additional data to + * process is added externally. Clearing ITC early ensures that new data + * (with additional ITC activation) will not be missed. */ mhd_itc_clear (d->threading.itc); } ++i_s; @@ -823,27 +1282,43 @@ poll_update_statuses_from_fds (struct MHD_Daemon *restrict d, { const short revents = d->events.data.poll.fds[i_s].revents; + mhd_assert (! d->net.listen.is_broken); mhd_assert (d->events.data.poll.fds[i_s].fd == d->net.listen.fd); mhd_assert (mhd_SOCKET_REL_MARKER_LISTEN == \ d->events.data.poll.rel[i_s].fd_id); + dbg_print_fd_state_update ("lstn", \ + d->events.data.poll.fds[i_s].fd, \ + 0 != (revents & (MHD_POLL_IN | POLLIN)), \ + 0 != (revents & (MHD_POLL_OUT | POLLOUT)), \ + 0 != (revents & (POLLERR | POLLNVAL | POLLHUP))); if (0 != (revents & (POLLERR | POLLNVAL | POLLHUP))) { --num_events; - mhd_LOG_MSG (d, MHD_SC_ITC_STATUS_ERROR, \ - "System reported that the listening socket has an error " \ - "status. The daemon will not listen any more."); + log_listen_broken (d); /* Close the listening socket unless the master daemon should close it */ if (! mhd_D_HAS_MASTER (d)) mhd_socket_close (d->net.listen.fd); + d->events.accept_pending = false; + d->net.listen.is_broken = true; /* Stop monitoring socket to avoid spinning with busy-waiting */ d->net.listen.fd = MHD_INVALID_SOCKET; } - else if (0 != - (d->events.data.poll.fds[i_s].revents & (MHD_POLL_IN | POLLIN))) + else { - --num_events; - d->events.act_req.accept = true; + const bool has_new_conns = (0 != (revents & (MHD_POLL_IN | POLLIN))); + if (has_new_conns) + { + --num_events; + d->events.accept_pending = true; + } + else + { + /* Check whether the listen socket was monitored for incoming + connections */ + if (0 != (d->events.data.poll.fds[i_s].events & POLLIN)) + d->events.accept_pending = false; + } } ++i_s; } @@ -908,17 +1383,42 @@ static MHD_FN_PAR_NONNULL_ (1) bool get_all_net_updates_by_poll (struct MHD_Daemon *restrict d, bool listen_only) { +#ifdef mhd_DEBUG_POLLING_FDS +# ifdef MHD_SOCKETS_KIND_POSIX + static const char poll_fn_name[] = "poll"; +# else /* MHD_SOCKETS_KIND_WINSOCK */ + static const char poll_fn_name[] = "WSAPoll"; +# endif /* MHD_SOCKETS_KIND_WINSOCK */ +#endif /* mhd_DEBUG_POLLING_FDS */ unsigned int num_fds; + int max_wait; int num_events; + mhd_assert (mhd_POLL_TYPE_POLL == d->events.poll_type); num_fds = poll_update_fds (d, listen_only); // TODO: handle empty list situation + max_wait = get_max_wait (d); // TODO: use correct timeout value +#ifdef mhd_DEBUG_POLLING_FDS + fprintf (stderr, + "### (Starting) %s(fds, %u, %d)...\n", + poll_fn_name, + num_fds, + max_wait); +#endif /* mhd_DEBUG_POLLING_FDS */ num_events = mhd_poll (d->events.data.poll.fds, num_fds, - get_max_wait (d)); // TODO: use correct timeout value + max_wait); // TODO: use correct timeout value +#ifdef mhd_DEBUG_POLLING_FDS + fprintf (stderr, + "### (Finished) %s(fds, %u, %d) -> %d\n", + poll_fn_name, + num_fds, + max_wait, + num_events); +#endif /* mhd_DEBUG_POLLING_FDS */ if (0 > num_events) { int err; @@ -966,8 +1466,8 @@ get_all_net_updates_by_poll (struct MHD_Daemon *restrict d, * listen socket states */ static MHD_FN_PAR_NONNULL_ (1) bool -poll_update_statuses_from_eevents (struct MHD_Daemon *restrict d, - unsigned int num_events) +update_statuses_from_eevents (struct MHD_Daemon *restrict d, + unsigned int num_events) { unsigned int i; struct epoll_event *const restrict events = @@ -975,44 +1475,67 @@ poll_update_statuses_from_eevents (struct MHD_Daemon *restrict d, for (i = 0; num_events > i; ++i) { struct epoll_event *const e = events + i; +#ifdef MHD_SUPPORT_THREADS if (((uint64_t) mhd_SOCKET_REL_MARKER_ITC) == e->data.u64) /* uint64_t is in the system header */ { + mhd_assert (mhd_ITC_IS_VALID (d->threading.itc)); + dbg_print_fd_state_update ( \ + "ITC", \ + mhd_itc_r_fd (d->threading.itc), \ + 0 != (e->events & EPOLLIN), \ + 0 != (e->events & EPOLLOUT), \ + 0 != (e->events & (EPOLLPRI | EPOLLERR | EPOLLHUP))); + if (0 != (e->events & (EPOLLPRI | EPOLLERR | EPOLLHUP))) { - mhd_LOG_MSG (d, MHD_SC_ITC_STATUS_ERROR, \ - "System reported that ITC has an error status."); + log_itc_broken (d); /* ITC is broken, need to stop the daemon thread now as otherwise application will not be able to stop the thread. */ return false; } if (0 != (e->events & EPOLLIN)) { - /* Clear ITC here, as before any other data processing. - * Any external events may activate ITC again if any data to process is - * added externally. Cleaning ITC early ensures guaranteed that new data - * will not be missed. */ + /* Clear ITC here, before other data processing. + * Any external events will activate ITC again if additional data to + * process is added externally. Clearing ITC early ensures that new data + * (with additional ITC activation) will not be missed. */ mhd_itc_clear (d->threading.itc); } } - else if (((uint64_t) mhd_SOCKET_REL_MARKER_LISTEN) == e->data.u64) /* uint64_t is in the system header */ + else +#endif /* MHD_SUPPORT_THREADS */ + if (((uint64_t) mhd_SOCKET_REL_MARKER_LISTEN) == e->data.u64) /* uint64_t is in the system header */ { + mhd_assert (MHD_INVALID_SOCKET != d->net.listen.fd); + dbg_print_fd_state_update ( \ + "lstn", \ + d->net.listen.fd, \ + 0 != (e->events & EPOLLIN), \ + 0 != (e->events & EPOLLOUT), \ + 0 != (e->events & (EPOLLPRI | EPOLLERR | EPOLLHUP))); if (0 != (e->events & (EPOLLPRI | EPOLLERR | EPOLLHUP))) { - mhd_LOG_MSG (d, MHD_SC_ITC_STATUS_ERROR, \ - "System reported that the listening socket has an error " \ - "status. The daemon will not listen any more."); - - // TODO: remove listen from epoll monitoring + log_listen_broken (d); /* Close the listening socket unless the master daemon should close it */ if (! mhd_D_HAS_MASTER (d)) mhd_socket_close (d->net.listen.fd); + else + { + /* Ignore possible error as the socket could be already removed + from the epoll monitoring by closing the socket */ + (void) epoll_ctl (d->events.data.epoll.e_fd, + EPOLL_CTL_DEL, + d->net.listen.fd, + NULL); + } - /* Stop monitoring socket to avoid spinning with busy-waiting */ + d->events.accept_pending = false; + d->net.listen.is_broken = true; d->net.listen.fd = MHD_INVALID_SOCKET; } - if (0 != (e->events & EPOLLIN)) - d->events.act_req.accept = true; + else + d->events.accept_pending = (0 != (e->events & EPOLLIN)); } else { @@ -1057,10 +1580,25 @@ get_all_net_updates_by_epoll (struct MHD_Daemon *restrict d) max_wait = get_max_wait (d); // TODO: use correct timeout value do { +#ifdef mhd_DEBUG_POLLING_FDS + fprintf (stderr, + "### (Starting) epoll_wait(%d, events, %d, %d)...\n", + d->events.data.epoll.e_fd, + (int) d->events.data.epoll.num_elements, + max_wait); +#endif /* mhd_DEBUG_POLLING_FDS */ num_events = epoll_wait (d->events.data.epoll.e_fd, d->events.data.epoll.events, (int) d->events.data.epoll.num_elements, max_wait); +#ifdef mhd_DEBUG_POLLING_FDS + fprintf (stderr, + "### (Finished) epoll_wait(%d, events, %d, %d) -> %d\n", + d->events.data.epoll.e_fd, + (int) d->events.data.epoll.num_elements, + max_wait, + num_events); +#endif /* mhd_DEBUG_POLLING_FDS */ max_wait = 0; if (0 > num_events) { @@ -1073,7 +1611,7 @@ get_all_net_updates_by_epoll (struct MHD_Daemon *restrict d) } return true; /* EINTR, try next time */ } - if (! poll_update_statuses_from_eevents (d, (unsigned int) num_events)) + if (! update_statuses_from_eevents (d, (unsigned int) num_events)) return false; events_processed += (unsigned int) num_events; /* Avoid reading too many events */ @@ -1087,17 +1625,36 @@ get_all_net_updates_by_epoll (struct MHD_Daemon *restrict d) #endif /* MHD_SUPPORT_EPOLL */ +/** + * Perform one round of daemon connection and data processing. + * + * This function do the following: + * + poll all connections and daemon FDs (if internal polling is used); + * + resume connections pending to be resumed; + * + update connection statuses based on socket states (recv/send ready or + * disconnect detection); + * + receive, send and/or parse connections data as needed, including call of + * callbacks for processing requests and response generation; + * + close broken connections; + * + accept new connection (if needed); + * + cleanup closed "upgraded" connections. + * @param d the daemon to use + * @return 'true' on success, + * 'false' if daemon is broken + */ static MHD_FN_PAR_NONNULL_ (1) bool process_all_events_and_data (struct MHD_Daemon *restrict d) { switch (d->events.poll_type) { case mhd_POLL_TYPE_EXT: - return false; // TODO: implement + mhd_assert (mhd_WM_INT_HAS_EXT_EVENTS (d->wmode_int)); + if (! ext_events_process_net_updates_and_resume_conn (d)) + return false; break; #ifdef MHD_SUPPORT_SELECT case mhd_POLL_TYPE_SELECT: - if (! get_all_net_updates_by_select (d, false)) + if (! get_all_net_updates_by_select_and_resume_conn (d, false)) return false; break; #endif /* MHD_SUPPORT_SELECT */ @@ -1105,12 +1662,14 @@ process_all_events_and_data (struct MHD_Daemon *restrict d) case mhd_POLL_TYPE_POLL: if (! get_all_net_updates_by_poll (d, false)) return false; + daemon_resume_conns_if_needed (d); break; #endif /* MHD_SUPPORT_POLL */ #ifdef MHD_SUPPORT_EPOLL case mhd_POLL_TYPE_EPOLL: if (! get_all_net_updates_by_epoll (d)) return false; + daemon_resume_conns_if_needed (d); break; #endif /* MHD_SUPPORT_EPOLL */ #ifndef MHD_SUPPORT_SELECT @@ -1128,19 +1687,104 @@ process_all_events_and_data (struct MHD_Daemon *restrict d) MHD_PANIC ("Daemon data integrity broken"); break; } - if (d->events.act_req.accept) - { - if (daemon_accept_new_conns (d)) - d->events.act_req.accept = false; - else if (! d->net.listen.non_block) - d->events.act_req.accept = false; - } + + if (d->events.accept_pending && ! d->conns.block_new) + d->events.accept_pending = ! daemon_accept_new_conns (d); + daemon_process_all_active_conns (d); daemon_cleanup_upgraded_conns (d); return ! d->threading.stop_requested; } +static +MHD_FN_PAR_NONNULL_ (1) enum MHD_StatusCode +process_reg_events_int (struct MHD_Daemon *MHD_RESTRICT daemon, + uint_fast64_t *MHD_RESTRICT next_max_wait) +{ + enum MHD_StatusCode res; + + if (mhd_DAEMON_STATE_STARTED > daemon->state) + return MHD_SC_TOO_EARLY; + if (! mhd_WM_INT_HAS_EXT_EVENTS (daemon->wmode_int)) + return MHD_SC_EXTERNAL_EVENT_ONLY; + if (mhd_DAEMON_STATE_STARTED < daemon->state) + return MHD_SC_TOO_LATE; + +#ifdef MHD_SUPPORT_THREADS + if (daemon->events.data.extr.itc_data.is_broken) + return MHD_SC_DAEMON_SYS_DATA_BROKEN; +#endif /* MHD_SUPPORT_THREADS */ + + if (daemon->net.listen.is_broken) + return MHD_SC_DAEMON_SYS_DATA_BROKEN; + + /* Ignore returned value */ + (void) process_all_events_and_data (daemon); + + if (NULL != next_max_wait) + *next_max_wait = MHD_WAIT_INDEFINITELY; + + res = ext_events_update_registrations (daemon); + if (MHD_SC_OK != res) + return res; + +#ifdef MHD_SUPPORT_THREADS + if (daemon->events.data.extr.itc_data.is_broken) + { + log_itc_broken (daemon); + return MHD_SC_DAEMON_SYS_DATA_BROKEN; + } +#endif /* MHD_SUPPORT_THREADS */ + + if (daemon->net.listen.is_broken) + { + log_listen_broken (daemon); + return MHD_SC_DAEMON_SYS_DATA_BROKEN; + } + + if (NULL != next_max_wait) + *next_max_wait = mhd_daemon_get_wait_max (daemon); + + return MHD_SC_OK; +} + + +MHD_EXTERN_ +MHD_FN_PAR_NONNULL_ (1) enum MHD_StatusCode +MHD_daemon_process_reg_events (struct MHD_Daemon *MHD_RESTRICT daemon, + uint_fast64_t *MHD_RESTRICT next_max_wait) +{ + enum MHD_StatusCode res; +#ifdef mhd_DEBUG_POLLING_FDS + fprintf (stderr, + "### (Starting) MHD_daemon_process_reg_events(daemon, [%s])...\n", + (NULL != next_max_wait) ? "non-NULL" : "NULL"); +#endif + res = process_reg_events_int (daemon, + next_max_wait); +#ifdef mhd_DEBUG_POLLING_FDS + if (NULL == next_max_wait) + fprintf (stderr, + "### (Finished) MHD_daemon_process_reg_events(daemon, [NULL]) ->" + "%u\n", + (unsigned int) res); + else if (MHD_WAIT_INDEFINITELY == *next_max_wait) + fprintf (stderr, + "### (Finished) MHD_daemon_process_reg_events(daemon, " + "->MHD_WAIT_INDEFINITELY) ->%u\n", + (unsigned int) res); + else + fprintf (stderr, + "### (Finished) MHD_daemon_process_reg_events(daemon, ->%llu) " + "->%u\n", + (unsigned long long) *next_max_wait, + (unsigned int) res); +#endif + return res; +} + + /** * The entry point for the daemon worker thread * @param cls the closure @@ -1167,9 +1811,6 @@ mhd_worker_all_events (void *cls) while (! d->threading.stop_requested) { - if (d->threading.resume_requested) - mhd_daemon_resume_conns (d); - if (! process_all_events_and_data (d)) break; } @@ -1179,7 +1820,7 @@ mhd_worker_all_events (void *cls) "The daemon thread is stopping, but termination has not " \ "been requested for the daemon."); } - close_all_daemon_conns (d); + mhd_daemon_close_all_conns (d); return (mhd_THRD_RTRN_TYPE) 0; } diff --git a/src/mhd2/events_process.h b/src/mhd2/events_process.h @@ -66,4 +66,13 @@ MHD_INTERNAL uint_fast64_t mhd_daemon_get_wait_max (struct MHD_Daemon *restrict d) MHD_FN_PAR_NONNULL_ALL_; +/** + * Close all daemon connections. + * Must not be called when any other connections processing function is running + * @param d the daemon to use + */ +MHD_INTERNAL void +mhd_daemon_close_all_conns (struct MHD_Daemon *d) +MHD_FN_PAR_NONNULL_ALL_; + #endif /* ! MHD_EVENTS_PROCESS_H */ diff --git a/src/mhd2/extr_events_funcs.c b/src/mhd2/extr_events_funcs.c @@ -0,0 +1,44 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2025 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/extr_events_funcs.c + * @brief The implementation of the external events internal helper functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" +#include "extr_events_funcs.h" + +#ifdef MHD_SUPPORT_LOG_FUNCTIONALITY + +#include "daemon_logger.h" + +MHD_INTERNAL void +mhd_log_extr_event_dereg_failed (struct MHD_Daemon *restrict d) +{ + mhd_LOG_MSG (d, \ + MHD_SC_EXTR_EVENT_DEREG_FAILED, \ + "Application failed to de-register FD: registration callback " \ + "returned non-NULL value"); +} + + +#endif /* MHD_SUPPORT_LOG_FUNCTIONALITY */ diff --git a/src/mhd2/extr_events_funcs.h b/src/mhd2/extr_events_funcs.h @@ -0,0 +1,134 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2025 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/extr_events_funcs.h + * @brief The definition of the external events internal helper functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_EXTR_EVENTS_FUNCS_H +#define MHD_EXTR_EVENTS_FUNCS_H 1 + +#include "mhd_sys_options.h" + +#ifdef mhd_DEBUG_POLLING_FDS +# include <stdio.h> +# include "mhd_daemon.h" +# include "mhd_assert.h" +# include "sys_null_macro.h" +#endif /* mhd_DEBUG_POLLING_FDS */ + +#ifdef MHD_SUPPORT_LOG_FUNCTIONALITY + +struct MHD_Daemon; /* forward declaration */ + +/** + * Log message about failed de-registration of FDs + */ +MHD_INTERNAL void +mhd_log_extr_event_dereg_failed (struct MHD_Daemon *restrict d); + +#else /* ! MHD_SUPPORT_LOG_FUNCTIONALITY */ + +/** + * Log message about failed de-registration of FDs (no-op implementation) + */ +#define mhd_log_extr_event_dereg_failed(d) ((void) 0) + +#endif /* ! MHD_SUPPORT_LOG_FUNCTIONALITY */ + +#ifdef mhd_DEBUG_POLLING_FDS +/** + * Call application event registration callback + * @param d the daemon to use + * @param fd the FD to register + * @param watch_for events/statuses to watch for + * @param app_cntx_old the previous application FD context + * @param ecb_cntx the MHD FD context + */ +MHD_static_inline_ +MHD_FN_PAR_NONNULL_ (1) void * +mhd_daemon_extr_event_reg (struct MHD_Daemon *d, + MHD_Socket fd, + enum MHD_FdState watch_for, + void *app_cntx_old, + struct MHD_EventUpdateContext *ecb_cntx) +{ + void *res; + char state_str[] = "x:x:x"; + const char *reg_type; + const char *fd_rel; + + mhd_assert (mhd_WM_INT_HAS_EXT_EVENTS (d->wmode_int)); + mhd_assert (mhd_POLL_TYPE_EXT == d->events.poll_type); + mhd_assert (mhd_SOCKET_REL_MARKER_EMPTY != (mhd_SockRelMarker) ecb_cntx); + + res = + d->events.data.extr.cb_data.cb (d->events.data.extr.cb_data.cls, + fd, + watch_for, + app_cntx_old, + ecb_cntx); + + if (NULL == app_cntx_old) + reg_type = " Registration"; + else if (MHD_FD_STATE_NONE == watch_for) + reg_type = "De-registration"; + else + reg_type = "Re-registration"; + + state_str[0] = MHD_FD_STATE_IS_SET_RECV (watch_for) ? 'R' : '-'; + state_str[2] = MHD_FD_STATE_IS_SET_SEND (watch_for) ? 'W' : '-'; + state_str[4] = MHD_FD_STATE_IS_SET_EXCEPT (watch_for) ? 'E' : '-'; + + switch ((mhd_SockRelMarker) ecb_cntx) + { + case mhd_SOCKET_REL_MARKER_ITC: + fd_rel = "ITC: "; + break; + case mhd_SOCKET_REL_MARKER_LISTEN: + fd_rel = "lstn:"; + break; + default: + fd_rel = "conn:"; + break; + } + fprintf (stderr, + "### %s callback (cls, [%s %2llu], %s, 0x%08llX, ptr) " + "-> 0x%08llX\n", + reg_type, + fd_rel, + (unsigned long long) fd, + state_str, + (unsigned long long) app_cntx_old, + (unsigned long long) res); + + return res; +} +#else /* ! mhd_DEBUG_POLLING_FDS */ +# define mhd_daemon_extr_event_reg(d,fd,w_for,app_cntx_old,ecb_cntx) \ + d->events.data.extr.cb_data.cb (d->events.data.extr.cb_data.cls, \ + fd, w_for, app_cntx_old, ecb_cntx) + +#endif /* ! mhd_DEBUG_POLLING_FDS */ + + +#endif /* ! MHD_EXTR_EVENTS_FUNCS_H */ diff --git a/src/mhd2/mhd_conn_socket.h b/src/mhd2/mhd_conn_socket.h @@ -151,7 +151,7 @@ struct mhd_ConnSocket struct mhd_ConnSocketState state; /** - * The receive / send / error readiness the socket + * The receive / send / error readiness of the socket */ enum mhd_SocketNetState ready; diff --git a/src/mhd2/mhd_connection.h b/src/mhd2/mhd_connection.h @@ -456,12 +456,31 @@ enum MHD_FIXED_ENUM_ mhd_HttpStage }; + +/** + * The connection's external event data + */ +struct mhd_ConnExtrEvents +{ + /** + * Connection's application context for the external events monitoring + */ + void *app_cntx; + + /** + * The last targets for which the connection FD has been registration for + */ + enum MHD_FdState reg_for; +}; + + struct mhd_ConnDebugData { bool closing_started; bool pre_cleaned; bool removed_from_daemon; bool tls_inited; + bool avoid_accept4; }; /** @@ -506,6 +525,11 @@ struct MHD_Connection */ struct mhd_ConnSocket sk; + /** + * The connection's external event data + */ + struct mhd_ConnExtrEvents extr_event; + #ifdef MHD_SUPPORT_HTTPS /** * Connection-specific TLS data. @@ -549,7 +573,7 @@ struct MHD_Connection struct MHD_UpgradedHandle upgr; /** - * Double-linke list of HTTP-Upgraded connections waiting for clean-up + * Double-linked list of HTTP-Upgraded connections waiting for clean-up */ mhd_DLNKDL_LINKS (MHD_Connection,upgr_cleanup); #endif /* MHD_SUPPORT_UPGRADE */ @@ -568,12 +592,12 @@ struct MHD_Connection /** * True if connection is suspended */ - bool suspended; + volatile bool suspended; /** * True if connection is resuming */ - bool resuming; + volatile bool resuming; /** * Request-specific data diff --git a/src/mhd2/mhd_daemon.h b/src/mhd2/mhd_daemon.h @@ -215,9 +215,9 @@ typedef unsigned char *mhd_SockRelMarker; #endif -#define mhd_SOCKET_REL_MARKER_EMPTY ((mhd_SockRelMarker) 0) +#define mhd_SOCKET_REL_MARKER_EMPTY ((mhd_SockRelMarker) (0)) -#define mhd_SOCKET_REL_MARKER_ITC ((mhd_SockRelMarker) - 1) +#define mhd_SOCKET_REL_MARKER_ITC ((mhd_SockRelMarker) (-1)) #define mhd_SOCKET_REL_MARKER_LISTEN (mhd_SOCKET_REL_MARKER_ITC - 1) /** @@ -316,10 +316,10 @@ struct mhd_DaemonEventsEPollData #endif /** - * Daemon's data for external events for sockets monitoring. + * Daemon's data for external events callback. * Internal version of struct MHD_WorkModeExternalEventLoopCBParam. */ -struct mhd_DaemonEventsExternal +struct mhd_DaemonEventsExternalCallback { /** * Socket registration callback @@ -331,6 +331,74 @@ struct mhd_DaemonEventsExternal void *cls; }; + +#ifdef MHD_SUPPORT_THREADS +/** + * External events data for ITC FD + */ +struct mhd_DaemonEventsExternalDaemonItcData +{ + /** + * Application context for ITC FD + */ + void *app_cntx; + + /** + * Set to 'true' when active state was detected on ITC by + * external polling + */ + bool is_active; + + /** + * Set to 'true' when error state was detected on ITC by + * external polling. + * The daemon may become non-functional. + */ + bool is_broken; +}; +#endif /* MHD_SUPPORT_THREADS */ + +/** + * External events data for the listen socket + */ +struct mhd_DaemonEventsExternalDaemonListenData +{ + /** + * Application context for ITC FD + */ + void *app_cntx; +}; + +/** + * Daemon's data for external events for sockets monitoring. + */ +struct mhd_DaemonEventsExternal +{ + /** + * Daemon's data for external events callback. + * Internal version of struct MHD_WorkModeExternalEventLoopCBParam. + */ + struct mhd_DaemonEventsExternalCallback cb_data; + +#ifdef MHD_SUPPORT_THREADS + /** + * External events data for ITC FD + */ + struct mhd_DaemonEventsExternalDaemonItcData itc_data; +#endif /* MHD_SUPPORT_THREADS */ + + /** + * External events data for the listen socket + */ + struct mhd_DaemonEventsExternalDaemonListenData listen_data; + + /** + * If set to 'true' then all FDs must be registered each round. + * If set to 'false' then only changed FDs must be registered. + */ + bool reg_all; +}; + /** * Type-specific events monitoring data */ @@ -362,9 +430,8 @@ union mhd_DaemonEventMonitoringTypeSpecificData /** * Daemon's data for external events for sockets monitoring. - * Internal version of struct MHD_WorkModeExternalEventLoopCBParam. */ - struct mhd_DaemonEventsExternal ext; + struct mhd_DaemonEventsExternal extr; }; @@ -374,9 +441,11 @@ union mhd_DaemonEventMonitoringTypeSpecificData struct mhd_DaemonEventActionRequired { /** - * If 'true' then connection is waiting to be accepted + * If 'true' connection resuming is required. + * TODO: implement resuming + * TODO: add initialisation */ - bool accept; + bool resume; }; @@ -403,10 +472,9 @@ struct mhd_DaemonEventMonitoringData struct mhd_DaemonEventActionRequired act_req; /** - * Indicate that daemon already has some data to be processed on the next - * cycle + * When set to 'true' the listen socket has new incoming connection(s). */ - bool zero_wait; + bool accept_pending; /** * The list of the daemon's connections that need processing @@ -452,6 +520,12 @@ struct mhd_ListenSocket */ MHD_Socket fd; /** + * Set to 'true' when unrecoverable error is detected on the listen socket. + * If set to 'true', but @a fd is not #MHD_INVALID_SOCKET then "broken" state + * has not yet processed by MHD. + */ + bool is_broken; + /** * The type of the listening socket @a fd */ enum mhd_SocketType type; @@ -485,6 +559,12 @@ struct mhd_DaemonNetworkSettings /** * The daemon network/sockets data + * + * This structure holds mostly static information—typically initialised once + * when the daemon starts, and possibly updated once later (e.g., if the + * listening socket fails or is closed). + * + * It does NOT contain any operational states. */ struct mhd_DaemonNetwork { @@ -1019,6 +1099,12 @@ struct MHD_Daemon /** * The daemon network/sockets data + * + * This structure holds mostly static information—typically initialised once + * when the daemon starts, and possibly updated once later (e.g., if the + * listening socket fails or is closed). + * + * It does NOT contain any operational states. */ struct mhd_DaemonNetwork net; diff --git a/src/mhd2/mhd_dbg_print.h b/src/mhd2/mhd_dbg_print.h @@ -0,0 +1,61 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2025 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/mhd_dbg_print.h + * @brief The declarations of interl debug-print helpers + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_DBG_PRINT_H +#define MHD_DBG_PRINT_H 1 + +#include "mhd_sys_options.h" + +#ifdef mhd_DEBUG_POLLING_FDS +# include "sys_bool_type.h" +# include "mhd_socket_type.h" +#endif /* mhd_DEBUG_POLLING_FDS */ + + +#ifdef mhd_DEBUG_POLLING_FDS +/** + * Debug-printf request of FD polling/monitoring + * @param fd_name the name of FD ("ITC", "lstn" or "conn") + * @param fd the FD value + * @param r_ready the request for read (or receive) readiness + * @param w_ready the request for write (or send) readiness + * @param e_ready the request for exception (or error) readiness + * @note Implemented in src/mhd2/events_process.c + */ +MHD_INTERNAL void +mhd_dbg_print_fd_mon_req (const char *fd_name, + MHD_Socket fd, + bool r_ready, + bool w_ready, + bool e_ready) +MHD_FN_PAR_NONNULL_ALL_; + +#else /* ! mhd_DEBUG_POLLING_FDS */ +# define mhd_dbg_print_fd_mon_req(fd_n,fd,r_ready,w_ready,e_ready) ((void) 0) +#endif /* ! mhd_DEBUG_POLLING_FDS */ + + +#endif /* ! MHD_DBG_PRINT_H */ diff --git a/src/mhd2/mhd_dlinked_list.h b/src/mhd2/mhd_dlinked_list.h @@ -144,14 +144,14 @@ mhd_assert (NULL == (p_obj)->links_name.next); \ mhd_assert ((p_obj) != (p_list)->first); \ mhd_assert ((p_obj) != (p_list)->last); \ - mhd_assert (((p_list)->first) || (! ((p_list)->last))); \ - mhd_assert ((! ((p_list)->first)) || ((p_list)->last)); \ if (NULL != (p_list)->first) \ { mhd_assert (NULL == (p_list)->first->links_name.prev); \ - mhd_assert ((p_obj) != (p_list)->first->links_name.next); \ - (p_obj)->links_name.next = (p_list)->first; \ - (p_obj)->links_name.next->links_name.prev = (p_obj); } else \ - { (p_list)->last = (p_obj); } \ + mhd_assert ((p_obj) != (p_list)->first->links_name.next); \ + mhd_assert (NULL != (p_list)->last); \ + ((p_obj)->links_name.next = (p_list)->first) \ + ->links_name.prev = (p_obj); } else \ + { mhd_assert (NULL == (p_list)->last); \ + (p_list)->last = (p_obj); } \ (p_list)->first = (p_obj); } while (0) /** @@ -167,14 +167,14 @@ mhd_assert (NULL == (p_obj)->links_name.next); \ mhd_assert ((p_obj) != (p_list)->first); \ mhd_assert ((p_obj) != (p_list)->last); \ - mhd_assert (((p_list)->first) || (! ((p_list)->last))); \ - mhd_assert ((! ((p_list)->first)) || ((p_list)->last)); \ if (NULL != (p_list)->last) \ { mhd_assert (NULL == (p_list)->last->links_name.next); \ - mhd_assert ((p_obj) != (p_list)->last->links_name.prev); \ - (p_obj)->links_name.prev = (p_list)->last; \ - (p_obj)->links_name.prev->links_name.next = (p_obj); } else \ - { (p_list)->first = (p_obj); } \ + mhd_assert ((p_obj) != (p_list)->last->links_name.prev); \ + mhd_assert (NULL != (p_list)->first); \ + ((p_obj)->links_name.prev = (p_list)->last) \ + ->links_name.next = (p_obj); } else \ + { mhd_assert (NULL == (p_list)->first); \ + (p_list)->first = (p_obj); } \ (p_list)->last = (p_obj); } while (0) /** @@ -188,16 +188,18 @@ mhd_assert (NULL != (p_list)->first); \ mhd_assert (NULL != (p_list)->last); \ mhd_assert ((p_obj) != (p_obj)->links_name.prev); \ - mhd_assert ((p_obj) != (p_obj)->links_name.next); \ + mhd_assert ((p_list)->last != (p_obj)->links_name.prev); \ + mhd_assert ((p_obj) != (p_obj)->links_name.next); \ + mhd_assert ((p_list)->first != (p_obj)->links_name.next); \ if (NULL != (p_obj)->links_name.next) \ - { mhd_assert (NULL != (p_obj)->links_name.next->links_name.prev); \ + { mhd_assert ((p_obj) == (p_obj)->links_name.next->links_name.prev); \ mhd_assert ((p_obj) != (p_list)->last); \ (p_obj)->links_name.next->links_name.prev = \ (p_obj)->links_name.prev; } else \ { mhd_assert ((p_obj) == (p_list)->last); \ (p_list)->last = (p_obj)->links_name.prev; } \ if (NULL != (p_obj)->links_name.prev) \ - { mhd_assert (NULL != (p_obj)->links_name.prev->links_name.next); \ + { mhd_assert ((p_obj) == (p_obj)->links_name.prev->links_name.next); \ mhd_assert ((p_obj) != (p_list)->first); \ (p_obj)->links_name.prev->links_name.next = \ (p_obj)->links_name.next; } else \ diff --git a/src/mhd2/mhd_recv.c b/src/mhd2/mhd_recv.c @@ -61,7 +61,8 @@ mhd_recv_plain (struct MHD_Connection *restrict c, if (0 <= res) { *received = (size_t) res; - if (buf_size > (size_t) res) + if ((buf_size > (size_t) res) + || ! c->sk.props.is_nonblck) c->sk.ready = (enum mhd_SocketNetState) /* Clear 'recv-ready' */ (((unsigned int) c->sk.ready) & (~(enum mhd_SocketNetState) @@ -109,6 +110,11 @@ mhd_recv_tls (struct MHD_Connection *restrict c, mhd_SOCKET_NET_STATE_RECV_READY)); else if (mhd_SOCKET_ERR_NO_ERROR == res) { + if (! c->sk.props.is_nonblck) + c->sk.ready = (enum mhd_SocketNetState) /* Clear 'recv-ready' */ + (((unsigned int) c->sk.ready) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_RECV_READY)); if (res == buf_size) { if (mhd_tls_conn_has_data_in (c->tls)) diff --git a/src/mhd2/mhd_send.c b/src/mhd2/mhd_send.c @@ -805,7 +805,7 @@ mhd_send_plain (struct MHD_Connection *restrict c, full_buf_sent = (buf_size == (size_t) res); - if (! full_buf_sent) + if (! full_buf_sent || ! c->sk.props.is_nonblck) c->sk.ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ (((unsigned int) c->sk.ready) & (~(enum mhd_SocketNetState) @@ -858,6 +858,12 @@ mhd_send_tls (struct MHD_Connection *restrict c, return res; } + if (! c->sk.props.is_nonblck) + c->sk.ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ + (((unsigned int) c->sk.ready) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_SEND_READY)); + /* If there is a need to push the data from network buffers * call post_send_setopt(). */ if (push_data && (buf_size == *sent)) @@ -1104,7 +1110,8 @@ mhd_send_hdr_and_body (struct MHD_Connection *restrict connection, return err; } - if ((header_size + body_size) > *sent) + if (((header_size + body_size) > *sent) + || ! connection->sk.props.is_nonblck) connection->sk.ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ (((unsigned int) connection->sk.ready) & (~(enum mhd_SocketNetState) @@ -1354,7 +1361,7 @@ mhd_send_sendfile (struct MHD_Connection *restrict c, return mhd_SOCKET_ERR_INTR; } - if ((mhd_SOCKET_ERR_AGAIN == ret) || + if ((mhd_SOCKET_ERR_AGAIN == ret) || ! c->sk.props.is_nonblck || ((mhd_SOCKET_ERR_NO_ERROR == ret) && (send_size > sent_bytes))) c->sk.ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ (((unsigned int) c->sk.ready) @@ -1502,6 +1509,13 @@ send_iov_nontls (struct MHD_Connection *restrict connection, if (1) { size_t track_sent = (size_t) *sent; + + if (! connection->sk.props.is_nonblck) + connection->sk.ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ + (((unsigned int) connection->sk.ready) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_SEND_READY)); + /* Adjust the internal tracking information for the iovec to * take this last send into account. */ while ((0 != track_sent) && (r_iov->iov[r_iov->sent].iov_len <= track_sent)) diff --git a/src/mhd2/request_resume.c b/src/mhd2/request_resume.c @@ -0,0 +1,68 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2025 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + GNU 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +/** + * @file src/mhd2/request_resume.c + * @brief The implementation of MHD_request_resume() function + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "mhd_cntnr_ptr.h" + +#ifdef mhd_DEBUG_SUSPEND_RESUME +# include <stdio.h> +#endif /* mhd_DEBUG_SUSPEND_RESUME */ + +#include "mhd_daemon.h" +#include "mhd_connection.h" + +#include "daemon_funcs.h" + +#include "mhd_public_api.h" + +MHD_EXTERN_ MHD_FN_PAR_NONNULL_ALL_ void +MHD_request_resume (struct MHD_Request *request) +{ + struct MHD_Connection *c = mhd_CNTNR_PTR (request, \ + struct MHD_Connection, \ + rq); + struct MHD_Daemon *d = c->daemon; + + if (! c->suspended) + { +#ifdef mhd_DEBUG_SUSPEND_RESUME + fprintf (stderr, + "%%%%%% Requested conn resume, FD: %2llu -> " + "failed as not suspended\n", + (unsigned long long) c->sk.fd); +#endif /* mhd_DEBUG_SUSPEND_RESUME */ + return; + } + c->resuming = true; +#ifdef mhd_DEBUG_SUSPEND_RESUME + fprintf (stderr, + "%%%%%% Requested conn resume, FD: %2llu\n", + (unsigned long long) c->sk.fd); +#endif /* mhd_DEBUG_SUSPEND_RESUME */ + d->threading.resume_requested = true; + mhd_daemon_trigger_itc (d); +} diff --git a/src/mhd2/stream_funcs.c b/src/mhd2/stream_funcs.c @@ -31,7 +31,11 @@ #include "mhd_assert.h" #include "mhd_unreachable.h" +#ifdef mhd_DEBUG_CONN_ADD_CLOSE +# include <stdio.h> +#endif /* mhd_DEBUG_CONN_ADD_CLOSE */ #include <string.h> +#include "extr_events_funcs.h" #ifdef MHD_SUPPORT_EPOLL # include <sys/epoll.h> #endif @@ -53,6 +57,7 @@ #include "daemon_funcs.h" #include "conn_mark_ready.h" #include "stream_process_reply.h" +#include "extr_events_funcs.h" #ifdef MHD_SUPPORT_HTTPS # include "mhd_tls_funcs.h" @@ -566,12 +571,31 @@ mhd_stream_finish_req_serving (struct MHD_Connection *restrict c, (0 == c->read_buffer_offset) ? MHD_EVENT_LOOP_INFO_RECV : MHD_EVENT_LOOP_INFO_PROCESS; + // TODO: move request reset to special function memset (&c->rq, 0, sizeof(c->rq)); + // TODO: move reply reset to special function /* iov (if any) will be deallocated by mhd_pool_reset */ memset (&c->rp, 0, sizeof(c->rp)); - // TODO: set all rq and tp pointers to NULL manually. Do the same in other places. +#ifndef HAVE_NULL_PTR_ALL_ZEROS + // TODO: move request reset to special function + mhd_DLINKEDL_INIT_LIST (&(c->rq), fields); +#ifdef MHD_SUPPORT_POST_PARSER + mhd_DLINKEDL_INIT_LIST (&(c->rq), post_fields); +#endif /* MHD_SUPPORT_POST_PARSER */ + c->rq.version = NULL; + c->rq.url = NULL; + c->rq.field_lines.start = NULL; + c->rq.app_context = NULL; + c->rq.hdrs.rq_line.rq_tgt = NULL; + c->rq.hdrs.rq_line.rq_tgt_qmark = NULL; + + // TODO: move reply reset to special function + c->rp.app_act_ctx.connection = NULL; + c->rp.response = NULL; + c->rp.resp_iov.iov = NULL; +#endif /* ! HAVE_NULL_PTR_ALL_ZEROS */ c->write_buffer = NULL; c->write_buffer_size = 0; @@ -672,6 +696,48 @@ mhd_stream_update_activity_mark (struct MHD_Connection *restrict c) } +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void +mhd_stream_resumed_activity_mark (struct MHD_Connection *restrict c) +{ + struct MHD_Daemon *const restrict d = c->daemon; +#if defined(MHD_SUPPORT_THREADS) + mhd_assert (! mhd_D_HAS_WORKERS (d)); +#endif /* MHD_SUPPORT_THREADS */ + + mhd_assert (! c->suspended); + mhd_assert (c->resuming); + + /* Update of activity for connections without timeout timer unless + * no timeout is set */ + if (0 != c->connection_timeout_ms) + c->last_activity = mhd_monotonic_msec_counter (); // TODO: Get and use time value one time per round + + if (mhd_D_HAS_THR_PER_CONN (d)) + return; /* each connection has personal timeout */ + + if (c->connection_timeout_ms == d->conns.cfg.timeout) + mhd_DLINKEDL_INS_FIRST_D (&(d->conns.def_timeout), c, by_timeout); + else + mhd_DLINKEDL_INS_FIRST_D (&(d->conns.cust_timeout), c, by_timeout); +} + + +MHD_INTERNAL +MHD_FN_PAR_NONNULL_ALL_ void +mhd_conn_remove_from_timeout_lists (struct MHD_Connection *restrict c) +{ + if (mhd_D_HAS_THR_PER_CONN (c->daemon)) + return; + + if (c->connection_timeout_ms == c->daemon->conns.cfg.timeout) + mhd_DLINKEDL_DEL_D (&(c->daemon->conns.def_timeout), \ + c, by_timeout); + else + mhd_DLINKEDL_DEL_D (&(c->daemon->conns.cust_timeout), \ + c, by_timeout); +} + + MHD_INTERNAL MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3) void mhd_conn_start_closing (struct MHD_Connection *restrict c, @@ -682,6 +748,16 @@ mhd_conn_start_closing (struct MHD_Connection *restrict c, enum MHD_RequestEndedCode end_code; enum MHD_StatusCode sc; +#ifdef mhd_DEBUG_CONN_ADD_CLOSE + fprintf (stderr, + "&&& mhd_conn_start_closing([FD: %2llu], %u, %s%s%s)...\n", + (unsigned long long) c->sk.fd, + (unsigned int) reason, + log_msg ? "\"" : "", + log_msg ? log_msg : "[NULL]", + log_msg ? "\"" : ""); +#endif /* mhd_DEBUG_CONN_ADD_CLOSE */ + sc = MHD_SC_INTERNAL_ERROR; switch (reason) { @@ -750,6 +826,11 @@ mhd_conn_start_closing (struct MHD_Connection *restrict c, close_hard = true; end_code = MHD_REQUEST_ENDED_NO_RESOURCES; break; + case mhd_CONN_CLOSE_EXTR_EVENT_REG_FAILED: + close_hard = true; + end_code = MHD_REQUEST_ENDED_BY_EXT_EVENT_ERROR; + sc = MHD_SC_EXTR_EVENT_REG_FAILED; + break; case mhd_CONN_CLOSE_NO_SYS_RESOURCES: close_hard = true; end_code = MHD_REQUEST_ENDED_NO_RESOURCES; @@ -914,14 +995,10 @@ mhd_conn_start_closing (struct MHD_Connection *restrict c, #endif c->rq.app_aware = false; - if (! mhd_D_HAS_THR_PER_CONN (c->daemon)) + if (! c->suspended) { - if (c->connection_timeout_ms == c->daemon->conns.cfg.timeout) - mhd_DLINKEDL_DEL_D (&(c->daemon->conns.def_timeout), \ - c, by_timeout); - else - mhd_DLINKEDL_DEL_D (&(c->daemon->conns.cust_timeout), \ - c, by_timeout); + mhd_assert (! c->resuming); + mhd_conn_remove_from_timeout_lists (c); } #ifndef NDEBUG @@ -941,8 +1018,23 @@ mhd_conn_pre_clean_part1 (struct MHD_Connection *restrict c) if (NULL != c->rq.cntn.lbuf.data) mhd_daemon_free_lbuf (c->daemon, &(c->rq.cntn.lbuf)); + if (mhd_WM_INT_HAS_EXT_EVENTS (c->daemon->wmode_int)) + { + struct MHD_Daemon *const d = c->daemon; + if (NULL != c->extr_event.app_cntx) + { + c->extr_event.app_cntx = + mhd_daemon_extr_event_reg (d, + c->sk.fd, + MHD_FD_STATE_NONE, + c->extr_event.app_cntx, + (struct MHD_EventUpdateContext *) c); + if (NULL != c->extr_event.app_cntx) + mhd_log_extr_event_dereg_failed (d); + } + } #ifdef MHD_SUPPORT_EPOLL - if (mhd_POLL_TYPE_EPOLL == c->daemon->events.poll_type) + else if (mhd_POLL_TYPE_EPOLL == c->daemon->events.poll_type) { struct epoll_event event; @@ -965,7 +1057,11 @@ MHD_INTERNAL MHD_FN_PAR_NONNULL_ (1) void mhd_conn_pre_clean (struct MHD_Connection *restrict c) { - // TODO: support suspended connections +#ifdef mhd_DEBUG_CONN_ADD_CLOSE + fprintf (stderr, + "&&& Closing connection, FD: %2llu\n", + (unsigned long long) c->sk.fd); +#endif /* mhd_DEBUG_CONN_ADD_CLOSE */ mhd_assert (c->dbg.closing_started); mhd_assert (! c->dbg.pre_cleaned); diff --git a/src/mhd2/stream_funcs.h b/src/mhd2/stream_funcs.h @@ -142,6 +142,24 @@ MHD_INTERNAL void mhd_stream_update_activity_mark (struct MHD_Connection *restrict c) MHD_FN_PAR_NONNULL_ALL_; + +/** + * Update last activity mark on the resumed connection + * @param c the connection to update + */ +MHD_INTERNAL void +mhd_stream_resumed_activity_mark (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + + +/** + * Remove connection from time-out lists + * @param c the connection to process + */ +MHD_INTERNAL void +mhd_conn_remove_from_timeout_lists (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + /** * Check whether connection's timeout is expired. * @param c the connection to update @@ -229,6 +247,11 @@ enum mhd_ConnCloseReason mhd_CONN_CLOSE_INT_ERROR , /** + * Failed to register the connection for the external event monitoring + */ + mhd_CONN_CLOSE_EXTR_EVENT_REG_FAILED + , + /** * No system resources available to handle connection */ mhd_CONN_CLOSE_NO_SYS_RESOURCES @@ -321,6 +344,29 @@ MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_CSTR_ (3); #define mhd_conn_start_closing_app_abort(c) \ mhd_conn_start_closing ((c), mhd_CONN_CLOSE_APP_ABORTED, NULL) + +#ifdef MHD_SUPPORT_LOG_FUNCTIONALITY +/** + * Perform initial clean-up and mark for closing. + * Set the reason to "socket error" + * @param c the connection for pre-closing + */ +#define mhd_conn_start_closing_ext_event_failed(c) \ + mhd_conn_start_closing ((c), \ + mhd_CONN_CLOSE_EXTR_EVENT_REG_FAILED, \ + "The application failed to register FD for " \ + "the external events monitoring.") +#else /* ! MHD_SUPPORT_LOG_FUNCTIONALITY */ +/** + * Perform initial clean-up and mark for closing. + * Set the reason to "socket error" + * @param c the connection for pre-closing + */ +#define mhd_conn_start_closing_ext_event_failed(c) \ + mhd_conn_start_closing ((c), \ + mhd_CONN_CLOSE_EXTR_EVENT_REG_FAILED, NULL) +#endif /* ! MHD_SUPPORT_LOG_FUNCTIONALITY */ + /** * Perform initial clean-up and mark for closing. * Set the reason to "socket error" diff --git a/src/mhd2/stream_process_request.c b/src/mhd2/stream_process_request.c @@ -40,6 +40,10 @@ #include "sys_malloc.h" +#ifdef mhd_DEBUG_SUSPEND_RESUME +# include <stdio.h> +#endif /* mhd_DEBUG_SUSPEND_RESUME */ + #include "mhd_str_types.h" #include "mhd_str_macros.h" #include "mhd_str.h" @@ -3002,6 +3006,12 @@ mhd_stream_call_app_request_cb (struct MHD_Connection *restrict c) #endif /* MHD_SUPPORT_POST_PARSER */ case mhd_ACTION_SUSPEND: c->suspended = true; +#ifdef mhd_DEBUG_SUSPEND_RESUME + fprintf (stderr, + "%%%%%% Suspending connection, FD: %2llu\n", + (unsigned long long) c->sk.fd); +#endif /* mhd_DEBUG_SUSPEND_RESUME */ + c->rq.app_act.head_act.act = mhd_ACTION_NO_ACTION; return false; #ifdef MHD_SUPPORT_UPGRADE case mhd_ACTION_UPGRADE: @@ -3063,6 +3073,12 @@ mhd_stream_process_upload_action (struct MHD_Connection *restrict c, return false; case mhd_UPLOAD_ACTION_SUSPEND: c->suspended = true; +#ifdef mhd_DEBUG_SUSPEND_RESUME + fprintf (stderr, + "%%%%%% Suspending connection, FD: %2llu\n", + (unsigned long long) c->sk.fd); +#endif /* mhd_DEBUG_SUSPEND_RESUME */ + memset (&(c->rq.app_act.upl_act), 0, sizeof(c->rq.app_act.upl_act)); return false; #ifdef MHD_SUPPORT_UPGRADE case mhd_UPLOAD_ACTION_UPGRADE: diff --git a/src/mhd2/stream_process_states.c b/src/mhd2/stream_process_states.c @@ -55,6 +55,10 @@ # include "upgrade_proc.h" #endif /* MHD_SUPPORT_UPGRADE */ +#ifdef mhd_DEBUG_SUSPEND_RESUME +# include <stdio.h> +#endif /* mhd_DEBUG_SUSPEND_RESUME */ + MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void mhd_conn_event_loop_state_update (struct MHD_Connection *restrict c) { @@ -180,6 +184,26 @@ mhd_conn_event_loop_state_update (struct MHD_Connection *restrict c) /** + * Finalise resuming of the connection + * @param c the connection to resume + */ +static MHD_FN_PAR_NONNULL_ALL_ void +finish_resume (struct MHD_Connection *restrict c) +{ + mhd_assert (c->resuming); + c->resuming = false; + +#ifdef mhd_DEBUG_SUSPEND_RESUME + fprintf (stderr, + "%%%%%% Resumed connection, FD: %2llu\n", + (unsigned long long) c->sk.fd); +#endif /* mhd_DEBUG_SUSPEND_RESUME */ + mhd_assert (! c->suspended); + mhd_assert (MHD_EVENT_LOOP_INFO_PROCESS == c->event_loop_info); +} + + +/** * Update current processing state: need to receive, need to send. * Mark stream as ready or not ready for processing. * Grow the receive buffer if neccesary, close stream if no buffer space left, @@ -232,8 +256,8 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) struct MHD_Daemon *const d = c->daemon; bool daemon_closing; - /* 'daemon' is not used if epoll is not available and asserts are disabled */ - (void) d; /* Mute compiler warning */ + if (c->suspended) + return true; if ((c->sk.state.rmt_shut_wr) && (mhd_HTTP_STAGE_START_REPLY > c->stage)) { @@ -248,13 +272,8 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) } } - mhd_assert (c->resuming || ! c->suspended); if (c->resuming) - { - // TODO: finish resuming, update activity mark - // TODO: move to special function - (void) 0; - } + finish_resume (c); if ((mhd_SOCKET_ERR_NO_ERROR != c->sk.state.discnt_err) || (0 != (c->sk.ready & mhd_SOCKET_NET_STATE_ERROR_READY))) @@ -278,6 +297,8 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) return false; } + mhd_assert (! c->suspended); + while (! c->suspended) { #ifdef MHD_SUPPORT_HTTPS @@ -509,8 +530,16 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) if (c->suspended) { - // TODO: process - mhd_assert (0 && "Not implemented yet"); + /* Do not perform any network activity while suspended */ + c->event_loop_info = MHD_EVENT_LOOP_INFO_PROCESS; + + mhd_conn_mark_unready (c, d); + mhd_conn_remove_from_timeout_lists (c); +#ifdef mhd_DEBUG_SUSPEND_RESUME + fprintf (stderr, + "%%%%%% Connection suspended, FD: %2llu\n", + (unsigned long long) c->sk.fd); +#endif /* mhd_DEBUG_SUSPEND_RESUME */ return true; } diff --git a/src/mhd2/sys_sockets_headers.h b/src/mhd2/sys_sockets_headers.h @@ -124,9 +124,9 @@ #ifdef HAVE_DCLR_MSG_MORE # ifdef __linux__ -/* MSG_MORE signal kernel to buffer outbond data and works like - * TCP_CORK per call without actually setting TCP_CORK value. - * It's known to work on Linux. Add more OSes if they are compatible. */ +/* MSG_MORE signal kernel to buffer outbound data and works like + TCP_CORK for a single call without actually setting the TCP_CORK flag. + It's known to work on Linux. Add more OSes if they are compatible. */ /** * Indicate MSG_MORE is usable for buffered send(). */ @@ -141,6 +141,12 @@ #endif +#if defined(HAVE_ACCEPT4) && (defined(HAVE_DCLR_SOCK_NONBLOCK) || \ + defined(HAVE_DCLR_SOCK_CLOEXEC) || defined(HAVE_DCLR_SOCK_NOSIGPIPE)) +# define mhd_USE_ACCEPT4 1 +#endif + + /** * mhd_SCKT_OPT_BOOL is the type for bool parameters * for setsockopt()/getsockopt() functions