1
0
mirror of https://github.com/moparisthebest/curl synced 2024-12-21 23:58:49 -05:00

http2: make sure stream errors don't needlessly close the connection

With HTTP/2 each transfer is made in an indivial logical stream over the
connection, making most previous errors that caused the connection to get
forced-closed now instead just kill the stream and not the connection.

Fixes #941
This commit is contained in:
Daniel Stenberg 2016-08-11 14:00:23 +02:00
parent a6ddd6555e
commit 3533def3d5
11 changed files with 152 additions and 88 deletions

View File

@ -63,6 +63,7 @@ problems may have been fixed or changed somewhat since this was written!
7.5 ASCII FTP 7.5 ASCII FTP
7.6 FTP with NULs in URL parts 7.6 FTP with NULs in URL parts
7.7 FTP and empty path parts in the URL 7.7 FTP and empty path parts in the URL
7.8 Premature transfer end but healthy control channel
8. TELNET 8. TELNET
8.1 TELNET and time limtiations don't work 8.1 TELNET and time limtiations don't work
@ -441,6 +442,17 @@ problems may have been fixed or changed somewhat since this was written!
the user wants to reach the root dir (this exception SHALL remain even when the user wants to reach the root dir (this exception SHALL remain even when
this bug is fixed). this bug is fixed).
7.8 Premature transfer end but healthy control channel
When 'multi_done' is called before the transfer has been completed the normal
way, it is considered a "premature" transfer end. In this situation, libcurl
closes the connection assuming it doesn't know the state of the connection so
it can't be reused for subsequent requests.
With FTP however, this isn't necessarily true but there are a bunch of
situations (listed in the ftp_done code) where it *could* keep the connection
alive even in this situation - but the current code doesn't. Fixing this would
allow libcurl to reuse FTP connections better.
8. TELNET 8. TELNET

View File

@ -1371,25 +1371,26 @@ CURLcode Curl_socket(struct connectdata *conn,
} }
#ifdef CURLDEBUG
/* /*
* Curl_conncontrol() is used to set the conn->bits.close bit on or off. It * Curl_conncontrol() marks streams or connection for closure.
* MUST be called with the connclose() or connkeep() macros with a stated
* reason. The reason is only shown in debug builds but helps to figure out
* decision paths when connections are or aren't re-used as expected.
*/ */
void Curl_conncontrol(struct connectdata *conn, bool closeit, void Curl_conncontrol(struct connectdata *conn,
const char *reason) int ctrl /* see defines in header */
{ #ifdef CURLDEBUG
#if defined(CURL_DISABLE_VERBOSE_STRINGS) , const char *reason
(void) reason;
#endif #endif
if(closeit != conn->bits.close) { )
infof(conn->data, "Marked for [%s]: %s\n", closeit?"closure":"keep alive", {
reason); /* close if a connection, or a stream that isn't multiplexed */
bool closeit = (ctrl == CONNCTRL_CONNECTION) ||
((ctrl == CONNCTRL_STREAM) && !(conn->handler->flags & PROTOPT_STREAM));
if((ctrl == CONNCTRL_STREAM) &&
(conn->handler->flags & PROTOPT_STREAM))
DEBUGF(infof(conn->data, "Kill stream: %s\n", reason));
else if(closeit != conn->bits.close) {
DEBUGF(infof(conn->data, "Marked for [%s]: %s\n",
closeit?"closure":"keep alive", reason));
conn->bits.close = closeit; /* the only place in the source code that conn->bits.close = closeit; /* the only place in the source code that
should assign this bit */ should assign this bit */
} }
} }
#endif

View File

@ -7,7 +7,7 @@
* | (__| |_| | _ <| |___ * | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____| * \___|\___/|_| \_\_____|
* *
* Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al. * Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel@haxx.se>, et al.
* *
* This software is licensed as described in the file COPYING, which * This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms * you should have received as part of this distribution. The terms
@ -104,21 +104,37 @@ CURLcode Curl_socket(struct connectdata *conn,
void Curl_tcpnodelay(struct connectdata *conn, curl_socket_t sockfd); void Curl_tcpnodelay(struct connectdata *conn, curl_socket_t sockfd);
#ifdef CURLDEBUG
/* /*
* Curl_connclose() sets the bit.close bit to TRUE with an explanation. * Curl_conncontrol() marks the end of a connection/stream. The 'closeit'
* Nothing else. * argument specifies if it is the end of a connection or a stream.
*
* For stream-based protocols (such as HTTP/2), a stream close will not cause
* a connection close. Other protocols will close the connection for both
* cases.
*
* It sets the bit.close bit to TRUE (with an explanation for debug builds),
* when the connection will close.
*/ */
#define CONNCTRL_KEEP 0 /* undo a marked closure */
#define CONNCTRL_CONNECTION 1
#define CONNCTRL_STREAM 2
void Curl_conncontrol(struct connectdata *conn, void Curl_conncontrol(struct connectdata *conn,
bool closeit, int closeit
const char *reason); #ifdef CURLDEBUG
#define connclose(x,y) Curl_conncontrol(x,TRUE, y) , const char *reason
#define connkeep(x,y) Curl_conncontrol(x, FALSE, y) #endif
);
#ifdef CURLDEBUG
#define streamclose(x,y) Curl_conncontrol(x, CONNCTRL_STREAM, y)
#define connclose(x,y) Curl_conncontrol(x, CONNCTRL_CONNECTION, y)
#define connkeep(x,y) Curl_conncontrol(x, CONNCTRL_KEEP, y)
#else /* if !CURLDEBUG */ #else /* if !CURLDEBUG */
#define streamclose(x,y) Curl_conncontrol(x, CONNCTRL_STREAM)
#define connclose(x,y) (x)->bits.close = TRUE #define connclose(x,y) Curl_conncontrol(x, CONNCTRL_CONNECTION)
#define connkeep(x,y) (x)->bits.close = FALSE #define connkeep(x,y) Curl_conncontrol(x, CONNCTRL_KEEP)
#endif #endif
#endif /* HEADER_CURL_CONNECT_H */ #endif /* HEADER_CURL_CONNECT_H */

View File

@ -462,7 +462,7 @@ static CURLcode http_perhapsrewind(struct connectdata *conn)
#endif #endif
/* This is not NTLM or many bytes left to send: close */ /* This is not NTLM or many bytes left to send: close */
connclose(conn, "Mid-auth HTTP and much data left to send"); streamclose(conn, "Mid-auth HTTP and much data left to send");
data->req.size = 0; /* don't download any more than 0 bytes */ data->req.size = 0; /* don't download any more than 0 bytes */
/* There still is data left to send, but this connection is marked for /* There still is data left to send, but this connection is marked for
@ -1452,9 +1452,8 @@ CURLcode Curl_http_done(struct connectdata *conn,
{ {
struct Curl_easy *data = conn->data; struct Curl_easy *data = conn->data;
struct HTTP *http = data->req.protop; struct HTTP *http = data->req.protop;
#ifdef USE_NGHTTP2
struct http_conn *httpc = &conn->proto.httpc; infof(data, "Curl_http_done: called premature == %d\n", premature);
#endif
Curl_unencode_cleanup(conn); Curl_unencode_cleanup(conn);
@ -1467,7 +1466,7 @@ CURLcode Curl_http_done(struct connectdata *conn,
* Do not close CONNECT_ONLY connections. */ * Do not close CONNECT_ONLY connections. */
if((data->req.httpcode != 401) && (data->req.httpcode != 407) && if((data->req.httpcode != 401) && (data->req.httpcode != 407) &&
!data->set.connect_only) !data->set.connect_only)
connclose(conn, "Negotiate transfer completed"); streamclose(conn, "Negotiate transfer completed");
Curl_cleanup_negotiate(data); Curl_cleanup_negotiate(data);
} }
#endif #endif
@ -1484,27 +1483,7 @@ CURLcode Curl_http_done(struct connectdata *conn,
http->send_buffer = NULL; /* clear the pointer */ http->send_buffer = NULL; /* clear the pointer */
} }
#ifdef USE_NGHTTP2 Curl_http2_done(conn, premature);
if(http->header_recvbuf) {
DEBUGF(infof(data, "free header_recvbuf!!\n"));
Curl_add_buffer_free(http->header_recvbuf);
http->header_recvbuf = NULL; /* clear the pointer */
Curl_add_buffer_free(http->trailer_recvbuf);
http->trailer_recvbuf = NULL; /* clear the pointer */
if(http->push_headers) {
/* if they weren't used and then freed before */
for(; http->push_headers_used > 0; --http->push_headers_used) {
free(http->push_headers[http->push_headers_used - 1]);
}
free(http->push_headers);
http->push_headers = NULL;
}
}
if(http->stream_id) {
nghttp2_session_set_stream_user_data(httpc->h2, http->stream_id, 0);
http->stream_id = 0;
}
#endif
if(HTTPREQ_POST_FORM == data->set.httpreq) { if(HTTPREQ_POST_FORM == data->set.httpreq) {
data->req.bytecount = http->readbytecount + http->writebytecount; data->req.bytecount = http->readbytecount + http->writebytecount;
@ -3118,7 +3097,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
signal the end of the document. */ signal the end of the document. */
infof(data, "no chunk, no close, no size. Assume close to " infof(data, "no chunk, no close, no size. Assume close to "
"signal end\n"); "signal end\n");
connclose(conn, "HTTP: No end-of-message indicator"); streamclose(conn, "HTTP: No end-of-message indicator");
} }
} }
@ -3199,7 +3178,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
*/ */
if(!k->upload_done) { if(!k->upload_done) {
infof(data, "HTTP error before end of send, stop sending\n"); infof(data, "HTTP error before end of send, stop sending\n");
connclose(conn, "Stop sending data before everything sent"); streamclose(conn, "Stop sending data before everything sent");
k->upload_done = TRUE; k->upload_done = TRUE;
k->keepon &= ~KEEP_SEND; /* don't send */ k->keepon &= ~KEEP_SEND; /* don't send */
if(data->state.expect100header) if(data->state.expect100header)
@ -3503,7 +3482,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
/* Negative Content-Length is really odd, and we know it /* Negative Content-Length is really odd, and we know it
happens for example when older Apache servers send large happens for example when older Apache servers send large
files */ files */
connclose(conn, "negative content-length"); streamclose(conn, "negative content-length");
infof(data, "Negative content-length: %" CURL_FORMAT_CURL_OFF_T infof(data, "Negative content-length: %" CURL_FORMAT_CURL_OFF_T
", closing after transfer\n", contentlength); ", closing after transfer\n", contentlength);
} }
@ -3576,7 +3555,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
* the connection will close when this request has been * the connection will close when this request has been
* served. * served.
*/ */
connclose(conn, "Connection: close used"); streamclose(conn, "Connection: close used");
} }
else if(checkprefix("Transfer-Encoding:", k->p)) { else if(checkprefix("Transfer-Encoding:", k->p)) {
/* One or more encodings. We check for chunked and/or a compression /* One or more encodings. We check for chunked and/or a compression

View File

@ -185,7 +185,7 @@ const struct Curl_handler Curl_handler_http2 = {
ZERO_NULL, /* readwrite */ ZERO_NULL, /* readwrite */
PORT_HTTP, /* defport */ PORT_HTTP, /* defport */
CURLPROTO_HTTP, /* protocol */ CURLPROTO_HTTP, /* protocol */
PROTOPT_NONE /* flags */ PROTOPT_STREAM /* flags */
}; };
const struct Curl_handler Curl_handler_http2_ssl = { const struct Curl_handler Curl_handler_http2_ssl = {
@ -205,7 +205,7 @@ const struct Curl_handler Curl_handler_http2_ssl = {
ZERO_NULL, /* readwrite */ ZERO_NULL, /* readwrite */
PORT_HTTP, /* defport */ PORT_HTTP, /* defport */
CURLPROTO_HTTPS, /* protocol */ CURLPROTO_HTTPS, /* protocol */
PROTOPT_SSL /* flags */ PROTOPT_SSL | PROTOPT_STREAM /* flags */
}; };
/* /*
@ -489,8 +489,11 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
} }
stream = data_s->req.protop; stream = data_s->req.protop;
if(!stream) if(!stream) {
DEBUGF(infof(conn->data, "No proto pointer for stream: %x\n",
stream_id));
return NGHTTP2_ERR_CALLBACK_FAILURE; return NGHTTP2_ERR_CALLBACK_FAILURE;
}
DEBUGF(infof(data_s, "on_frame_recv() header %x stream %x\n", DEBUGF(infof(data_s, "on_frame_recv() header %x stream %x\n",
frame->hd.type, stream_id)); frame->hd.type, stream_id));
@ -979,6 +982,43 @@ static int error_callback(nghttp2_session *session,
} }
#endif #endif
void Curl_http2_done(struct connectdata *conn, bool premature)
{
struct Curl_easy *data = conn->data;
struct HTTP *http = data->req.protop;
struct http_conn *httpc = &conn->proto.httpc;
if(http->header_recvbuf) {
DEBUGF(infof(data, "free header_recvbuf!!\n"));
Curl_add_buffer_free(http->header_recvbuf);
http->header_recvbuf = NULL; /* clear the pointer */
Curl_add_buffer_free(http->trailer_recvbuf);
http->trailer_recvbuf = NULL; /* clear the pointer */
if(http->push_headers) {
/* if they weren't used and then freed before */
for(; http->push_headers_used > 0; --http->push_headers_used) {
free(http->push_headers[http->push_headers_used - 1]);
}
free(http->push_headers);
http->push_headers = NULL;
}
}
if(premature) {
/* RST_STREAM */
nghttp2_submit_rst_stream(httpc->h2, NGHTTP2_FLAG_NONE, http->stream_id,
NGHTTP2_STREAM_CLOSED);
if(http->stream_id == httpc->pause_stream_id) {
infof(data, "stopped the pause stream!\n");
httpc->pause_stream_id = 0;
}
}
if(http->stream_id) {
nghttp2_session_set_stream_user_data(httpc->h2, http->stream_id, 0);
http->stream_id = 0;
}
}
/* /*
* Initialize nghttp2 for a Curl connection * Initialize nghttp2 for a Curl connection
*/ */
@ -1378,6 +1418,8 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
socket is not read. But it seems that usually streams are socket is not read. But it seems that usually streams are
notified with its drain property, and socket is read again notified with its drain property, and socket is read again
quickly. */ quickly. */
DEBUGF(infof(data, "stream %x is paused, pause id: %x\n",
stream->stream_id, httpc->pause_stream_id));
*err = CURLE_AGAIN; *err = CURLE_AGAIN;
return -1; return -1;
} }

View File

@ -7,7 +7,7 @@
* | (__| |_| | _ <| |___ * | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____| * \___|\___/|_| \_\_____|
* *
* Copyright (C) 1998 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al. * Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel@haxx.se>, et al.
* *
* This software is licensed as described in the file COPYING, which * This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms * you should have received as part of this distribution. The terms
@ -51,6 +51,7 @@ CURLcode Curl_http2_switched(struct connectdata *conn,
/* called from Curl_http_setup_conn */ /* called from Curl_http_setup_conn */
void Curl_http2_setup_conn(struct connectdata *conn); void Curl_http2_setup_conn(struct connectdata *conn);
void Curl_http2_setup_req(struct Curl_easy *data); void Curl_http2_setup_req(struct Curl_easy *data);
void Curl_http2_done(struct connectdata *conn, bool premature);
#else /* USE_NGHTTP2 */ #else /* USE_NGHTTP2 */
#define Curl_http2_init(x) CURLE_UNSUPPORTED_PROTOCOL #define Curl_http2_init(x) CURLE_UNSUPPORTED_PROTOCOL
#define Curl_http2_send_request(x) CURLE_UNSUPPORTED_PROTOCOL #define Curl_http2_send_request(x) CURLE_UNSUPPORTED_PROTOCOL
@ -61,6 +62,7 @@ void Curl_http2_setup_req(struct Curl_easy *data);
#define Curl_http2_setup_req(x) #define Curl_http2_setup_req(x)
#define Curl_http2_init_state(x) #define Curl_http2_init_state(x)
#define Curl_http2_init_userset(x) #define Curl_http2_init_userset(x)
#define Curl_http2_done(x,y)
#endif #endif
#endif /* HEADER_CURL_HTTP2_H */ #endif /* HEADER_CURL_HTTP2_H */

View File

@ -574,7 +574,7 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn,
free(data->req.newurl); free(data->req.newurl);
data->req.newurl = NULL; data->req.newurl = NULL;
/* failure, close this connection to avoid re-use */ /* failure, close this connection to avoid re-use */
connclose(conn, "proxy CONNECT failure"); streamclose(conn, "proxy CONNECT failure");
Curl_closesocket(conn, conn->sock[sockindex]); Curl_closesocket(conn, conn->sock[sockindex]);
conn->sock[sockindex] = CURL_SOCKET_BAD; conn->sock[sockindex] = CURL_SOCKET_BAD;
} }

View File

@ -569,10 +569,9 @@ static CURLcode multi_done(struct connectdata **connp,
result = CURLE_ABORTED_BY_CALLBACK; result = CURLE_ABORTED_BY_CALLBACK;
} }
if((!premature && if(conn->send_pipe->size + conn->recv_pipe->size != 0 &&
conn->send_pipe->size + conn->recv_pipe->size != 0 && !data->set.reuse_forbid &&
!data->set.reuse_forbid && !conn->bits.close) {
!conn->bits.close)) {
/* Stop if pipeline is not empty and we do not have to close /* Stop if pipeline is not empty and we do not have to close
connection. */ connection. */
DEBUGF(infof(data, "Connection still in use, no more multi_done now!\n")); DEBUGF(infof(data, "Connection still in use, no more multi_done now!\n"));
@ -685,7 +684,7 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi,
/* If the handle is in a pipeline and has started sending off its /* If the handle is in a pipeline and has started sending off its
request but not received its response yet, we need to close request but not received its response yet, we need to close
connection. */ connection. */
connclose(data->easy_conn, "Removed with partial response"); streamclose(data->easy_conn, "Removed with partial response");
/* Set connection owner so that the DONE function closes it. We can /* Set connection owner so that the DONE function closes it. We can
safely do this here since connection is killed. */ safely do this here since connection is killed. */
data->easy_conn->data = easy; data->easy_conn->data = easy;
@ -1298,7 +1297,9 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
return CURLM_BAD_EASY_HANDLE; return CURLM_BAD_EASY_HANDLE;
do { do {
bool disconnect_conn = FALSE; /* A "stream" here is a logical stream if the protocol can handle that
(HTTP/2), or the full connection for older protocols */
bool stream_error = FALSE;
rc = CURLM_OK; rc = CURLM_OK;
/* Handle the case when the pipe breaks, i.e., the connection /* Handle the case when the pipe breaks, i.e., the connection
@ -1376,8 +1377,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
/* Force connection closed if the connection has indeed been used */ /* Force connection closed if the connection has indeed been used */
if(data->mstate > CURLM_STATE_DO) { if(data->mstate > CURLM_STATE_DO) {
connclose(data->easy_conn, "Disconnected with pending data"); streamclose(data->easy_conn, "Disconnected with pending data");
disconnect_conn = TRUE; stream_error = TRUE;
} }
result = CURLE_OPERATION_TIMEDOUT; result = CURLE_OPERATION_TIMEDOUT;
(void)multi_done(&data->easy_conn, result, TRUE); (void)multi_done(&data->easy_conn, result, TRUE);
@ -1426,7 +1427,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
/* Add this handle to the send or pend pipeline */ /* Add this handle to the send or pend pipeline */
result = Curl_add_handle_to_pipeline(data, data->easy_conn); result = Curl_add_handle_to_pipeline(data, data->easy_conn);
if(result) if(result)
disconnect_conn = TRUE; stream_error = TRUE;
else { else {
if(async) if(async)
/* We're now waiting for an asynchronous name lookup */ /* We're now waiting for an asynchronous name lookup */
@ -1518,7 +1519,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
if(result) { if(result) {
/* failure detected */ /* failure detected */
disconnect_conn = TRUE; stream_error = TRUE;
break; break;
} }
} }
@ -1558,7 +1559,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
else if(result) { else if(result) {
/* failure detected */ /* failure detected */
/* Just break, the cleaning up is handled all in one place */ /* Just break, the cleaning up is handled all in one place */
disconnect_conn = TRUE; stream_error = TRUE;
break; break;
} }
break; break;
@ -1578,7 +1579,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
/* failure detected */ /* failure detected */
Curl_posttransfer(data); Curl_posttransfer(data);
multi_done(&data->easy_conn, result, TRUE); multi_done(&data->easy_conn, result, TRUE);
disconnect_conn = TRUE; stream_error = TRUE;
} }
break; break;
@ -1595,7 +1596,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
/* failure detected */ /* failure detected */
Curl_posttransfer(data); Curl_posttransfer(data);
multi_done(&data->easy_conn, result, TRUE); multi_done(&data->easy_conn, result, TRUE);
disconnect_conn = TRUE; stream_error = TRUE;
} }
break; break;
@ -1670,7 +1671,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
if(drc) { if(drc) {
/* a failure here pretty much implies an out of memory */ /* a failure here pretty much implies an out of memory */
result = drc; result = drc;
disconnect_conn = TRUE; stream_error = TRUE;
} }
else else
retry = (newurl)?TRUE:FALSE; retry = (newurl)?TRUE:FALSE;
@ -1703,7 +1704,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
} }
else { else {
/* Have error handler disconnect conn if we can't retry */ /* Have error handler disconnect conn if we can't retry */
disconnect_conn = TRUE; stream_error = TRUE;
free(newurl); free(newurl);
} }
} }
@ -1712,7 +1713,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
Curl_posttransfer(data); Curl_posttransfer(data);
if(data->easy_conn) if(data->easy_conn)
multi_done(&data->easy_conn, result, FALSE); multi_done(&data->easy_conn, result, FALSE);
disconnect_conn = TRUE; stream_error = TRUE;
} }
} }
break; break;
@ -1734,7 +1735,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
/* failure detected */ /* failure detected */
Curl_posttransfer(data); Curl_posttransfer(data);
multi_done(&data->easy_conn, result, FALSE); multi_done(&data->easy_conn, result, FALSE);
disconnect_conn = TRUE; stream_error = TRUE;
} }
break; break;
@ -1763,7 +1764,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
/* failure detected */ /* failure detected */
Curl_posttransfer(data); Curl_posttransfer(data);
multi_done(&data->easy_conn, result, FALSE); multi_done(&data->easy_conn, result, FALSE);
disconnect_conn = TRUE; stream_error = TRUE;
} }
break; break;
@ -1885,10 +1886,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
if(!(data->easy_conn->handler->flags & PROTOPT_DUAL) && if(!(data->easy_conn->handler->flags & PROTOPT_DUAL) &&
result != CURLE_HTTP2_STREAM) result != CURLE_HTTP2_STREAM)
connclose(data->easy_conn, "Transfer returned error"); streamclose(data->easy_conn, "Transfer returned error");
Curl_posttransfer(data); Curl_posttransfer(data);
multi_done(&data->easy_conn, result, FALSE); multi_done(&data->easy_conn, result, TRUE);
} }
else if(done) { else if(done) {
followtype follow=FOLLOW_NONE; followtype follow=FOLLOW_NONE;
@ -1944,7 +1945,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
if(!result) if(!result)
newurl = NULL; /* allocation was handed over Curl_follow() */ newurl = NULL; /* allocation was handed over Curl_follow() */
else else
disconnect_conn = TRUE; stream_error = TRUE;
} }
multistate(data, CURLM_STATE_DONE); multistate(data, CURLM_STATE_DONE);
@ -2045,7 +2046,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
Curl_removeHandleFromPipeline(data, data->easy_conn->send_pipe); Curl_removeHandleFromPipeline(data, data->easy_conn->send_pipe);
Curl_removeHandleFromPipeline(data, data->easy_conn->recv_pipe); Curl_removeHandleFromPipeline(data, data->easy_conn->recv_pipe);
if(disconnect_conn) { if(stream_error) {
/* Don't attempt to send data over a connection that timed out */ /* Don't attempt to send data over a connection that timed out */
bool dead_connection = result == CURLE_OPERATION_TIMEDOUT; bool dead_connection = result == CURLE_OPERATION_TIMEDOUT;
/* disconnect properly */ /* disconnect properly */
@ -2069,7 +2070,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
/* aborted due to progress callback return code must close the /* aborted due to progress callback return code must close the
connection */ connection */
result = CURLE_ABORTED_BY_CALLBACK; result = CURLE_ABORTED_BY_CALLBACK;
connclose(data->easy_conn, "Aborted by callback"); streamclose(data->easy_conn, "Aborted by callback");
/* if not yet in DONE state, go there, otherwise COMPLETED */ /* if not yet in DONE state, go there, otherwise COMPLETED */
multistate(data, (data->mstate < CURLM_STATE_DONE)? multistate(data, (data->mstate < CURLM_STATE_DONE)?

View File

@ -2830,6 +2830,17 @@ CURLcode Curl_disconnect(struct connectdata *conn, bool dead_connection)
return CURLE_OK; return CURLE_OK;
} }
/*
* If this connection isn't marked to force-close, leave it open if there
* are other users of it
*/
if(!conn->bits.close &&
(conn->send_pipe->size + conn->recv_pipe->size)) {
DEBUGF(infof(data, "Curl_disconnect, usecounter: %d\n",
conn->send_pipe->size + conn->recv_pipe->size));
return CURLE_OK;
}
if(conn->dns_entry != NULL) { if(conn->dns_entry != NULL) {
Curl_resolv_unlock(data, conn->dns_entry); Curl_resolv_unlock(data, conn->dns_entry);
conn->dns_entry = NULL; conn->dns_entry = NULL;

View File

@ -818,6 +818,7 @@ struct Curl_handler {
#define PROTOPT_CREDSPERREQUEST (1<<7) /* requires login credentials per #define PROTOPT_CREDSPERREQUEST (1<<7) /* requires login credentials per
request instead of per connection */ request instead of per connection */
#define PROTOPT_ALPN_NPN (1<<8) /* set ALPN and/or NPN for this */ #define PROTOPT_ALPN_NPN (1<<8) /* set ALPN and/or NPN for this */
#define PROTOPT_STREAM (1<<9) /* a protocol with individual logical streams */
/* return the count of bytes sent, or -1 on error */ /* return the count of bytes sent, or -1 on error */
typedef ssize_t (Curl_send)(struct connectdata *conn, /* connection data */ typedef ssize_t (Curl_send)(struct connectdata *conn, /* connection data */

View File

@ -32,8 +32,8 @@ ftp://%HOSTIP:%FTPPORT/161
# Verify data after the test has been "shot" # Verify data after the test has been "shot"
<verify> <verify>
# This gets QUIT sent because CURLE_PARTIAL_FILE does NOT mark the control # This doesn't send QUIT because of known bug:
# connection as bad # "7.8 Premature transfer end but healthy control channel"
<protocol> <protocol>
USER anonymous USER anonymous
PASS ftp@example.com PASS ftp@example.com
@ -42,7 +42,6 @@ EPSV
TYPE I TYPE I
SIZE 161 SIZE 161
RETR 161 RETR 161
QUIT
</protocol> </protocol>
# CURLE_PARTIAL_FILE = 18 # CURLE_PARTIAL_FILE = 18
<errorcode> <errorcode>