← Commit list

ef362b78ac

Stefan Eissing
Stefan Eissing
2026-09-14
SUCCESS Duration: 423s

Subject

multi-event: fix timeout of pending transfers

Binary

No binary (build failed)

Architecture & Compiler Flags

Architecture: x86_64-linux-gnu Compiler: gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0

—

Build Log

(log yok)

Commit Diff GitHub →

docs/libcurl/libcurl-env-dbg.md+4 -0
@@ -224,3 +224,7 @@ This allows testing of connection reuse in mixed `STARTTLS` needs.
## `CURL_DBG_SUPPRESS_CONNECT_HDS`
Existence of this variable suppresses the collection of CONNECT headers.
+
+## `CURL_DBG_MAX_TOTAL_CONNECTIONS`
+
+Set the `max_total_connections` for a `multi` handle.
lib/multi.c+22 -5
@@ -47,6 +47,7 @@
#include "http2.h"
#include "socketpair.h"
#include "bufref.h"
+#include "curlx/strparse.h"
/* initial multi->xfers table size for a full multi */
#define CURL_XFER_TABLE_SIZE 128
@@ -283,8 +284,18 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size,
goto error;
#ifdef DEBUGBUILD
- if(getenv("CURL_DEBUG"))
- multi->admin->set.verbose = TRUE;
+ {
+ const char *p;
+ if(getenv("CURL_DEBUG"))
+ multi->admin->set.verbose = TRUE;
+ p = getenv("CURL_DBG_MAX_TOTAL_CONNECTIONS");
+ if(p) {
+ curl_off_t l;
+ if(!curlx_str_number(&p, &l, UINT32_MAX)) {
+ multi->max_total_connections = (uint32_t)l;
+ }
+ }
+ }
#endif
Curl_uint32_tbl_add(&multi->xfers, multi->admin, &multi->admin->mid);
Curl_uint32_bset_add(&multi->process, multi->admin->mid);
@@ -3206,9 +3217,15 @@ static CURLMcode multi_run_dirty(struct Curl_multi *multi,
CURL_TRC_M(data, "multi_run_dirty");
if(!Curl_uint32_bset_contains(&multi->process, mid)) {
- /* We are no longer processing this transfer */
- Curl_uint32_bset_remove(&multi->dirty, mid);
- continue;
+ if(Curl_uint32_bset_contains(&multi->pending, mid)) {
+ /* Something happened on the pending transfer, act on it. */
+ move_pending_to_connect(multi, data);
+ }
+ else {
+ /* We are no longer processing this transfer */
+ Curl_uint32_bset_remove(&multi->dirty, mid);
+ continue;
+ }
}
(*pnum)++;
tests/http/test_02_download.py+27 -0
@@ -716,3 +716,30 @@ class TestDownload:
f'got {r.exit_code}\n{r.dump_logs()}'
if r.exit_code == 0:
r.check_response(http_status=431)
+
+ # download 2 http/1.1 resources, limited to a single connection max
+ @pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug")
+ def test_02_37_pending_timeout(self, env: Env, httpd):
+ # Run 2 http/1.1 transfers in parallel with a connection limit of 1
+ # - xfer 0 has a delay response, making xfer 1 pending during its run
+ # - xfer 1 will timeout while pending, waiting on the connection
+ proto = 'http/1.1'
+ run_env = os.environ.copy()
+ run_env['CURL_DBG_MAX_TOTAL_CONNECTIONS'] = '1'
+ curl = CurlClient(env=env, run_env=run_env)
+ url1 = f'https://{env.authority_for(env.domain1, proto)}' \
+ '/curltest/tweak/?&delay=3s'
+ url2 = f'https://{env.authority_for(env.domain1, proto)}/data.json'
+ r = curl.http_download(urls=[url1, url2], alpn_proto=proto, extra_args=[
+ '--http1.1', '--parallel'
+ ], url_options={
+ url1: ['--max-time', '10'],
+ url2: ['--max-time', '1']
+ })
+ r.check_exit_code(28)
+ xfers = {stat['xfer_id']: stat for stat in r.stats}
+ assert xfers[0]['http_code'] == 200, f'{r.stats[0]}' # succeeded
+ assert xfers[1]['exitcode'] == 28, f'{r.stats[1]}' # timed out
+ m = re.search(r'timed out after (\d+) milliseconds', xfers[1]['errormsg'])
+ assert m, f'{r.stats[1]}'
+ assert int(m.group(1)) < 3000, f'{r.stats[1]}'