]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
multi: add notifications API
authorStefan Eissing <stefan@eissing.org>
Mon, 1 Sep 2025 09:58:16 +0000 (11:58 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 7 Oct 2025 08:55:31 +0000 (10:55 +0200)
Add infrastructure to colled and dispatch notifications for transfers
and the multi handle in general. Applications can register a callback
and en-/disable notification type the are interested in.

Without a callback installed, notifications are not collected. Same when
a notification type has not been enabled.

Memory allocation failures on adding notifications lead to a general
multi failure state and result in CURLM_OUT_OF_MEMORY returned from
curl_multi_perform() and curl_multi_socket*() invocations.

Closes #18432

24 files changed:
.github/scripts/spellcheck.curl
docs/libcurl/Makefile.inc
docs/libcurl/curl_multi_notify_disable.md [new file with mode: 0644]
docs/libcurl/curl_multi_notify_enable.md [new file with mode: 0644]
docs/libcurl/curl_multi_setopt.md
docs/libcurl/opts/CURLMOPT_NOTIFYDATA.md [new file with mode: 0644]
docs/libcurl/opts/CURLMOPT_NOTIFYFUNCTION.md [new file with mode: 0644]
docs/libcurl/opts/Makefile.inc
docs/libcurl/symbols-in-versions
include/curl/multi.h
include/curl/typecheck-gcc.h
lib/Makefile.inc
lib/libcurl.def
lib/multi.c
lib/multi_ntfy.c [new file with mode: 0644]
lib/multi_ntfy.h [new file with mode: 0644]
lib/multihandle.h
scripts/singleuse.pl
src/tool_operate.c
tests/data/test1135
tests/data/test3207
tests/data/test500
tests/http/testenv/curl.py
tests/unit/unit3214.c

index c24edf2b3bc88ae40e7d526ac181de064e94cb56..1d8be5ed3eee77ec5c29bc8a804fb19c3e9c17b0 100644 (file)
@@ -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
index 9bc665d1c625117ad9ad8d7ff98ae5cc0c24bc73..6ac49d9fb29c369d9b673df9ac78545965095452 100644 (file)
@@ -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 \
diff --git a/docs/libcurl/curl_multi_notify_disable.md b/docs/libcurl/curl_multi_notify_disable.md
new file mode 100644 (file)
index 0000000..f4f28eb
--- /dev/null
@@ -0,0 +1,66 @@
+---
+c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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.
diff --git a/docs/libcurl/curl_multi_notify_enable.md b/docs/libcurl/curl_multi_notify_enable.md
new file mode 100644 (file)
index 0000000..d3ab02d
--- /dev/null
@@ -0,0 +1,66 @@
+---
+c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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.
index e646eced62c78cd62a7d321c4d6a4dee17146a15..adb38e0de5d041ffa1eabf13d9ef1fc8a9aefff6 100644 (file)
@@ -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)
diff --git a/docs/libcurl/opts/CURLMOPT_NOTIFYDATA.md b/docs/libcurl/opts/CURLMOPT_NOTIFYDATA.md
new file mode 100644 (file)
index 0000000..3349c77
--- /dev/null
@@ -0,0 +1,72 @@
+---
+c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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.
diff --git a/docs/libcurl/opts/CURLMOPT_NOTIFYFUNCTION.md b/docs/libcurl/opts/CURLMOPT_NOTIFYFUNCTION.md
new file mode 100644 (file)
index 0000000..979fea8
--- /dev/null
@@ -0,0 +1,129 @@
+---
+c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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.
index 98691cac3cc19c81bc32db63e1d5030630e0c776..39192b639280aa42a380157693b81a74561647a2 100644 (file)
@@ -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                 \
index 43435cb16b5ba8fde0e273393e2a7b3ffef0a8ca..75db554e2a05f4404a957775d05c593eee4a1fc0 100644 (file)
@@ -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
index 99e4413c9fe9395965f8338ae641689b0cd0fe7e..c486a3a5cc40b483b83297b485a6065645ba5254 100644 (file)
@@ -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
index 07fba246d4c496915fb3f6a05d32d102aa06c8d9..de2cfb715a6731ffcb34754c7e69853318e7b730 100644 (file)
         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();                            \
 /* 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)
   (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,
index 500c690561f406fbaaf841bb98627cedcb074171..f06af2ca70e395cc4e9276ae1e786716b750d976 100644 (file)
@@ -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          \
index d2f5d8318f2bea2ee6e945481cb1a36bae48516b..803f372041a0254c4b76bab102862c8e82496246 100644 (file)
@@ -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
index ced03eea9fbfc0646b67c4c0f6bcde3299d1e55b..58e384b68ae92c77c9f3266328f20227a033ddbb 100644 (file)
@@ -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)
diff --git a/lib/multi_ntfy.c b/lib/multi_ntfy.c
new file mode 100644 (file)
index 0000000..95ce82f
--- /dev/null
@@ -0,0 +1,212 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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;
+}
diff --git a/lib/multi_ntfy.h b/lib/multi_ntfy.h
new file mode 100644 (file)
index 0000000..d920b32
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef HEADER_CURL_MULTI_NTFY_H
+#define HEADER_CURL_MULTI_NTFY_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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 */
index ae41044adc2ed4102220eea45d5daaf6ab4a1505..69f977bb94ed3513427aefcb7594f0cccdd999de 100644 (file)
@@ -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
index 2e8a8e0afd225374edc69d7253d0c4535ac4d2a7..b4cbe3ff4a12fc5e2972d5379e68e00aad15b909 100755 (executable)
@@ -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',
index 3c29d28ec3a091a3c39d8b556be628d590c8e3f6..fc5a9656252f2c296b2c750ea1c782e7a8074987 100644 (file)
@@ -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 :
index 405d4b8fc36e061d33c876b13cb14016df25dc91..51ec53e4d3f1dd85a07966406fb3f376bfe6a060 100644 (file)
@@ -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
index 4c223699ee5860d2614b006b461a9e461357d78e..7902d0ae298f2db929821ff89d0ad2f0f12d55f3 100644 (file)
@@ -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>
index 0b084ac003b12f5bac97acce5d9138f54151637b..7904110c7e0a7264c9539b96f1945c00b729e894 100644 (file)
@@ -55,7 +55,7 @@ Accept: */*
 \r
 </protocol>
 <limits>
-Allocations: 81
+Allocations: 82
 Maximum allocated: 33400
 </limits>
 </verify>
index f8e7b12a2641e4004a3a57d96d66917f91359a42..dcff774a63aea38fc7df460b594d48e2c4983d40 100644 (file)
@@ -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
 
index 08ce6fff394456e861c6eca4753ca4aaa9772d07..d992a35dc0db6cb54a767fbfaec219402f0f1398 100644 (file)
@@ -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