lib: client writer tweaks

- keep pause state at request
- keep presense of content decoding writers at request
- only add cw-pause writer when content decoding happens
- define macro for passing data down the writer chain

scorecard: add option to test with h2o server

Closes #22335
This commit is contained in:
Stefan Eissing 2026-07-16 11:26:33 +02:00 committed by Daniel Stenberg
parent f65253056a
commit b41c28e70a
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
8 changed files with 138 additions and 173 deletions

View file

@ -97,7 +97,6 @@ static void cw_out_buf_free(struct cw_out_buf *cwbuf)
struct cw_out_ctx {
struct Curl_cwriter super;
struct cw_out_buf *buf;
BIT(paused);
BIT(errored);
};
@ -171,8 +170,7 @@ static void cw_get_writefunc(struct Curl_easy *data, cw_out_type otype,
}
}
static CURLcode cw_out_cb_write(struct cw_out_ctx *ctx,
struct Curl_easy *data,
static CURLcode cw_out_cb_write(struct Curl_easy *data,
curl_write_callback wcb,
void *wcb_data,
cw_out_type otype,
@ -200,7 +198,7 @@ static CURLcode cw_out_cb_write(struct cw_out_ctx *ctx,
failf(data, "Write callback asked for PAUSE when not supported");
return CURLE_WRITE_ERROR;
}
ctx->paused = TRUE;
data->req.writer.paused = TRUE;
CURL_TRC_WRITE(data, "[OUT] PAUSE requested by client");
result = Curl_xfer_pause_recv(data, TRUE);
return result ? result : CURLE_AGAIN;
@ -245,18 +243,18 @@ static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx,
*pconsumed = 0;
if(otype == CW_OUT_BODY_0LEN) {
DEBUGASSERT(!blen);
return cw_out_cb_write(ctx, data, wcb, wcb_data, otype,
return cw_out_cb_write(data, wcb, wcb_data, otype,
buf, blen, &nwritten);
}
else {
while(blen && !ctx->paused) {
while(blen && !data->req.writer.paused) {
if(!flush_all && blen < min_write)
break;
wlen = max_write ? CURLMIN(blen, max_write) : blen;
if(otype == CW_OUT_BODY)
result = Curl_pgrs_deliver_check(data, wlen);
if(!result)
result = cw_out_cb_write(ctx, data, wcb, wcb_data, otype,
result = cw_out_cb_write(data, wcb, wcb_data, otype,
buf, wlen, &nwritten);
if(result)
return result;
@ -314,7 +312,7 @@ static CURLcode cw_out_flush_chain(struct cw_out_ctx *ctx,
if(!cwbuf)
return CURLE_OK;
if(ctx->paused)
if(data->req.writer.paused)
return CURLE_OK;
/* write the end of the chain until it blocks or gets empty */
@ -327,7 +325,7 @@ static CURLcode cw_out_flush_chain(struct cw_out_ctx *ctx,
return result;
if(*plast) {
/* could not write last, paused again? */
DEBUGASSERT(ctx->paused);
DEBUGASSERT(data->req.writer.paused);
return CURLE_OK;
}
}
@ -457,7 +455,7 @@ static CURLcode cw_out_do_flush(struct Curl_easy *data,
if(ctx->errored)
return CURLE_WRITE_ERROR;
if(ctx->paused)
if(data->req.writer.paused)
return CURLE_OK; /* not doing it */
result = cw_out_flush_chain(ctx, data, &ctx->buf, flush_all);
@ -486,33 +484,6 @@ const struct Curl_cwtype Curl_cwt_out = {
sizeof(struct cw_out_ctx)
};
bool Curl_cw_out_is_paused(struct Curl_easy *data)
{
struct Curl_cwriter *cw_out;
struct cw_out_ctx *ctx;
cw_out = Curl_cwriter_get_by_type(data, &Curl_cwt_out);
if(!cw_out)
return FALSE;
ctx = (struct cw_out_ctx *)cw_out;
return (bool)ctx->paused;
}
CURLcode Curl_cw_out_unpause(struct Curl_easy *data)
{
struct Curl_cwriter *cw_out;
CURLcode result = CURLE_OK;
cw_out = Curl_cwriter_get_by_type(data, &Curl_cwt_out);
if(cw_out) {
struct cw_out_ctx *ctx = (struct cw_out_ctx *)cw_out;
CURL_TRC_WRITE(data, "[OUT] unpause");
ctx->paused = FALSE;
}
return result;
}
CURLcode Curl_cw_out_done(struct Curl_easy *data)
{
struct Curl_cwriter *cw_out;

View file

@ -34,16 +34,6 @@ struct Curl_easy;
*/
extern const struct Curl_cwtype Curl_cwt_out;
/**
* Return TRUE iff 'cw-out' client write has paused data.
*/
bool Curl_cw_out_is_paused(struct Curl_easy *data);
/**
* Flush any buffered date to the client, chunk collation still applies.
*/
CURLcode Curl_cw_out_unpause(struct Curl_easy *data);
/**
* Mark EndOfStream reached and flush ALL data to the client.
*/

View file

@ -100,7 +100,6 @@ static CURLcode cw_pause_flush(struct Curl_easy *data,
struct Curl_cwriter *cw_pause)
{
struct cw_pause_ctx *ctx = (struct cw_pause_ctx *)cw_pause;
bool decoding = Curl_cwriter_is_content_decoding(data);
CURLcode result = CURLE_OK;
/* write the end of the chain until it blocks or gets empty */
@ -112,7 +111,7 @@ static CURLcode cw_pause_flush(struct Curl_easy *data,
while((*plast)->next) /* got to last in list */
plast = &(*plast)->next;
if(Curl_bufq_peek(&(*plast)->b, &buf, &blen)) {
wlen = (decoding && ((*plast)->type & CLIENTWRITE_BODY)) ?
wlen = ((*plast)->type & CLIENTWRITE_BODY) ?
CURLMIN(blen, CW_PAUSE_DEC_WRITE_CHUNK) : blen;
result = Curl_cwriter_write(data, cw_pause->next, (*plast)->type,
(const char *)buf, wlen);
@ -151,7 +150,6 @@ static CURLcode cw_pause_write(struct Curl_easy *data,
struct cw_pause_ctx *ctx = writer->ctx;
CURLcode result = CURLE_OK;
size_t wlen = 0;
bool decoding = Curl_cwriter_is_content_decoding(data);
if(ctx->buf && !Curl_cwriter_is_paused(data)) {
result = cw_pause_flush(data, writer);
@ -164,7 +162,7 @@ static CURLcode cw_pause_write(struct Curl_easy *data,
DEBUGASSERT(!ctx->buf);
/* content decoding might blow up size considerably, write smaller
* chunks to make pausing need buffer less. */
wlen = (decoding && (type & CLIENTWRITE_BODY)) ?
wlen = (type & CLIENTWRITE_BODY) ?
CURLMIN(blen, CW_PAUSE_DEC_WRITE_CHUNK) : blen;
if(wlen < blen)
wtype &= ~CLIENTWRITE_EOS;

View file

@ -101,10 +101,16 @@ struct SingleRequest {
/* Client Writer stack, handles transfer- and content-encodings, protocol
* checks, pausing by client callbacks. */
struct Curl_cwriter *writer_stack;
struct {
struct Curl_cwriter *stack;
BIT(paused);
} writer;
/* Client Reader stack, handles transfer- and content-encodings, protocol
* checks, pausing by client callbacks. */
struct Curl_creader *reader_stack;
struct {
struct Curl_creader *stack;
BIT(paused);
} reader;
struct bufq sendbuf; /* data which needs to be send to the server */
size_t sendbuf_hds_len; /* amount of header bytes in sendbuf */
time_t timeofdoc;

View file

@ -46,24 +46,24 @@
static void cl_reset_writer(struct Curl_easy *data)
{
struct Curl_cwriter *writer = data->req.writer_stack;
struct Curl_cwriter *writer = data->req.writer.stack;
while(writer) {
data->req.writer_stack = writer->next;
data->req.writer.stack = writer->next;
writer->cwt->do_close(data, writer);
curlx_free(writer);
writer = data->req.writer_stack;
writer = data->req.writer.stack;
}
}
static void cl_reset_reader(struct Curl_easy *data)
{
struct Curl_creader *reader = data->req.reader_stack;
struct Curl_creader *reader = data->req.reader.stack;
data->req.reader_started = FALSE;
while(reader) {
data->req.reader_stack = reader->next;
data->req.reader.stack = reader->next;
reader->crt->do_close(data, reader);
curlx_free(reader);
reader = data->req.reader_stack;
reader = data->req.reader.stack;
}
}
@ -95,7 +95,7 @@ void Curl_client_reset(struct Curl_easy *data)
CURLcode Curl_client_start(struct Curl_easy *data)
{
if(data->req.rewind_read) {
struct Curl_creader *r = data->req.reader_stack;
struct Curl_creader *r = data->req.reader.stack;
CURLcode result = CURLE_OK;
CURL_TRC_READ(data, "client start, rewind readers");
@ -124,25 +124,6 @@ void Curl_creader_set_rewind(struct Curl_easy *data, bool enable)
data->req.rewind_read = !!enable;
}
/* Write data using an unencoding writer stack. */
CURLcode Curl_cwriter_write(struct Curl_easy *data,
struct Curl_cwriter *writer, int type,
const char *buf, size_t nbytes)
{
if(!writer)
return CURLE_WRITE_ERROR;
return writer->cwt->do_write(data, writer, type, buf, nbytes);
}
CURLcode Curl_cwriter_flush(struct Curl_easy *data,
struct Curl_cwriter *writer)
{
if(!writer) {
return CURLE_WRITE_ERROR;
}
return writer->cwt->do_flush(data, writer);
}
CURLcode Curl_cwriter_def_init(struct Curl_easy *data,
struct Curl_cwriter *writer)
{
@ -340,47 +321,39 @@ static const struct Curl_cwtype cw_raw = {
sizeof(struct Curl_cwriter)
};
static void cwriter_add(struct Curl_easy *data,
struct Curl_cwriter *writer)
{
struct Curl_cwriter **anchor = &data->req.writer.stack;
/* Insert the writer as first in its phase.
* Skip existing writers of lower phases. */
while(*anchor && (*anchor)->phase < writer->phase)
anchor = &((*anchor)->next);
writer->next = *anchor;
*anchor = writer;
}
static CURLcode do_init_writer_stack(struct Curl_easy *data)
{
struct Curl_cwriter *writer;
CURLcode result;
DEBUGASSERT(!data->req.writer_stack);
result = Curl_cwriter_create(&data->req.writer_stack,
DEBUGASSERT(!data->req.writer.stack);
result = Curl_cwriter_create(&data->req.writer.stack,
data, &Curl_cwt_out, CURL_CW_CLIENT);
if(result)
return result;
/* This places the "pause" writer behind the "download" writer that
* is added below. Meaning the "download" can do checks on content length
* and other things *before* write outs are buffered for paused transfers. */
result = Curl_cwriter_create(&writer, data, &Curl_cwt_pause,
CURL_CW_PROTOCOL);
if(!result) {
result = Curl_cwriter_add(data, writer);
if(result)
Curl_cwriter_free(data, writer);
}
if(result)
return result;
result = Curl_cwriter_create(&writer, data, &cw_download, CURL_CW_PROTOCOL);
if(!result) {
result = Curl_cwriter_add(data, writer);
if(result)
Curl_cwriter_free(data, writer);
}
if(result)
return result;
cwriter_add(data, writer);
result = Curl_cwriter_create(&writer, data, &cw_raw, CURL_CW_RAW);
if(!result) {
result = Curl_cwriter_add(data, writer);
if(result)
Curl_cwriter_free(data, writer);
}
if(result)
return result;
cwriter_add(data, writer);
return result;
}
@ -405,14 +378,14 @@ CURLcode Curl_client_write(struct Curl_easy *data, int type, const char *buf,
DEBUGASSERT(!(type & CLIENTWRITE_INFO) ||
((type & ~(CLIENTWRITE_INFO | CLIENTWRITE_EOS)) == 0));
if(!data->req.writer_stack) {
if(!data->req.writer.stack) {
result = do_init_writer_stack(data);
if(result)
return result;
DEBUGASSERT(data->req.writer_stack);
DEBUGASSERT(data->req.writer.stack);
}
result = Curl_cwriter_write(data, data->req.writer_stack, type, buf, len);
result = Curl_cwriter_write(data, data->req.writer.stack, type, buf, len);
CURL_TRC_WRITE(data, "client_write(type=%x, len=%zu) -> %d",
(unsigned int)type, len, (int)result);
return result;
@ -422,14 +395,14 @@ CURLcode Curl_client_flush(struct Curl_easy *data)
{
CURLcode result;
if(!data->req.writer_stack) {
if(!data->req.writer.stack) {
result = do_init_writer_stack(data);
if(result)
return result;
DEBUGASSERT(data->req.writer_stack);
DEBUGASSERT(data->req.writer.stack);
}
result = Curl_cwriter_flush(data, data->req.writer_stack);
result = Curl_cwriter_flush(data, data->req.writer.stack);
CURL_TRC_WRITE(data, "client_flush() -> %d", (int)result);
return result;
}
@ -476,18 +449,33 @@ size_t Curl_cwriter_count(struct Curl_easy *data, Curl_cwriter_phase phase)
struct Curl_cwriter *w;
size_t n = 0;
for(w = data->req.writer_stack; w; w = w->next) {
for(w = data->req.writer.stack; w; w = w->next) {
if(w->phase == phase)
++n;
}
return n;
}
static CURLcode cwriter_ensure_pause_writer(struct Curl_easy *data)
{
struct Curl_cwriter *writer =
Curl_cwriter_get_by_type(data, &Curl_cwt_pause);
CURLcode result = CURLE_OK;
if(!writer) {
result = Curl_cwriter_create(&writer, data, &Curl_cwt_pause,
CURL_CW_BEFORE_DECODE);
if(!result)
cwriter_add(data, writer);
}
return result;
}
CURLcode Curl_cwriter_add(struct Curl_easy *data,
struct Curl_cwriter *writer)
{
CURLcode result;
struct Curl_cwriter **anchor = &data->req.writer_stack;
struct Curl_cwriter **anchor = &data->req.writer.stack;
if(!*anchor) {
result = do_init_writer_stack(data);
@ -495,12 +483,16 @@ CURLcode Curl_cwriter_add(struct Curl_easy *data,
return result;
}
/* Insert the writer as first in its phase.
* Skip existing writers of lower phases. */
while(*anchor && (*anchor)->phase < writer->phase)
anchor = &((*anchor)->next);
writer->next = *anchor;
*anchor = writer;
if(writer->phase == CURL_CW_CONTENT_DECODE) {
/* On adding a content decoder, add the pause writer. Do this
* BEFORE the given writer as any failure will make the
* caller destroy the writer again. */
result = cwriter_ensure_pause_writer(data);
if(result)
return result;
}
cwriter_add(data, writer);
return CURLE_OK;
}
@ -508,7 +500,7 @@ struct Curl_cwriter *Curl_cwriter_get_by_name(struct Curl_easy *data,
const char *name)
{
struct Curl_cwriter *writer;
for(writer = data->req.writer_stack; writer; writer = writer->next) {
for(writer = data->req.writer.stack; writer; writer = writer->next) {
if(!strcmp(name, writer->cwt->name))
return writer;
}
@ -519,34 +511,17 @@ struct Curl_cwriter *Curl_cwriter_get_by_type(struct Curl_easy *data,
const struct Curl_cwtype *cwt)
{
struct Curl_cwriter *writer;
for(writer = data->req.writer_stack; writer; writer = writer->next) {
for(writer = data->req.writer.stack; writer; writer = writer->next) {
if(writer->cwt == cwt)
return writer;
}
return NULL;
}
bool Curl_cwriter_is_content_decoding(struct Curl_easy *data)
{
struct Curl_cwriter *writer;
for(writer = data->req.writer_stack; writer; writer = writer->next) {
if(writer->phase == CURL_CW_CONTENT_DECODE)
return TRUE;
}
return FALSE;
}
bool Curl_cwriter_is_paused(struct Curl_easy *data)
{
return Curl_cw_out_is_paused(data);
}
CURLcode Curl_cwriter_unpause(struct Curl_easy *data)
{
CURLcode result = Curl_cw_out_unpause(data);
if(!result)
result = Curl_cwriter_flush(data, data->req.writer_stack);
return result;
data->req.writer.paused = FALSE;
return Curl_cwriter_flush(data, data->req.writer.stack);
}
CURLcode Curl_creader_read(struct Curl_easy *data,
@ -1141,9 +1116,9 @@ static CURLcode do_init_reader_stack(struct Curl_easy *data,
DEBUGASSERT(r);
DEBUGASSERT(r->crt);
DEBUGASSERT(r->phase == CURL_CR_CLIENT);
DEBUGASSERT(!data->req.reader_stack);
DEBUGASSERT(!data->req.reader.stack);
data->req.reader_stack = r;
data->req.reader.stack = r;
clen = r->crt->total_length(data, r);
/* if we do not have 0 length init, and crlf conversion is wanted,
* add the reader for it */
@ -1184,7 +1159,7 @@ CURLcode Curl_creader_add(struct Curl_easy *data,
struct Curl_creader *reader)
{
CURLcode result;
struct Curl_creader **anchor = &data->req.reader_stack;
struct Curl_creader **anchor = &data->req.reader.stack;
if(!*anchor) {
result = Curl_creader_set_fread(data, data->state.infilesize);
@ -1227,11 +1202,11 @@ CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen,
DEBUGASSERT(eos);
*nread = 0;
if(!data->req.reader_stack) {
if(!data->req.reader.stack) {
result = Curl_creader_set_fread(data, data->state.infilesize);
if(result)
return result;
DEBUGASSERT(data->req.reader_stack);
DEBUGASSERT(data->req.reader.stack);
}
if(!data->req.reader_started) {
Curl_rlimit_start(&data->progress.ul.rlimit, Curl_pgrs_now(data), -1);
@ -1248,7 +1223,7 @@ CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen,
if(ul_avail < (curl_off_t)blen)
blen = (size_t)ul_avail;
}
result = Curl_creader_read(data, data->req.reader_stack, buf, blen,
result = Curl_creader_read(data, data->req.reader.stack, buf, blen,
nread, eos);
out:
@ -1259,7 +1234,7 @@ out:
bool Curl_creader_needs_rewind(struct Curl_easy *data)
{
struct Curl_creader *reader = data->req.reader_stack;
struct Curl_creader *reader = data->req.reader.stack;
while(reader) {
if(reader->crt->needs_rewind(data, reader)) {
CURL_TRC_READ(data, "client reader needs rewind before next request");
@ -1445,13 +1420,13 @@ out:
curl_off_t Curl_creader_total_length(struct Curl_easy *data)
{
struct Curl_creader *r = data->req.reader_stack;
struct Curl_creader *r = data->req.reader.stack;
return r ? r->crt->total_length(data, r) : -1;
}
curl_off_t Curl_creader_client_length(struct Curl_easy *data)
{
struct Curl_creader *r = data->req.reader_stack;
struct Curl_creader *r = data->req.reader.stack;
while(r && r->phase != CURL_CR_CLIENT)
r = r->next;
return r ? r->crt->total_length(data, r) : -1;
@ -1459,7 +1434,7 @@ curl_off_t Curl_creader_client_length(struct Curl_easy *data)
CURLcode Curl_creader_resume_from(struct Curl_easy *data, curl_off_t offset)
{
struct Curl_creader *r = data->req.reader_stack;
struct Curl_creader *r = data->req.reader.stack;
while(r && r->phase != CURL_CR_CLIENT)
r = r->next;
return r ? r->crt->resume_from(data, r, offset) : CURLE_READ_ERROR;
@ -1467,7 +1442,7 @@ CURLcode Curl_creader_resume_from(struct Curl_easy *data, curl_off_t offset)
CURLcode Curl_creader_unpause(struct Curl_easy *data)
{
struct Curl_creader *reader = data->req.reader_stack;
struct Curl_creader *reader = data->req.reader.stack;
CURLcode result = CURLE_OK;
while(reader) {
@ -1482,7 +1457,7 @@ CURLcode Curl_creader_unpause(struct Curl_easy *data)
bool Curl_creader_is_paused(struct Curl_easy *data)
{
struct Curl_creader *reader = data->req.reader_stack;
struct Curl_creader *reader = data->req.reader.stack;
while(reader) {
if(reader->crt->is_paused(data, reader))
@ -1494,7 +1469,7 @@ bool Curl_creader_is_paused(struct Curl_easy *data)
void Curl_creader_done(struct Curl_easy *data, int premature)
{
struct Curl_creader *reader = data->req.reader_stack;
struct Curl_creader *reader = data->req.reader.stack;
while(reader) {
reader->crt->done(data, reader, premature);
reader = reader->next;
@ -1505,7 +1480,7 @@ struct Curl_creader *Curl_creader_get_by_type(struct Curl_easy *data,
const struct Curl_crtype *crt)
{
struct Curl_creader *r;
for(r = data->req.reader_stack; r; r = r->next) {
for(r = data->req.reader.stack; r; r = r->next) {
if(r->crt == crt)
return r;
}

View file

@ -24,6 +24,7 @@
*
***************************************************************************/
#include "curl_setup.h"
#include "cw-out.h"
/**
* Type of data that is being written to the client (application)
@ -104,6 +105,7 @@ typedef enum {
CURL_CW_RAW, /* raw data written, before any decoding */
CURL_CW_TRANSFER_DECODE, /* remove transfer-encodings */
CURL_CW_PROTOCOL, /* after transfer, but before content decoding */
CURL_CW_BEFORE_DECODE, /* after protocol, but before content decoding */
CURL_CW_CONTENT_DECODE, /* remove content-encodings */
CURL_CW_CLIENT /* data written to client */
} Curl_cwriter_phase;
@ -176,22 +178,16 @@ struct Curl_cwriter *Curl_cwriter_get_by_type(struct Curl_easy *data,
struct Curl_cwriter *Curl_cwriter_get_by_name(struct Curl_easy *data,
const char *name);
/**
* Convenience method for calling `writer->do_write()` that
* checks for NULL writer.
*/
CURLcode Curl_cwriter_write(struct Curl_easy *data,
struct Curl_cwriter *writer, int type,
const char *buf, size_t nbytes);
CURLcode Curl_cwriter_flush(struct Curl_easy *data,
struct Curl_cwriter *writer);
/* Convenience method for calling `writer->do_write()` that
* checks for NULL writer. */
#define Curl_cwriter_write(d, w, t, b, n) \
((w) ? (w)->cwt->do_write((d), (w), (t), (b), (n)) : CURLE_WRITE_ERROR)
/**
* Return TRUE iff client writer is paused.
*/
bool Curl_cwriter_is_paused(struct Curl_easy *data);
#define Curl_cwriter_flush(d, w) \
((w) ? (w)->cwt->do_flush((d), (w)) : CURLE_WRITE_ERROR)
bool Curl_cwriter_is_content_decoding(struct Curl_easy *data);
/* TRUE if client writer is paused. */
#define Curl_cwriter_is_paused(d) ((bool)(d)->req.writer.paused)
/**
* Unpause client writer and flush any buffered date to the client.