multi: add xfer_buf to multi handle

- can be borrowed by transfer during recv-write operation
- needs to be released before borrowing again
- adjustis size to `data->set.buffer_size`
- used in transfer.c readwrite_data()

Closes #12805
This commit is contained in:
Stefan Eissing 2024-01-26 12:05:08 +01:00 committed by Daniel Stenberg
parent c54d0ff6b3
commit 476adfeac0
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
10 changed files with 113 additions and 25 deletions

View File

@ -42,6 +42,11 @@ transfer as that may lead to unintended consequences.
The maximum size was 512kB until 7.88.0.
Starting in libcurl 8.7.0, there is just a single transfer buffer allocated
per multi handle. This buffer is used by all easy handles added to a multi
handle no matter how many parallel transfers there are. The buffer remains
allocated as long as there are active transfers.
# DEFAULT
CURL_MAX_WRITE_SIZE (16kB)

View File

@ -395,8 +395,6 @@ bool Curl_conncache_return_conn(struct Curl_easy *data,
important that details from this (unrelated) disconnect does not
taint meta-data in the data handle. */
struct conncache *connc = data->state.conn_cache;
connc->closure_handle->state.buffer = data->state.buffer;
connc->closure_handle->set.buffer_size = data->set.buffer_size;
Curl_disconnect(connc->closure_handle, conn_candidate,
/* dead_connection */ FALSE);
}
@ -522,12 +520,9 @@ Curl_conncache_extract_oldest(struct Curl_easy *data)
void Curl_conncache_close_all_connections(struct conncache *connc)
{
struct connectdata *conn;
char buffer[READBUFFER_MIN + 1];
SIGPIPE_VARIABLE(pipe_st);
if(!connc->closure_handle)
return;
connc->closure_handle->state.buffer = buffer;
connc->closure_handle->set.buffer_size = READBUFFER_MIN;
conn = conncache_find_first_connection(connc);
while(conn) {
@ -541,7 +536,6 @@ void Curl_conncache_close_all_connections(struct conncache *connc)
conn = conncache_find_first_connection(connc);
}
connc->closure_handle->state.buffer = NULL;
sigpipe_ignore(connc->closure_handle, &pipe_st);
Curl_hostcache_clean(connc->closure_handle,

View File

@ -1021,7 +1021,6 @@ fail:
#ifndef CURL_DISABLE_COOKIES
free(outcurl->cookies);
#endif
free(outcurl->state.buffer);
Curl_dyn_free(&outcurl->state.headerb);
Curl_altsvc_cleanup(&outcurl->asi);
Curl_hsts_cleanup(&outcurl->hsts);

View File

@ -94,6 +94,7 @@ static CURLMcode add_next_timeout(struct curltime now,
static CURLMcode multi_timeout(struct Curl_multi *multi,
long *timeout_ms);
static void process_pending_handles(struct Curl_multi *multi);
static void multi_xfer_buf_free(struct Curl_multi *multi);
#ifdef DEBUGBUILD
static const char * const multi_statename[]={
@ -189,6 +190,10 @@ static void mstate(struct Curl_easy *data, CURLMstate state
/* changing to COMPLETED means there's one less easy handle 'alive' */
DEBUGASSERT(data->multi->num_alive > 0);
data->multi->num_alive--;
if(!data->multi->num_alive) {
/* free the transfer buffer when we have no more active transfers */
multi_xfer_buf_free(data->multi);
}
}
/* if this state has an init-function, run it */
@ -784,7 +789,6 @@ static CURLcode multi_done(struct Curl_easy *data,
data->state.lastconnect_id = -1;
}
Curl_safefree(data->state.buffer);
return result;
}
@ -1891,12 +1895,9 @@ static CURLcode readrewind(struct Curl_easy *data)
*/
CURLcode Curl_preconnect(struct Curl_easy *data)
{
if(!data->state.buffer) {
data->state.buffer = malloc(data->set.buffer_size + 1);
if(!data->state.buffer)
return CURLE_OUT_OF_MEMORY;
}
/* this used to do data->state.buffer allocation,
maybe remove completely now? */
(void)data;
return CURLE_OK;
}
@ -2450,7 +2451,6 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
{
char *newurl = NULL;
bool retry = FALSE;
DEBUGASSERT(data->state.buffer);
/* check if over send speed */
send_timeout_ms = 0;
if(data->set.max_send_speed)
@ -2883,6 +2883,7 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi)
Curl_free_multi_ssl_backend_data(multi->ssl_backend_data);
#endif
multi_xfer_buf_free(multi);
free(multi);
return CURLM_OK;
@ -3819,3 +3820,64 @@ struct Curl_easy **curl_multi_get_handles(struct Curl_multi *multi)
}
return a;
}
CURLcode Curl_multi_xfer_buf_borrow(struct Curl_easy *data,
char **pbuf, size_t *pbuflen)
{
DEBUGASSERT(data);
DEBUGASSERT(data->multi);
*pbuf = NULL;
*pbuflen = 0;
if(!data->multi) {
failf(data, "transfer has no multi handle");
return CURLE_FAILED_INIT;
}
if(!data->set.buffer_size) {
failf(data, "transfer buffer size is 0");
return CURLE_FAILED_INIT;
}
if(data->multi->xfer_buf_borrowed) {
failf(data, "attempt to borrow xfer_buf when already borrowed");
return CURLE_AGAIN;
}
if(data->multi->xfer_buf &&
data->set.buffer_size > data->multi->xfer_buf_len) {
/* not large enough, get a new one */
free(data->multi->xfer_buf);
data->multi->xfer_buf = NULL;
data->multi->xfer_buf_len = 0;
}
if(!data->multi->xfer_buf) {
data->multi->xfer_buf = malloc((size_t)data->set.buffer_size);
if(!data->multi->xfer_buf) {
failf(data, "could not allocate xfer_buf of %zu bytes",
(size_t)data->set.buffer_size);
return CURLE_OUT_OF_MEMORY;
}
data->multi->xfer_buf_len = data->set.buffer_size;
}
data->multi->xfer_buf_borrowed = TRUE;
*pbuf = data->multi->xfer_buf;
*pbuflen = data->multi->xfer_buf_len;
return CURLE_OK;
}
void Curl_multi_xfer_buf_release(struct Curl_easy *data, char *buf)
{
(void)buf;
DEBUGASSERT(data);
DEBUGASSERT(data->multi);
DEBUGASSERT(!buf || data->multi->xfer_buf == buf);
data->multi->xfer_buf_borrowed = FALSE;
}
static void multi_xfer_buf_free(struct Curl_multi *multi)
{
DEBUGASSERT(multi);
Curl_safefree(multi->xfer_buf);
multi->xfer_buf_len = 0;
multi->xfer_buf_borrowed = FALSE;
}

View File

@ -124,6 +124,10 @@ struct Curl_multi {
times of all currently set timers */
struct Curl_tree *timetree;
/* buffer used for transfer data, lazy initialized */
char *xfer_buf; /* the actual buffer */
size_t xfer_buf_len; /* the allocated length */
#if defined(USE_SSL)
struct multi_ssl_backend_data *ssl_backend_data;
#endif
@ -171,6 +175,7 @@ struct Curl_multi {
#endif
BIT(dead); /* a callback returned error, everything needs to crash and
burn */
BIT(xfer_buf_borrowed); /* xfer_buf is currently being borrowed */
#ifdef DEBUGBUILD
BIT(warned); /* true after user warned of DEBUGBUILD */
#endif

View File

@ -94,4 +94,28 @@ CURLMcode Curl_multi_add_perform(struct Curl_multi *multi,
/* Return the value of the CURLMOPT_MAX_CONCURRENT_STREAMS option */
unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi);
/**
* Borrow the transfer buffer from the multi, suitable
* for the given transfer `data`. The buffer may only be used in one
* multi processing of the easy handle. It MUST be returned to the
* multi before it can be borrowed again.
* Pointers into the buffer remain only valid as long as it is borrowed.
*
* @param data the easy handle
* @param pbuf on return, the buffer to use or NULL on error
* @param pbuflen on return, the size of *pbuf or 0 on error
* @return CURLE_OK when buffer is available and is returned.
* CURLE_OUT_OF_MEMORy on failure to allocate the buffer,
* CURLE_FAILED_INIT if the easy handle is without multi.
* CURLE_AGAIN if the buffer is borrowed already.
*/
CURLcode Curl_multi_xfer_buf_borrow(struct Curl_easy *data,
char **pbuf, size_t *pbuflen);
/**
* Release the borrowed buffer. All references into the buffer become
* invalid after this.
* @param buf the buffer pointer borrowed for coding error checks.
*/
void Curl_multi_xfer_buf_release(struct Curl_easy *data, char *buf);
#endif /* HEADER_CURL_MULTIIF_H */

View File

@ -2210,9 +2210,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
* The application kindly asks for a differently sized receive buffer.
* If it seems reasonable, we'll use it.
*/
if(data->state.buffer)
return CURLE_BAD_FUNCTION_ARGUMENT;
arg = va_arg(param, long);
if(arg > READBUFFER_MAX)

View File

@ -466,15 +466,18 @@ static CURLcode readwrite_data(struct Curl_easy *data,
{
struct connectdata *conn = data->conn;
CURLcode result = CURLE_OK;
char *buf;
size_t blen;
char *buf, *xfer_buf;
size_t blen, xfer_blen;
int maxloops = 10;
curl_off_t total_received = 0;
bool is_multiplex = FALSE;
DEBUGASSERT(data->state.buffer);
*done = FALSE;
result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen);
if(result)
goto out;
/* This is where we loop until we have read everything there is to
read or we get a CURLE_AGAIN */
do {
@ -489,8 +492,8 @@ static CURLcode readwrite_data(struct Curl_easy *data,
is_multiplex = Curl_conn_is_multiplex(conn, FIRSTSOCKET);
}
buf = data->state.buffer;
bytestoread = data->set.buffer_size;
buf = xfer_buf;
bytestoread = xfer_blen;
/* Observe any imposed speed limit */
if(bytestoread && data->set.max_recv_speed) {
@ -564,6 +567,7 @@ static CURLcode readwrite_data(struct Curl_easy *data,
}
out:
Curl_multi_xfer_buf_release(data, xfer_buf);
if(result)
DEBUGF(infof(data, "readwrite_data() -> %d", result));
return result;

View File

@ -280,7 +280,6 @@ CURLcode Curl_close(struct Curl_easy **datap)
data->state.referer = NULL;
up_free(data);
Curl_safefree(data->state.buffer);
Curl_dyn_free(&data->state.headerb);
Curl_safefree(data->state.ulbuf);
Curl_flush_cookies(data, TRUE);

View File

@ -1337,7 +1337,6 @@ struct UrlState {
struct dynbuf headerb; /* buffer to store headers in */
struct curl_slist *hstslist; /* list of HSTS files set by
curl_easy_setopt(HSTS) calls */
char *buffer; /* download buffer */
char *ulbuf; /* allocated upload buffer or NULL */
curl_off_t current_speed; /* the ProgressShow() function sets this,
bytes / second */