Note that when you use the multi interface, all easy handles added to the same
multi handle share the connection cache by default without using this option.
+Connection limits set with CURLMOPT_MAX_HOST_CONNECTIONS(3) and
+CURLMOPT_MAX_TOTAL_CONNECTIONS(3) also apply to transfers using a shared
+connection cache. Each transfer applies the limits of the multi handle it
+runs on to the shared cache.
+
## CURL_LOCK_DATA_PSL
The Public Suffix List stored in the share object is made available to all
return oldest_idle;
}
+/* Evict an idle connection to make room in the pool. A pool owned by
+ * a share has no multi that could perform a controlled shutdown of the
+ * connection; terminate it right away. Otherwise, hand it to the
+ * transfer's multi for shutdown. Expects the pool to be locked. */
+static void cpool_evict_conn(struct cpool *cpool,
+ struct Curl_easy *data,
+ struct connectdata *conn)
+{
+ if(cpool->share) {
+ cpool_remove_conn(cpool, conn);
+ Curl_cshutdn_terminate(cpool->idata, conn, TRUE);
+ }
+ else
+ Curl_conn_terminate(data, conn, FALSE);
+}
+
int Curl_cpool_check_limits(struct Curl_easy *data,
struct connectdata *conn)
{
if(!cpool)
return CPOOL_LIMIT_OK;
- if(cpool->idata->multi) {
- dest_limit = cpool->idata->multi->max_host_connections;
- total_limit = cpool->idata->multi->max_total_connections;
+ /* multi determines the limits, no matter who owns the pool */
+ if(data->multi) {
+ dest_limit = data->multi->max_host_connections;
+ total_limit = data->multi->max_total_connections;
}
if(!dest_limit && !total_limit)
" from %zu to reach destination limit of %zu",
oldest_idle->connection_id,
Curl_llist_count(&bundle->conns), dest_limit);
- Curl_conn_terminate(cpool->idata, oldest_idle, FALSE);
+ cpool_evict_conn(cpool, data, oldest_idle);
/* in case the bundle was destroyed in disconnect, look it up again */
bundle = cpool_find_bundle(cpool, conn);
live = bundle ? Curl_llist_count(&bundle->conns) : 0;
}
- shutdowns = Curl_cshutdn_dest_count(cpool->idata, conn->destination);
+ shutdowns = Curl_cshutdn_dest_count(data, conn->destination);
}
if((live + shutdowns) >= dest_limit) {
res = CPOOL_LIMIT_DEST;
}
if(total_limit) {
- shutdowns = Curl_cshutdn_count(cpool->idata);
+ shutdowns = Curl_cshutdn_count(data);
while((cpool->num_conn + shutdowns) >= total_limit) {
if(shutdowns) {
/* close one connection in shutdown right away, if we can */
FMT_OFF_T " from %zu to reach total "
"limit of %zu",
oldest_idle->connection_id, cpool->num_conn, total_limit);
- Curl_conn_terminate(cpool->idata, oldest_idle, FALSE);
+ cpool_evict_conn(cpool, data, oldest_idle);
}
- shutdowns = Curl_cshutdn_count(cpool->idata);
+ shutdowns = Curl_cshutdn_count(data);
}
if((cpool->num_conn + shutdowns) >= total_limit) {
res = CPOOL_LIMIT_TOTAL;
oldest_idle = cpool_get_oldest_idle(cpool, Curl_pgrs_now(data));
kept = (oldest_idle != conn);
if(oldest_idle) {
- Curl_conn_terminate(data, oldest_idle, FALSE);
+ cpool_evict_conn(cpool, data, oldest_idle);
}
}
if(do_lock)
@pytest.mark.parametrize("proto", Env.http_h1_h2_protos())
@pytest.mark.parametrize("max_host_conns", [0, 1, 5])
- def test_02_33_max_host_conns(self, env: Env, httpd, nghttpx, proto, max_host_conns):
+ @pytest.mark.parametrize("share_connect", [False, True])
+ def test_02_33_max_host_conns(self, env: Env, httpd, nghttpx, proto, max_host_conns, share_connect):
if not env.curl_is_debug():
pytest.skip('only works for curl debug builds')
if not env.curl_is_verbose():
client = LocalClient(name='cli_hx_download', env=env, run_env=run_env)
if not client.exists():
pytest.skip(f'example client not built: {client.name}')
+ extra_args = ['-S'] if share_connect else [] # share connections
r = client.run(args=[
'-n', f'{count}',
'-m', f'{max_parallel}',
'-x', # always use a fresh connection
'-M', str(max_host_conns), # limit conns per host
'-r', f'{env.domain1}:{port}:127.0.0.1',
- '-V', proto, url
- ])
+ ] + extra_args + ['-V', proto, url])
r.check_exit_code(0)
srcfile = os.path.join(httpd.docs_dir, docname)
self.check_downloads(client, srcfile, count)
@pytest.mark.parametrize("proto", Env.http_h1_h2_protos())
@pytest.mark.parametrize("max_total_conns", [0, 1, 5])
- def test_02_34_max_total_conns(self, env: Env, httpd, nghttpx, proto, max_total_conns):
+ @pytest.mark.parametrize("share_connect", [False, True])
+ def test_02_34_max_total_conns(self, env: Env, httpd, nghttpx, proto, max_total_conns, share_connect):
if not env.curl_is_debug():
pytest.skip('only works for curl debug builds')
if not env.curl_is_verbose():
client = LocalClient(name='cli_hx_download', env=env, run_env=run_env)
if not client.exists():
pytest.skip(f'example client not built: {client.name}')
+ extra_args = ['-S'] if share_connect else [] # share connections
r = client.run(args=[
'-n', f'{count}',
'-m', f'{max_parallel}',
'-x', # always use a fresh connection
'-T', str(max_total_conns), # limit total connections
'-r', f'{env.domain1}:{port}:127.0.0.1',
- '-V', proto, url
- ])
+ ] + extra_args + ['-V', proto, url])
r.check_exit_code(0)
srcfile = os.path.join(httpd.docs_dir, docname)
self.check_downloads(client, srcfile, count)
# run connection pressure, many small transfers, not reusing connections,
# limited total
@pytest.mark.parametrize("proto", ['http/1.1'])
- def test_19_07_shutdown_by_curl(self, env: Env, httpd, proto):
+ @pytest.mark.parametrize("share_connect", [False, True])
+ def test_19_07_shutdown_by_curl(self, env: Env, httpd, proto, share_connect):
if not env.curl_is_debug():
pytest.skip('only works for curl debug builds')
count = 500
})
if not client.exists():
pytest.skip(f'example client not built: {client.name}')
+ extra_args = ['-S'] if share_connect else [] # share connections
r = client.run(args=[
'-n', f'{count}', # that many transfers
'-C', env.ca.cert_file,
'-f', # forbid conn reuse
'-m', '10', # max parallel
'-T', '5', # max total conns at a time
- '-V', proto,
- url
- ])
+ ] + extra_args + ['-V', proto, url])
r.check_exit_code(0)
shutdowns = [line for line in r.trace_lines
if re.match(r'.*SHUTDOWN] shutdown, done=1', line)]
" -M number max concurrent connections to a host\n"
" -P number pause transfer after `number` response bytes\n"
" -r <host>:<port>:<addr> resolve information\n"
+ " -S share connections between easy handles\n"
" -T number max concurrent connections total\n"
" -V http_version (http/1.1, h2, h3) http version to use\n"
" -6 use IPv6 for resolving the FIRST URL\n"
size_t max_host_conns = 0;
size_t max_total_conns = 0;
int fresh_connect = 0;
+ int share_connect = 0;
char *cafile = NULL;
bool first_ipv6 = FALSE;
CURLcode result = CURLE_OK;
(void)URL;
- while((ch = cgetopt(test_argc, test_argv, "aefhm:n:xA:C:F:M:P:r:T:V:6"))
+ while((ch = cgetopt(test_argc, test_argv, "aefhm:n:xA:C:F:M:P:r:ST:V:6"))
!= -1) {
const char *opt = coptarg;
curl_off_t num;
curlx_free(resolve);
resolve = curlx_strdup(coptarg);
break;
+ case 'S':
+ share_connect = 1;
+ break;
case 'T':
if(!curlx_str_number(&opt, &num, LONG_MAX))
max_total_conns = (size_t)num;
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
-#if 0
- curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
-#endif
+ if(share_connect)
+ curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS);