← Commit list

357808f4ad

Stefan Eissing
Stefan Eissing
2025-10-07
SUCCESS Duration: 149s

Subject

multi: add notifications API

Binary

No binary (build failed)

Architecture & Compiler Flags

Architecture: x86_64-linux-gnu Compiler: gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0

—

Build Log

(log yok)

Commit Diff GitHub →

.github/scripts/spellcheck.curl+2 -1
@@ -133,9 +133,10 @@ curl_multi_setopt
curl_multi_assign
curl_multi_get_handles
curl_multi_get_offt
+curl_multi_notify_disable
+curl_multi_notify_enable
curl_pushheader_bynum
curl_pushheader_byname
-curl_multi_waitfds
curl_easy_option_by_name
curl_easy_option_by_id
curl_easy_option_next
docs/libcurl/Makefile.inc+2 -0
@@ -78,6 +78,8 @@ man_MANS = \
curl_multi_get_offt.3 \
curl_multi_info_read.3 \
curl_multi_init.3 \
+ curl_multi_notify_disable.3 \
+ curl_multi_notify_enable.3 \
curl_multi_perform.3 \
curl_multi_poll.3 \
curl_multi_remove_handle.3 \
docs/libcurl/curl_multi_notify_disable.md+66 -0
@@ -0,0 +1,66 @@
+---
+c: Copyright (C) Daniel Stenberg, <[email protected]>, et al.
+SPDX-License-Identifier: curl
+Title: curl_multi_notify_disable
+Section: 3
+Source: libcurl
+See-also:
+ - CURLMOPT_NOTIFYFUNCTION (3)
+ - CURLMOPT_NOTIFYDATA (3)
+ - curl_multi_notify_enable (3)
+Protocol:
+ - All
+Added-in: 8.17.0
+---
+
+# NAME
+
+curl_multi_notify_disable - disable a notification type
+
+# SYNOPSIS
+
+~~~c
+#include <curl/curl.h>
+CURLMcode curl_multi_notify_disable(CURLM *multi_handle,
+ unsigned int notification);
+~~~
+
+# DESCRIPTION
+
+Disables collecting the given notification type in the multi handle. A
+callback function installed via CURLMOPT_NOTIFYFUNCTION(3) is no longer
+called when this notification happens.
+
+Only when a notification callback is installed *and* a notification
+is enabled are these collected and dispatched to the callback.
+
+Several notification types can be enabled at the same time. Disabling
+an already disabled notification is not an error.
+
+A notification can be enabled again via curl_multi_notify_enable(3).
+
+# %PROTOCOLS%
+
+# EXAMPLE
+
+~~~c
+int main(void)
+{
+ int rc;
+ CURLM *multi = curl_multi_init();
+
+ rc = curl_multi_notify_disable(multi, CURLM_NTFY_INFO_READ);
+}
+~~~
+
+# %AVAILABILITY%
+
+# RETURN VALUE
+
+This function returns a CURLMcode indicating success or error.
+
+CURLM_OK (0) means everything was OK, non-zero means an error occurred, see
+libcurl-errors(3).
+
+The return code is for the whole multi stack. Problems still might have
+occurred on individual transfers even when one of these functions return OK.
docs/libcurl/curl_multi_notify_enable.md+66 -0
@@ -0,0 +1,66 @@
+---
+c: Copyright (C) Daniel Stenberg, <[email protected]>, et al.
+SPDX-License-Identifier: curl
+Title: curl_multi_notify_enable
+Section: 3
+Source: libcurl
+See-also:
+ - CURLMOPT_NOTIFYFUNCTION (3)
+ - CURLMOPT_NOTIFYDATA (3)
+ - curl_multi_notify_disable (3)
+Protocol:
+ - All
+Added-in: 8.17.0
+---
+
+# NAME
+
+curl_multi_notify_enable - enable a notification type
+
+# SYNOPSIS
+
+~~~c
+#include <curl/curl.h>
+CURLMcode curl_multi_notify_enable(CURLM *multi_handle,
+ unsigned int notification);
+~~~
+
+# DESCRIPTION
+
+Enables collecting the given notification type in the multi handle. A
+callback function installed via CURLMOPT_NOTIFYFUNCTION(3) is called
+when this notification happens.
+
+Only when a notification callback is installed *and* a notification
+is enabled are these collected and dispatched to the callback.
+
+Several notification types can be enabled at the same time. Enabling
+an already enabled notification is not an error.
+
+A notification can be disabled again via curl_multi_notify_disable(3).
+
+# %PROTOCOLS%
+
+# EXAMPLE
+
+~~~c
+int main(void)
+{
+ int rc;
+ CURLM *multi = curl_multi_init();
+
+ rc = curl_multi_notify_enable(multi, CURLM_NTFY_INFO_READ);
+}
+~~~
+
+# %AVAILABILITY%
+
+# RETURN VALUE
+
+This function returns a CURLMcode indicating success or error.
+
+CURLM_OK (0) means everything was OK, non-zero means an error occurred, see
+libcurl-errors(3).
+
+The return code is for the whole multi stack. Problems still might have
+occurred on individual transfers even when one of these functions return OK.
docs/libcurl/curl_multi_setopt.md+8 -0
@@ -72,6 +72,14 @@ Max simultaneously open connections. See CURLMOPT_MAX_TOTAL_CONNECTIONS(3)
Signal that the network has changed. See CURLMOPT_NETWORK_CHANGED(3)
+## CURLMOPT_NOTIFYDATA
+
+Custom pointer passed to the notify callback. See CURLMOPT_NOTIFYDATA(3)
+
+## CURLMOPT_NOTIFYFUNCTION
+
+Callback that receives notifications. See CURLMOPT_NOTIFYFUNCTION(3)
+
## CURLMOPT_PIPELINING
Enable HTTP multiplexing. See CURLMOPT_PIPELINING(3)
docs/libcurl/opts/CURLMOPT_NOTIFYDATA.md+72 -0
@@ -0,0 +1,72 @@
+---
+c: Copyright (C) Daniel Stenberg, <[email protected]>, et al.
+SPDX-License-Identifier: curl
+Title: CURLMOPT_NOTIFYDATA
+Section: 3
+Source: libcurl
+See-also:
+ - CURLMOPT_NOTIFYFUNCTION (3)
+ - curl_multi_notify_disable (3)
+ - curl_multi_notify_enable (3)
+Protocol:
+ - All
+Added-in: 8.17.0
+---
+
+# NAME
+
+CURLMOPT_NOTIFYDATA - custom pointer passed to the notification callback
+
+# SYNOPSIS
+
+~~~c
+#include <curl/curl.h>
+
+CURLMcode curl_multi_setopt(CURLM *handle, CURLMOPT_NOTIFYDATA, void *pointer);
+~~~
+
+# DESCRIPTION
+
+A data *pointer* to pass to the notification callback set with the
+CURLMOPT_NOTIFYFUNCTION(3) option.
+
+This pointer is not touched by libcurl but is only passed in as the
+notification callback's **clientp** argument.
+
+# DEFAULT
+
+NULL
+
+# %PROTOCOLS%
+
+# EXAMPLE
+
+~~~c
+struct priv {
+ void *ours;
+};
+
+static void ntfy_cb(CURLM *multi, unsigned int notification,
+ CURL *easy, void *ntfyp)
+{
+ struct priv *p = ntfyp;
+ printf("my ptr: %p\n", p->ours);
+ /* ... */
+}
+
+int main(void)
+{
+ struct priv setup;
+ CURLM *multi = curl_multi_init();
+ /* ... use socket callback and custom pointer */
+ curl_multi_setopt(multi, CURLMOPT_NOTIFYFUNCTION, ntfy_cb);
+ curl_multi_setopt(multi, CURLMOPT_NOTIFYDATA, &setup);
+ curl_multi_notify_enable(multi, CURLM_NTFY_INFO_READ);
+}
+~~~
+
+# %AVAILABILITY%
+
+# RETURN VALUE
+
+Returns CURLM_OK.
docs/libcurl/opts/CURLMOPT_NOTIFYFUNCTION.md+129 -0
@@ -0,0 +1,129 @@
+---
+c: Copyright (C) Daniel Stenberg, <[email protected]>, et al.
+SPDX-License-Identifier: curl
+Title: CURLMOPT_NOTIFYFUNCTION
+Section: 3
+Source: libcurl
+See-also:
+ - CURLMOPT_NOTIFYDATA (3)
+ - curl_multi_socket_action (3)
+ - curl_multi_notify_disable (3)
+ - curl_multi_notify_enable (3)
+Protocol:
+ - All
+Added-in: 8.17.0
+---
+
+# NAME
+
+CURLMOPT_NOTIFYFUNCTION - callback receiving notifications
+
+# SYNOPSIS
+
+~~~c
+#include <curl/curl.h>
+
+void ntfy_callback(CURLM *multi, /* multi handle */
+ unsigned int notification, /* notification type */
+ CURL *easy, /* easy handle */
+ void *ntfyp); /* private ntfy pointer */
+
+CURLMcode curl_multi_setopt(CURLM *handle, CURLMOPT_NOTIFYFUNCTION, ntfy_callback);
+~~~
+
+# DESCRIPTION
+
+Pass a pointer to your callback function, which should match the prototype
+shown above.
+
+When the multi handle processes transfers, changes can be observed
+by receiving notifications about them. This can eliminate the need to
+constantly interrogate the multi handle to observe such changes to
+act on them.
+
+Notifications are collected and dispatched to the application's callback
+function at an appropriate time.
+
+The notify callback is different from other callbacks in that it
+can use more libcurl API functions. Apart from curl_multi_perform(3),
+curl_multi_socket(3), curl_multi_socket_action(3), curl_multi_socket_all(3)
+and curl_multi_cleanup(3) it may call all other methods on the
+multi and easy handles. This includes adding and removing easy
+handles to/from the multi handle.
+
+This callback may get invoked at any time when interacting with libcurl.
+This may even happen after all transfers are done and *may also*
+happen *during* a call to curl_multi_cleanup(3) when cached connections
+are shut down.
+
+# CALLBACK ARGUMENTS
+
+*multi* identifies the multi handle that triggered the notification.
+
+**notification** is the type of notification, e.g. what happened. The
+following types are available:
+
+## CURLM_NTFY_INFO_READ
+
+When enabled via curl_multi_notify_enable(3), this informs the application
+that there are new messages to be processed via curl_multi_info_read(3).
+
+This notification happens whenever a message is added to an empty
+message stack in the multi handle and not for subsequent additions. The
+notification callback is then expected to read all available message,
+emptying the stack, so a subsequent addition triggers the notification
+again.
+
+The *easy* handle passed is an internal handle.
+
+## CURLM_NTFY_EASY_DONE
+
+When enabled via curl_multi_notify_enable(3), this notification is triggered
+when a an easy handle has finished. This happens both for
+successful and failed transfers.
+
+The *easy* handle passed is the transfer that is done. This *may* be
+an internal handle when DoH or other features are used.
+
+*easy* identifies the transfer involved. This may be one of the
+application's own easy handle or an internal handle.
+
+**ntfyp** is set with CURLMOPT_NOTIFYDATA(3).
+
+# DEFAULT
+
+NULL (no callback)
+
+# %PROTOCOLS%
+
+# EXAMPLE
+
+~~~c
+struct priv {
+ void *ours;
+};
+
+static void ntfy_cb(CURLM *multi, unsigned int notification,
+ CURL *easy, void *ntfyp)
+{
+ struct priv *p = ntfyp;
+ printf("my ptr: %p\n", p->ours);
+ /* ... */
+}
+
+int main(void)
+{
+ struct priv setup;
+ CURLM *multi = curl_multi_init();
+ /* ... use socket callback and custom pointer */
+ curl_multi_setopt(multi, CURLMOPT_NOTIFYFUNCTION, ntfy_cb);
+ curl_multi_setopt(multi, CURLMOPT_NOTIFYDATA, &setup);
+ curl_multi_notify_enable(multi, CURLM_NTFY_INFO_READ);
+}
+~~~
+
+# %AVAILABILITY%
+
+# RETURN VALUE
+
+Returns CURLM_OK.
docs/libcurl/opts/Makefile.inc+2 -0
@@ -114,6 +114,8 @@ man_MANS = \
CURLMOPT_MAX_TOTAL_CONNECTIONS.3 \
CURLMOPT_MAXCONNECTS.3 \
CURLMOPT_NETWORK_CHANGED.3 \
+ CURLMOPT_NOTIFYDATA.3 \
+ CURLMOPT_NOTIFYFUNCTION.3 \
CURLMOPT_PIPELINING.3 \
CURLMOPT_PIPELINING_SERVER_BL.3 \
CURLMOPT_PIPELINING_SITE_BL.3 \
docs/libcurl/symbols-in-versions+4 -0
@@ -545,6 +545,8 @@ CURLM_BAD_SOCKET 7.15.4
CURLM_CALL_MULTI_PERFORM 7.9.6
CURLM_CALL_MULTI_SOCKET 7.15.5
CURLM_INTERNAL_ERROR 7.9.6
+CURLM_NTFY_EASY_DONE 8.17.0
+CURLM_NTFY_INFO_READ 8.17.0
CURLM_OK 7.9.6
CURLM_OUT_OF_MEMORY 7.9.6
CURLM_RECURSIVE_API_CALL 7.59.0
@@ -568,6 +570,8 @@ CURLMOPT_MAX_PIPELINE_LENGTH 7.30.0
CURLMOPT_MAX_TOTAL_CONNECTIONS 7.30.0
CURLMOPT_MAXCONNECTS 7.16.3
CURLMOPT_NETWORK_CHANGED 8.16.0
+CURLMOPT_NOTIFYDATA 8.17.0
+CURLMOPT_NOTIFYFUNCTION 8.17.0
CURLMOPT_PIPELINING 7.16.0
CURLMOPT_PIPELINING_SERVER_BL 7.30.0
CURLMOPT_PIPELINING_SITE_BL 7.30.0
include/curl/multi.h+27 -0
@@ -398,6 +398,12 @@ typedef enum {
/* network has changed, adjust caches/connection reuse */
CURLOPT(CURLMOPT_NETWORK_CHANGED, CURLOPTTYPE_LONG, 17),
+ /* This is the notify callback function pointer */
+ CURLOPT(CURLMOPT_NOTIFYFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 18),
+
+ /* This is the argument passed to the notify callback */
+ CURLOPT(CURLMOPT_NOTIFYDATA, CURLOPTTYPE_OBJECTPOINT, 19),
+
CURLMOPT_LASTENTRY /* the last unused */
} CURLMoption;
@@ -520,6 +526,27 @@ CURL_EXTERN CURLMcode curl_multi_waitfds(CURLM *multi,
unsigned int size,
unsigned int *fd_count);
+/*
+ * Notifications dispatched by a multi handle, when enabled.
+ */
+#define CURLM_NTFY_INFO_READ 0
+#define CURLM_NTFY_EASY_DONE 1
+
+/*
+ * Callback to install via CURLMOPT_NOTIFYFUNCTION.
+ */
+typedef void (*curl_notify_callback)(CURLM *multi,
+ unsigned int notification,
+ CURL *easy,
+ void *user_data);
+
+
+CURL_EXTERN CURLMcode curl_multi_notify_disable(CURLM *multi,
+ unsigned int notification);
+
+CURL_EXTERN CURLMcode curl_multi_notify_enable(CURLM *multi,
+ unsigned int notification);
+
#ifdef __cplusplus
} /* end of extern "C" */
#endif
include/curl/typecheck-gcc.h+12 -1
@@ -208,6 +208,9 @@
if(curlcheck_charpp_option(option)) \
if(!curlcheck_ptrptr(value, char)) \
Wcurl_multi_setopt_err_charpp(); \
+ if((option) == CURLMOPT_NOTIFYFUNCTION) \
+ if(!curlcheck_multintfy_cb(value)) \
+ Wcurl_multi_setopt_err_ntfycb(); \
if((option) == CURLMOPT_PUSHFUNCTION) \
if(!curlcheck_multipush_cb(value)) \
Wcurl_multi_setopt_err_pushcb(); \
@@ -224,7 +227,8 @@
/* evaluates to true if the option takes a data argument to pass to a
callback */
#define curlcheck_multicb_data_option(option) \
- ((option) == CURLMOPT_PUSHDATA || \
+ ((option) == CURLMOPT_NOTIFYDATA || \
+ (option) == CURLMOPT_PUSHDATA || \
(option) == CURLMOPT_SOCKETDATA || \
(option) == CURLMOPT_TIMERDATA || \
0)
@@ -250,6 +254,11 @@
(curlcheck_NULL(expr) || \
curlcheck_cb_compatible((expr), curl_push_callback))
+/* evaluates to true if expr is of type curl_push_callback */
+#define curlcheck_multintfy_cb(expr) \
+ (curlcheck_NULL(expr) || \
+ curlcheck_cb_compatible((expr), curl_notify_callback))
+
/*
* For now, just make sure that the functions are called with three arguments
*/
@@ -275,6 +284,8 @@ CURLWARNING(Wcurl_multi_setopt_err_charpp,
"curl_multi_setopt expects a 'char **' argument")
CURLWARNING(Wcurl_multi_setopt_err_pushcb,
"curl_multi_setopt expects a curl_push_callback argument")
+CURLWARNING(Wcurl_multi_setopt_err_ntfycb,
+ "curl_multi_setopt expects a curl_notify_callback argument")
CURLWARNING(Wcurl_multi_setopt_err_socketcb,
"curl_multi_setopt expects a curl_socket_callback argument")
CURLWARNING(Wcurl_multi_setopt_err_timercb,
lib/Makefile.inc+2 -0
@@ -225,6 +225,7 @@ LIB_CFILES = \
mqtt.c \
multi.c \
multi_ev.c \
+ multi_ntfy.c \
netrc.c \
noproxy.c \
openldap.c \
@@ -357,6 +358,7 @@ LIB_HFILES = \
mqtt.h \
multihandle.h \
multi_ev.h \
+ multi_ntfy.h \
multiif.h \
netrc.h \
noproxy.h \
lib/libcurl.def+2 -0
@@ -57,6 +57,8 @@ curl_multi_get_handles
curl_multi_get_offt
curl_multi_info_read
curl_multi_init
+curl_multi_notify_disable
+curl_multi_notify_enable
curl_multi_perform
curl_multi_poll
curl_multi_remove_handle
lib/multi.c+60 -3
@@ -171,8 +171,15 @@ static void mstate(struct Curl_easy *data, CURLMstate state
#endif
data->mstate = state;
-
- if(state == MSTATE_COMPLETED) {
+ switch(state) {
+ case MSTATE_DONE:
+ CURLM_NTFY(data, CURLM_NTFY_EASY_DONE);
+ break;
+ case MSTATE_COMPLETED:
+ /* we sometimes directly jump to COMPLETED, trigger also a notification
+ * in that case. */
+ if(oldstate < MSTATE_DONE)
+ CURLM_NTFY(data, CURLM_NTFY_EASY_DONE);
/* changing to COMPLETED means it is in process and needs to go */
DEBUGASSERT(Curl_uint_bset_contains(&data->multi->process, data->mid));
Curl_uint_bset_remove(&data->multi->process, data->mid);
@@ -182,6 +189,9 @@ static void mstate(struct Curl_easy *data, CURLMstate state
/* free the transfer buffer when we have no more active transfers */
multi_xfer_bufs_free(data->multi);
}
+ break;
+ default:
+ break;
}
/* if this state has an init-function, run it */
@@ -215,6 +225,8 @@ static void ph_freeentry(void *p)
*/
static void multi_addmsg(struct Curl_multi *multi, struct Curl_message *msg)
{
+ if(!Curl_llist_count(&multi->msglist))
+ CURLM_NTFY(multi->admin, CURLM_NTFY_INFO_READ);
Curl_llist_append(&multi->msglist, msg, &msg->list);
}
@@ -232,6 +244,7 @@ struct Curl_multi *Curl_multi_handle(unsigned int xfer_table_size,
multi->magic = CURL_MULTI_HANDLE;
Curl_dnscache_init(&multi->dnscache, dnssize);
+ Curl_mntfy_init(multi);
Curl_multi_ev_init(multi, ev_hashsize);
Curl_uint_tbl_init(&multi->xfers, NULL);
Curl_uint_bset_init(&multi->process);
@@ -246,7 +259,8 @@ struct Curl_multi *Curl_multi_handle(unsigned int xfer_table_size,
multi->max_concurrent_streams = 100;
multi->last_timeout_ms = -1;
- if(Curl_uint_bset_resize(&multi->process, xfer_table_size) ||
+ if(Curl_mntfy_resize(multi) ||
+ Curl_uint_bset_resize(&multi->process, xfer_table_size) ||
Curl_uint_bset_resize(&multi->pending, xfer_table_size) ||
Curl_uint_bset_resize(&multi->dirty, xfer_table_size) ||
Curl_uint_bset_resize(&multi->msgsent, xfer_table_size) ||
@@ -305,6 +319,7 @@ error:
multi->admin->multi = NULL;
Curl_close(&multi->admin);
}
+ Curl_mntfy_cleanup(multi);
Curl_uint_bset_destroy(&multi->process);
Curl_uint_bset_destroy(&multi->dirty);
@@ -2754,6 +2769,9 @@ CURLMcode curl_multi_perform(CURLM *m, int *running_handles)
if(multi->in_callback)
return CURLM_RECURSIVE_API_CALL;
+ if(multi->in_ntfy_callback)
+ return CURLM_RECURSIVE_API_CALL;
+
sigpipe_init(&pipe_st);
if(Curl_uint_bset_first(&multi->process, &mid)) {
CURL_TRC_M(multi->admin, "multi_perform(running=%u)",
@@ -2785,6 +2803,9 @@ CURLMcode curl_multi_perform(CURLM *m, int *running_handles)
if(multi_ischanged(m, TRUE))
process_pending_handles(m);
+ if(!returncode)
+ returncode = Curl_mntfy_dispatch_all(multi);
+
/*
* Simply remove all expired timers from the splay since handles are dealt
* with unconditionally by this function and curl_multi_timeout() requires
@@ -2831,6 +2852,8 @@ CURLMcode curl_multi_cleanup(CURLM *m)
unsigned int mid;
if(multi->in_callback)
return CURLM_RECURSIVE_API_CALL;
+ if(multi->in_ntfy_callback)
+ return CURLM_RECURSIVE_API_CALL;
/* First remove all remaining easy handles,
* close internal ones. admin handle is special */
@@ -2900,6 +2923,7 @@ CURLMcode curl_multi_cleanup(CURLM *m)
#endif
multi_xfer_bufs_free(multi);
+ Curl_mntfy_cleanup(multi);
#ifdef DEBUGBUILD
if(Curl_uint_tbl_count(&multi->xfers)) {
multi_xfer_tbl_dump(multi);
@@ -3180,6 +3204,9 @@ out:
if(multi_ischanged(multi, TRUE))
process_pending_handles(multi);
+ if(!result)
+ result = Curl_mntfy_dispatch_all(multi);
+
if(running_handles) {
unsigned int running = Curl_multi_xfers_running(multi);
*running_handles = (running < INT_MAX) ? (int)running : INT_MAX;
@@ -3269,6 +3296,12 @@ CURLMcode curl_multi_setopt(CURLM *m,
}
break;
}
+ case CURLMOPT_NOTIFYFUNCTION:
+ multi->ntfy.ntfy_cb = va_arg(param, curl_notify_callback);
+ break;
+ case CURLMOPT_NOTIFYDATA:
+ multi->ntfy.ntfy_cb_data = va_arg(param, void *);
+ break;
default:
res = CURLM_UNKNOWN_OPTION;
break;
@@ -3285,6 +3318,8 @@ CURLMcode curl_multi_socket(CURLM *m, curl_socket_t s, int *running_handles)
struct Curl_multi *multi = m;
if(multi->in_callback)
return CURLM_RECURSIVE_API_CALL;
+ if(multi->in_ntfy_callback)
+ return CURLM_RECURSIVE_API_CALL;
return multi_socket(multi, FALSE, s, 0, running_handles);
}
@@ -3294,6 +3329,8 @@ CURLMcode curl_multi_socket_action(CURLM *m, curl_socket_t s,
struct Curl_multi *multi = m;
if(multi->in_callback)
return CURLM_RECURSIVE_API_CALL;
+ if(multi->in_ntfy_callback)
+ return CURLM_RECURSIVE_API_CALL;
return multi_socket(multi, FALSE, s, ev_bitmask, running_handles);
}
@@ -3302,6 +3339,8 @@ CURLMcode curl_multi_socket_all(CURLM *m, int *running_handles)
struct Curl_multi *multi = m;
if(multi->in_callback)
return CURLM_RECURSIVE_API_CALL;
+ if(multi->in_ntfy_callback)
+ return CURLM_RECURSIVE_API_CALL;
return multi_socket(multi, TRUE, CURL_SOCKET_BAD, 0, running_handles);
}
@@ -3996,6 +4035,24 @@ void Curl_multi_clear_dirty(struct Curl_easy *data)
Curl_uint_bset_remove(&data->multi->dirty, data->mid);
}
+CURLMcode curl_multi_notify_enable(CURLM *m, unsigned int notification)
+{
+ struct Curl_multi *multi = m;
+
+ if(!GOOD_MULTI_HANDLE(multi))
+ return CURLM_BAD_HANDLE;
+ return Curl_mntfy_enable(multi, notification);
+}
+
+CURLMcode curl_multi_notify_disable(CURLM *m, unsigned int notification)
+{
+ struct Curl_multi *multi = m;
+
+ if(!GOOD_MULTI_HANDLE(multi))
+ return CURLM_BAD_HANDLE;
+ return Curl_mntfy_disable(multi, notification);
+}
+
#ifdef DEBUGBUILD
static void multi_xfer_dump(struct Curl_multi *multi, unsigned int mid,
void *entry)
lib/multi_ntfy.c+212 -0
@@ -0,0 +1,212 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <[email protected]>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#include <curl/curl.h>
+
+#include "urldata.h"
+#include "curl_trc.h"
+#include "multihandle.h"
+#include "multiif.h"
+#include "multi_ntfy.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+struct mntfy_entry {
+ unsigned int mid;
+ unsigned int type;
+};
+
+#define CURL_MNTFY_CHUNK_SIZE 128
+
+struct mntfy_chunk {
+ struct mntfy_chunk *next;
+ size_t r_offset;
+ size_t w_offset;
+ struct mntfy_entry entries[CURL_MNTFY_CHUNK_SIZE];
+};
+
+static struct mntfy_chunk *mnfty_chunk_create(void)
+{
+ return calloc(1, sizeof(struct mntfy_chunk));
+}
+
+static void mnfty_chunk_destroy(struct mntfy_chunk *chunk)
+{
+ free(chunk);
+}
+
+static void mnfty_chunk_reset(struct mntfy_chunk *chunk)
+{
+ memset(chunk, 0, sizeof(*chunk));
+}
+
+static bool mntfy_chunk_append(struct mntfy_chunk *chunk,
+ struct Curl_easy *data,
+ unsigned int type)
+{
+ struct mntfy_entry *e;
+
+ if(chunk->w_offset >= CURL_MNTFY_CHUNK_SIZE)
+ return FALSE;
+ e = &chunk->entries[chunk->w_offset++];
+ e->mid = data->mid;
+ e->type = type;
+ return TRUE;
+}
+
+static struct mntfy_chunk *mntfy_non_full_tail(struct curl_multi_ntfy *mntfy)
+{
+ struct mntfy_chunk *chunk;
+ if(!mntfy->tail) {
+ chunk = mnfty_chunk_create();
+ if(!chunk)
+ return NULL;
+ DEBUGASSERT(!mntfy->head);
+ mntfy->head = mntfy->tail = chunk;
+ return chunk;
+ }
+ else if(mntfy->tail->w_offset < CURL_MNTFY_CHUNK_SIZE)
+ return mntfy->tail;
+ else { /* tail is full. */
+ chunk = mnfty_chunk_create();
+ if(!chunk)
+ return NULL;
+ DEBUGASSERT(mntfy->head);
+ mntfy->tail->next = chunk;
+ mntfy->tail = chunk;
+ return chunk;
+ }
+}
+
+static void mntfy_chunk_dispatch_all(struct Curl_multi *multi,
+ struct mntfy_chunk *chunk)
+{
+ struct mntfy_entry *e;
+ struct Curl_easy *data;
+
+ if(multi->ntfy.ntfy_cb) {
+ while((chunk->r_offset < chunk->w_offset) && !multi->ntfy.failure) {
+ e = &chunk->entries[chunk->r_offset];
+ data = e->mid ? Curl_multi_get_easy(multi, e->mid) : multi->admin;
+ /* only when notification has not been disabled in the meantime */
+ if(data && Curl_uint_bset_contains(&multi->ntfy.enabled, e->type)) {
+ /* this may cause new notifications to be added! */
+ CURL_TRC_M(multi->admin, "[NTFY] dispatch %d to xfer %u",
+ e->type, e->mid);
+ multi->ntfy.ntfy_cb(multi, e->type, data, multi->ntfy.ntfy_cb_data);
+ }
+ /* once dispatched, safe to increment */
+ chunk->r_offset++;
+ }
+ }
+ mnfty_chunk_reset(chunk);
+}
+
+void Curl_mntfy_init(struct Curl_multi *multi)
+{
+ memset(&multi->ntfy, 0, sizeof(multi->ntfy));
+ Curl_uint_bset_init(&multi->ntfy.enabled);
+}
+
+CURLMcode Curl_mntfy_resize(struct Curl_multi *multi)
+{
+ if(Curl_uint_bset_resize(&multi->ntfy.enabled, CURLM_NTFY_EASY_DONE + 1))
+ return CURLM_OUT_OF_MEMORY;
+ return CURLM_OK;
+}
+
+void Curl_mntfy_cleanup(struct Curl_multi *multi)
+{
+ while(multi->ntfy.head) {
+ struct mntfy_chunk *chunk = multi->ntfy.head;
+ multi->ntfy.head = chunk->next;
+ mnfty_chunk_destroy(chunk);
+ }
+ multi->ntfy.tail = NULL;
+ Curl_uint_bset_destroy(&multi->ntfy.enabled);
+}
+
+CURLMcode Curl_mntfy_enable(struct Curl_multi *multi, unsigned int type)
+{
+ if(type > CURLM_NTFY_EASY_DONE)
+ return CURLM_UNKNOWN_OPTION;
+ Curl_uint_bset_add(&multi->ntfy.enabled, type);
+ return CURLM_OK;
+}
+
+CURLMcode Curl_mntfy_disable(struct Curl_multi *multi, unsigned int type)
+{
+ if(type > CURLM_NTFY_EASY_DONE)
+ return CURLM_UNKNOWN_OPTION;
+ Curl_uint_bset_remove(&multi->ntfy.enabled, type);
+ return CURLM_OK;
+}
+
+void Curl_mntfy_add(struct Curl_easy *data, unsigned int type)
+{
+ struct Curl_multi *multi = data ? data->multi : NULL;
+ if(multi && multi->ntfy.ntfy_cb && !multi->ntfy.failure &&
+ Curl_uint_bset_contains(&multi->ntfy.enabled, type)) {
+ /* append to list of outstanding notifications */
+ struct mntfy_chunk *tail = mntfy_non_full_tail(&multi->ntfy);
+ CURL_TRC_M(data, "[NTFY] add %d for xfer %u", type, data->mid);
+ if(tail)
+ mntfy_chunk_append(tail, data, type);
+ else
+ multi->ntfy.failure = CURLM_OUT_OF_MEMORY;
+ }
+}
+
+CURLMcode Curl_mntfy_dispatch_all(struct Curl_multi *multi)
+{
+ DEBUGASSERT(!multi->in_ntfy_callback);
+ multi->in_ntfy_callback = TRUE;
+ while(multi->ntfy.head && !multi->ntfy.failure) {
+ struct mntfy_chunk *chunk = multi->ntfy.head;
+ /* this may cause new notifications to be added! */
+ mntfy_chunk_dispatch_all(multi, chunk);
+ DEBUGASSERT(chunk->r_offset == chunk->w_offset);
+
+ if(chunk == multi->ntfy.tail) /* last one, keep */
+ break;
+ DEBUGASSERT(chunk->next);
+ DEBUGASSERT(multi->ntfy.head != multi->ntfy.tail);
+ multi->ntfy.head = chunk->next;
+ mnfty_chunk_destroy(chunk);
+ }
+ multi->in_ntfy_callback = FALSE;
+
+ if(multi->ntfy.failure) {
+ CURLMcode result = multi->ntfy.failure;
+ multi->ntfy.failure = CURLM_OK; /* reset, once delivered */
+ return result;
+ }
+ return CURLM_OK;
+}
lib/multi_ntfy.h+57 -0
@@ -0,0 +1,57 @@
+#ifndef HEADER_CURL_MULTI_NTFY_H
+#define HEADER_CURL_MULTI_NTFY_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <[email protected]>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "uint-bset.h"
+
+struct Curl_easy;
+struct Curl_multi;
+
+struct curl_multi_ntfy {
+ curl_notify_callback ntfy_cb;
+ void *ntfy_cb_data;
+ struct uint_bset enabled;
+ CURLMcode failure;
+ struct mntfy_chunk *head;
+ struct mntfy_chunk *tail;
+};
+
+void Curl_mntfy_init(struct Curl_multi *multi);
+CURLMcode Curl_mntfy_resize(struct Curl_multi *multi);
+void Curl_mntfy_cleanup(struct Curl_multi *multi);
+
+CURLMcode Curl_mntfy_enable(struct Curl_multi *multi, unsigned int type);
+CURLMcode Curl_mntfy_disable(struct Curl_multi *multi, unsigned int type);
+
+void Curl_mntfy_add(struct Curl_easy *data, unsigned int type);
+
+#define CURLM_NTFY(d,t) \
+ do { if((d) && (d)->multi && (d)->multi->ntfy.ntfy_cb) \
+ Curl_mntfy_add((d), (t)); } while(0)
+
+CURLMcode Curl_mntfy_dispatch_all(struct Curl_multi *multi);
+
+
+#endif /* HEADER_CURL_MULTI_NTFY_H */
lib/multihandle.h+4 -0
@@ -30,6 +30,7 @@
#include "cshutdn.h"
#include "hostip.h"
#include "multi_ev.h"
+#include "multi_ntfy.h"
#include "psl.h"
#include "socketpair.h"
#include "uint-bset.h"
@@ -134,6 +135,8 @@ struct Curl_multi {
/* multi event related things */
struct curl_multi_ev ev;
+ /* multi notification related things */
+ struct curl_multi_ntfy ntfy;
/* `proto_hash` is a general key-value store for protocol implementations
* with the lifetime of the multi handle. The number of elements kept here
@@ -178,6 +181,7 @@ struct Curl_multi {
BIT(multiplexing); /* multiplexing wanted */
BIT(recheckstate); /* see Curl_multi_connchanged */
BIT(in_callback); /* true while executing a callback */
+ BIT(in_ntfy_callback); /* true while dispatching notifications */
#ifdef USE_OPENSSL
BIT(ssl_seeded);
#endif
scripts/singleuse.pl+2 -0
@@ -116,6 +116,8 @@ my %api = (
'curl_multi_get_offt' => 'API',
'curl_multi_info_read' => 'API',
'curl_multi_init' => 'API',
+ 'curl_multi_notify_disable' => 'API',
+ 'curl_multi_notify_enable' => 'API',
'curl_multi_perform' => 'API',
'curl_multi_remove_handle' => 'API',
'curl_multi_setopt' => 'API',
src/tool_operate.c+30 -36
@@ -1464,26 +1464,8 @@ struct contextuv {
struct datauv *uv;
};
-static CURLcode check_finished(struct parastate *s);
-
-static void check_multi_info(struct datauv *uv)
-{
- CURLcode result;
-
- result = check_finished(uv->s);
- if(result && !uv->s->result)
- uv->s->result = result;
-
- if(uv->s->more_transfers) {
- result = add_parallel_transfers(uv->s->multi, uv->s->share,
- &uv->s->more_transfers,
- &uv->s->added_transfers);
- if(result && !uv->s->result)
- uv->s->result = result;
- if(result)
- uv_stop(uv->loop);
- }
-}
+static void mnotify(CURLM *multi, unsigned int notification,
+ CURL *easy, void *user_data);
/* callback from libuv on socket activity */
static void on_uv_socket(uv_poll_t *req, int status, int events)
@@ -1510,7 +1492,6 @@ static void on_uv_timeout(uv_timer_t *req)
if(uv && uv->s) {
curl_multi_socket_action(uv->s->multi, CURL_SOCKET_TIMEOUT, 0,
&uv->s->still_running);
- check_multi_info(uv);
}
}
@@ -1596,8 +1577,6 @@ static int cb_socket(CURL *easy, curl_socket_t s, int action,
uv_poll_stop(&c->poll_handle);
destroy_context(c);
curl_multi_assign(uv->s->multi, s, NULL);
- /* check if we can do more now */
- check_multi_info(uv);
}
break;
default:
@@ -1641,10 +1620,6 @@ static CURLcode parallel_event(struct parastate *s)
curl_mfprintf(tool_stderr, "parallel_event: uv_run() returned\n");
#endif
- result = check_finished(s);
- if(result && !s->result)
- s->result = result;
-
/* early exit called */
if(s->wrapitup) {
if(s->still_running && !s->wrapitup_processed) {
@@ -1657,13 +1632,6 @@ static CURLcode parallel_event(struct parastate *s)
}
break;
}
-
- if(s->more_transfers) {
- result = add_parallel_transfers(s->multi, s->share, &s->more_transfers,
- &s->added_transfers);
- if(result && !s->result)
- s->result = result;
- }
}
result = s->result;
@@ -1758,6 +1726,27 @@ static CURLcode check_finished(struct parastate *s)
return result;
}
+static void mnotify(CURLM *multi, unsigned int notification,
+ CURL *easy, void *user_data)
+{
+ struct parastate *s = user_data;
+ CURLcode result;
+
+ (void)multi;
+ (void)easy;
+
+ switch(notification) {
+ case CURLM_NTFY_INFO_READ:
+ result = check_finished(s);
+ /* remember first failure */
+ if(result && !s->result)
+ s->result = result;
+ break;
+ default:
+ break;
+ }
+}
+
static CURLcode parallel_transfers(CURLSH *share)
{
CURLcode result;
@@ -1775,6 +1764,10 @@ static CURLcode parallel_transfers(CURLSH *share)
if(!s->multi)
return CURLE_OUT_OF_MEMORY;
+ (void)curl_multi_setopt(s->multi, CURLMOPT_NOTIFYFUNCTION, mnotify);
+ (void)curl_multi_setopt(s->multi, CURLMOPT_NOTIFYDATA, s);
+ (void)curl_multi_notify_enable(s->multi, CURLM_NTFY_INFO_READ);
+
result = add_parallel_transfers(s->multi, s->share,
&s->more_transfers, &s->added_transfers);
if(result) {
@@ -1813,13 +1806,14 @@ static CURLcode parallel_transfers(CURLSH *share)
s->mcode = curl_multi_poll(s->multi, NULL, 0, 1000, NULL);
if(!s->mcode)
s->mcode = curl_multi_perform(s->multi, &s->still_running);
- if(!s->mcode)
- result = check_finished(s);
}
(void)progress_meter(s->multi, &s->start, TRUE);
}
+ /* Result is the first failed transfer - if there was one. */
+ result = s->result;
+
/* Make sure to return some kind of error if there was a multi problem */
if(s->mcode) {
result = (s->mcode == CURLM_OUT_OF_MEMORY) ? CURLE_OUT_OF_MEMORY :
tests/data/test1135+2 -0
@@ -109,6 +109,8 @@ curl_multi_get_offt
curl_pushheader_bynum
curl_pushheader_byname
curl_multi_waitfds
+curl_multi_notify_disable
+curl_multi_notify_enable
curl_easy_option_by_name
curl_easy_option_by_id
curl_easy_option_next
tests/data/test3207+1 -1
@@ -172,7 +172,7 @@ https://localhost:%HTTPSPORT/%TESTNUMBER %CERTDIR/certs/test-ca.crt
# Verify data after the test has been "shot"
<verify>
<limits>
-Allocations: 13500
+Allocations: 13600
</limits>
</verify>
</testcase>
tests/data/test500+1 -1
@@ -55,7 +55,7 @@ Accept: */*
</protocol>
<limits>
-Allocations: 81
+Allocations: 82
Maximum allocated: 33400
</limits>
</verify>
tests/http/testenv/curl.py+3 -0
@@ -126,6 +126,9 @@ class DTraceProfile:
'-n', f'profile-97 /pid == {self._pid}/ {{ @[ustack()] = count(); }} tick-60s {{ exit(0); }}',
'-o', f'{self._file}'
]
+ if sys.platform.startswith('darwin'):
+ # macOS seems to like this for producing symbols in user stacks
+ args.extend(['-p', f'{self._pid}'])
self._proc = subprocess.Popen(args, text=True, cwd=self._run_dir, shell=False)
assert self._proc
tests/unit/unit3214.c+1 -1
@@ -43,7 +43,7 @@ static void checksize(const char *name, size_t size, size_t allowed)
/* the maximum sizes we allow specific structs to grow to */
#define MAX_CURL_EASY 5800
#define MAX_CONNECTDATA 1300
-#define MAX_CURL_MULTI 750
+#define MAX_CURL_MULTI 850
#define MAX_CURL_HTTPPOST 112
#define MAX_CURL_SLIST 16
#define MAX_CURL_KHKEY 24