]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
CURLOPT_MAXLIFETIME_CONN: maximum allowed lifetime for conn reuse
authorJeffrey Tolar <tolar@yahooinc.com>
Sat, 18 Sep 2021 16:29:44 +0000 (11:29 -0500)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 6 Oct 2021 12:38:59 +0000 (14:38 +0200)
... and close connections that are too old instead of reusing them.

By default, this behavior is disabled.

Bug: https://curl.se/mail/lib-2021-09/0058.html
Closes #7751

16 files changed:
docs/libcurl/curl_easy_setopt.3
docs/libcurl/opts/CURLOPT_FORBID_REUSE.3
docs/libcurl/opts/CURLOPT_MAXAGE_CONN.3
docs/libcurl/opts/CURLOPT_MAXLIFETIME_CONN.3 [new file with mode: 0644]
docs/libcurl/opts/Makefile.inc
docs/libcurl/symbols-in-versions
include/curl/curl.h
lib/easyoptions.c
lib/setopt.c
lib/url.c
lib/urldata.h
packages/OS400/curl.inc.in
tests/data/Makefile.inc
tests/data/test1542 [new file with mode: 0644]
tests/libtest/Makefile.inc
tests/libtest/lib1542.c [new file with mode: 0644]

index b1abf43e381a7489159b66dc7c701be334951217..feb9a395588b2b916dcd4c1d0ae7e99775600462 100644 (file)
@@ -492,7 +492,9 @@ Use a new connection. \fICURLOPT_FRESH_CONNECT(3)\fP
 .IP CURLOPT_FORBID_REUSE
 Prevent subsequent connections from re-using this. See \fICURLOPT_FORBID_REUSE(3)\fP
 .IP CURLOPT_MAXAGE_CONN
-Limit the age of connections for reuse. See \fICURLOPT_MAXAGE_CONN(3)\fP
+Limit the age (idle time) of connections for reuse. See \fICURLOPT_MAXAGE_CONN(3)\fP
+.IP CURLOPT_MAXLIFETIME_CONN
+Limit the age (since creation) of connections for reuse. See \fICURLOPT_MAXLIFETIME_CONN(3)\fP
 .IP CURLOPT_CONNECTTIMEOUT
 Timeout for the connection phase. See \fICURLOPT_CONNECTTIMEOUT(3)\fP
 .IP CURLOPT_CONNECTTIMEOUT_MS
index a1b2a8272ec0b87685e491193dd5e307f4ee02f7..f2d6dd37e21187f1913b62dc66ded3b6b974a70d 100644 (file)
@@ -57,3 +57,4 @@ Always
 Returns CURLE_OK
 .SH "SEE ALSO"
 .BR CURLOPT_FRESH_CONNECT "(3), " CURLOPT_MAXCONNECTS "(3), "
+.BR CURLOPT_MAXLIFETIME_CONN "(3), "
index 0624e2e8f8c02fc62e97a9f97c4c974e2652deca..40127c80138e189993b84bb9a5d7d09a8d9f39b6 100644 (file)
@@ -29,8 +29,8 @@ CURLOPT_MAXAGE_CONN \- max idle time allowed for reusing a connection
 CURLcode curl_easy_setopt(CURL *handle, CURLOPT_MAXAGE_CONN, long maxage);
 .SH DESCRIPTION
 Pass a long as parameter containing \fImaxage\fP - the maximum time in seconds
-that you allow an existing connection to have to be considered for reuse for
-this request.
+that you allow an existing connection to have been idle to be considered for
+reuse for this request.
 
 The "connection cache" that holds previously used connections. When a new
 request is to be done, it will consider any connection that matches for
@@ -62,4 +62,4 @@ Added in libcurl 7.65.0
 Returns CURLE_OK.
 .SH "SEE ALSO"
 .BR CURLOPT_TIMEOUT "(3), " CURLOPT_FORBID_REUSE "(3), "
-.BR CURLOPT_FRESH_CONNECT "(3), "
+.BR CURLOPT_FRESH_CONNECT "(3), " CURLOPT_MAXLIFETIME_CONN "(3), "
diff --git a/docs/libcurl/opts/CURLOPT_MAXLIFETIME_CONN.3 b/docs/libcurl/opts/CURLOPT_MAXLIFETIME_CONN.3
new file mode 100644 (file)
index 0000000..eeb6c13
--- /dev/null
@@ -0,0 +1,66 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 2021, 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.
+.\" *
+.\" **************************************************************************
+.\"
+.TH CURLOPT_MAXLIFETIME_CONN 3 "10 Nov 2021" "libcurl 7.80.0" "curl_easy_setopt options"
+.SH NAME
+CURLOPT_MAXLIFETIME_CONN \- max lifetime (since creation) allowed for reusing a connection
+.SH SYNOPSIS
+#include <curl/curl.h>
+
+CURLcode curl_easy_setopt(CURL *handle, CURLOPT_MAXLIFETIME_CONN, long maxlifetime);
+.SH DESCRIPTION
+Pass a long as parameter containing \fImaxlifetime\fP - the maximum time in
+seconds, since the creation of the connection, that you allow an existing
+connection to have to be considered for reuse for this request.
+
+libcurl features a connection cache that holds previously used connections.
+When a new request is to be done, it will consider any connection that matches
+for reuse. The \fICURLOPT_MAXLIFETIME_CONN(3)\fP limit prevents libcurl from
+trying very old connections for reuse. This can be used for client-side load
+balancing. If a connection is found in the cache that is older than this set
+\fImaxlifetime\fP, it will instead be closed once any in-progress transfers
+complete.
+
+If set to 0, this behavior is disabled: all connections are eligible for reuse.
+.SH DEFAULT
+Default \fImaxlifetime\fP is 0 seconds (i.e., disabled).
+.SH PROTOCOLS
+All
+.SH EXAMPLE
+.nf
+CURL *curl = curl_easy_init();
+if(curl) {
+  curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
+
+  /* only allow each connection to be reused for 30 seconds */
+  curl_easy_setopt(curl, CURLOPT_MAXLIFETIME_CONN, 30L);
+
+  curl_easy_perform(curl);
+}
+.fi
+.SH AVAILABILITY
+Added in libcurl 7.80.0
+.SH RETURN VALUE
+Returns CURLE_OK.
+.SH "SEE ALSO"
+.BR CURLOPT_TIMEOUT "(3), " CURLOPT_FORBID_REUSE "(3), "
+.BR CURLOPT_FRESH_CONNECT "(3), " CURLOPT_MAXAGE_CONN "(3), "
index 7bac21024d4547a2dc8a5acdcf48f4230e1c2c3d..55a0a3b7c4d0a6ebe28385d5fd336963546cddc0 100644 (file)
@@ -228,6 +228,7 @@ man_MANS =                                      \
   CURLOPT_MAXCONNECTS.3                         \
   CURLOPT_MAXFILESIZE.3                         \
   CURLOPT_MAXFILESIZE_LARGE.3                   \
+  CURLOPT_MAXLIFETIME_CONN.3                    \
   CURLOPT_MAXREDIRS.3                           \
   CURLOPT_MAX_RECV_SPEED_LARGE.3                \
   CURLOPT_MAX_SEND_SPEED_LARGE.3                \
index 1dbcdbd3e67e6e8c43e699bcbea17cd8675ea553..a8f2e08beac2ea475ebdc405e70965850bccbfc1 100644 (file)
@@ -499,6 +499,7 @@ CURLOPT_MAXAGE_CONN             7.65.0
 CURLOPT_MAXCONNECTS             7.7
 CURLOPT_MAXFILESIZE             7.10.8
 CURLOPT_MAXFILESIZE_LARGE       7.11.0
+CURLOPT_MAXLIFETIME_CONN        7.80.0
 CURLOPT_MAXREDIRS               7.5
 CURLOPT_MAX_RECV_SPEED_LARGE    7.15.5
 CURLOPT_MAX_SEND_SPEED_LARGE    7.15.5
index fb33eeb156bc0fb38f91a1f692d23ed6abebbc8c..6b6ac8a05e888fe625581d304335fb5efa079515 100644 (file)
@@ -2058,7 +2058,8 @@ typedef enum {
   /* alt-svc cache file name to possibly read from/write to */
   CURLOPT(CURLOPT_ALTSVC, CURLOPTTYPE_STRINGPOINT, 287),
 
-  /* maximum age of a connection to consider it for reuse (in seconds) */
+  /* maximum age (idle time) of a connection to consider it for reuse
+   * (in seconds) */
   CURLOPT(CURLOPT_MAXAGE_CONN, CURLOPTTYPE_LONG, 288),
 
   /* SASL authorisation identity */
@@ -2127,6 +2128,10 @@ typedef enum {
   /* Data passed to the CURLOPT_PREREQFUNCTION callback */
   CURLOPT(CURLOPT_PREREQDATA, CURLOPTTYPE_CBPOINT, 313),
 
+  /* maximum age (since creation) of a connection to consider it for reuse
+   * (in seconds) */
+  CURLOPT(CURLOPT_MAXLIFETIME_CONN, CURLOPTTYPE_LONG, 314),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
 
index bc149e7be4bf64249ce53391069e31f1af97d771..b6131d4321ca0502fe11ae17a0b2981963398794 100644 (file)
@@ -165,6 +165,7 @@ struct curl_easyoption Curl_easyopts[] = {
   {"MAXCONNECTS", CURLOPT_MAXCONNECTS, CURLOT_LONG, 0},
   {"MAXFILESIZE", CURLOPT_MAXFILESIZE, CURLOT_LONG, 0},
   {"MAXFILESIZE_LARGE", CURLOPT_MAXFILESIZE_LARGE, CURLOT_OFF_T, 0},
+  {"MAXLIFETIME_CONN", CURLOPT_MAXLIFETIME_CONN, CURLOT_LONG, 0},
   {"MAXREDIRS", CURLOPT_MAXREDIRS, CURLOT_LONG, 0},
   {"MAX_RECV_SPEED_LARGE", CURLOPT_MAX_RECV_SPEED_LARGE, CURLOT_OFF_T, 0},
   {"MAX_SEND_SPEED_LARGE", CURLOPT_MAX_SEND_SPEED_LARGE, CURLOT_OFF_T, 0},
@@ -358,6 +359,6 @@ struct curl_easyoption Curl_easyopts[] = {
  */
 int Curl_easyopts_check(void)
 {
-  return ((CURLOPT_LASTENTRY%10000) != (313 + 1));
+  return ((CURLOPT_LASTENTRY%10000) != (314 + 1));
 }
 #endif
index 8e19389ae1ae656a4e6e86eb54932f07cefb72d5..65fe252f4758ac5e5d190ddbaa878b5408611a50 100644 (file)
@@ -2938,6 +2938,12 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
       return CURLE_BAD_FUNCTION_ARGUMENT;
     data->set.maxage_conn = arg;
     break;
+  case CURLOPT_MAXLIFETIME_CONN:
+    arg = va_arg(param, long);
+    if(arg < 0)
+      return CURLE_BAD_FUNCTION_ARGUMENT;
+    data->set.maxlifetime_conn = arg;
+    break;
   case CURLOPT_TRAILERFUNCTION:
 #ifndef CURL_DISABLE_HTTP
     data->set.trailer_callback = va_arg(param, curl_trailer_callback);
index 5c31cadd681ac85c9ae0b42aad26bd7f00b39604..1603b3072766ff75309f92e6bf6d7d234a958129 100644 (file)
--- a/lib/url.c
+++ b/lib/url.c
@@ -622,6 +622,7 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data)
   set->upkeep_interval_ms = CURL_UPKEEP_INTERVAL_DEFAULT;
   set->maxconnects = DEFAULT_CONNCACHE_SIZE; /* for easy handles */
   set->maxage_conn = 118;
+  set->maxlifetime_conn = 0;
   set->http09_allowed = FALSE;
   set->httpwant =
 #ifdef USE_NGHTTP2
@@ -962,21 +963,36 @@ socks_proxy_info_matches(const struct proxy_info *data,
 #define socks_proxy_info_matches(x,y) FALSE
 #endif
 
-/* A connection has to have been idle for a shorter time than 'maxage_conn' to
-   be subject for reuse. The success rate is just too low after this. */
+/* A connection has to have been idle for a shorter time than 'maxage_conn'
+   (the success rate is just too low after this), or created less than
+   'maxlifetime_conn' ago, to be subject for reuse. */
 
 static bool conn_maxage(struct Curl_easy *data,
                         struct connectdata *conn,
                         struct curltime now)
 {
-  timediff_t idletime = Curl_timediff(now, conn->lastused);
+  timediff_t idletime, lifetime;
+
+  idletime = Curl_timediff(now, conn->lastused);
   idletime /= 1000; /* integer seconds is fine */
 
   if(idletime > data->set.maxage_conn) {
-    infof(data, "Too old connection (%ld seconds), disconnect it",
+    infof(data, "Too old connection (%ld seconds idle), disconnect it",
           idletime);
     return TRUE;
   }
+
+  lifetime = Curl_timediff(now, conn->created);
+  lifetime /= 1000; /* integer seconds is fine */
+
+  if(data->set.maxlifetime_conn && lifetime > data->set.maxlifetime_conn) {
+    infof(data,
+          "Too old connection (%ld seconds since creation), disconnect it",
+          lifetime);
+    return TRUE;
+  }
+
+
   return FALSE;
 }
 
index 47cb9e282642b8afe7865f65ed94ed1fc6c963d8..92df52467d4e77071950c04e655cb40b747d6e19 100644 (file)
@@ -1678,6 +1678,8 @@ struct UserDefined {
   long server_response_timeout; /* in milliseconds, 0 means no timeout */
   long maxage_conn;     /* in seconds, max idle time to allow a connection that
                            is to be reused */
+  long maxlifetime_conn; /* in seconds, max time since creation to allow a
+                            connection that is to be reused */
   long tftp_blksize;    /* in bytes, 0 means use default */
   curl_off_t filesize;  /* size of file to upload, -1 means unknown */
   long low_speed_limit; /* bytes/second */
index 94b2deba9829c9f84e7b4ea19e8d91c094e369fa..b6a37a60af02d6362da081ac42cd52ed8e9b73b7 100644 (file)
      d                 c                   40309
      d  CURLOPT_PROXY_CAINFO_BLOB...
      d                 c                   40310
+     d  CURLOPT_MAXLIFETIME_CONN...
+     d                 c                   00314
       *
       /if not defined(CURL_NO_OLDIES)
      d  CURLOPT_FILE   c                   10001
index 41249fdadcceb3624b2d5a0c3586b98e3af12bde..57f2abf69fa47574e6089c5bf9a6b8f6823ae2ff 100644 (file)
@@ -189,7 +189,7 @@ test1508 test1509 test1510 test1511 test1512 test1513 test1514 test1515 \
 test1516 test1517 test1518 test1519 test1520 test1521 test1522 test1523 \
 test1524 test1525 test1526 test1527 test1528 test1529 test1530 test1531 \
 test1532 test1533 test1534 test1535 test1536 test1537 test1538 test1539 \
-test1540 \
+test1540          test1542 \
 \
 test1550 test1551 test1552 test1553 test1554 test1555 test1556 test1557 \
 test1558 test1559 test1560 test1561 test1562 test1563 test1564 test1565 \
diff --git a/tests/data/test1542 b/tests/data/test1542
new file mode 100644 (file)
index 0000000..6a9b7f0
--- /dev/null
@@ -0,0 +1,67 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+connection re-use
+persistent connection
+CURLOPT_MAXLIFETIME_CONN
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data nocheck="yes">
+HTTP/1.1 200 OK\r
+Content-Length: 0\r
+\r
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+<tool>
+lib%TESTNUMBER
+</tool>
+ <name>
+connection reuse with CURLOPT_MAXLIFETIME_CONN
+ </name>
+ <command>
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+GET /%TESTNUMBER HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+\r
+GET /%TESTNUMBER HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+\r
+GET /%TESTNUMBER HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+\r
+GET /%TESTNUMBER HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+Accept: */*\r
+\r
+</protocol>
+<file name="log/stderr%TESTNUMBER" mode="text">
+== Info: Connection #0 to host %HOSTIP left intact
+== Info: Connection #0 to host %HOSTIP left intact
+== Info: Connection #0 to host %HOSTIP left intact
+== Info: Closing connection 0
+== Info: Connection #1 to host %HOSTIP left intact
+</file>
+<stripfile>
+$_ = '' if (($_ !~ /left intact/) && ($_ !~ /Closing connection/))
+</stripfile>
+</verify>
+</testcase>
index 0f70ceb4bd014bd776250f5c03ac1f1f32e0c789..ade1012905d3c48920915433c2f80ba53b77104e 100644 (file)
@@ -55,7 +55,7 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect                \
  lib1518         lib1520 lib1521 lib1522 lib1523 \
  lib1525 lib1526 lib1527 lib1528 lib1529 lib1530 lib1531 lib1532 lib1533 \
  lib1534 lib1535 lib1536 lib1537 lib1538 lib1539 \
- lib1540         \
+ lib1540         lib1542 \
  lib1550 lib1551 lib1552 lib1553 lib1554 lib1555 lib1556 lib1557 \
  lib1558 lib1559 lib1560 lib1564 lib1565 lib1567 lib1568 lib1569 \
  lib1591 lib1592 lib1593 lib1594 lib1596 \
@@ -569,6 +569,10 @@ lib1540_SOURCES = lib1540.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
 lib1540_LDADD = $(TESTUTIL_LIBS)
 lib1540_CPPFLAGS = $(AM_CPPFLAGS)
 
+lib1542_SOURCES = lib1542.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(WARNLESS)
+lib1542_LDADD = $(TESTUTIL_LIBS)
+lib1542_CPPFLAGS = $(AM_CPPFLAGS)
+
 lib1550_SOURCES = lib1550.c $(SUPPORTFILES)
 lib1550_CPPFLAGS = $(AM_CPPFLAGS) -DLIB1517
 
diff --git a/tests/libtest/lib1542.c b/tests/libtest/lib1542.c
new file mode 100644 (file)
index 0000000..4e17d9d
--- /dev/null
@@ -0,0 +1,86 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2021, 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.
+ *
+ ***************************************************************************/
+
+/*
+ * Test CURLOPT_MAXLIFETIME_CONN:
+ * Send four requests, sleeping between the second and third and setting
+ * MAXLIFETIME_CONN between the third and fourth. The first three requests
+ * should use the same connection, and the fourth request should close the
+ * first connection and open a second.
+ */
+
+#include "test.h"
+#include "testutil.h"
+#include "testtrace.h"
+#include "warnless.h"
+#include "memdebug.h"
+
+#if defined(WIN32) || defined(_WIN32)
+#define sleep(sec) Sleep ((sec)*1000)
+#endif
+
+int test(char *URL)
+{
+  CURL *easy = NULL;
+  int res = 0;
+
+  global_init(CURL_GLOBAL_ALL);
+
+  res_easy_init(easy);
+
+  easy_setopt(easy, CURLOPT_URL, URL);
+
+  libtest_debug_config.nohex = 1;
+  libtest_debug_config.tracetime = 0;
+  easy_setopt(easy, CURLOPT_DEBUGDATA, &libtest_debug_config);
+  easy_setopt(easy, CURLOPT_DEBUGFUNCTION, libtest_debug_cb);
+  easy_setopt(easy, CURLOPT_VERBOSE, 1L);
+
+  res = curl_easy_perform(easy);
+  if(res)
+    goto test_cleanup;
+
+  res = curl_easy_perform(easy);
+  if(res)
+    goto test_cleanup;
+
+  /* CURLOPT_MAXLIFETIME_CONN is inclusive - the connection needs to be 2
+   * seconds old */
+  sleep(2);
+
+  res = curl_easy_perform(easy);
+  if(res)
+    goto test_cleanup;
+
+  easy_setopt(easy, CURLOPT_MAXLIFETIME_CONN, 1L);
+
+  res = curl_easy_perform(easy);
+  if(res)
+    goto test_cleanup;
+
+test_cleanup:
+
+  curl_easy_cleanup(easy);
+  curl_global_cleanup();
+
+  return (int)res;
+}