multi: add CURLMOPT_NETWORK_CHANGED to signal network changed

New multi option CURLMOPT_NETWORK_CHANGED with a long bitmask value:

- CURLM_NWCOPT_CLEAR_CONNS: do not reuse existing connections, close all
  idle connections.

- CURLM_NWCOPT_CLEAR_DNS: clear the multi's DNS cache.

All other bits reserved for future extensions.

Fixes #17225
Reported-by: ウさん
Closes #17613
This commit is contained in:
Stefan Eissing
2025-06-13 12:38:44 +02:00
committed by Daniel Stenberg
parent 7b8594176d
commit 55c045c863
19 changed files with 354 additions and 4 deletions

View File

@@ -68,6 +68,10 @@ CURLMOPT_MAX_HOST_CONNECTIONS(3)
Max simultaneously open connections. See CURLMOPT_MAX_TOTAL_CONNECTIONS(3)
## CURLMOPT_NETWORK_CHANGED
Signal that the network has changed. See CURLMOPT_NETWORK_CHANGED(3)
## CURLMOPT_PIPELINING
Enable HTTP multiplexing. See CURLMOPT_PIPELINING(3)

View File

@@ -0,0 +1,80 @@
---
c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
SPDX-License-Identifier: curl
Title: CURLMOPT_NETWORK_CHANGED
Section: 3
Source: libcurl
See-also:
- CURLOPT_FRESH_CONNECT (3)
- CURLOPT_FORBID_REUSE (3)
Protocol:
- All
Added-in: 8.16.0
---
# NAME
CURLMOPT_NETWORK_CHANGED - signal network changed
# SYNOPSIS
~~~c
#include <curl/curl.h>
CURLMcode curl_multi_setopt(CURLM *handle, CURLMOPT_NETWORK_CHANGED,
long value);
~~~
# DESCRIPTION
Pass a long with a bitmask to tell libcurl how the multi
handle should react. The following values in the mask are
defined. All bits not mentioned are reserved for future
extensions.
This option can be set at any time and repeatedly. Each call only
affects the *currently* cached connections and DNS information.
Any connection created or DNS information added afterwards is
cached the usual way again. Phrasing it another way: the option is
not persisted but setting it serves as a "trigger"
to clear the caches.
The call affects only the connection and DNS cache of the multi handle
itself and not the ones owned by SHARE handles.
## CURLM_NWCOPT_CLEAR_CONNS
No longer reuse any existing connection in the multi handle's
connection cache. This closes all connections that are not in use.
Ongoing transfers continue on the connections they operate on.
## CURLM_NWCOPT_CLEAR_DNS
Clear the multi handle's DNS cache.
# DEFAULT
0, which has no effect.
# %PROTOCOLS%
# EXAMPLE
~~~c
int main(void)
{
CURLM *m = curl_multi_init();
/* do transfers on the multi handle */
/* do not reuse existing connections */
curl_multi_setopt(m, CURLMOPT_NETWORK_CHANGED, CURLM_NWCOPT_CLEAR_CONNS);
}
~~~
# %AVAILABILITY%
# RETURN VALUE
curl_multi_setopt(3) returns a CURLMcode indicating success or error.
CURLM_OK (0) means everything was OK, non-zero means an error occurred, see
libcurl-errors(3).

View File

@@ -8,6 +8,7 @@ See-also:
- CURLOPT_FRESH_CONNECT (3)
- CURLOPT_MAXCONNECTS (3)
- CURLOPT_MAXLIFETIME_CONN (3)
- CURLMOPT_NETWORK_CHANGED (3)
Protocol:
- All
Added-in: 7.7

View File

@@ -10,6 +10,7 @@ See-also:
- CURLOPT_FORBID_REUSE (3)
- CURLOPT_MAXAGE_CONN (3)
- CURLOPT_MAXLIFETIME_CONN (3)
- CURLMOPT_NETWORK_CHANGED (3)
Added-in: 7.7
---

View File

@@ -108,6 +108,7 @@ man_MANS = \
CURLMOPT_MAX_PIPELINE_LENGTH.3 \
CURLMOPT_MAX_TOTAL_CONNECTIONS.3 \
CURLMOPT_MAXCONNECTS.3 \
CURLMOPT_NETWORK_CHANGED.3 \
CURLMOPT_PIPELINING.3 \
CURLMOPT_PIPELINING_SERVER_BL.3 \
CURLMOPT_PIPELINING_SITE_BL.3 \

View 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_NWCOPT_CLEAR_CONNS 8.16.0
CURLM_NWCOPT_CLEAR_DNS 8.16.0
CURLM_OK 7.9.6
CURLM_OUT_OF_MEMORY 7.9.6
CURLM_RECURSIVE_API_CALL 7.59.0
@@ -559,6 +561,7 @@ CURLMOPT_MAX_HOST_CONNECTIONS 7.30.0
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_PIPELINING 7.16.0
CURLMOPT_PIPELINING_SERVER_BL 7.30.0
CURLMOPT_PIPELINING_SITE_BL 7.30.0

View File

@@ -395,9 +395,23 @@ typedef enum {
/* maximum number of concurrent streams to support on a connection */
CURLOPT(CURLMOPT_MAX_CONCURRENT_STREAMS, CURLOPTTYPE_LONG, 16),
/* network has changed, adjust caches/connection reuse */
CURLOPT(CURLMOPT_NETWORK_CHANGED, CURLOPTTYPE_LONG, 17),
CURLMOPT_LASTENTRY /* the last unused */
} CURLMoption;
/* Definition of bits for the CURLMOPT_NETWORK_CHANGED argument: */
/* - CURLM_NWCOPT_CLEAR_CONNS tells libcurl to prevent further reuse
of existing connections. Connections that are idle will be closed.
Ongoing transfers will continue with the connection they have. */
#define CURLM_NWCOPT_CLEAR_CONNS (1L<<0)
/* - CURLM_NWCOPT_CLEAR_DNS tells libcurl to prevent further reuse
of existing connections. Connections that are idle will be closed.
Ongoing transfers will continue with the connection they have. */
#define CURLM_NWCOPT_CLEAR_DNS (1L<<0)
/*
* Name: curl_multi_setopt()

View File

@@ -709,7 +709,8 @@ static int cpool_reap_dead_cb(struct Curl_easy *data,
struct connectdata *conn, void *param)
{
struct cpool_reaper_ctx *rctx = param;
if(Curl_conn_seems_dead(conn, data, &rctx->now)) {
if((!CONN_INUSE(conn) && conn->bits.no_reuse) ||
Curl_conn_seems_dead(conn, data, &rctx->now)) {
/* stop the iteration here, pass back the connection that was pruned */
Curl_conn_terminate(data, conn, FALSE);
return 1;
@@ -849,6 +850,40 @@ void Curl_cpool_do_locked(struct Curl_easy *data,
cb(conn, data, cbdata);
}
static int cpool_mark_stale(struct Curl_easy *data,
struct connectdata *conn, void *param)
{
(void)data;
(void)param;
conn->bits.no_reuse = TRUE;
return 0;
}
static int cpool_reap_no_reuse(struct Curl_easy *data,
struct connectdata *conn, void *param)
{
(void)data;
(void)param;
if(!CONN_INUSE(conn) && conn->bits.no_reuse) {
Curl_conn_terminate(data, conn, FALSE);
return 1;
}
return 0; /* continue iteration */
}
void Curl_cpool_nw_changed(struct Curl_easy *data)
{
struct cpool *cpool = cpool_get_instance(data);
if(cpool) {
CPOOL_LOCK(cpool, data);
cpool_foreach(data, cpool, NULL, cpool_mark_stale);
while(cpool_foreach(data, cpool, NULL, cpool_reap_no_reuse))
;
CPOOL_UNLOCK(cpool, data);
}
}
#if 0
/* Useful for debugging the connection pool */
void Curl_cpool_print(struct cpool *cpool)

View File

@@ -163,4 +163,8 @@ void Curl_cpool_do_locked(struct Curl_easy *data,
struct connectdata *conn,
Curl_cpool_conn_do_cb *cb, void *cbdata);
/* Close all unused connections, prevent reuse of existing ones. */
void Curl_cpool_nw_changed(struct Curl_easy *data);
#endif /* HEADER_CURL_CONNCACHE_H */

View File

@@ -1102,6 +1102,7 @@ void curl_easy_reset(CURL *d)
data->progress.hide = TRUE;
data->state.current_speed = -1; /* init to negative == impossible */
data->state.retrycount = 0; /* reset the retry counter */
data->state.recent_conn_id = -1; /* clear remembered connection id */
/* zero out authentication data: */
memset(&data->state.authhost, 0, sizeof(struct auth));

View File

@@ -290,6 +290,16 @@ void Curl_dnscache_prune(struct Curl_easy *data)
dnscache_unlock(data, dnscache);
}
void Curl_dnscache_clear(struct Curl_easy *data)
{
struct Curl_dnscache *dnscache = dnscache_get(data);
if(dnscache) {
dnscache_lock(data, dnscache);
Curl_hash_clean(&dnscache->entries);
dnscache_unlock(data, dnscache);
}
}
#ifdef USE_ALARM_TIMEOUT
/* Beware this is a global and unique instance. This is used to store the
return address that we can jump back to from inside a signal handler. This

View File

@@ -130,6 +130,9 @@ void Curl_dnscache_destroy(struct Curl_dnscache *dns);
/* prune old entries from the DNS cache */
void Curl_dnscache_prune(struct Curl_easy *data);
/* clear the DNS cache */
void Curl_dnscache_clear(struct Curl_easy *data);
/* IPv4 threadsafe resolve function used for synch and asynch builds */
struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port);

View File

@@ -3235,6 +3235,16 @@ CURLMcode curl_multi_setopt(CURLM *m,
multi->max_concurrent_streams = (unsigned int)streams;
}
break;
case CURLMOPT_NETWORK_CHANGED: {
long val = va_arg(param, long);
if(val & CURLM_NWCOPT_CLEAR_DNS) {
Curl_dnscache_clear(multi->admin);
}
if(val & CURLM_NWCOPT_CLEAR_CONNS) {
Curl_cpool_nw_changed(multi->admin);
}
break;
}
default:
res = CURLM_UNKNOWN_OPTION;
break;

View File

@@ -843,7 +843,7 @@ static bool url_match_connect_config(struct connectdata *conn,
struct url_conn_match *m)
{
/* connect-only or to-be-closed connections will not be reused */
if(conn->connect_only || conn->bits.close)
if(conn->connect_only || conn->bits.close || conn->bits.no_reuse)
return FALSE;
/* ip_version must match */

View File

@@ -423,6 +423,7 @@ struct ConnectBits {
BIT(parallel_connect); /* set TRUE when a parallel connect attempt has
started (happy eyeballs) */
BIT(aborted); /* connection was aborted, e.g. in unclean state */
BIT(no_reuse); /* connection should not be reused */
BIT(shutdown_handler); /* connection shutdown: handler shut down */
BIT(shutdown_filters); /* connection shutdown: filters shut down */
BIT(in_cpool); /* connection is kept in a connection pool */

View File

@@ -273,7 +273,7 @@ test3000 test3001 test3002 test3003 test3004 test3005 test3006 test3007 \
test3008 test3009 test3010 test3011 test3012 test3013 test3014 test3015 \
test3016 test3017 test3018 test3019 test3020 test3021 test3022 test3023 \
test3024 test3025 test3026 test3027 test3028 test3029 test3030 test3031 \
test3032 \
test3032 test3033 \
\
test3100 test3101 test3102 test3103 test3104 test3105 \
\

53
tests/data/test3033 Normal file
View File

@@ -0,0 +1,53 @@
<testcase>
<info>
<keywords>
curl_easy_setopt
connection reuse
libtest
</keywords>
</info>
#
# Server-side
<reply>
<data>
HTTP/1.1 200 OK
Content-Length: 6
-foo-
</data>
<datacheck>
[0] no network change
-foo-
[1] signal network change
-foo-
[2] no network change
-foo-
</datacheck>
</reply>
#
# Client-side
<client>
<server>
http
</server>
<name>
CURLOPT_FRESH_CONNECT=2
</name>
<tool>
lib%TESTNUMBER
</tool>
<command>
http://%HOSTIP:%HTTPPORT/%TESTNUMBER
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<errorcode>
0
</errorcode>
</verify>
</testcase>

View File

@@ -93,6 +93,6 @@ TESTS_C = \
lib2402.c lib2404.c lib2405.c \
lib2502.c \
lib2700.c \
lib3010.c lib3025.c lib3026.c lib3027.c \
lib3010.c lib3025.c lib3026.c lib3027.c lib3033.c \
lib3100.c lib3101.c lib3102.c lib3103.c lib3104.c lib3105.c \
lib3207.c lib3208.c

129
tests/libtest/lib3033.c Normal file
View File

@@ -0,0 +1,129 @@
/***************************************************************************
* _ _ ____ _
* 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 "testtrace.h"
#include "testutil.h"
#include "memdebug.h"
static CURLcode t3033_req_test(CURLM *multi, CURL *easy,
char *url_3033, int index)
{
CURLMsg *msg = NULL;
CURLcode res = CURLE_OK;
int still_running = 0;
if(index == 1) {
curl_multi_setopt(multi, CURLMOPT_NETWORK_CHANGED,
CURLM_NWCOPT_CLEAR_CONNS);
curl_mprintf("[1] signal network change\n");
}
else {
curl_mprintf("[%d] no network change\n", index);
}
curl_easy_reset(easy);
curl_easy_setopt(easy, CURLOPT_URL, url_3033);
easy_setopt(easy, CURLOPT_DEBUGDATA, &libtest_debug_config);
easy_setopt(easy, CURLOPT_DEBUGFUNCTION, libtest_debug_cb);
easy_setopt(easy, CURLOPT_VERBOSE, 1L);
curl_multi_add_handle(multi, easy);
do {
CURLMcode mres;
int num;
curl_multi_perform(multi, &still_running);
mres = curl_multi_wait(multi, NULL, 0, TEST_HANG_TIMEOUT, &num);
if(mres != CURLM_OK) {
curl_mfprintf(stderr, "curl_multi_wait() returned %d\n", mres);
res = TEST_ERR_MAJOR_BAD;
goto test_cleanup;
}
} while(still_running);
do {
long num_connects = 0L;
msg = curl_multi_info_read(multi, &still_running);
if(msg) {
if(msg->msg != CURLMSG_DONE)
continue;
res = msg->data.result;
if(res != CURLE_OK) {
curl_mfprintf(stderr, "curl_multi_info_read() returned %d\n", res);
goto test_cleanup;
}
curl_easy_getinfo(easy, CURLINFO_NUM_CONNECTS, &num_connects);
if(index == 1 && num_connects == 0) {
curl_mprintf("[1] should not reuse connection in pool\n");
res = TEST_ERR_MAJOR_BAD;
goto test_cleanup;
}
else if(index == 2 && num_connects) {
curl_mprintf("[2] should have reused connection from [1]\n");
res = TEST_ERR_MAJOR_BAD;
goto test_cleanup;
}
}
} while(msg);
test_cleanup:
curl_multi_remove_handle(multi, easy);
return res;
}
static CURLcode test_lib3033(char *URL)
{
CURL *curl = NULL;
CURLM *multi = NULL;
CURLcode res = CURLE_OK;
global_init(CURL_GLOBAL_ALL);
multi_init(multi);
easy_init(curl);
libtest_debug_config.nohex = 1;
libtest_debug_config.tracetime = 1;
res = t3033_req_test(multi, curl, URL, 0);
if(res != CURLE_OK)
goto test_cleanup;
res = t3033_req_test(multi, curl, URL, 1);
if(res != CURLE_OK)
goto test_cleanup;
res = t3033_req_test(multi, curl, URL, 2);
if(res != CURLE_OK)
goto test_cleanup;
test_cleanup:
curl_easy_cleanup(curl);
curl_multi_cleanup(multi);
curl_global_cleanup();
return res; /* return the final return code */
}