summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Stenberg <daniel@haxx.se>2012-11-30 14:52:38 +0100
committerDaniel Stenberg <daniel@haxx.se>2012-12-04 22:14:23 +0100
commit8b02afd9a938d6db150fa1ba34cca64de873f5d8 (patch)
treeb7567f4eb85251a0ef63fd9c84580ac50e40f337
parent3202cc61627c07ef3311fc4b3d9880ad7bcd77d2 (diff)
downloadcurl-8b02afd9a938d6db150fa1ba34cca64de873f5d8.tar.gz
multi: fix re-sending request on early connection close
This handling already works with the easy-interface code. When a request is sent on a re-used connection that gets closed by the server at the same time as the request is sent, the situation may occur so that we can send the request and we discover the broken connection as a RECV_ERROR in the PERFORM state and then the request needs to be retried on a fresh connection. Test 64 broke with 'multi-always-internally'.
-rw-r--r--lib/multi.c45
1 files changed, 31 insertions, 14 deletions
diff --git a/lib/multi.c b/lib/multi.c
index f7ad9cbef..d2d154fa7 100644
--- a/lib/multi.c
+++ b/lib/multi.c
@@ -1539,6 +1539,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
break;
case CURLM_STATE_PERFORM:
+ {
+ char *newurl = NULL;
+ bool retry = FALSE;
+
/* check if over send speed */
if((data->set.max_send_speed > 0) &&
(data->progress.ulspeed > data->set.max_send_speed)) {
@@ -1586,13 +1590,29 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
easy->easy_conn->writechannel_inuse = FALSE;
}
+ if(done || (easy->result == CURLE_RECV_ERROR)) {
+ /* If CURLE_RECV_ERROR happens early enough, we assume it was a race
+ * condition and the server closed the re-used connection exactly when
+ * we wanted to use it, so figure out if that is indeed the case.
+ */
+ CURLcode ret = Curl_retry_request(easy->easy_conn, &newurl);
+ if(!ret)
+ retry = (newurl)?TRUE:FALSE;
+
+ if(retry)
+ /* if we are to retry, set the result to OK */
+ easy->result = CURLE_OK;
+ }
+
if(easy->result) {
- /* The transfer phase returned error, we mark the connection to get
+ /*
+ * The transfer phase returned error, we mark the connection to get
* closed to prevent being re-used. This is because we can't possibly
* know if the connection is in a good shape or not now. Unless it is
* a protocol which uses two "channels" like FTP, as then the error
* happened in the data connection.
*/
+
if(!(easy->easy_conn->handler->flags & PROTOPT_DUAL))
easy->easy_conn->bits.close = TRUE;
@@ -1600,14 +1620,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
Curl_done(&easy->easy_conn, easy->result, FALSE);
}
else if(done) {
- char *newurl = NULL;
- bool retry = FALSE;
followtype follow=FOLLOW_NONE;
- easy->result = Curl_retry_request(easy->easy_conn, &newurl);
- if(!easy->result)
- retry = (newurl)?TRUE:FALSE;
-
/* call this even if the readwrite function returned error */
Curl_posttransfer(data);
@@ -1640,11 +1654,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
if(CURLE_OK == easy->result) {
multistate(easy, CURLM_STATE_CONNECT);
result = CURLM_CALL_MULTI_PERFORM;
+ newurl = NULL; /* handed over the memory ownership to
+ Curl_follow(), make sure we don't free() it
+ here */
}
- else if(newurl)
- /* Since we "took it", we are in charge of freeing this on
- failure */
- free(newurl);
}
else {
/* after the transfer is done, go DONE */
@@ -1652,13 +1665,14 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
/* but first check to see if we got a location info even though we're
not following redirects */
if(data->req.location) {
+ if(newurl)
+ free(newurl);
newurl = data->req.location;
data->req.location = NULL;
easy->result = Curl_follow(data, newurl, FOLLOW_FAKE);
- if(easy->result) {
+ newurl = NULL; /* allocation was handed over */
+ if(easy->result)
disconnect_conn = TRUE;
- free(newurl);
- }
}
multistate(easy, CURLM_STATE_DONE);
@@ -1666,7 +1680,10 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
}
}
+ if(newurl)
+ free(newurl);
break;
+ }
case CURLM_STATE_DONE: