mirror of
https://github.com/curl/curl.git
synced 2026-08-25 12:43:37 +03:00
multi: timeout improvements
- Move expire timeout code from multi into splay.c - keep a "time_base" timestamp to calculate timediff_t for actual timeout values. Unfortunately this means our timeouts will go wrong after ~500,000 years of continuous operations... - use timediff_t as key in splay instead of curltime - use timediff_t in transfers expire times instead of curltime - re-comment splay.c for better understanding how it works - replace splay nodes double-linked "same" list with a single link, we almost never have duplicate keys - keep transfer `mid` in splay nodes instead of the transfer pointer - keep registered bit in splay node for tracking instead of separate bit in transfer - adapt unit1309.c to changes in timediff_t and mid Closes #22584
This commit is contained in:
parent
d854ab4673
commit
3d6d93a6be
19 changed files with 600 additions and 458 deletions
|
|
@ -902,7 +902,7 @@ static CURLcode cf_ip_happy_connect(struct Curl_cfilter *cf,
|
|||
}
|
||||
#endif
|
||||
cf_ip_happy_ctx_clear(ctx, data);
|
||||
Curl_expire_done(data, EXPIRE_HAPPY_EYEBALLS);
|
||||
Curl_expire_clear(data, EXPIRE_HAPPY_EYEBALLS);
|
||||
/* whatever errors were reported by ballers, clear our errorbuf */
|
||||
Curl_reset_fail(data);
|
||||
data->info.numconnects++; /* to track the # of connections made */
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ void Curl_shutdown_start(struct Curl_easy *data, int sockindex,
|
|||
data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS);
|
||||
/* Set a timer, unless we operate on the admin handle */
|
||||
if(data->mid)
|
||||
Curl_expire_ex(data, conn->shutdown.timeout_ms, EXPIRE_SHUTDOWN);
|
||||
Curl_expire(data, conn->shutdown.timeout_ms, EXPIRE_SHUTDOWN);
|
||||
CURL_TRC_M(data, "shutdown start on%s connection",
|
||||
sockindex ? " secondary" : "");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ static void cshutdn_perform(struct cshutdn *cshutdn,
|
|||
}
|
||||
|
||||
if(next_expire_ms)
|
||||
Curl_expire_ex(admin, next_expire_ms, EXPIRE_SHUTDOWN);
|
||||
Curl_expire(admin, next_expire_ms, EXPIRE_SHUTDOWN);
|
||||
}
|
||||
|
||||
static void cshutdn_terminate_all(struct cshutdn *cshutdn,
|
||||
|
|
|
|||
|
|
@ -339,14 +339,15 @@ void Curl_trc_timer(struct Curl_easy *data, int tid, const char *fmt, ...)
|
|||
|
||||
void Curl_trc_easy_timers(struct Curl_easy *data)
|
||||
{
|
||||
if(CURL_TRC_TIMER_is_verbose(data)) {
|
||||
if(CURL_TRC_TIMER_is_verbose(data) && data->multi) {
|
||||
if(data->state.timeouts.first < EXPIRE_LAST) {
|
||||
struct expire_timers *timeouts = &data->state.timeouts;
|
||||
const struct curltime *pnow = Curl_pgrs_now(data);
|
||||
timediff_t base_us =
|
||||
Curl_timeouts_offset_us(&data->multi->timeouts, Curl_pgrs_now(data));
|
||||
expire_id eid = data->state.timeouts.first;
|
||||
for(; eid < EXPIRE_LAST; eid = timeouts->next[eid]) {
|
||||
CURL_TRC_TIMER(data, eid, "expires in %" FMT_TIMEDIFF_T "us",
|
||||
curlx_ptimediff_us(&timeouts->time[eid], pnow));
|
||||
timeouts->offset_us[eid] - base_us);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -216,6 +216,11 @@ timediff_t curlx_timediff_ceil_ms(struct curltime newer,
|
|||
return (diff * 1000) + ((newer.tv_usec - older.tv_usec + 999) / 1000);
|
||||
}
|
||||
|
||||
timediff_t curlx_us_to_ceil_ms(timediff_t us)
|
||||
{
|
||||
return (us / 1000) + ((us > 0) && (us % 1000));
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns: time difference in number of microseconds. For too large diffs it
|
||||
* returns max value.
|
||||
|
|
|
|||
|
|
@ -59,6 +59,9 @@ timediff_t curlx_ptimediff_ms(const struct curltime *newer,
|
|||
timediff_t curlx_timediff_ceil_ms(struct curltime newer,
|
||||
struct curltime older);
|
||||
|
||||
/* Returns milliseconds from microseconds, rounded up. */
|
||||
timediff_t curlx_us_to_ceil_ms(timediff_t us);
|
||||
|
||||
/*
|
||||
* Make sure that the first argument (newer) is the more recent time and older
|
||||
* is the older time, as otherwise you get a weird negative time-diff back...
|
||||
|
|
|
|||
|
|
@ -1486,7 +1486,7 @@ static void http_exp100_continue(struct Curl_easy *data,
|
|||
struct cr_exp100_ctx *ctx = reader->ctx;
|
||||
if(ctx->state > EXP100_SEND_DATA) {
|
||||
ctx->state = EXP100_SEND_DATA;
|
||||
Curl_expire_done(data, EXPIRE_100_TIMEOUT);
|
||||
Curl_expire_clear(data, EXPIRE_100_TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1546,7 +1546,7 @@ static void cr_exp100_done(struct Curl_easy *data,
|
|||
{
|
||||
struct cr_exp100_ctx *ctx = reader->ctx;
|
||||
ctx->state = premature ? EXP100_FAILED : EXP100_SEND_DATA;
|
||||
Curl_expire_done(data, EXPIRE_100_TIMEOUT);
|
||||
Curl_expire_clear(data, EXPIRE_100_TIMEOUT);
|
||||
}
|
||||
|
||||
static const struct Curl_crtype cr_exp100 = {
|
||||
|
|
|
|||
270
lib/multi.c
270
lib/multi.c
|
|
@ -76,7 +76,7 @@ static CURLMcode add_next_timeout(const struct curltime *pnow,
|
|||
struct Curl_multi *multi,
|
||||
struct Curl_easy *data);
|
||||
static void multi_timeout(struct Curl_multi *multi,
|
||||
struct curltime *expire_time,
|
||||
timediff_t *pexire_offset_us,
|
||||
int *timeout_ms);
|
||||
static void multi_schedule_pending(struct Curl_multi *multi);
|
||||
static void multi_xfer_bufs_free(struct Curl_multi *multi);
|
||||
|
|
@ -143,7 +143,7 @@ static void mstate_enter_completed(struct Curl_easy *data,
|
|||
/* Important: reset the conn pointer so that we do not point to memory
|
||||
that could be freed anytime */
|
||||
Curl_detach_connection(data);
|
||||
Curl_expire_clear(data); /* stop all timers */
|
||||
Curl_expire_clear_all(data); /* stop all timers */
|
||||
}
|
||||
|
||||
/* always use this function to change state, to make debugging easier */
|
||||
|
|
@ -235,6 +235,8 @@ struct Curl_multi *Curl_multi_handle(uint32_t xfer_table_size,
|
|||
|
||||
multi->magic = CURLMULTI_MAGIC_NUMBER;
|
||||
|
||||
curlx_pnow(&multi->now);
|
||||
Curl_timeouts_init(&multi->timeouts, &multi->now);
|
||||
Curl_dnscache_init(&multi->dnscache, dnssize);
|
||||
Curl_mntfy_init(multi);
|
||||
Curl_multi_ev_init(multi, ev_hashsize);
|
||||
|
|
@ -800,7 +802,7 @@ CURLMcode Curl_multi_remove_handle(struct Curl_multi *multi,
|
|||
/* The timer must be shut down before data->multi is set to NULL, else
|
||||
data's splaynode would remain in the splay tree after curl_easy_cleanup is
|
||||
called. Do it after multi_done() in case that sets another time! */
|
||||
Curl_expire_clear(data);
|
||||
Curl_expire_clear_all(data);
|
||||
|
||||
/* If in `msgsent`, it was deducted from `multi->xfers_alive` already. */
|
||||
if(!Curl_uint32_bset_contains(&multi->msgsent, data->mid))
|
||||
|
|
@ -1542,7 +1544,6 @@ static CURLMcode multi_wait(struct Curl_multi *multi,
|
|||
bool extrawait) /* when no socket, wait */
|
||||
{
|
||||
size_t i;
|
||||
struct curltime expire_time;
|
||||
int timeout_internal;
|
||||
int nevents = 0;
|
||||
struct easy_pollset ps;
|
||||
|
|
@ -1621,7 +1622,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi,
|
|||
* Use the shorter one of the internal and the caller requested timeout.
|
||||
* If we are called with `!extrawait` and multi_timeout() reports no
|
||||
* timeouts exist, do not wait. */
|
||||
multi_timeout(multi, &expire_time, &timeout_internal);
|
||||
multi_timeout(multi, NULL, &timeout_internal);
|
||||
if((timeout_internal >= 0) && (timeout_internal < timeout_ms))
|
||||
timeout_ms = timeout_internal;
|
||||
|
||||
|
|
@ -2929,25 +2930,22 @@ static CURLMcode multi_perform(struct Curl_multi *multi,
|
|||
* then and then we risk this loop to remove timers that actually have not
|
||||
* been handled!
|
||||
*/
|
||||
if(multi->timetree) {
|
||||
struct Curl_tree *t = NULL;
|
||||
do {
|
||||
multi->timetree = Curl_splaygetbest(&start, multi->timetree, &t);
|
||||
if(t) {
|
||||
/* the removed may have another timeout in queue */
|
||||
struct Curl_easy *data = Curl_splayget(t);
|
||||
data->state.timeouts.registered = FALSE;
|
||||
(void)add_next_timeout(&start, multi, data);
|
||||
if(data->mstate == MSTATE_PENDING) {
|
||||
bool stream_unused;
|
||||
CURLcode result_unused;
|
||||
if(multi_handle_timeout(data, &stream_unused, &result_unused)) {
|
||||
infof(data, "PENDING handle timeout");
|
||||
move_pending_to_connect(multi, data);
|
||||
}
|
||||
}
|
||||
while(Curl_timeouts_remove_expired(&multi->timeouts, &start, &mid)) {
|
||||
/* the removed may have another timeout in queue */
|
||||
struct Curl_easy *data = Curl_multi_get_easy(multi, mid);
|
||||
if(!data) {
|
||||
DEBUGASSERT(0);
|
||||
continue;
|
||||
}
|
||||
(void)add_next_timeout(&start, multi, data);
|
||||
if(data->mstate == MSTATE_PENDING) {
|
||||
bool stream_unused;
|
||||
CURLcode result_unused;
|
||||
if(multi_handle_timeout(data, &stream_unused, &result_unused)) {
|
||||
infof(data, "PENDING handle timeout");
|
||||
move_pending_to_connect(multi, data);
|
||||
}
|
||||
} while(t);
|
||||
}
|
||||
}
|
||||
|
||||
if(running_handles) {
|
||||
|
|
@ -3130,12 +3128,10 @@ void Curl_multi_will_close(struct Curl_easy *data, curl_socket_t s)
|
|||
static void multi_timeouts_init(struct Curl_easy *data)
|
||||
{
|
||||
data->state.timeouts.first = EXPIRE_LAST;
|
||||
data->state.timeouts.registered = FALSE;
|
||||
data->state.timeouts.splaynode.registered = FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* add_next_timeout()
|
||||
*
|
||||
* Each Curl_easy has a list of timeouts. The add_next_timeout() is called
|
||||
* when it has been removed from the splay tree because the timeout has
|
||||
* expired. This function is then to advance in the list to pick the next
|
||||
|
|
@ -3150,11 +3146,10 @@ static CURLMcode add_next_timeout(const struct curltime *pnow,
|
|||
struct Curl_easy *data)
|
||||
{
|
||||
struct expire_timers *timeouts = &data->state.timeouts;
|
||||
timediff_t now_us = Curl_timeouts_offset_us(&multi->timeouts, pnow);
|
||||
|
||||
DEBUGASSERT(!timeouts->registered);
|
||||
while(timeouts->first < EXPIRE_LAST) {
|
||||
timediff_t us = curlx_ptimediff_us(&timeouts->time[timeouts->first], pnow);
|
||||
if(us <= 0) /* remove already expired timer */
|
||||
if(timeouts->offset_us[timeouts->first] <= now_us) /* already expired */
|
||||
timeouts->first = timeouts->next[timeouts->first];
|
||||
else /* timeouts are sorted, first is first in the future now */
|
||||
break;
|
||||
|
|
@ -3163,10 +3158,8 @@ static CURLMcode add_next_timeout(const struct curltime *pnow,
|
|||
if(timeouts->first < EXPIRE_LAST) {
|
||||
/* Insert this node again into the splay. Keep the timer in the list in
|
||||
case we need to recompute future timers. */
|
||||
Curl_splayset(&timeouts->splaynode, data);
|
||||
multi->timetree = Curl_splayinsert(&timeouts->time[timeouts->first],
|
||||
multi->timetree, &timeouts->splaynode);
|
||||
timeouts->registered = TRUE;
|
||||
Curl_timeouts_add(&multi->timeouts, data,
|
||||
timeouts->offset_us[timeouts->first]);
|
||||
}
|
||||
return CURLM_OK;
|
||||
}
|
||||
|
|
@ -3175,24 +3168,21 @@ static void multi_mark_expired_as_dirty(struct Curl_multi *multi,
|
|||
const struct curltime *ts)
|
||||
{
|
||||
struct Curl_easy *data = NULL;
|
||||
struct Curl_tree *t = NULL;
|
||||
uint32_t mid;
|
||||
|
||||
/*
|
||||
* The loop following here will go on as long as there are expire-times left
|
||||
* to process (compared to `ts`) in the splay and 'data' will be
|
||||
* re-assigned for every expired handle we deal with.
|
||||
*/
|
||||
while(1) {
|
||||
while(Curl_timeouts_remove_expired(&multi->timeouts, ts, &mid)) {
|
||||
/* Check if there is one (more) expired timer to deal with! This function
|
||||
extracts a matching node if there is one */
|
||||
multi->timetree = Curl_splaygetbest(ts, multi->timetree, &t);
|
||||
if(!t)
|
||||
return;
|
||||
|
||||
data = Curl_splayget(t); /* assign this for next loop */
|
||||
if(!data)
|
||||
data = Curl_multi_get_easy(multi, mid);
|
||||
if(!data) {
|
||||
DEBUGASSERT(0);
|
||||
continue;
|
||||
data->state.timeouts.registered = FALSE;
|
||||
}
|
||||
#ifdef CURLVERBOSE
|
||||
if(CURL_TRC_TIMER_is_verbose(data)) {
|
||||
if(data->state.timeouts.first < EXPIRE_LAST) {
|
||||
|
|
@ -3281,7 +3271,7 @@ static CURLMcode multi_socket(struct Curl_multi *multi,
|
|||
if the same timeout is still the one to run after this call. That
|
||||
handles the case when the application asks libcurl to run the timeout
|
||||
prematurely. */
|
||||
memset(&multi->last_expire_ts, 0, sizeof(multi->last_expire_ts));
|
||||
multi->last_expire_offset_us = 0;
|
||||
|
||||
/* Applications may set `socket_cb` *after* having added transfers
|
||||
* first. *Then* kick off processing with a
|
||||
|
|
@ -3513,62 +3503,40 @@ static bool multi_has_dirties(struct Curl_multi *multi)
|
|||
}
|
||||
|
||||
static void multi_timeout(struct Curl_multi *multi,
|
||||
struct curltime *expire_time,
|
||||
timediff_t *pexire_offset_us,
|
||||
int *timeout_ms)
|
||||
{
|
||||
static const struct curltime tv_zero = { 0, 0 };
|
||||
VERBOSE(struct Curl_easy *data = NULL);
|
||||
|
||||
if(multi->dead) {
|
||||
*expire_time = tv_zero;
|
||||
if(pexire_offset_us)
|
||||
*pexire_offset_us = 0;
|
||||
*timeout_ms = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if(multi_has_dirties(multi)) {
|
||||
*expire_time = *multi_now(multi);
|
||||
if(pexire_offset_us)
|
||||
*pexire_offset_us = Curl_timeouts_offset_us(&multi->timeouts,
|
||||
multi_now(multi));
|
||||
*timeout_ms = 0;
|
||||
return;
|
||||
}
|
||||
else if(multi->timetree) {
|
||||
const struct curltime *pnow = multi_now(multi);
|
||||
/* splay the lowest to the bottom */
|
||||
multi->timetree = Curl_splay(&tv_zero, multi->timetree);
|
||||
/* this will not return NULL from a non-empty tree, but some compilers
|
||||
* are not convinced of that. Analyzers are hard. */
|
||||
*expire_time = multi->timetree ? multi->timetree->key : tv_zero;
|
||||
|
||||
/* 'multi->timetree' will be non-NULL here but the compilers sometimes
|
||||
yell at us if we assume so */
|
||||
if(multi->timetree &&
|
||||
curlx_ptimediff_us(&multi->timetree->key, pnow) > 0) {
|
||||
/* some time left before expiration */
|
||||
timediff_t diff_ms =
|
||||
curlx_timediff_ceil_ms(multi->timetree->key, *pnow);
|
||||
VERBOSE(data = Curl_splayget(multi->timetree));
|
||||
if(diff_ms > INT_MAX)
|
||||
diff_ms = INT_MAX;
|
||||
*timeout_ms = (int)diff_ms;
|
||||
}
|
||||
else {
|
||||
if(multi->timetree)
|
||||
VERBOSE(data = Curl_splayget(multi->timetree));
|
||||
/* 0 means immediately */
|
||||
*timeout_ms = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
*expire_time = tv_zero;
|
||||
*timeout_ms = -1;
|
||||
}
|
||||
const struct curltime *pnow = multi_now(multi);
|
||||
uint32_t mid;
|
||||
|
||||
*timeout_ms = Curl_timeouts_next_ms(&multi->timeouts, pnow,
|
||||
pexire_offset_us, &mid);
|
||||
#ifdef CURLVERBOSE
|
||||
if(CURL_TRC_TIMER_is_verbose(data) &&
|
||||
(data->state.timeouts.first < EXPIRE_LAST)) {
|
||||
CURL_TRC_TIMER(data, data->state.timeouts.first,
|
||||
"gives multi timeout in %dms", *timeout_ms);
|
||||
}
|
||||
if(mid != UINT32_MAX) {
|
||||
struct Curl_easy *data = Curl_multi_get_easy(multi, mid);
|
||||
if(data && CURL_TRC_TIMER_is_verbose(data) &&
|
||||
(data->state.timeouts.first < EXPIRE_LAST)) {
|
||||
CURL_TRC_TIMER(data, data->state.timeouts.first,
|
||||
"gives multi timeout in %dms", *timeout_ms);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
CURLMcode curl_multi_timeout(CURLM *m,
|
||||
|
|
@ -3578,10 +3546,9 @@ CURLMcode curl_multi_timeout(CURLM *m,
|
|||
CURLMcode mresult;
|
||||
|
||||
if(CURL_MAPI_ENTER(&guard, m, multi_timeout, &mresult)) {
|
||||
struct curltime expire_time;
|
||||
int itimeout_ms;
|
||||
|
||||
multi_timeout(m, &expire_time, &itimeout_ms);
|
||||
multi_timeout(m, NULL, &itimeout_ms);
|
||||
*timeout_ms = (long)itimeout_ms;
|
||||
mresult = CURLM_OK;
|
||||
}
|
||||
|
|
@ -3595,14 +3562,14 @@ CURLMcode curl_multi_timeout(CURLM *m,
|
|||
*/
|
||||
CURLMcode Curl_update_timer(struct Curl_multi *multi)
|
||||
{
|
||||
struct curltime expire_ts = { 0, 0 };
|
||||
timediff_t timeouts_offset_us = 0;
|
||||
int timeout_ms;
|
||||
int rc;
|
||||
bool set_value = FALSE;
|
||||
|
||||
if(!multi->timer_cb || multi->dead)
|
||||
return CURLM_OK;
|
||||
multi_timeout(multi, &expire_ts, &timeout_ms);
|
||||
multi_timeout(multi, &timeouts_offset_us, &timeout_ms);
|
||||
|
||||
if(timeout_ms < 0 && multi->last_timeout_ms < 0) {
|
||||
/* nothing to do */
|
||||
|
|
@ -3617,7 +3584,7 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi)
|
|||
CURL_TRC_M(multi->admin, "[TIMER] set %dms, none before", timeout_ms);
|
||||
set_value = TRUE;
|
||||
}
|
||||
else if(curlx_ptimediff_us(&multi->last_expire_ts, &expire_ts)) {
|
||||
else if(multi->last_expire_offset_us != timeouts_offset_us) {
|
||||
/* We had a timeout before and have one now, the absolute timestamp
|
||||
* differs. The relative timeout_ms may be the same, but the starting
|
||||
* point differs. Let the application restart its timer. */
|
||||
|
|
@ -3634,7 +3601,7 @@ CURLMcode Curl_update_timer(struct Curl_multi *multi)
|
|||
if(set_value) {
|
||||
struct Curl_mapi_guard guard;
|
||||
|
||||
multi->last_expire_ts = expire_ts;
|
||||
multi->last_expire_offset_us = timeouts_offset_us;
|
||||
multi->last_timeout_ms = timeout_ms;
|
||||
CURL_CBAPI_MULTI_START(&guard, multi, multi_timer_cb);
|
||||
rc = multi->timer_cb(multi, timeout_ms, multi->timer_userp);
|
||||
|
|
@ -3663,8 +3630,7 @@ static bool multi_timeouts_check(struct Curl_easy *data)
|
|||
return FALSE;
|
||||
}
|
||||
if((timeouts->next[eid] < EXPIRE_LAST) &&
|
||||
(curlx_ptimediff_ms(&timeouts->time[eid],
|
||||
&timeouts->time[timeouts->next[eid]]) > 0)) {
|
||||
(timeouts->offset_us[eid] > timeouts->offset_us[timeouts->next[eid]])) {
|
||||
failf(data, "expire timeouts not sorted: %d happens after %d but "
|
||||
"is listed before", (int)eid, (int)timeouts->next[eid]);
|
||||
return FALSE;
|
||||
|
|
@ -3675,11 +3641,9 @@ static bool multi_timeouts_check(struct Curl_easy *data)
|
|||
#endif
|
||||
|
||||
/*
|
||||
* multi_deltimeout()
|
||||
*
|
||||
* Remove a given timestamp from the list of timeouts.
|
||||
*/
|
||||
static void multi_deltimeout(struct Curl_easy *data, expire_id eid)
|
||||
static void multi_clear_timeout(struct Curl_easy *data, expire_id eid)
|
||||
{
|
||||
struct expire_timers *timeouts = &data->state.timeouts;
|
||||
expire_id orig_first = timeouts->first;
|
||||
|
|
@ -3693,9 +3657,8 @@ static void multi_deltimeout(struct Curl_easy *data, expire_id eid)
|
|||
anchor = &timeouts->next[*anchor];
|
||||
}
|
||||
DEBUGASSERT(multi_timeouts_check(data));
|
||||
if(timeouts->registered) {
|
||||
if(Curl_timeouts_has(data)) {
|
||||
struct Curl_multi *multi = data->multi;
|
||||
int rc;
|
||||
|
||||
if(!multi) {
|
||||
DEBUGASSERT(0);
|
||||
|
|
@ -3703,31 +3666,22 @@ static void multi_deltimeout(struct Curl_easy *data, expire_id eid)
|
|||
}
|
||||
if((timeouts->first >= EXPIRE_LAST) || /* no more timeouts */
|
||||
(timeouts->first != orig_first)) { /* active timeout changed */
|
||||
rc = Curl_splayremove(multi->timetree, &timeouts->splaynode,
|
||||
&multi->timetree);
|
||||
if(rc)
|
||||
infof(data, "Internal error removing splay node = %d", rc);
|
||||
timeouts->registered = FALSE;
|
||||
Curl_timeouts_remove(&multi->timeouts, data);
|
||||
}
|
||||
if((timeouts->first < EXPIRE_LAST) && !timeouts->registered) {
|
||||
multi->timetree = Curl_splayinsert(&timeouts->time[timeouts->first],
|
||||
multi->timetree,
|
||||
&timeouts->splaynode);
|
||||
timeouts->registered = TRUE;
|
||||
if((timeouts->first < EXPIRE_LAST) && !Curl_timeouts_has(data)) {
|
||||
Curl_timeouts_add(&multi->timeouts, data,
|
||||
timeouts->offset_us[timeouts->first]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* multi_addtimeout()
|
||||
*
|
||||
* Add a timestamp to the list of timeouts. Keep the list sorted so that head
|
||||
* of list is always the timeout nearest in time.
|
||||
*
|
||||
*/
|
||||
static CURLMcode multi_addtimeout(struct Curl_easy *data,
|
||||
struct curltime *stamp,
|
||||
expire_id eid)
|
||||
static CURLMcode multi_set_timeout(struct Curl_easy *data,
|
||||
const struct curltime *stamp,
|
||||
expire_id eid)
|
||||
{
|
||||
struct expire_timers *timeouts = &data->state.timeouts;
|
||||
expire_id *anchor = &timeouts->first;
|
||||
|
|
@ -3737,11 +3691,12 @@ static CURLMcode multi_addtimeout(struct Curl_easy *data,
|
|||
return CURLM_BAD_FUNCTION_ARGUMENT;
|
||||
}
|
||||
/* remove from list, store time and re-insert */
|
||||
multi_deltimeout(data, eid);
|
||||
memcpy(&timeouts->time[eid], stamp, sizeof(*stamp));
|
||||
multi_clear_timeout(data, eid);
|
||||
timeouts->offset_us[eid] =
|
||||
Curl_timeouts_offset_us(&data->multi->timeouts, stamp);
|
||||
|
||||
while(*anchor < EXPIRE_LAST) {
|
||||
timediff_t ms = curlx_ptimediff_ms(&timeouts->time[*anchor], stamp);
|
||||
if(ms > 0) /* *anchor's time is after eid's time */
|
||||
if(timeouts->offset_us[*anchor] > timeouts->offset_us[eid])
|
||||
break;
|
||||
anchor = &timeouts->next[*anchor];
|
||||
}
|
||||
|
|
@ -3754,8 +3709,17 @@ static CURLMcode multi_addtimeout(struct Curl_easy *data,
|
|||
return CURLM_OK;
|
||||
}
|
||||
|
||||
void Curl_expire_ex(struct Curl_easy *data,
|
||||
timediff_t milli, expire_id eid)
|
||||
/*
|
||||
* given a number of milliseconds from now to use to set the 'act before
|
||||
* this'-time for the transfer, to be extracted by curl_multi_timeout()
|
||||
*
|
||||
* The timeout will be added to a queue of timeouts if it defines a moment in
|
||||
* time that is later than the current head of queue.
|
||||
*
|
||||
* Expire replaces a former timeout using the same id if already set.
|
||||
*/
|
||||
void Curl_expire(struct Curl_easy *data,
|
||||
timediff_t milli, expire_id eid)
|
||||
{
|
||||
struct Curl_multi *multi = data->multi;
|
||||
struct expire_timers *timeouts = &data->state.timeouts;
|
||||
|
|
@ -3775,96 +3739,58 @@ void Curl_expire_ex(struct Curl_easy *data,
|
|||
set = *Curl_pgrs_now(data);
|
||||
set.tv_sec += (time_t)(milli / 1000); /* may be a 64 to 32-bit conversion */
|
||||
set.tv_usec += (int)(milli % 1000) * 1000;
|
||||
|
||||
if(set.tv_usec >= 1000000) {
|
||||
set.tv_sec++;
|
||||
set.tv_usec -= 1000000;
|
||||
}
|
||||
|
||||
/* Add the timeout, will replace any previous value for this timer. */
|
||||
multi_addtimeout(data, &set, eid);
|
||||
multi_set_timeout(data, &set, eid);
|
||||
DEBUGASSERT(timeouts->first < EXPIRE_LAST);
|
||||
|
||||
if(timeouts->registered) {
|
||||
int rc;
|
||||
/* timeouts->splaynode is in splay tree already. If the first timer
|
||||
if(Curl_timeouts_has(data)) {
|
||||
/* data has already a timeout registered. If the first timer
|
||||
* was NOT the one we just set AND is still the first one,
|
||||
* nothing changed from the splay tree's point of view. The
|
||||
* set timer triggers after the one already in the tree. Leave. */
|
||||
* nothing changed from the timeouts point of view. The
|
||||
* set timer triggers after the one already registered. Leave. */
|
||||
if((prev_id != eid) && (prev_id == timeouts->first))
|
||||
return;
|
||||
|
||||
/* Since this is an updated time, we must remove the previous entry from
|
||||
the splay tree first and then re-add the new value */
|
||||
rc = Curl_splayremove(multi->timetree, &timeouts->splaynode,
|
||||
&multi->timetree);
|
||||
if(rc)
|
||||
infof(data, "Internal error removing splay node = %d", rc);
|
||||
/* Since this is an updated time, we must remove data from
|
||||
* timeouts and then add it again. */
|
||||
Curl_timeouts_remove(&multi->timeouts, data);
|
||||
}
|
||||
|
||||
/* Indicate that we are in the splay tree and insert the new timer expiry
|
||||
value since it is our local minimum. */
|
||||
Curl_splayset(&timeouts->splaynode, data);
|
||||
multi->timetree = Curl_splayinsert(&timeouts->time[timeouts->first],
|
||||
multi->timetree, &timeouts->splaynode);
|
||||
timeouts->registered = TRUE;
|
||||
/* Insert the new timer expiry since it is our local minimum. */
|
||||
Curl_timeouts_add(&multi->timeouts, data,
|
||||
timeouts->offset_us[timeouts->first]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Curl_expire()
|
||||
*
|
||||
* given a number of milliseconds from now to use to set the 'act before
|
||||
* this'-time for the transfer, to be extracted by curl_multi_timeout()
|
||||
*
|
||||
* The timeout will be added to a queue of timeouts if it defines a moment in
|
||||
* time that is later than the current head of queue.
|
||||
*
|
||||
* Expire replaces a former timeout using the same id if already set.
|
||||
*/
|
||||
void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id)
|
||||
{
|
||||
Curl_expire_ex(data, milli, id);
|
||||
}
|
||||
|
||||
/*
|
||||
* Curl_expire_done()
|
||||
*
|
||||
* Removes the expire timer. Marks it as done.
|
||||
*
|
||||
*/
|
||||
void Curl_expire_done(struct Curl_easy *data, expire_id id)
|
||||
void Curl_expire_clear(struct Curl_easy *data, expire_id id)
|
||||
{
|
||||
/* remove the timer, if there */
|
||||
multi_deltimeout(data, id);
|
||||
multi_clear_timeout(data, id);
|
||||
CURL_TRC_TIMER(data, id, "cleared");
|
||||
}
|
||||
|
||||
/*
|
||||
* Curl_expire_clear()
|
||||
*
|
||||
* Clear ALL timeout values for this handle.
|
||||
*/
|
||||
void Curl_expire_clear(struct Curl_easy *data)
|
||||
void Curl_expire_clear_all(struct Curl_easy *data)
|
||||
{
|
||||
struct Curl_multi *multi = data->multi;
|
||||
struct expire_timers *timeouts = &data->state.timeouts;
|
||||
|
||||
/* this is only interesting while there is still an associated multi struct
|
||||
remaining! */
|
||||
if(!multi)
|
||||
return;
|
||||
|
||||
if(timeouts->registered) {
|
||||
if(Curl_timeouts_remove(&multi->timeouts, data)) {
|
||||
/* Since this is an cleared time, we must remove the previous entry from
|
||||
the splay tree */
|
||||
int rc;
|
||||
|
||||
rc = Curl_splayremove(multi->timetree, &timeouts->splaynode,
|
||||
&multi->timetree);
|
||||
if(rc)
|
||||
infof(data, "Internal error clearing splay node = %d", rc);
|
||||
|
||||
/* clear the timeouts */
|
||||
multi_timeouts_init(data);
|
||||
|
||||
if(data->id >= 0)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include "multi_ntfy.h"
|
||||
#include "psl.h"
|
||||
#include "socketpair.h"
|
||||
#include "splay.h"
|
||||
#include "uint-bset.h"
|
||||
#include "uint-spbset.h"
|
||||
#include "uint-table.h"
|
||||
|
|
@ -132,9 +133,8 @@ struct Curl_multi {
|
|||
|
||||
/* current time for transfers running in this multi handle */
|
||||
struct curltime now;
|
||||
/* timetree points to the splay-tree of time nodes to figure out expire
|
||||
times of all currently set timers */
|
||||
struct Curl_tree *timetree;
|
||||
/* expiration times for all attached easy handles */
|
||||
struct Curl_timeouts timeouts;
|
||||
|
||||
/* buffer used for transfer data, lazy initialized */
|
||||
char *xfer_buf; /* the actual buffer */
|
||||
|
|
@ -161,7 +161,7 @@ struct Curl_multi {
|
|||
|
||||
struct cshutdn cshutdn; /* connection shutdown handling */
|
||||
struct cpool cpool; /* connection pool (bundles) */
|
||||
struct curltime last_expire_ts; /* timestamp of last expiry */
|
||||
timediff_t last_expire_offset_us; /* times offset of last expiry */
|
||||
|
||||
size_t max_host_connections; /* if >0, a fixed limit of the maximum number
|
||||
of connections per host */
|
||||
|
|
|
|||
|
|
@ -27,11 +27,9 @@
|
|||
* Prototypes for library-wide functions provided by multi.c
|
||||
*/
|
||||
|
||||
void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id);
|
||||
void Curl_expire_ex(struct Curl_easy *data,
|
||||
timediff_t milli, expire_id eid);
|
||||
void Curl_expire_clear(struct Curl_easy *data);
|
||||
void Curl_expire_done(struct Curl_easy *data, expire_id id);
|
||||
void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id eid);
|
||||
void Curl_expire_clear(struct Curl_easy *data, expire_id id);
|
||||
void Curl_expire_clear_all(struct Curl_easy *data);
|
||||
CURLMcode Curl_update_timer(struct Curl_multi *multi) WARN_UNUSED_RESULT;
|
||||
void Curl_attach_connection(struct Curl_easy *data,
|
||||
struct connectdata *conn,
|
||||
|
|
|
|||
392
lib/splay.c
392
lib/splay.c
|
|
@ -23,173 +23,267 @@
|
|||
***************************************************************************/
|
||||
#include "curl_setup.h"
|
||||
|
||||
#include "urldata.h"
|
||||
#include "splay.h"
|
||||
|
||||
/*
|
||||
* This macro compares two node keys i and j and returns:
|
||||
*
|
||||
* negative value: when i is smaller than j
|
||||
* zero : when i is equal to j
|
||||
* positive when : when i is larger than j
|
||||
*/
|
||||
#define splay_compare(i, j) curlx_ptimediff_us(i, j)
|
||||
|
||||
void Curl_timeouts_init(struct Curl_timeouts *timeouts,
|
||||
const struct curltime *ptime_base)
|
||||
{
|
||||
timeouts->tree = NULL;
|
||||
timeouts->time_base = ptime_base ? *ptime_base : curlx_now();
|
||||
}
|
||||
|
||||
bool Curl_timeouts_has(struct Curl_easy *data)
|
||||
{
|
||||
struct Curl_tree *node = data ? &data->state.timeouts.splaynode : NULL;
|
||||
return node && node->registered;
|
||||
}
|
||||
|
||||
timediff_t Curl_timeouts_offset_us(struct Curl_timeouts *timeouts,
|
||||
const struct curltime *pts)
|
||||
{
|
||||
return curlx_ptimediff_us(pts, &timeouts->time_base);
|
||||
}
|
||||
|
||||
int Curl_timeouts_next_ms(struct Curl_timeouts *timeouts,
|
||||
const struct curltime *pnow,
|
||||
timediff_t *pexpire_offset_us,
|
||||
uint32_t *pmid)
|
||||
{
|
||||
if(timeouts->tree) { /* splay the lowest key to the root */
|
||||
timeouts->tree = Curl_splay(TIMEDIFF_T_MIN, timeouts->tree);
|
||||
}
|
||||
|
||||
if(timeouts->tree) {
|
||||
timediff_t elapsed_us = Curl_timeouts_offset_us(timeouts, pnow);
|
||||
timediff_t delta_us = timeouts->tree->key - elapsed_us;
|
||||
if(pmid)
|
||||
*pmid = timeouts->tree->id;
|
||||
if(pexpire_offset_us)
|
||||
*pexpire_offset_us = timeouts->tree->key;
|
||||
if(delta_us > 0) { /* expires in the future */
|
||||
timediff_t ms = curlx_us_to_ceil_ms(delta_us);
|
||||
return (ms > INT_MAX) ? INT_MAX : (int)ms;
|
||||
}
|
||||
else /* has expired */
|
||||
return 0;
|
||||
}
|
||||
if(pmid)
|
||||
*pmid = UINT32_MAX;
|
||||
if(pexpire_offset_us)
|
||||
*pexpire_offset_us = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool Curl_timeouts_remove_expired(struct Curl_timeouts *timeouts,
|
||||
const struct curltime *ts,
|
||||
uint32_t *pmid)
|
||||
{
|
||||
if(timeouts->tree) {
|
||||
struct Curl_tree *t = NULL;
|
||||
timediff_t elapsed_us = Curl_timeouts_offset_us(timeouts, ts);
|
||||
timeouts->tree = Curl_splaygetbest(elapsed_us, timeouts->tree, &t);
|
||||
if(t) {
|
||||
*pmid = t->id;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
*pmid = UINT32_MAX;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void Curl_timeouts_add(struct Curl_timeouts *timeouts,
|
||||
struct Curl_easy *data,
|
||||
timediff_t offset_us)
|
||||
{
|
||||
struct Curl_tree *node = &data->state.timeouts.splaynode;
|
||||
DEBUGASSERT(!node->registered);
|
||||
timeouts->tree = Curl_splayinsert(offset_us, timeouts->tree,
|
||||
node, data->mid);
|
||||
}
|
||||
|
||||
bool Curl_timeouts_remove(struct Curl_timeouts *timeouts,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
struct Curl_tree *node = &data->state.timeouts.splaynode;
|
||||
if(node->registered) {
|
||||
int rc = Curl_splayremove(timeouts->tree, node, &timeouts->tree);
|
||||
#ifdef DEBUGBUILD
|
||||
if(rc)
|
||||
curl_mfprintf(stderr, "Internal error removing splay node = %d\n", rc);
|
||||
#else
|
||||
(void)rc;
|
||||
#endif
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Splay using the key i (which may or may not be in the tree.) The starting
|
||||
* root is t.
|
||||
* Splay using the key i (which may or may not be in the tree).
|
||||
* This rotates the tree, so:
|
||||
* - root->smaller has all nodes smaller than `key`
|
||||
* - root->larger has all nodes larger than `key`
|
||||
* - root->key may equal `key` or not
|
||||
* <https://en.wikipedia.org/wiki/Splay_tree>
|
||||
*/
|
||||
struct Curl_tree *Curl_splay(const struct curltime *pkey,
|
||||
struct Curl_tree *t)
|
||||
struct Curl_tree *Curl_splay(timediff_t key,
|
||||
struct Curl_tree *root)
|
||||
{
|
||||
struct Curl_tree N, *l, *r, *y;
|
||||
|
||||
if(!t)
|
||||
if(!root)
|
||||
return NULL;
|
||||
N.smaller = N.larger = NULL;
|
||||
l = r = &N;
|
||||
|
||||
for(;;) {
|
||||
timediff_t comp = splay_compare(pkey, &t->key);
|
||||
if(comp < 0) {
|
||||
if(!t->smaller)
|
||||
if(key < root->key) {
|
||||
/* key is somewhere in root->smaller branch */
|
||||
if(!root->smaller) /* which is empty, done */
|
||||
break;
|
||||
if(splay_compare(pkey, &t->smaller->key) < 0) {
|
||||
y = t->smaller; /* rotate smaller */
|
||||
t->smaller = y->larger;
|
||||
y->larger = t;
|
||||
t = y;
|
||||
if(!t->smaller)
|
||||
if(key < root->smaller->key) {
|
||||
/* key is somewhere in root->smaller->smaller, make a "Zig step" */
|
||||
y = root->smaller;
|
||||
root->smaller = y->larger;
|
||||
y->larger = root;
|
||||
root = y;
|
||||
if(!root->smaller)
|
||||
break;
|
||||
}
|
||||
r->smaller = t; /* link smaller */
|
||||
r = t;
|
||||
t = t->smaller;
|
||||
/* Making root->smaller the new root, the old root is no longer
|
||||
* referenced. Remember it in the N tree's `r`ight/larger side.
|
||||
* Everything in old root is smaller than what the right side
|
||||
* of N already has, so it gets added to r->smaller. */
|
||||
r->smaller = root;
|
||||
r = root;
|
||||
root = root->smaller;
|
||||
}
|
||||
else if(comp > 0) {
|
||||
if(!t->larger)
|
||||
else if(key > root->key) {
|
||||
/* key is somewhere in root->larger branch */
|
||||
if(!root->larger) /* which is empty, done */
|
||||
break;
|
||||
if(splay_compare(pkey, &t->larger->key) > 0) {
|
||||
y = t->larger; /* rotate larger */
|
||||
t->larger = y->smaller;
|
||||
y->smaller = t;
|
||||
t = y;
|
||||
if(!t->larger)
|
||||
if(key > root->larger->key) {
|
||||
/* key is somewhere in root->larger->larger, make a "Zig step" */
|
||||
y = root->larger;
|
||||
root->larger = y->smaller;
|
||||
y->smaller = root;
|
||||
root = y;
|
||||
if(!root->larger)
|
||||
break;
|
||||
}
|
||||
l->larger = t; /* link larger */
|
||||
l = t;
|
||||
t = t->larger;
|
||||
/* Making root->larger the new root, the old root is no longer
|
||||
* referenced. Remember it in the N tree's `l`eft/smaller side.
|
||||
* Everything in old root is larger than what the left side
|
||||
* of N already has, so it gets added to l->larger. */
|
||||
l->larger = root;
|
||||
l = root;
|
||||
root = root->larger;
|
||||
}
|
||||
else
|
||||
else /* exact match, root is key, done */
|
||||
break;
|
||||
}
|
||||
|
||||
l->larger = t->smaller; /* assemble */
|
||||
r->smaller = t->larger;
|
||||
t->smaller = N.larger;
|
||||
t->larger = N.smaller;
|
||||
/* Put it all together again.
|
||||
* root->smaller has everything larger than current `l`.
|
||||
* root->larger has everything smaller than current `r`. */
|
||||
l->larger = root->smaller;
|
||||
r->smaller = root->larger;
|
||||
root->smaller = N.larger;
|
||||
root->larger = N.smaller;
|
||||
|
||||
return t;
|
||||
return root;
|
||||
}
|
||||
|
||||
static const struct curltime SPLAY_SUBNODE = {
|
||||
~0, -1
|
||||
};
|
||||
|
||||
/* Insert key i into the tree t. Return a pointer to the resulting tree or
|
||||
* NULL if something went wrong.
|
||||
*
|
||||
* @unittest: 1309
|
||||
*/
|
||||
struct Curl_tree *Curl_splayinsert(const struct curltime *pkey,
|
||||
struct Curl_tree *t,
|
||||
struct Curl_tree *node)
|
||||
struct Curl_tree *Curl_splayinsert(timediff_t key,
|
||||
struct Curl_tree *root,
|
||||
struct Curl_tree *node,
|
||||
uint32_t id)
|
||||
{
|
||||
DEBUGASSERT(node);
|
||||
|
||||
if(t) {
|
||||
t = Curl_splay(pkey, t);
|
||||
DEBUGASSERT(t);
|
||||
if(splay_compare(pkey, &t->key) == 0) {
|
||||
/* There already exists a node in the tree with the same key. Build a
|
||||
doubly-linked circular list of nodes. We add the new 'node' struct to
|
||||
the end of this list. */
|
||||
|
||||
node->key = SPLAY_SUBNODE; /* identify this node as a subnode */
|
||||
node->samen = t;
|
||||
node->samep = t->samep;
|
||||
t->samep->samen = node;
|
||||
t->samep = node;
|
||||
|
||||
return t; /* the root node always stays the same */
|
||||
node->key = key;
|
||||
node->id = id;
|
||||
node->same = NULL;
|
||||
node->registered = TRUE;
|
||||
if(root) {
|
||||
root = Curl_splay(key, root);
|
||||
DEBUGASSERT(root);
|
||||
if(key == root->key) {
|
||||
/* There already exists a node in the tree with the same key.
|
||||
Append the new node to the `same` list. */
|
||||
struct Curl_tree **panchor = &root->same;
|
||||
while(*panchor)
|
||||
panchor = &(*panchor)->same;
|
||||
*panchor = node;
|
||||
return root; /* the root node always stays the same */
|
||||
}
|
||||
}
|
||||
|
||||
if(!t) {
|
||||
/* node becomes the new root. Insert old root as sub-branch. */
|
||||
if(!root) {
|
||||
node->smaller = node->larger = NULL;
|
||||
}
|
||||
else if(splay_compare(pkey, &t->key) < 0) {
|
||||
node->smaller = t->smaller;
|
||||
node->larger = t;
|
||||
t->smaller = NULL;
|
||||
else if(key < root->key) {
|
||||
node->smaller = root->smaller;
|
||||
node->larger = root;
|
||||
root->smaller = NULL;
|
||||
}
|
||||
else {
|
||||
node->larger = t->larger;
|
||||
node->smaller = t;
|
||||
t->larger = NULL;
|
||||
node->larger = root->larger;
|
||||
node->smaller = root;
|
||||
root->larger = NULL;
|
||||
}
|
||||
node->key = *pkey;
|
||||
|
||||
/* no identical nodes (yet), we are the only one in the list of nodes */
|
||||
node->samen = node;
|
||||
node->samep = node;
|
||||
return node;
|
||||
}
|
||||
|
||||
/* Finds and deletes the best-fit node from the tree. Return a pointer to the
|
||||
resulting tree. best-fit means the smallest node if it is not larger than
|
||||
the key */
|
||||
struct Curl_tree *Curl_splaygetbest(const struct curltime *pkey,
|
||||
struct Curl_tree *t,
|
||||
struct Curl_tree *Curl_splaygetbest(timediff_t key,
|
||||
struct Curl_tree *root,
|
||||
struct Curl_tree **removed)
|
||||
{
|
||||
static const struct curltime tv_zero = { 0, 0 };
|
||||
struct Curl_tree *x;
|
||||
|
||||
if(!t) {
|
||||
if(!root) {
|
||||
*removed = NULL; /* none removed since there was no root */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* find smallest */
|
||||
t = Curl_splay(&tv_zero, t);
|
||||
DEBUGASSERT(t);
|
||||
if(splay_compare(pkey, &t->key) < 0) {
|
||||
root = Curl_splay(TIMEDIFF_T_MIN, root);
|
||||
DEBUGASSERT(root);
|
||||
if(key < root->key) {
|
||||
/* even the smallest is too big */
|
||||
*removed = NULL;
|
||||
return t;
|
||||
return root;
|
||||
}
|
||||
|
||||
/* FIRST! Check if there is a list with identical keys */
|
||||
x = t->samen;
|
||||
if(x != t) {
|
||||
/* there is, pick one from the list */
|
||||
|
||||
/* 'x' is the new root node */
|
||||
|
||||
x->key = t->key;
|
||||
x->larger = t->larger;
|
||||
x->smaller = t->smaller;
|
||||
x->samep = t->samep;
|
||||
t->samep->samen = x;
|
||||
|
||||
*removed = t;
|
||||
if(root->same) {
|
||||
x = root->same;
|
||||
DEBUGASSERT(x->key == root->key);
|
||||
/* 'x' becomes the new root node */
|
||||
x->larger = root->larger;
|
||||
x->smaller = root->smaller;
|
||||
root->same = NULL;
|
||||
root->registered = FALSE;
|
||||
*removed = root;
|
||||
return x; /* new root */
|
||||
}
|
||||
|
||||
/* we splayed the tree to the smallest element, there is no smaller */
|
||||
x = t->larger;
|
||||
*removed = t;
|
||||
x = root->larger;
|
||||
root->registered = FALSE;
|
||||
*removed = root;
|
||||
|
||||
return x;
|
||||
}
|
||||
|
|
@ -205,87 +299,79 @@ struct Curl_tree *Curl_splaygetbest(const struct curltime *pkey,
|
|||
*
|
||||
* @unittest: 1309
|
||||
*/
|
||||
int Curl_splayremove(struct Curl_tree *t,
|
||||
int Curl_splayremove(struct Curl_tree *root,
|
||||
struct Curl_tree *removenode,
|
||||
struct Curl_tree **newroot)
|
||||
{
|
||||
struct Curl_tree *x;
|
||||
|
||||
if(!t)
|
||||
if(!root)
|
||||
return 1;
|
||||
|
||||
DEBUGASSERT(removenode);
|
||||
|
||||
if(splay_compare(&SPLAY_SUBNODE, &removenode->key) == 0) {
|
||||
/* It is a subnode within a 'same' linked list and thus we can unlink it
|
||||
easily. */
|
||||
DEBUGASSERT(removenode->samen != removenode);
|
||||
if(removenode->samen == removenode)
|
||||
/* A non-subnode should never be set to SPLAY_SUBNODE */
|
||||
return 3;
|
||||
|
||||
removenode->samep->samen = removenode->samen;
|
||||
removenode->samen->samep = removenode->samep;
|
||||
|
||||
/* Ensures that double-remove gets caught. */
|
||||
removenode->samen = removenode;
|
||||
|
||||
*newroot = t; /* return the same root */
|
||||
return 0;
|
||||
}
|
||||
|
||||
t = Curl_splay(&removenode->key, t);
|
||||
DEBUGASSERT(t);
|
||||
|
||||
/* First make sure that we got the same root node as the one we want
|
||||
to remove, as otherwise we might be trying to remove a node that
|
||||
is not actually in the tree.
|
||||
|
||||
We cannot compare the keys here as a double remove in quick
|
||||
succession of a node with key != SPLAY_SUBNODE && same != NULL
|
||||
could return the same key but a different node. */
|
||||
DEBUGASSERT(t == removenode);
|
||||
if(t != removenode)
|
||||
if(!removenode->registered)
|
||||
return 2;
|
||||
|
||||
/* Check if there is a list with identical sizes, as then we are trying to
|
||||
remove the root node of a list of nodes with identical keys. */
|
||||
x = t->samen;
|
||||
if(x != t) {
|
||||
root = Curl_splay(removenode->key, root);
|
||||
DEBUGASSERT(root);
|
||||
|
||||
/* First make sure that we got the same root key as the one we want
|
||||
to remove, as otherwise we might be trying to remove a node that
|
||||
is not actually in the tree. */
|
||||
if(root->key != removenode->key) {
|
||||
DEBUGASSERT(0);
|
||||
return 2;
|
||||
}
|
||||
|
||||
if(root != removenode) {
|
||||
/* Should be in the root->same list then */
|
||||
struct Curl_tree **panchor;
|
||||
for(panchor = &root->same; *panchor; panchor = &(*panchor)->same) {
|
||||
if(*panchor == removenode) {
|
||||
*panchor = removenode->same;
|
||||
removenode->same = NULL;
|
||||
removenode->registered = FALSE;
|
||||
*newroot = root;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/* not found in same list, error */
|
||||
DEBUGASSERT(0);
|
||||
return 2;
|
||||
}
|
||||
/* removing the root node */
|
||||
if(root->same) {
|
||||
/* 'x' is the new root node, we make it use the root node's
|
||||
smaller/larger links */
|
||||
|
||||
x->key = t->key;
|
||||
x->larger = t->larger;
|
||||
x->smaller = t->smaller;
|
||||
x->samep = t->samep;
|
||||
t->samep->samen = x;
|
||||
x = root->same;
|
||||
x->larger = root->larger;
|
||||
x->smaller = root->smaller;
|
||||
root->same = NULL;
|
||||
}
|
||||
else {
|
||||
/* Remove the root node */
|
||||
if(!t->smaller)
|
||||
x = t->larger;
|
||||
if(!root->smaller)
|
||||
x = root->larger;
|
||||
else {
|
||||
x = Curl_splay(&removenode->key, t->smaller);
|
||||
x = Curl_splay(removenode->key, root->smaller);
|
||||
DEBUGASSERT(x);
|
||||
x->larger = t->larger;
|
||||
x->larger = root->larger;
|
||||
}
|
||||
}
|
||||
|
||||
*newroot = x; /* store new root pointer */
|
||||
|
||||
removenode->registered = FALSE;
|
||||
*newroot = x; /* return new root */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* set and get the custom payload for this tree node */
|
||||
void Curl_splayset(struct Curl_tree *node, void *payload)
|
||||
void Curl_splayset(struct Curl_tree *node, uint32_t id)
|
||||
{
|
||||
DEBUGASSERT(node);
|
||||
node->ptr = payload;
|
||||
node->id = id;
|
||||
}
|
||||
|
||||
void *Curl_splayget(struct Curl_tree *node)
|
||||
uint32_t Curl_splayget(struct Curl_tree *node)
|
||||
{
|
||||
DEBUGASSERT(node);
|
||||
return node->ptr;
|
||||
return node->id;
|
||||
}
|
||||
|
|
|
|||
61
lib/splay.h
61
lib/splay.h
|
|
@ -27,33 +27,66 @@
|
|||
|
||||
#include "curlx/timeval.h"
|
||||
|
||||
struct Curl_easy;
|
||||
|
||||
/* only use function calls to access this struct */
|
||||
struct Curl_tree {
|
||||
struct Curl_tree *smaller; /* smaller node */
|
||||
struct Curl_tree *larger; /* larger node */
|
||||
struct Curl_tree *samen; /* points to the next node with identical key */
|
||||
struct Curl_tree *samep; /* points to the prev node with identical key */
|
||||
struct curltime key; /* this node's "sort" key */
|
||||
void *ptr; /* data the splay code does not care about */
|
||||
struct Curl_tree *same; /* points to the next node with identical key */
|
||||
timediff_t key; /* this node's "sort" key */
|
||||
uint32_t id; /* provided id for this node */
|
||||
BIT(registered); /* node is registered in splay tree */
|
||||
};
|
||||
|
||||
struct Curl_tree *Curl_splay(const struct curltime *pkey,
|
||||
struct Curl_tree *t);
|
||||
struct Curl_timeouts {
|
||||
struct Curl_tree *tree;
|
||||
struct curltime time_base;
|
||||
};
|
||||
|
||||
struct Curl_tree *Curl_splayinsert(const struct curltime *pkey,
|
||||
struct Curl_tree *t,
|
||||
struct Curl_tree *node);
|
||||
void Curl_timeouts_init(struct Curl_timeouts *timeouts,
|
||||
const struct curltime *ptime_base);
|
||||
|
||||
struct Curl_tree *Curl_splaygetbest(const struct curltime *pkey,
|
||||
struct Curl_tree *t,
|
||||
bool Curl_timeouts_has(struct Curl_easy *data);
|
||||
|
||||
timediff_t Curl_timeouts_offset_us(struct Curl_timeouts *timeouts,
|
||||
const struct curltime *pts);
|
||||
|
||||
int Curl_timeouts_next_ms(struct Curl_timeouts *timeouts,
|
||||
const struct curltime *pnow,
|
||||
timediff_t *pexpire_offset_us,
|
||||
uint32_t *pmid);
|
||||
|
||||
bool Curl_timeouts_remove_expired(struct Curl_timeouts *timeouts,
|
||||
const struct curltime *ts,
|
||||
uint32_t *pmid);
|
||||
|
||||
void Curl_timeouts_add(struct Curl_timeouts *timeouts,
|
||||
struct Curl_easy *data,
|
||||
timediff_t offset_us);
|
||||
|
||||
/* Returns TRUE if data was registered in timeouts before */
|
||||
bool Curl_timeouts_remove(struct Curl_timeouts *timeouts,
|
||||
struct Curl_easy *data);
|
||||
|
||||
struct Curl_tree *Curl_splay(timediff_t key,
|
||||
struct Curl_tree *root);
|
||||
|
||||
struct Curl_tree *Curl_splayinsert(timediff_t key,
|
||||
struct Curl_tree *root,
|
||||
struct Curl_tree *node,
|
||||
uint32_t id);
|
||||
|
||||
struct Curl_tree *Curl_splaygetbest(timediff_t key,
|
||||
struct Curl_tree *root,
|
||||
struct Curl_tree **removed);
|
||||
|
||||
int Curl_splayremove(struct Curl_tree *t,
|
||||
int Curl_splayremove(struct Curl_tree *root,
|
||||
struct Curl_tree *removenode,
|
||||
struct Curl_tree **newroot);
|
||||
|
||||
/* set and get the custom payload for this tree node */
|
||||
void Curl_splayset(struct Curl_tree *node, void *payload);
|
||||
void *Curl_splayget(struct Curl_tree *node);
|
||||
void Curl_splayset(struct Curl_tree *node, uint32_t id);
|
||||
uint32_t Curl_splayget(struct Curl_tree *node);
|
||||
|
||||
#endif /* HEADER_CURL_SPLAY_H */
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ CURLcode Curl_close(struct Curl_easy **datap)
|
|||
}
|
||||
DEBUGASSERT(!data->conn || data->state.internal);
|
||||
|
||||
Curl_expire_clear(data); /* shut off any timers left */
|
||||
Curl_expire_clear_all(data); /* shut off any timers left */
|
||||
|
||||
if(data->state.rangestringalloc)
|
||||
curlx_free(data->state.range);
|
||||
|
|
|
|||
|
|
@ -514,10 +514,10 @@ typedef enum {
|
|||
|
||||
struct expire_timers {
|
||||
struct Curl_tree splaynode; /* for the splay stuff */
|
||||
struct curltime time[EXPIRE_LAST];
|
||||
/* microsecond offset from Curl_timeouts base timestamp */
|
||||
timediff_t offset_us[EXPIRE_LAST];
|
||||
expire_id next[EXPIRE_LAST];
|
||||
expire_id first;
|
||||
BIT(registered); /* timeout node is registered in splay tree */
|
||||
};
|
||||
|
||||
/* individual pieces of the URL */
|
||||
|
|
|
|||
|
|
@ -862,7 +862,7 @@ CURLcode Curl_async_take_result(struct Curl_easy *data,
|
|||
if(result)
|
||||
return result;
|
||||
|
||||
Curl_expire_done(data, EXPIRE_ASYNC_NAME);
|
||||
Curl_expire_clear(data, EXPIRE_ASYNC_NAME);
|
||||
|
||||
/* A failure is an authoritative negative answer, eligible for
|
||||
negative caching, only when every A/AAAA query performed came
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue