a->connected = TRUE;
}
}
- else if(a->result == CURLE_WEIRD_SERVER_REPLY)
- a->inconclusive = TRUE;
+ else {
+ if(a->result == CURLE_WEIRD_SERVER_REPLY)
+ a->inconclusive = TRUE;
+ if(a->cf)
+ Curl_conn_cf_discard_chain(&a->cf, data);
+ }
}
return a->result;
}
struct curltime last_attempt_started;
timediff_t attempt_delay_ms;
int last_attempt_ai_family;
+ uint32_t max_concurrent;
uint8_t transport;
};
struct Curl_cfilter *cf,
struct Curl_easy *data)
{
- struct Curl_cfilter *cf_prev = a->cf;
struct Curl_cfilter *wcf;
CURLcode result;
- /* When restarting, we tear down and existing filter *after* we
- * started up the new one. This gives us a new socket number and
- * probably a new local port. Which may prevent confusion. */
+ if(a->cf)
+ Curl_conn_cf_discard_chain(&a->cf, data);
+
a->result = CURLE_OK;
a->connected = FALSE;
a->inconclusive = FALSE;
}
a->result = cf_ip_attempt_connect(a, data, &dummy);
}
- if(cf_prev)
- Curl_conn_cf_discard_chain(&cf_prev, data);
return result;
}
struct Curl_cfilter *cf,
cf_ip_connect_create *cf_create,
uint8_t transport,
- timediff_t attempt_delay_ms)
+ timediff_t attempt_delay_ms,
+ uint32_t max_concurrent)
{
memset(bs, 0, sizeof(*bs));
bs->cf_create = cf_create;
bs->transport = transport;
bs->attempt_delay_ms = attempt_delay_ms;
+ bs->max_concurrent = max_concurrent;
bs->last_attempt_ai_family = AF_INET; /* so AF_INET6 is next */
if(transport == TRNSPRT_UNIX) {
return CURLE_OK;
}
+static void cf_ip_ballers_prune(struct cf_ip_ballers *bs,
+ struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ uint32_t max_concurrent)
+{
+ struct cf_ip_attempt *a = NULL, **panchor;
+ uint32_t ongoing = 0;
+
+ for(a = bs->running; a; a = a->next) {
+ if(!a->result && !a->connected)
+ ++ongoing;
+ }
+
+ panchor = &bs->running;
+ while(*panchor && (ongoing > max_concurrent)) {
+ a = *panchor;
+ if(!a->result && !a->connected) {
+ *panchor = a->next;
+ a->next = NULL;
+ cf_ip_attempt_free(a, data);
+ --ongoing;
+ CURL_TRC_CF(data, cf, "discarding oldest attempt to keep limit");
+ }
+ else {
+ panchor = &a->next;
+ }
+ }
+}
+
static CURLcode cf_ip_ballers_run(struct cf_ip_ballers *bs,
struct Curl_cfilter *cf,
struct Curl_easy *data,
struct cf_ip_attempt *a = NULL, **panchor;
bool do_more;
timediff_t next_expire_ms;
- int inconclusive, ongoing;
+ uint32_t inconclusive, ongoing;
VERBOSE(int i);
if(bs->winner)
if(ai) { /* try another address */
struct Curl_sockaddr_ex addr;
+
+ /* Discard oldest to make room for new attempt */
+ if(bs->max_concurrent)
+ cf_ip_ballers_prune(bs, cf, data, bs->max_concurrent - 1);
+
result = Curl_socket_addr_from_ai(&addr, ai, bs->transport);
if(result)
goto out;
*done = TRUE;
for(a = bs->running; a; a = a->next) {
bool bdone = FALSE;
- if(a->shutdown)
+ if(a->shutdown || !a->cf)
continue;
a->result = a->cf->cft->do_shutdown(a->cf, data, &bdone);
if(a->result || bdone)
for(a = bs->running; a; a = a->next) {
if(a->result)
continue;
- if(a->cf->cft->has_data_pending(a->cf, data))
+ if(a->cf && a->cf->cft->has_data_pending(a->cf, data))
return TRUE;
}
return FALSE;
memset(&tmax, 0, sizeof(tmax));
for(a = bs->running; a; a = a->next) {
memset(&t, 0, sizeof(t));
- if(!a->cf->cft->query(a->cf, data, query, NULL, &t)) {
+ if(a->cf && !a->cf->cft->query(a->cf, data, query, NULL, &t)) {
if((t.tv_sec || t.tv_usec) && curlx_ptimediff_us(&t, &tmax) > 0)
tmax = t;
}
struct cf_ip_attempt *a;
for(a = bs->running; a; a = a->next) {
- if(!a->cf->cft->query(a->cf, data, CF_QUERY_CONNECT_REPLY_MS,
- &breply_ms, NULL)) {
+ if(a->cf && !a->cf->cft->query(a->cf, data, CF_QUERY_CONNECT_REPLY_MS,
+ &breply_ms, NULL)) {
if(breply_ms >= 0 && (reply_ms < 0 || breply_ms < reply_ms))
reply_ms = breply_ms;
}
return result;
}
+#define IP_HE_MAX_CONCURRENT_ATTEMPTS 6
+
static CURLcode cf_ip_happy_init(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
ctx->started = *Curl_pgrs_now(data);
return cf_ip_ballers_init(&ctx->ballers, cf->conn->ip_version, cf,
ctx->cf_create, ctx->transport,
- data->set.happy_eyeballs_timeout);
+ data->set.happy_eyeballs_timeout,
+ IP_HE_MAX_CONCURRENT_ATTEMPTS);
}
static void cf_ip_happy_ctx_clear(struct Curl_cfilter *cf,
timediff_t max_duration_ms;
CURLcode result_exp;
const char *pref_family;
+ uint32_t max_concurrent;
};
struct ai_family_stats {
struct curltime ended;
struct ai_family_stats cf4;
struct ai_family_stats cf6;
+ uint32_t max_concurrent;
+ uint32_t ongoing;
};
static const struct test_case *current_tc;
static void cf_test_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct cf_test_ctx *ctx = cf->ctx;
- infof(data, "%04dms: cf[%s] destroyed",
- (int)curlx_timediff_ms(curlx_now(), current_tr->started), ctx->id);
+ current_tr->ongoing--;
+ infof(data, "%04dms: cf[%s] destroyed, now %u ongoing",
+ (int)curlx_timediff_ms(curlx_now(), current_tr->started),
+ ctx->id, current_tr->ongoing);
curlx_free(ctx);
cf->ctx = NULL;
}
ctx->ai_family = addr->family;
ctx->transport = transport;
ctx->started = curlx_now();
+ current_tr->ongoing++;
+ if(current_tr->ongoing > current_tr->max_concurrent)
+ current_tr->max_concurrent = current_tr->ongoing;
#ifdef USE_IPV6
if(ctx->ai_family == AF_INET6) {
ctx->stats = ¤t_tr->cf6;
if(ctx->stats->creations == 1)
ctx->stats->first_created = created_at;
ctx->stats->last_created = created_at;
- infof(data, "%04dms: cf[%s] created", (int)created_at, ctx->id);
+ infof(data, "%04dms: cf[%s] created, now %u ongoing",
+ (int)created_at, ctx->id, current_tr->ongoing);
result = Curl_cf_create(&cf, &cft_test, ctx);
if(result)
fail(msg);
}
}
+ if(tr->max_concurrent != tc->max_concurrent) {
+ curl_msprintf(msg, "%d: expected max %u ongoing, but reported %u",
+ tc->id, tc->max_concurrent, tr->max_concurrent);
+ fail(msg);
+ }
}
static void test_connect(CURL *easy, const struct test_case *tc)
static const struct test_case TEST_CASES[] = {
/* TIMEOUT_MS, FAIL_MS CREATED DURATION Result, HE_PREF */
- /* CNCT HE v4 v6 v4 v6 MIN MAX */
+ /* CNCT HE v4 v6 v4 v6 MIN MAX MAX_CONCURRENT */
{ 1, TURL, "test.com:123:192.0.2.1", CURL_IPRESOLVE_WHATEVER,
- CNCT_TMOT, 150, 250, 250, 1, 0, 200, TC_TMOT, R_FAIL, NULL },
+ CNCT_TMOT, 150, 250, 250, 1, 0, 200, TC_TMOT, R_FAIL, NULL,
+ 1 },
/* 1 ipv4, fails after ~200ms, reports COULDNT_CONNECT */
{ 2, TURL, "test.com:123:192.0.2.1,192.0.2.2", CURL_IPRESOLVE_WHATEVER,
- CNCT_TMOT, 150, 250, 250, 2, 0, 400, TC_TMOT, R_FAIL, NULL },
+ CNCT_TMOT, 150, 250, 250, 2, 0, 400, TC_TMOT, R_FAIL, NULL,
+ 2 },
/* 2 ipv4, fails after ~400ms, reports COULDNT_CONNECT */
#ifdef USE_IPV6
{ 3, TURL, "test.com:123:::1", CURL_IPRESOLVE_WHATEVER,
- CNCT_TMOT, 150, 250, 250, 0, 1, 200, TC_TMOT, R_FAIL, NULL },
+ CNCT_TMOT, 150, 250, 250, 0, 1, 200, TC_TMOT, R_FAIL, NULL,
+ 1 },
/* 1 ipv6, fails after ~200ms, reports COULDNT_CONNECT */
{ 4, TURL, "test.com:123:::1,::2", CURL_IPRESOLVE_WHATEVER,
- CNCT_TMOT, 150, 250, 250, 0, 2, 400, TC_TMOT, R_FAIL, NULL },
+ CNCT_TMOT, 150, 250, 250, 0, 2, 400, TC_TMOT, R_FAIL, NULL,
+ 2 },
/* 2 ipv6, fails after ~400ms, reports COULDNT_CONNECT */
{ 5, TURL, "test.com:123:192.0.2.1,::1", CURL_IPRESOLVE_WHATEVER,
- CNCT_TMOT, 150, 250, 250, 1, 1, 350, TC_TMOT, R_FAIL, "v6" },
+ CNCT_TMOT, 150, 250, 250, 1, 1, 350, TC_TMOT, R_FAIL, "v6",
+ 2 },
/* mixed ip4+6, v6 always first, v4 kicks in on HE, fails after ~350ms */
{ 6, TURL, "test.com:123:::1,192.0.2.1", CURL_IPRESOLVE_WHATEVER,
- CNCT_TMOT, 150, 250, 250, 1, 1, 350, TC_TMOT, R_FAIL, "v6" },
+ CNCT_TMOT, 150, 250, 250, 1, 1, 350, TC_TMOT, R_FAIL, "v6",
+ 2 },
/* mixed ip6+4, v6 starts, v4 never starts due to high HE, TIMEOUT */
{ 7, TURL, "test.com:123:192.0.2.1,::1", CURL_IPRESOLVE_V4,
- CNCT_TMOT, 150, 500, 500, 1, 0, 400, TC_TMOT, R_FAIL, NULL },
+ CNCT_TMOT, 150, 500, 500, 1, 0, 400, TC_TMOT, R_FAIL, NULL,
+ 1 },
/* mixed ip4+6, but only use v4, check it uses full connect timeout,
although another address of the 'wrong' family is available */
{ 8, TURL, "test.com:123:::1,192.0.2.1", CURL_IPRESOLVE_V6,
- CNCT_TMOT, 150, 500, 500, 0, 1, 400, TC_TMOT, R_FAIL, NULL },
+ CNCT_TMOT, 150, 500, 500, 0, 1, 400, TC_TMOT, R_FAIL, NULL,
+ 1 },
/* mixed ip4+6, but only use v6, check it uses full connect timeout,
although another address of the 'wrong' family is available */
{ 9, TURL, "test.com:123:::1,192.0.2.1,::2,::3", CURL_IPRESOLVE_WHATEVER,
- CNCT_TMOT, 50, 400, 400, 1, 3, 550, TC_TMOT, R_FAIL, NULL },
+ CNCT_TMOT, 50, 400, 400, 1, 3, 550, TC_TMOT, R_FAIL, NULL,
+ 4 },
/* 1 v4, 3 v6, fails after (3*HE)+400ms, ~550ms, COULDNT_CONNECT */
+ { 10, TURL, "test.com:123:::1,192.0.2.1,::2,::3,::4,::5,::6,::7,::8",
+ CURL_IPRESOLVE_WHATEVER,
+ CNCT_TMOT, 20, 500, 500, 1, 8, 550, TC_TMOT, R_FAIL, NULL,
+ 6 },
+ /* 1 v4, 8 v6, MUST meet limit of 6 concurrent attempts */
#endif
};