mirror of
https://github.com/curl/curl.git
synced 2026-07-31 13:58:04 +03:00
http: introduce CURLOPT_FORBID_RETRY_ON_REUSE
This patch introduces a curlopt to allow application to request curl to not retry a request upon a broken reused connection. Currently libcurl transparently retry a failed request over a new connection if all of the following are true: - The previous broken connection was a reused connection. - The previous broken connection was reset. - Max retries not exceeded. This behavior relies the follwing assumptions: - The connection reset might have occurred before the reused connection was picked up for reuse. - The connection reset only becomes visible because libcurl attempted to transmit request over such a connection. The above assumptions do not always hold in the field. The following sequence will produce identical externally visible behavior: - Libcurl transmits request over the connection. - The peer might have received the request, but the connection cuts off right before it could send a response (crash or whatever) - Libcurl receives connection reset shortly after. In this case libcurl will choose to establish a new TCP connection and issue the identical request for a second time. Given a client side internal state machine implementing a protocol that requires action upon a connection reset, this behavior has chance to cause the state machine to miss a state transition and affect correctness. This behavior used to be hardwired with no way to disable from users unless the user explicitly requests `CURLOPT_FORBID_REUSE` and `CURLOPT_FRESH_CONNECT` at the cost of significant overhead. The new boolean flag `CURLOPT_FORBID_RETRY_ON_REUSE` decouples retries from connection reuse, allowing a user to explicitly supress this retry behavior without losing connection reuse benefits. When the option is set, a connection reset will be returned as an error to the caller. The caller assumes responsibility to perform retries according to the application protocol in this case. The flag defaults to FALSE so that current behavior remains the same.
This commit is contained in:
parent
3c64ffaff4
commit
f92d9c0c5f
11 changed files with 139 additions and 2 deletions
|
|
@ -332,6 +332,12 @@ Callback for wildcard matching. See CURLOPT_FNMATCH_FUNCTION(3)
|
|||
|
||||
Follow HTTP redirects. See CURLOPT_FOLLOWLOCATION(3)
|
||||
|
||||
## CURLOPT_FORBID_RETRY_ON_REUSE
|
||||
|
||||
Prevent curl from transparently retry a transfer on a new connection when a
|
||||
previously reused connection was reset or otherwise no longer unavailable.
|
||||
See CURLOPT_FORBID_RETRY_ON_REUSE(3)
|
||||
|
||||
## CURLOPT_FORBID_REUSE
|
||||
|
||||
Prevent subsequent connections from reusing this. See CURLOPT_FORBID_REUSE(3)
|
||||
|
|
|
|||
106
docs/libcurl/opts/CURLOPT_FORBID_RETRY_ON_REUSE.md
Normal file
106
docs/libcurl/opts/CURLOPT_FORBID_RETRY_ON_REUSE.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
---
|
||||
c: Copyright (C) 2025 Hewlett Packard Enterprise Development LP
|
||||
SPDX-License-Identifier: curl
|
||||
Title: CURLOPT_FORBID_RETRY_ON_REUSE
|
||||
Section: 3
|
||||
Source: libcurl
|
||||
See-also:
|
||||
- CURLOPT_FRESH_CONNECT (3)
|
||||
- CURLOPT_FORBID_REUSE (3)
|
||||
Protocol:
|
||||
- All
|
||||
Added-in: 8.16.0
|
||||
---
|
||||
|
||||
# NAME
|
||||
|
||||
CURLOPT_FORBID_RETRY_ON_REUSE - prevent retry in case of reused connection is reset
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
~~~c
|
||||
#include <curl/curl.h>
|
||||
|
||||
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_FORBID_RETRY_ON_REUSE,
|
||||
long forbid_retry);
|
||||
~~~
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Pass a long. Set *forbid_retry* to 1 to prevent libcurl from resubmitting a
|
||||
transfer on a new connection when the previous reused connection was reset.
|
||||
Instead, the reset is returned to the caller as a `CURLE_RECV_ERROR`.
|
||||
|
||||
This option only affects retry decision if the connection used to perform
|
||||
the request (and from which CURL received a TCP reset) is reused.
|
||||
|
||||
Normally, libcurl considers connection resets on reused connections as a
|
||||
transient timing issue. The reset event is only visible to curl at the time
|
||||
libcurl attempts to issue transfer on that connection. As a result,
|
||||
sometimes libcurl may issue more than 1 identical transfers in a row on
|
||||
different connections with a single `curl_easy_perform()` call. Caller is
|
||||
not informed about any previous transfer attempts that may or may not have
|
||||
arrived at the server before the reset happened.
|
||||
|
||||
This can break transactional application-level protocols if the protocol
|
||||
state machine considers connection state changes as part of state
|
||||
transition edges, or the protocol involves non-idempotent requests with
|
||||
side effects.
|
||||
|
||||
Before introduction of this option, the only way to avoid unobservable
|
||||
retries was to set CURLOPT_FORBID_REUSE(3) to 1. However, without
|
||||
connection reuse and keepalive, the application pays significant
|
||||
overhead from the TCP and TLS handshake for every transfer. This option
|
||||
decouples implicit retry behavior from connection reuse, allowing the
|
||||
application to benefit from connection reuse without risking unobservable
|
||||
retries.
|
||||
|
||||
Set to 0 to have libcurl transparently retry the transfer on a new
|
||||
connection if the reused connection was reset (default behavior).
|
||||
|
||||
# DEFAULT
|
||||
|
||||
0
|
||||
|
||||
# %PROTOCOLS%
|
||||
|
||||
# EXAMPLE
|
||||
|
||||
~~~c
|
||||
#include <unistd.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
CURL *curl = curl_easy_init();
|
||||
if(curl) {
|
||||
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/");
|
||||
curl_easy_setopt(curl, CURLOPT_FORBID_RETRY_ON_REUSE, 1L);
|
||||
|
||||
/*
|
||||
* This request will establish a connection and retained it for reuse
|
||||
* after the transfer is done.
|
||||
*/
|
||||
curl_easy_perform(curl);
|
||||
|
||||
/* Wait long enough for the server to drop connection. */
|
||||
sleep(60);
|
||||
|
||||
/*
|
||||
* curl will fail this request from the reset instead of
|
||||
* transmitting the same transfer again over a new connection.
|
||||
*/
|
||||
curl_easy_perform(curl);
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
# %AVAILABILITY%
|
||||
|
||||
# RETURN VALUE
|
||||
|
||||
curl_easy_setopt(3) returns a CURLcode indicating success or error.
|
||||
|
||||
CURLE_OK (0) means everything was OK, non-zero means an error occurred, see
|
||||
libcurl-errors(3).
|
||||
|
|
@ -185,6 +185,7 @@ man_MANS = \
|
|||
CURLOPT_FNMATCH_DATA.3 \
|
||||
CURLOPT_FNMATCH_FUNCTION.3 \
|
||||
CURLOPT_FOLLOWLOCATION.3 \
|
||||
CURLOPT_FORBID_RETRY_ON_REUSE.3 \
|
||||
CURLOPT_FORBID_REUSE.3 \
|
||||
CURLOPT_FRESH_CONNECT.3 \
|
||||
CURLOPT_FTP_ACCOUNT.3 \
|
||||
|
|
|
|||
|
|
@ -646,6 +646,7 @@ CURLOPT_FILETIME 7.5
|
|||
CURLOPT_FNMATCH_DATA 7.21.0
|
||||
CURLOPT_FNMATCH_FUNCTION 7.21.0
|
||||
CURLOPT_FOLLOWLOCATION 7.1
|
||||
CURLOPT_FORBID_RETRY_ON_REUSE 8.16.0
|
||||
CURLOPT_FORBID_REUSE 7.7
|
||||
CURLOPT_FRESH_CONNECT 7.7
|
||||
CURLOPT_FTP_ACCOUNT 7.13.0
|
||||
|
|
|
|||
|
|
@ -2258,6 +2258,10 @@ typedef enum {
|
|||
/* set TLS supported signature algorithms */
|
||||
CURLOPT(CURLOPT_SSL_SIGNATURE_ALGORITHMS, CURLOPTTYPE_STRINGPOINT, 328),
|
||||
|
||||
/* prevent transparent retries on a new connection when a previously
|
||||
* reused connection was reset or otherwise no longer unavailable. */
|
||||
CURLOPT(CURLOPT_FORBID_RETRY_ON_REUSE, CURLOPTTYPE_LONG, 329),
|
||||
|
||||
CURLOPT_LASTENTRY /* the last unused */
|
||||
} CURLoption;
|
||||
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ const struct curl_easyoption Curl_easyopts[] = {
|
|||
{"FNMATCH_DATA", CURLOPT_FNMATCH_DATA, CURLOT_CBPTR, 0},
|
||||
{"FNMATCH_FUNCTION", CURLOPT_FNMATCH_FUNCTION, CURLOT_FUNCTION, 0},
|
||||
{"FOLLOWLOCATION", CURLOPT_FOLLOWLOCATION, CURLOT_LONG, 0},
|
||||
{"FORBID_RETRY_ON_REUSE", CURLOPT_FORBID_RETRY_ON_REUSE, CURLOT_LONG, 0},
|
||||
{"FORBID_REUSE", CURLOPT_FORBID_REUSE, CURLOT_LONG, 0},
|
||||
{"FRESH_CONNECT", CURLOPT_FRESH_CONNECT, CURLOT_LONG, 0},
|
||||
{"FTPAPPEND", CURLOPT_APPEND, CURLOT_LONG, CURLOT_FLAG_ALIAS},
|
||||
|
|
@ -380,6 +381,6 @@ const struct curl_easyoption Curl_easyopts[] = {
|
|||
*/
|
||||
int Curl_easyopts_check(void)
|
||||
{
|
||||
return (CURLOPT_LASTENTRY % 10000) != (328 + 1);
|
||||
return (CURLOPT_LASTENTRY % 10000) != (329 + 1);
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -452,6 +452,13 @@ static CURLcode setopt_bool(struct Curl_easy *data, CURLoption option,
|
|||
*/
|
||||
s->reuse_forbid = enabled;
|
||||
break;
|
||||
case CURLOPT_FORBID_RETRY_ON_REUSE:
|
||||
/**
|
||||
* No retry under any circumstances even when a reset happens on a reused
|
||||
* connection.
|
||||
*/
|
||||
s->retry_on_reuse_forbid = enabled;
|
||||
break;
|
||||
case CURLOPT_FRESH_CONNECT:
|
||||
/*
|
||||
* This transfer shall not use a previously cached connection but
|
||||
|
|
|
|||
|
|
@ -667,7 +667,7 @@ CURLcode Curl_retry_request(struct Curl_easy *data, char **url)
|
|||
return CURLE_OK;
|
||||
|
||||
if((data->req.bytecount + data->req.headerbytecount == 0) &&
|
||||
conn->bits.reuse &&
|
||||
conn->bits.reuse && conn->bits.retry_on_reuse &&
|
||||
(!data->req.no_body || (conn->handler->protocol & PROTO_FAMILY_HTTP))
|
||||
#ifndef CURL_DISABLE_RTSP
|
||||
&& (data->set.rtspreq != RTSPREQ_RECEIVE)
|
||||
|
|
|
|||
|
|
@ -3514,6 +3514,10 @@ static CURLcode create_conn(struct Curl_easy *data,
|
|||
*************************************************************/
|
||||
if((conn->given->flags&PROTOPT_SSL) && conn->bits.httpproxy)
|
||||
conn->bits.tunnel_proxy = TRUE;
|
||||
|
||||
/* Carry over the no-retry-on-reuse into connection bits. */
|
||||
conn->bits.retry_on_reuse = !data->set.retry_on_reuse_forbid;
|
||||
|
||||
#endif
|
||||
|
||||
/*************************************************************
|
||||
|
|
|
|||
|
|
@ -379,6 +379,8 @@ struct ConnectBits {
|
|||
/* always modify bits.close with the connclose() and connkeep() macros! */
|
||||
BIT(close); /* if set, we close the connection after this request */
|
||||
BIT(reuse); /* if set, this is a reused connection */
|
||||
BIT(retry_on_reuse); /* if set, a reused connection after connection reset
|
||||
is allowed to retry the request */
|
||||
BIT(altused); /* this is an alt-svc "redirect" */
|
||||
BIT(conn_to_host); /* if set, this connection has a "connect to host"
|
||||
that overrides the host in the URL */
|
||||
|
|
@ -1605,6 +1607,7 @@ struct UserDefined {
|
|||
#endif
|
||||
BIT(reuse_forbid); /* forbidden to be reused, close after use */
|
||||
BIT(reuse_fresh); /* do not reuse an existing connection */
|
||||
BIT(retry_on_reuse_forbid); /* do not retry under connection reset */
|
||||
BIT(no_signal); /* do not use any signal/alarm handler */
|
||||
BIT(tcp_nodelay); /* whether to enable TCP_NODELAY or not */
|
||||
BIT(ignorecl); /* ignore content length */
|
||||
|
|
|
|||
|
|
@ -1398,6 +1398,8 @@ static CURLcode add_parallel_transfers(CURLM *multi, CURLSH *share,
|
|||
#ifdef DEBUGBUILD
|
||||
if(getenv("CURL_FORBID_REUSE"))
|
||||
(void)curl_easy_setopt(per->curl, CURLOPT_FORBID_REUSE, 1L);
|
||||
if(getenv("CURL_FORBID_RETRY_ON_REUSE"))
|
||||
(void)curl_easy_setopt(per->curl, CURLOPT_FORBID_RETRY_ON_REUSE, 1L);
|
||||
#endif
|
||||
|
||||
mcode = curl_multi_add_handle(multi, per->curl);
|
||||
|
|
@ -1868,6 +1870,8 @@ static CURLcode serial_transfers(CURLSH *share)
|
|||
#ifdef DEBUGBUILD
|
||||
if(getenv("CURL_FORBID_REUSE"))
|
||||
(void)curl_easy_setopt(per->curl, CURLOPT_FORBID_REUSE, 1L);
|
||||
if(getenv("CURL_FORBID_RETRY_ON_REUSE"))
|
||||
(void)curl_easy_setopt(per->curl, CURLOPT_FORBID_RETRY_ON_REUSE, 1L);
|
||||
|
||||
if(global->test_duphandle) {
|
||||
CURL *dup = curl_easy_duphandle(per->curl);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue