transfer: upload performance; avoid tiny send

Append to the upload buffer when only small amount remains in buffer
rather than performing a separate tiny send to empty buffer.

Avoid degenerative upload behavior which might cause curl to send mostly
1-byte DATA frames after exhausing the h2 send window size

Related discussion: https://github.com/nghttp2/nghttp2/issues/1722

Signed-off-by: Glenn Strauss <gstrauss@gluelogic.com>
Closes #8965
This commit is contained in:
Glenn Strauss 2022-06-06 13:02:30 -04:00 committed by Daniel Stenberg
parent aea8ac14df
commit 7f43f3dc59
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
3 changed files with 35 additions and 10 deletions

View File

@ -1820,7 +1820,9 @@ static CURLcode smtp_parse_address(struct Curl_easy *data, const char *fqma,
return result; return result;
} }
CURLcode Curl_smtp_escape_eob(struct Curl_easy *data, const ssize_t nread) CURLcode Curl_smtp_escape_eob(struct Curl_easy *data,
const ssize_t nread,
const ssize_t offset)
{ {
/* When sending a SMTP payload we must detect CRLF. sequences making sure /* When sending a SMTP payload we must detect CRLF. sequences making sure
they are sent as CRLF.. instead, as a . on the beginning of a line will they are sent as CRLF.. instead, as a . on the beginning of a line will
@ -1854,7 +1856,9 @@ CURLcode Curl_smtp_escape_eob(struct Curl_easy *data, const ssize_t nread)
/* This loop can be improved by some kind of Boyer-Moore style of /* This loop can be improved by some kind of Boyer-Moore style of
approach but that is saved for later... */ approach but that is saved for later... */
for(i = 0, si = 0; i < nread; i++) { if(offset)
memcpy(scratch, data->req.upload_fromhere, offset);
for(i = offset, si = offset; i < nread; i++) {
if(SMTP_EOB[smtp->eob] == data->req.upload_fromhere[i]) { if(SMTP_EOB[smtp->eob] == data->req.upload_fromhere[i]) {
smtp->eob++; smtp->eob++;

View File

@ -93,6 +93,8 @@ extern const struct Curl_handler Curl_handler_smtps;
#define SMTP_EOB_REPL "\x0d\x0a\x2e\x2e" #define SMTP_EOB_REPL "\x0d\x0a\x2e\x2e"
#define SMTP_EOB_REPL_LEN 4 #define SMTP_EOB_REPL_LEN 4
CURLcode Curl_smtp_escape_eob(struct Curl_easy *data, const ssize_t nread); CURLcode Curl_smtp_escape_eob(struct Curl_easy *data,
const ssize_t nread,
const ssize_t offset);
#endif /* HEADER_CURL_SMTP_H */ #endif /* HEADER_CURL_SMTP_H */

View File

@ -899,6 +899,9 @@ static void win_update_buffer_size(curl_socket_t sockfd)
#define win_update_buffer_size(x) #define win_update_buffer_size(x)
#endif #endif
#define curl_upload_refill_watermark(data) \
((ssize_t)((data)->set.upload_buffer_size >> 5))
/* /*
* Send data to upload to the server, when the socket is writable. * Send data to upload to the server, when the socket is writable.
*/ */
@ -920,13 +923,25 @@ static CURLcode readwrite_upload(struct Curl_easy *data,
do { do {
curl_off_t nbody; curl_off_t nbody;
ssize_t offset = 0;
if(0 != k->upload_present &&
k->upload_present < curl_upload_refill_watermark(data) &&
!k->upload_chunky &&/*(variable sized chunked header; append not safe)*/
!k->upload_done && /*!(k->upload_done once k->upload_present sent)*/
!(k->writebytecount + k->upload_present - k->pendingheader ==
data->state.infilesize)) {
offset = k->upload_present;
}
/* only read more data if there's no upload data already /* only read more data if there's no upload data already
present in the upload buffer */ present in the upload buffer, or if appending to upload buffer */
if(0 == k->upload_present) { if(0 == k->upload_present || offset) {
result = Curl_get_upload_buffer(data); result = Curl_get_upload_buffer(data);
if(result) if(result)
return result; return result;
if(offset && k->upload_fromhere != data->state.ulbuf)
memmove(data->state.ulbuf, k->upload_fromhere, offset);
/* init the "upload from here" pointer */ /* init the "upload from here" pointer */
k->upload_fromhere = data->state.ulbuf; k->upload_fromhere = data->state.ulbuf;
@ -959,12 +974,14 @@ static CURLcode readwrite_upload(struct Curl_easy *data,
sending_http_headers = FALSE; sending_http_headers = FALSE;
} }
result = Curl_fillreadbuffer(data, data->set.upload_buffer_size, k->upload_fromhere += offset;
result = Curl_fillreadbuffer(data, data->set.upload_buffer_size-offset,
&fillcount); &fillcount);
k->upload_fromhere -= offset;
if(result) if(result)
return result; return result;
nread = fillcount; nread = offset + fillcount;
} }
else else
nread = 0; /* we're done uploading/reading */ nread = 0; /* we're done uploading/reading */
@ -1006,7 +1023,9 @@ static CURLcode readwrite_upload(struct Curl_easy *data,
* That means the hex values for ASCII CR (0x0d) & LF (0x0a) * That means the hex values for ASCII CR (0x0d) & LF (0x0a)
* must be used instead of the escape sequences \r & \n. * must be used instead of the escape sequences \r & \n.
*/ */
for(i = 0, si = 0; i < nread; i++, si++) { if(offset)
memcpy(data->state.scratch, k->upload_fromhere, offset);
for(i = offset, si = offset; i < nread; i++, si++) {
if(k->upload_fromhere[i] == 0x0a) { if(k->upload_fromhere[i] == 0x0a) {
data->state.scratch[si++] = 0x0d; data->state.scratch[si++] = 0x0d;
data->state.scratch[si] = 0x0a; data->state.scratch[si] = 0x0a;
@ -1036,12 +1055,12 @@ static CURLcode readwrite_upload(struct Curl_easy *data,
#ifndef CURL_DISABLE_SMTP #ifndef CURL_DISABLE_SMTP
if(conn->handler->protocol & PROTO_FAMILY_SMTP) { if(conn->handler->protocol & PROTO_FAMILY_SMTP) {
result = Curl_smtp_escape_eob(data, nread); result = Curl_smtp_escape_eob(data, nread, offset);
if(result) if(result)
return result; return result;
} }
#endif /* CURL_DISABLE_SMTP */ #endif /* CURL_DISABLE_SMTP */
} /* if 0 == k->upload_present */ } /* if 0 == k->upload_present or appended to upload buffer */
else { else {
/* We have a partial buffer left from a previous "round". Use /* We have a partial buffer left from a previous "round". Use
that instead of reading more data */ that instead of reading more data */