]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
multi: add curl_multi_wakeup()
authorGergely Nagy <ngg@tresorit.com>
Sun, 17 Nov 2019 14:12:15 +0000 (15:12 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 25 Nov 2019 14:45:56 +0000 (15:45 +0100)
This commit adds curl_multi_wakeup() which was previously in the TODO
list under the curl_multi_unblock name.

On some platforms and with some configurations this feature might not be
available or can fail, in these cases a new error code
(CURLM_WAKEUP_FAILURE) is returned from curl_multi_wakeup().

Fixes #4418
Closes #4608

20 files changed:
docs/TODO
docs/libcurl/Makefile.inc
docs/libcurl/curl_multi_poll.3
docs/libcurl/curl_multi_wakeup.3 [new file with mode: 0644]
docs/libcurl/libcurl-errors.3
docs/libcurl/symbols-in-versions
include/curl/multi.h
lib/multi.c
lib/multihandle.h
lib/strerror.c
packages/OS400/curl.inc.in
tests/data/Makefile.inc
tests/data/test1135
tests/data/test1538
tests/data/test1564 [new file with mode: 0644]
tests/data/test1565 [new file with mode: 0644]
tests/libtest/Makefile.inc
tests/libtest/lib1564.c [new file with mode: 0644]
tests/libtest/lib1565.c [new file with mode: 0644]
tests/libtest/test.h

index 46b015c0291de585300eaac074115c6117d975cb..e75c1647bebf30a93a95652ab1595cae8feb3858 100644 (file)
--- a/docs/TODO
+++ b/docs/TODO
@@ -51,7 +51,6 @@
  2.4 Split connect and authentication process
  2.5 Edge-triggered sockets should work
  2.6 multi upkeep
- 2.7 curl_multi_unblock
 
  3. Documentation
  3.2 Provide cmake config-file
 
  See https://github.com/curl/curl/issues/3199
 
-2.7 curl_multi_unblock
-
- A portable way to unblock curl_multi_wait from another thread.
-
- See https://github.com/curl/curl/issues/4418 and
- https://github.com/curl/curl/wiki/curl_multi_unblock
-
 3. Documentation
 
 3.2 Provide cmake config-file
index bd88c9c386bd3aff76f001f175d6248a7aa3b06b..e1185e729074194a2d80f7f52272dc6e325a7ee6 100644 (file)
@@ -54,6 +54,7 @@ man_MANS = \
  curl_multi_socket_all.3 \
  curl_multi_strerror.3 \
  curl_multi_timeout.3 \
+ curl_multi_wakeup.3 \
  curl_multi_wait.3 \
  curl_share_cleanup.3 \
  curl_share_init.3 \
index 9fc72c55dcf1d08ef60ecba7aee18eadc623d810..bde80447ade9dbe2ff2e5e05d801a11a58ea747b 100644 (file)
@@ -48,10 +48,16 @@ total number of file descriptors on which interesting events occurred. This
 number can include both libcurl internal descriptors as well as descriptors
 provided in \fIextra_fds\fP.
 
+The \fIcurl_multi_wakeup(3)\fP function can be used from another thread to
+wake up this function and return faster. This is one of the details
+that makes this function different than \fIcurl_multi_wait(3)\fP which cannot
+be woken up this way.
+
 If no extra file descriptors are provided and libcurl has no file descriptor
 to offer to wait for, this function will instead wait during \fItimeout_ms\fP
 milliseconds (or shorter if an internal timer indicates so). This is the
-detail that makes this function different than \fIcurl_multi_wait(3)\fP.
+other detail that makes this function different than
+\fIcurl_multi_wait(3)\fP.
 
 This function is encouraged to be used instead of select(3) when using the
 multi interface to allow applications to easier circumvent the common problem
@@ -107,4 +113,5 @@ CURLMcode type, general libcurl multi interface error code. See
 .SH AVAILABILITY
 This function was added in libcurl 7.66.0.
 .SH "SEE ALSO"
-.BR curl_multi_fdset "(3), " curl_multi_perform "(3), " curl_multi_wait "(3)"
+.BR curl_multi_fdset "(3), " curl_multi_perform "(3), "
+.BR curl_multi_wait "(3), " curl_multi_wakeup "(3)"
diff --git a/docs/libcurl/curl_multi_wakeup.3 b/docs/libcurl/curl_multi_wakeup.3
new file mode 100644 (file)
index 0000000..01b462f
--- /dev/null
@@ -0,0 +1,47 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2019, 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.haxx.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.
+.\" *
+.\" **************************************************************************
+.TH curl_multi_wakeup 3 "17 Nov 2019" "libcurl 7.68.0" "libcurl Manual"
+.SH NAME
+curl_multi_wakeup - wakes up a sleeping curl_multi_poll call
+.SH SYNOPSIS
+#include <curl/curl.h>
+
+CURLMcode curl_multi_wakeup(CURLM *multi_handle);
+.ad
+.SH DESCRIPTION
+This function can be called from any thread and it wakes up a
+sleeping \fIcurl_multi_poll(3)\fP call that is currently (or will be)
+waiting for activity or a timeout.
+
+If the function is called when there is no \fIcurl_multi_poll(3)\fP call,
+it will cause the next call to return immediately.
+
+Calling this function only guarantees to wake up the current (or the next
+if there is no current) \fIcurl_multi_poll(3)\fP call, which means it is
+possible that multiple calls to this function will wake up the same waiting
+operation.
+
+This function has no effect on \fIcurl_multi_wait(3)\fP calls.
+.SH RETURN VALUE
+CURLMcode type, general libcurl multi interface error code.
+.SH "SEE ALSO"
+.BR curl_multi_poll "(3), " curl_multi_wait "(3)"
index 1f985da8e4c132e7e4407425392c644847526b6c..0305af43e1fc679179d066b010297628517e5053 100644 (file)
@@ -297,6 +297,8 @@ An easy handle already added to a multi handle was attempted to get added a
 second time. (Added in 7.32.1)
 .IP "CURLM_RECURSIVE_API_CALL (8)"
 An API function was called from inside a callback.
+.IP "CURLM_WAKEUP_FAILURE (9)"
+Wakeup is unavailable or failed.
 .SH "CURLSHcode"
 The "share" interface will return a CURLSHcode to indicate when an error has
 occurred.  Also consider \fIcurl_share_strerror(3)\fP.
index f3131d2a25fca03a80ff2b0f8aea55ec3b442bc9..d82439a5b4924539566219d51db57a69ba160558 100644 (file)
@@ -342,6 +342,7 @@ CURLM_INTERNAL_ERROR            7.9.6
 CURLM_OK                        7.9.6
 CURLM_OUT_OF_MEMORY             7.9.6
 CURLM_RECURSIVE_API_CALL        7.59.0
+CURLM_WAKEUP_FAILURE            7.68.0
 CURLM_UNKNOWN_OPTION            7.15.4
 CURLOPTTYPE_FUNCTIONPOINT       7.1
 CURLOPTTYPE_LONG                7.1
index b3921839547e3e7c37da080c792c96c5d5d9d3cb..89f09925966adfe501af5de10216d0dd78c5edab 100644 (file)
@@ -72,6 +72,7 @@ typedef enum {
                             attempted to get added - again */
   CURLM_RECURSIVE_API_CALL, /* an api function was called from inside a
                                callback */
+  CURLM_WAKEUP_FAILURE, /* wakeup is unavailable or failed */
   CURLM_LAST
 } CURLMcode;
 
@@ -187,6 +188,15 @@ CURL_EXTERN CURLMcode curl_multi_poll(CURLM *multi_handle,
                                       int timeout_ms,
                                       int *ret);
 
+/*
+ * Name:     curl_multi_wakeup()
+ *
+ * Desc:     wakes up a sleeping curl_multi_poll call.
+ *
+ * Returns:  CURLMcode type, general multi error code.
+ */
+CURL_EXTERN CURLMcode curl_multi_wakeup(CURLM *multi_handle);
+
  /*
   * Name:    curl_multi_perform()
   *
index 9faad0e2e5985c7972648224d9486c4d71c2aacc..f30e41a651d0f17706fe727e6e680c960093c432 100755 (executable)
@@ -46,6 +46,7 @@
 #include "connect.h"
 #include "http_proxy.h"
 #include "http2.h"
+#include "socketpair.h"
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
 #include "curl_memory.h"
@@ -367,6 +368,21 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */
 
   /* -1 means it not set by user, use the default value */
   multi->maxconnects = -1;
+
+#ifdef ENABLE_WAKEUP
+  if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, multi->wakeup_pair) < 0) {
+    multi->wakeup_pair[0] = CURL_SOCKET_BAD;
+    multi->wakeup_pair[1] = CURL_SOCKET_BAD;
+  }
+  else if(curlx_nonblock(multi->wakeup_pair[0], TRUE) < 0 ||
+          curlx_nonblock(multi->wakeup_pair[1], TRUE) < 0) {
+    sclose(multi->wakeup_pair[0]);
+    sclose(multi->wakeup_pair[1]);
+    multi->wakeup_pair[0] = CURL_SOCKET_BAD;
+    multi->wakeup_pair[1] = CURL_SOCKET_BAD;
+  }
+#endif
+
   return multi;
 
   error:
@@ -1005,7 +1021,8 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi,
                                  unsigned int extra_nfds,
                                  int timeout_ms,
                                  int *ret,
-                                 bool extrawait) /* when no socket, wait */
+                                 bool extrawait, /* when no socket, wait */
+                                 bool use_wakeup)
 {
   struct Curl_easy *data;
   curl_socket_t sockbunch[MAX_SOCKSPEREASYHANDLE];
@@ -1059,6 +1076,12 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi,
   curlfds = nfds; /* number of internal file descriptors */
   nfds += extra_nfds; /* add the externally provided ones */
 
+#ifdef ENABLE_WAKEUP
+  if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) {
+    ++nfds;
+  }
+#endif
+
   if(nfds > NUM_POLLS_ON_STACK) {
     /* 'nfds' is a 32 bit value and 'struct pollfd' is typically 8 bytes
        big, so at 2^29 sockets this value might wrap. When a process gets
@@ -1117,6 +1140,14 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi,
     ++nfds;
   }
 
+#ifdef ENABLE_WAKEUP
+  if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) {
+    ufds[nfds].fd = multi->wakeup_pair[0];
+    ufds[nfds].events = POLLIN;
+    ++nfds;
+  }
+#endif
+
   if(nfds) {
     int pollrc;
     /* wait... */
@@ -1140,6 +1171,29 @@ static CURLMcode Curl_multi_wait(struct Curl_multi *multi,
 
         extra_fds[i].revents = mask;
       }
+
+#ifdef ENABLE_WAKEUP
+      if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) {
+        if(ufds[curlfds + extra_nfds].revents & POLLIN) {
+          char buf[64];
+          while(1) {
+            /* the reading socket is non-blocking, try to read
+               data from it until it receives an error (except EINTR).
+               In normal cases it will get EAGAIN or EWOULDBLOCK
+               when there is no more data, breaking the loop. */
+            if(sread(multi->wakeup_pair[0], buf, sizeof(buf)) < 0) {
+#ifndef USE_WINSOCK
+              if(EINTR == SOCKERRNO)
+                continue;
+#endif
+              break;
+            }
+          }
+          /* do not count the wakeup socket into the returned value */
+          retcode--;
+        }
+      }
+#endif
     }
   }
 
@@ -1174,7 +1228,8 @@ CURLMcode curl_multi_wait(struct Curl_multi *multi,
                           int timeout_ms,
                           int *ret)
 {
-  return Curl_multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, FALSE);
+  return Curl_multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, FALSE,
+                         FALSE);
 }
 
 CURLMcode curl_multi_poll(struct Curl_multi *multi,
@@ -1183,7 +1238,55 @@ CURLMcode curl_multi_poll(struct Curl_multi *multi,
                           int timeout_ms,
                           int *ret)
 {
-  return Curl_multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, TRUE);
+  return Curl_multi_wait(multi, extra_fds, extra_nfds, timeout_ms, ret, TRUE,
+                         TRUE);
+}
+
+CURLMcode curl_multi_wakeup(struct Curl_multi *multi)
+{
+  /* this function is usually called from another thread,
+     it has to be careful only to access parts of the
+     Curl_multi struct that are constant */
+
+  /* GOOD_MULTI_HANDLE can be safely called */
+  if(!GOOD_MULTI_HANDLE(multi))
+    return CURLM_BAD_HANDLE;
+
+#ifdef ENABLE_WAKEUP
+  /* the wakeup_pair variable is only written during init and cleanup,
+     making it safe to access from another thread after the init part
+     and before cleanup */
+  if(multi->wakeup_pair[1] != CURL_SOCKET_BAD) {
+    char buf[1];
+    buf[0] = 1;
+    while(1) {
+      /* swrite() is not thread-safe in general, because concurrent calls
+         can have their messages interleaved, but in this case the content
+         of the messages does not matter, which makes it ok to call.
+
+         The write socket is set to non-blocking, this way this function
+         cannot block, making it safe to call even from the same thread
+         that will call Curl_multi_wait(). If swrite() returns that it
+         would block, it's considered successful because it means that
+         previous calls to this function will wake up the poll(). */
+      if(swrite(multi->wakeup_pair[1], buf, sizeof(buf)) < 0) {
+        int err = SOCKERRNO;
+        int return_success;
+#ifdef USE_WINSOCK
+        return_success = WSAEWOULDBLOCK == err;
+#else
+        if(EINTR == err)
+          continue;
+        return_success = EWOULDBLOCK == err || EAGAIN == err;
+#endif
+        if(!return_success)
+          return CURLM_WAKEUP_FAILURE;
+      }
+      return CURLM_OK;
+    }
+  }
+#endif
+  return CURLM_WAKEUP_FAILURE;
 }
 
 /*
@@ -2309,6 +2412,11 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi)
 
     Curl_hash_destroy(&multi->hostcache);
     Curl_psl_destroy(&multi->psl);
+
+#ifdef ENABLE_WAKEUP
+    sclose(multi->wakeup_pair[0]);
+    sclose(multi->wakeup_pair[1]);
+#endif
     free(multi);
 
     return CURLM_OK;
index b65bd96386afc815e90418393fa21f39851a6903..a26fb619a61f509bc6806599e019d6729f7637b8 100644 (file)
@@ -24,6 +24,7 @@
 
 #include "conncache.h"
 #include "psl.h"
+#include "socketpair.h"
 
 struct Curl_message {
   struct curl_llist_element list;
@@ -66,6 +67,10 @@ typedef enum {
 
 #define CURLPIPE_ANY (CURLPIPE_MULTIPLEX)
 
+#if defined(USE_SOCKETPAIR) && !defined(USE_BLOCKING_SOCKETS)
+#define ENABLE_WAKEUP
+#endif
+
 /* This is the struct known as CURLM on the outside */
 struct Curl_multi {
   /* First a simple identifier to easier detect if a user mix up
@@ -134,6 +139,11 @@ struct Curl_multi {
                                     previous callback */
   bool in_callback;            /* true while executing a callback */
   long max_concurrent_streams; /* max concurrent streams client to support */
+
+#ifdef ENABLE_WAKEUP
+  curl_socket_t wakeup_pair[2]; /* socketpair() used for wakeup
+                                   0 is used for read, 1 is used for write */
+#endif
 };
 
 #endif /* HEADER_CURL_MULTIHANDLE_H */
index ba2e7a6f97e700c03de3d3d4e9217d6213839c32..baf5451aee1e1d2b2d9b3447b914fe6506e32d9f 100644 (file)
@@ -389,6 +389,9 @@ curl_multi_strerror(CURLMcode error)
   case CURLM_RECURSIVE_API_CALL:
     return "API function called from within callback";
 
+  case CURLM_WAKEUP_FAILURE:
+    return "Wakeup is unavailable or failed";
+
   case CURLM_LAST:
     break;
   }
index 8be6c8986a9b1310c9290ae970744434ba8e6572..9a44eae4905228f7ea8a91c626144b174d1df24c 100644 (file)
      d                 c                   6
      d  CURLM_ADDED_ALREADY...
      d                 c                   7
-     d  CURLM_LAST     c                   8
+     d  CURLM_RECURSIVE_API_CALL...
+     d                 c                   8
+     d  CURLM_WAKEUP_FAILURE...
+     d                 c                   9
+     d  CURLM_LAST     c                   10
       *
      d CURLMSG         s             10i 0 based(######ptr######)               Enum
      d  CURLMSG_NONE   c                   0
index 23215655b99adf64f6175064138e64c353f143ce..c45bced5b7e45345c40c3edfe757da553e4ad357 100644 (file)
@@ -179,7 +179,7 @@ test1525 test1526 test1527 test1528 test1529 test1530 test1531 test1532 \
 test1533 test1534 test1535 test1536 test1537 test1538 \
 test1540 test1541 \
 test1550 test1551 test1552 test1553 test1554 test1555 test1556 test1557 \
-test1558 test1559 test1560 test1561 test1562 test1563 \
+test1558 test1559 test1560 test1561 test1562 test1563 test1564 test1565 \
 \
 test1590 test1591 test1592 test1593 test1594 test1595 test1596 \
 \
index eca6860fbf8d1ab250358e7f8048e9ac8946632e..37a55427fac40f59d85c51130a1a4fbf19703ec9 100644 (file)
@@ -92,6 +92,7 @@ CURL_EXTERN CURLMcode curl_multi_remove_handle(CURLM *multi_handle,
 CURL_EXTERN CURLMcode curl_multi_fdset(CURLM *multi_handle,
 CURL_EXTERN CURLMcode curl_multi_wait(CURLM *multi_handle,
 CURL_EXTERN CURLMcode curl_multi_poll(CURLM *multi_handle,
+CURL_EXTERN CURLMcode curl_multi_wakeup(CURLM *multi_handle);
 CURL_EXTERN CURLMcode curl_multi_perform(CURLM *multi_handle,
 CURL_EXTERN CURLMcode curl_multi_cleanup(CURLM *multi_handle);
 CURL_EXTERN CURLMsg *curl_multi_info_read(CURLM *multi_handle,
index 36f53040ba3e4af3cf676b487cc0d24022c3cf1b..3b22ebc27376273339cd7a2661d72a41ff3b5447 100644 (file)
@@ -139,7 +139,8 @@ m5: Invalid socket argument
 m6: Unknown option
 m7: The easy handle is already added to a multi handle
 m8: API function called from within callback
-m9: Unknown error
+m9: Wakeup is unavailable or failed
+m10: Unknown error
 s0: No error
 s1: Unknown share option
 s2: Share currently in use
diff --git a/tests/data/test1564 b/tests/data/test1564
new file mode 100644 (file)
index 0000000..279665b
--- /dev/null
@@ -0,0 +1,31 @@
+<testcase>
+<info>
+<keywords>
+multi
+wakeup
+</keywords>
+</info>
+
+# Server-side
+<reply>
+</reply>
+
+# Client-side
+<client>
+<server>
+none
+</server>
+<tool>
+lib1564
+</tool>
+<name>
+wakeup before poll with no easy handles
+</name>
+<command>
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+</verify>
+</testcase>
diff --git a/tests/data/test1565 b/tests/data/test1565
new file mode 100644 (file)
index 0000000..f554e0f
--- /dev/null
@@ -0,0 +1,41 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+multi
+multi-threaded
+wakeup
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data nocheck="yes">
+HTTP/1.1 200 OK
+Content-Length: 3
+
+OK
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+<tool>
+lib1565
+</tool>
+<name>
+wakeup from another thread
+</name>
+<command>
+http://%HOSTIP:%HTTPPORT/1
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+</verify>
+</testcase>
index 9ba72d7de5d5e037377a094233be5030cd228121..374a66747b6eee2b7dbdf278824be833bdf4e5b4 100644 (file)
@@ -31,7 +31,7 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect                \
  lib1534 lib1535 lib1536 lib1537 lib1538 \
  lib1540 lib1541 \
  lib1550 lib1551 lib1552 lib1553 lib1554 lib1555 lib1556 lib1557 \
- lib1558 lib1559 lib1560 \
+ lib1558 lib1559 lib1560 lib1564 lib1565 \
  lib1591 lib1592 lib1593 lib1594 lib1596 \
  lib1900 lib1905 lib1906 lib1907 \
  lib2033
@@ -536,6 +536,14 @@ lib1559_LDADD = $(TESTUTIL_LIBS)
 lib1560_SOURCES = lib1560.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
 lib1560_LDADD = $(TESTUTIL_LIBS)
 
+lib1564_SOURCES = lib1564.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
+lib1564_LDADD = $(TESTUTIL_LIBS)
+lib1564_CPPFLAGS = $(AM_CPPFLAGS)
+
+lib1565_SOURCES = lib1565.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
+lib1565_LDADD = $(TESTUTIL_LIBS)
+lib1565_CPPFLAGS = $(AM_CPPFLAGS)
+
 lib1591_SOURCES = lib1591.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
 lib1591_LDADD = $(TESTUTIL_LIBS)
 lib1591_CPPFLAGS = $(AM_CPPFLAGS) -DLIB1591
diff --git a/tests/libtest/lib1564.c b/tests/libtest/lib1564.c
new file mode 100644 (file)
index 0000000..225c8c6
--- /dev/null
@@ -0,0 +1,142 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2019, 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.haxx.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.
+ *
+ ***************************************************************************/
+#include "test.h"
+
+#include "testutil.h"
+#include "warnless.h"
+#include "memdebug.h"
+
+#define TEST_HANG_TIMEOUT 60 * 1000
+#define WAKEUP_NUM 1234567
+
+int test(char *URL)
+{
+  CURLM *multi = NULL;
+  int numfds;
+  int i;
+  int res = 0;
+  struct timeval time_before_wait, time_after_wait;
+
+  (void)URL;
+
+  start_test_timing();
+
+  global_init(CURL_GLOBAL_ALL);
+
+  multi_init(multi);
+
+  /* no wakeup */
+
+  time_before_wait = tutil_tvnow();
+  multi_poll(multi, NULL, 0, 1000, &numfds);
+  time_after_wait = tutil_tvnow();
+
+  if(tutil_tvdiff(time_after_wait, time_before_wait) < 500) {
+    fprintf(stderr, "%s:%d curl_multi_poll returned too early\n",
+            __FILE__, __LINE__);
+    res = TEST_ERR_MAJOR_BAD;
+    goto test_cleanup;
+  }
+
+  abort_on_test_timeout();
+
+  /* try a single wakeup */
+
+  multi_wakeup(multi);
+
+  time_before_wait = tutil_tvnow();
+  multi_poll(multi, NULL, 0, 1000, &numfds);
+  time_after_wait = tutil_tvnow();
+
+  if(tutil_tvdiff(time_after_wait, time_before_wait) > 500) {
+    fprintf(stderr, "%s:%d curl_multi_poll returned too late\n",
+            __FILE__, __LINE__);
+    res = TEST_ERR_MAJOR_BAD;
+    goto test_cleanup;
+  }
+
+  abort_on_test_timeout();
+
+  /* previous wakeup should not wake up this */
+
+  time_before_wait = tutil_tvnow();
+  multi_poll(multi, NULL, 0, 1000, &numfds);
+  time_after_wait = tutil_tvnow();
+
+  if(tutil_tvdiff(time_after_wait, time_before_wait) < 500) {
+    fprintf(stderr, "%s:%d curl_multi_poll returned too early\n",
+            __FILE__, __LINE__);
+    res = TEST_ERR_MAJOR_BAD;
+    goto test_cleanup;
+  }
+
+  abort_on_test_timeout();
+
+  /* try lots of wakeup */
+
+  for(i = 0; i < WAKEUP_NUM; ++i)
+    multi_wakeup(multi);
+
+  time_before_wait = tutil_tvnow();
+  multi_poll(multi, NULL, 0, 1000, &numfds);
+  time_after_wait = tutil_tvnow();
+
+  if(tutil_tvdiff(time_after_wait, time_before_wait) > 500) {
+    fprintf(stderr, "%s:%d curl_multi_poll returned too late\n",
+            __FILE__, __LINE__);
+    res = TEST_ERR_MAJOR_BAD;
+    goto test_cleanup;
+  }
+
+  abort_on_test_timeout();
+
+#if !defined(WIN32) && !defined(_WIN32) && !defined(__WIN32__) \
+    && !defined(__CYGWIN__)
+  /* Even lots of previous wakeups should not wake up this.
+
+     On Windows (particularly when using MinGW), the socketpair
+     used for curl_multi_wakeup() is really asynchronous,
+     meaning when it's called a lot, it can take some time
+     before all of the data can be read. Sometimes it can wake
+     up more than one curl_multi_poll() call. */
+
+  time_before_wait = tutil_tvnow();
+  multi_poll(multi, NULL, 0, 1000, &numfds);
+  time_after_wait = tutil_tvnow();
+
+  if(tutil_tvdiff(time_after_wait, time_before_wait) < 500) {
+    fprintf(stderr, "%s:%d curl_multi_poll returned too early\n",
+            __FILE__, __LINE__);
+    res = TEST_ERR_MAJOR_BAD;
+    goto test_cleanup;
+  }
+
+  abort_on_test_timeout();
+#endif
+
+test_cleanup:
+
+  curl_multi_cleanup(multi);
+  curl_global_cleanup();
+
+  return res;
+}
diff --git a/tests/libtest/lib1565.c b/tests/libtest/lib1565.c
new file mode 100644 (file)
index 0000000..b2fa40a
--- /dev/null
@@ -0,0 +1,204 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2019, 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.haxx.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.
+ *
+ ***************************************************************************/
+#include "test.h"
+
+#include "testutil.h"
+#include "warnless.h"
+#include "memdebug.h"
+
+#ifdef HAVE_PTHREAD_H
+#include <pthread.h>
+#include <unistd.h>
+
+#define TEST_HANG_TIMEOUT 60 * 1000
+#define CONN_NUM 3
+#define TIME_BETWEEN_START_SECS 2
+
+static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+static CURL *pending_handles[CONN_NUM];
+static int pending_num = 0;
+static int test_failure = 0;
+
+static CURLM *multi = NULL;
+static const char *url;
+
+static void *run_thread(void *ptr)
+{
+  CURL *easy = NULL;
+  int res = 0;
+  int i;
+
+  (void)ptr;
+
+  for(i = 0; i < CONN_NUM; i++) {
+    sleep(TIME_BETWEEN_START_SECS);
+
+    easy_init(easy);
+
+    easy_setopt(easy, CURLOPT_URL, url);
+    easy_setopt(easy, CURLOPT_VERBOSE, 0L);
+
+    pthread_mutex_lock(&lock);
+
+    if(test_failure) {
+      pthread_mutex_unlock(&lock);
+      goto test_cleanup;
+    }
+
+    pending_handles[pending_num] = easy;
+    pending_num++;
+    easy = NULL;
+
+    pthread_mutex_unlock(&lock);
+
+    multi_wakeup(multi);
+  }
+
+test_cleanup:
+
+  curl_easy_cleanup(easy);
+
+  pthread_mutex_lock(&lock);
+
+  if(!test_failure)
+    test_failure = res;
+
+  pthread_mutex_unlock(&lock);
+
+  return NULL;
+}
+
+int test(char *URL)
+{
+  int still_running;
+  int num;
+  int i;
+  int res = 0;
+  CURL *started_handles[CONN_NUM];
+  int started_num = 0;
+  int finished_num = 0;
+  pthread_t tid = 0;
+  struct CURLMsg *message;
+
+  start_test_timing();
+
+  global_init(CURL_GLOBAL_ALL);
+
+  multi_init(multi);
+
+  url = URL;
+
+  res = pthread_create(&tid, NULL, run_thread, NULL);
+  if(0 != res) {
+    fprintf(stderr, "%s:%d Couldn't create thread, errno %d\n",
+            __FILE__, __LINE__, res);
+    goto test_cleanup;
+  }
+
+  while(1) {
+    multi_perform(multi, &still_running);
+
+    abort_on_test_timeout();
+
+    while((message = curl_multi_info_read(multi, &num)) != NULL) {
+      if(message->msg == CURLMSG_DONE) {
+        res = message->data.result;
+        if(res)
+          goto test_cleanup;
+        multi_remove_handle(multi, message->easy_handle);
+        finished_num++;
+      }
+      else {
+        fprintf(stderr, "%s:%d Got an unexpected message from curl: %i\n",
+              __FILE__, __LINE__, (int)message->msg);
+        res = TEST_ERR_MAJOR_BAD;
+        goto test_cleanup;
+      }
+
+      abort_on_test_timeout();
+    }
+
+    if(CONN_NUM == finished_num)
+      break;
+
+    multi_poll(multi, NULL, 0, TEST_HANG_TIMEOUT, &num);
+
+    abort_on_test_timeout();
+
+    pthread_mutex_lock(&lock);
+
+    while(pending_num > 0) {
+      res_multi_add_handle(multi, pending_handles[pending_num - 1]);
+      if(res) {
+        pthread_mutex_unlock(&lock);
+        goto test_cleanup;
+      }
+
+      started_handles[started_num] = pending_handles[pending_num - 1];
+      started_num++;
+      pending_num--;
+    }
+
+    pthread_mutex_unlock(&lock);
+
+    abort_on_test_timeout();
+  }
+
+  if(CONN_NUM != started_num) {
+    fprintf(stderr, "%s:%d Not all connections started: %d of %d\n",
+            __FILE__, __LINE__, started_num, CONN_NUM);
+    goto test_cleanup;
+  }
+
+  if(CONN_NUM != finished_num) {
+    fprintf(stderr, "%s:%d Not all connections finished: %d of %d\n",
+            __FILE__, __LINE__, started_num, CONN_NUM);
+    goto test_cleanup;
+  }
+
+test_cleanup:
+
+  pthread_mutex_lock(&lock);
+  if(!test_failure)
+    test_failure = res;
+  pthread_mutex_unlock(&lock);
+
+  if(0 != tid)
+    pthread_join(tid, NULL);
+
+  curl_multi_cleanup(multi);
+  for(i = 0; i < pending_num; i++)
+    curl_easy_cleanup(pending_handles[i]);
+  for(i = 0; i < started_num; i++)
+    curl_easy_cleanup(started_handles[i]);
+  curl_global_cleanup();
+
+  return test_failure;
+}
+
+#else /* without pthread, this test doesn't work */
+int test(char *URL)
+{
+  (void)URL;
+  return 0;
+}
+#endif
index bb1acca0e295b1b5407a472e41c8e968755086f9..3c8323de4c64d4549b2a8658a4aa2a0d46b0e627 100644 (file)
@@ -355,6 +355,60 @@ extern int unitfail;
 
 /* ---------------------------------------------------------------- */
 
+#define exe_multi_poll(A,B,C,D,E,Y,Z) do {                          \
+  CURLMcode ec;                                                     \
+  if((ec = curl_multi_poll((A), (B), (C), (D), (E))) != CURLM_OK) { \
+    fprintf(stderr, "%s:%d curl_multi_poll() failed, "              \
+            "with code %d (%s)\n",                                  \
+            (Y), (Z), (int)ec, curl_multi_strerror(ec));            \
+    res = (int)ec;                                                  \
+  }                                                                 \
+  else if(*((E)) < 0) {                                             \
+    fprintf(stderr, "%s:%d curl_multi_poll() succeeded, "           \
+            "but returned invalid numfds value (%d)\n",             \
+            (Y), (Z), (int)*((E)));                                 \
+    res = TEST_ERR_NUM_HANDLES;                                     \
+  }                                                                 \
+} WHILE_FALSE
+
+#define res_multi_poll(A, B, C, D, E) \
+  exe_multi_poll((A), (B), (C), (D), (E), (__FILE__), (__LINE__))
+
+#define chk_multi_poll(A, B, C, D, E, Y, Z) do {     \
+  exe_multi_poll((A), (B), (C), (D), (E), (Y), (Z)); \
+  if(res)                                            \
+    goto test_cleanup;                               \
+} WHILE_FALSE
+
+#define multi_poll(A, B, C, D, E) \
+  chk_multi_poll((A), (B), (C), (D), (E), (__FILE__), (__LINE__))
+
+/* ---------------------------------------------------------------- */
+
+#define exe_multi_wakeup(A,Y,Z) do {                     \
+  CURLMcode ec;                                          \
+  if((ec = curl_multi_wakeup((A))) != CURLM_OK) {        \
+    fprintf(stderr, "%s:%d curl_multi_wakeup() failed, " \
+            "with code %d (%s)\n",                       \
+            (Y), (Z), (int)ec, curl_multi_strerror(ec)); \
+    res = (int)ec;                                       \
+  }                                                      \
+} WHILE_FALSE
+
+#define res_multi_wakeup(A) \
+  exe_multi_wakeup((A), (__FILE__), (__LINE__))
+
+#define chk_multi_wakeup(A, Y, Z) do { \
+  exe_multi_wakeup((A), (Y), (Z));     \
+  if(res)                              \
+    goto test_cleanup;                 \
+} WHILE_FALSE
+
+#define multi_wakeup(A) \
+  chk_multi_wakeup((A), (__FILE__), (__LINE__))
+
+/* ---------------------------------------------------------------- */
+
 #define exe_select_test(A, B, C, D, E, Y, Z) do {               \
     int ec;                                                     \
     if(select_wrapper((A), (B), (C), (D), (E)) == -1) {         \