multi: add dirty bitset

Add a bitset `dirty` to the multi handle. The presence of a transfer int
he "dirty" set means: this transfer has something to do ASAP.

"dirty" is set by multiplexing protocols like HTTP/2 and 3 when
encountering response data for another transfer than the current one.
"dirty" is set by protocols that want to be called.

Implementation:

* just an additional `uint_bset` in the multi handle
* `Curl_multi_mark_dirty()` to add a transfer to the dirty set.
* `multi_runsingle()` clears the dirty bit of the transfer at
   start. Without new dirty marks, this empties the set after
   al dirty transfers have been run.
* `multi_timeout()` immediately gives the current time and
   timeout_ms == 0 when dirty transfers are present.
* multi_event: marks all transfers tracked for a socket as dirty.
  Then marks all expired transfers as dirty. Then it runs
  all dirty transfers.

With this mechanism:

* Most uses of `EXPIRE_RUN_NOW` are replaced by `Curl_multi_mark_dirty()`
* `Curl_multi_mark_dirty()` is cheaper than querying if a transfer is
  already dirty or set for timeout. There is no need to check, just do it.
* `data->state.select_bits` is eliminated. We need no longer to
  simulate a poll event to make a transfer run.

Closes #17662
This commit is contained in:
Stefan Eissing 2025-06-18 12:34:43 +02:00 committed by Daniel Stenberg
parent 7aa8d1eea1
commit 779937f840
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
20 changed files with 162 additions and 285 deletions

View file

@ -358,16 +358,12 @@ static CURLcode sendrecv_dl(struct Curl_easy *data,
} while(maxloops--);
if(!rcvd_eagain || data_pending(data, rcvd_eagain)) {
if(!Curl_xfer_is_blocked(data) &&
(!rcvd_eagain || data_pending(data, rcvd_eagain))) {
/* Did not read until EAGAIN or there is still data pending
* in buffers. Mark as read-again via simulated SELECT results. */
data->state.select_bits = CURL_CSELECT_IN;
if((k->keepon & KEEP_SENDBITS) == KEEP_SEND)
data->state.select_bits |= CURL_CSELECT_OUT;
if(!Curl_xfer_is_blocked(data))
Curl_expire(data, 0, EXPIRE_RUN_NOW);
CURL_TRC_M(data, "sendrecv_dl() no EAGAIN/pending data, "
"set select_bits=%x", data->state.select_bits);
Curl_multi_mark_dirty(data);
CURL_TRC_M(data, "sendrecv_dl() no EAGAIN/pending data, mark as dirty");
}
if(((k->keepon & (KEEP_RECV|KEEP_SEND)) == KEEP_SEND) &&
@ -403,22 +399,6 @@ static CURLcode sendrecv_ul(struct Curl_easy *data, int *didwhat)
return CURLE_OK;
}
static int select_bits_paused(struct Curl_easy *data, int select_bits)
{
/* See issue #11982: we really need to be careful not to progress
* a transfer direction when that direction is paused. Not all parts
* of our state machine are handling PAUSED transfers correctly. So, we
* do not want to go there.
* NOTE: we are only interested in PAUSE, not HOLD. */
/* if there is data in a direction not paused, return false */
if(((select_bits & CURL_CSELECT_IN) && !Curl_xfer_recv_is_paused(data)) ||
((select_bits & CURL_CSELECT_OUT) && !Curl_xfer_send_is_paused(data)))
return FALSE;
return Curl_xfer_recv_is_paused(data) || Curl_xfer_send_is_paused(data);
}
/*
* Curl_sendrecv() is the low-level function to be called when data is to
* be read and written to/from the connection.
@ -430,14 +410,9 @@ CURLcode Curl_sendrecv(struct Curl_easy *data, struct curltime *nowp)
int didwhat = 0;
DEBUGASSERT(nowp);
if(data->state.select_bits) {
if(select_bits_paused(data, data->state.select_bits)) {
/* leave the bits unchanged, so they'll tell us what to do when
* this transfer gets unpaused. */
result = CURLE_OK;
goto out;
}
data->state.select_bits = 0;
if(Curl_xfer_is_blocked(data)) {
result = CURLE_OK;
goto out;
}
/* We go ahead and do a read if we have a readable socket or if the stream