Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

connecache: fix multi-thread use of shared connection cache #4557

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ task:
- pkg delete -y curl
configure_script:
- ./buildconf
- case `uname -r` in
13.0*)
export CC=clang;
export CFLAGS="-fsanitize=address,undefined,signed-integer-overflow -fno-sanitize-recover=undefined,integer -Wformat -Werror=format-security -Werror=array-bounds -g"
export CXXFLAGS="-fsanitize=address,undefined -fno-sanitize-recover=undefined,integer -Wformat -Werror=format-security -Werror=array-bounds -g"
export LDFLAGS="-fsanitize=address,undefined -fno-sanitize-recover=undefined,integer" ;;
esac
- ./configure --prefix="${HOME}"/install --enable-debug --with-libssh2 --with-brotli --with-gssapi --with-libidn2 --enable-manual --enable-ldap --enable-ldaps --with-librtmp --with-libmetalink --with-libpsl --with-nghttp2 || { tail -300 config.log; false; }
compile_script:
- make V=1
Expand Down
30 changes: 4 additions & 26 deletions lib/conncache.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,6 @@
#include "curl_memory.h"
#include "memdebug.h"

#ifdef CURLDEBUG
/* the debug versions of these macros make extra certain that the lock is
never doubly locked or unlocked */
#define CONN_LOCK(x) if((x)->share) { \
Curl_share_lock((x), CURL_LOCK_DATA_CONNECT, CURL_LOCK_ACCESS_SINGLE); \
DEBUGASSERT(!(x)->state.conncache_lock); \
(x)->state.conncache_lock = TRUE; \
}

#define CONN_UNLOCK(x) if((x)->share) { \
DEBUGASSERT((x)->state.conncache_lock); \
(x)->state.conncache_lock = FALSE; \
Curl_share_unlock((x), CURL_LOCK_DATA_CONNECT); \
}
#else
#define CONN_LOCK(x) if((x)->share) \
Curl_share_lock((x), CURL_LOCK_DATA_CONNECT, CURL_LOCK_ACCESS_SINGLE)
#define CONN_UNLOCK(x) if((x)->share) \
Curl_share_unlock((x), CURL_LOCK_DATA_CONNECT)
#endif

#define HASHKEY_SIZE 128

static void conn_llist_dtor(void *user, void *element)
Expand Down Expand Up @@ -122,6 +101,7 @@ static int bundle_remove_conn(struct connectbundle *cb_ptr,
}
curr = curr->next;
}
DEBUGASSERT(0);
return 0;
}

Expand Down Expand Up @@ -428,17 +408,15 @@ conncache_find_first_connection(struct conncache *connc)
*
* Return TRUE if stored, FALSE if closed.
*/
bool Curl_conncache_return_conn(struct connectdata *conn)
bool Curl_conncache_return_conn(struct Curl_easy *data,
struct connectdata *conn)
{
struct Curl_easy *data = conn->data;

/* data->multi->maxconnects can be negative, deal with it. */
size_t maxconnects =
(data->multi->maxconnects < 0) ? data->multi->num_easy * 4:
data->multi->maxconnects;
struct connectdata *conn_candidate = NULL;

conn->data = NULL; /* no owner anymore */
conn->lastused = Curl_now(); /* it was used up until now */
if(maxconnects > 0 &&
Curl_conncache_size(data) > maxconnects) {
Expand Down Expand Up @@ -541,7 +519,7 @@ Curl_conncache_extract_oldest(struct Curl_easy *data)
while(curr) {
conn = curr->ptr;

if(!CONN_INUSE(conn) && !conn->data) {
if(!CONN_INUSE(conn) && !conn->data && !conn->bits.close) {
/* Set higher score for the age passed since the connection was used */
score = Curl_timediff(now, conn->lastused);

Expand Down
24 changes: 23 additions & 1 deletion lib/conncache.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,27 @@ struct conncache {
#define BUNDLE_UNKNOWN 0 /* initial value */
#define BUNDLE_MULTIPLEX 2

#ifdef CURLDEBUG
/* the debug versions of these macros make extra certain that the lock is
never doubly locked or unlocked */
#define CONN_LOCK(x) if((x)->share) { \
Curl_share_lock((x), CURL_LOCK_DATA_CONNECT, CURL_LOCK_ACCESS_SINGLE); \
DEBUGASSERT(!(x)->state.conncache_lock); \
(x)->state.conncache_lock = TRUE; \
}

#define CONN_UNLOCK(x) if((x)->share) { \
DEBUGASSERT((x)->state.conncache_lock); \
(x)->state.conncache_lock = FALSE; \
Curl_share_unlock((x), CURL_LOCK_DATA_CONNECT); \
}
#else
#define CONN_LOCK(x) if((x)->share) \
Curl_share_lock((x), CURL_LOCK_DATA_CONNECT, CURL_LOCK_ACCESS_SINGLE)
#define CONN_UNLOCK(x) if((x)->share) \
Curl_share_unlock((x), CURL_LOCK_DATA_CONNECT)
#endif

struct connectbundle {
int multiuse; /* supports multi-use */
size_t num_connections; /* Number of connections in the bundle */
Expand All @@ -61,7 +82,8 @@ void Curl_conncache_unlock(struct Curl_easy *data);
size_t Curl_conncache_size(struct Curl_easy *data);
size_t Curl_conncache_bundle_size(struct connectdata *conn);

bool Curl_conncache_return_conn(struct connectdata *conn);
bool Curl_conncache_return_conn(struct Curl_easy *data,
struct connectdata *conn);
CURLcode Curl_conncache_add_conn(struct conncache *connc,
struct connectdata *conn) WARN_UNUSED_RESULT;
void Curl_conncache_remove_conn(struct Curl_easy *data,
Expand Down
2 changes: 1 addition & 1 deletion lib/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -1617,7 +1617,7 @@ CURLcode Curl_http_done(struct connectdata *conn,
Curl_add_buffer_free(&http->send_buffer);
}

Curl_http2_done(conn, premature);
Curl_http2_done(data, premature);
Curl_quic_done(data, premature);

Curl_mime_cleanpart(&http->form);
Expand Down
5 changes: 2 additions & 3 deletions lib/http2.c
Original file line number Diff line number Diff line change
Expand Up @@ -1169,11 +1169,10 @@ static void populate_settings(struct connectdata *conn,
httpc->local_settings_num = 3;
}

void Curl_http2_done(struct connectdata *conn, bool premature)
void Curl_http2_done(struct Curl_easy *data, bool premature)
{
struct Curl_easy *data = conn->data;
struct HTTP *http = data->req.protop;
struct http_conn *httpc = &conn->proto.httpc;
struct http_conn *httpc = &data->conn->proto.httpc;

/* there might be allocated resources done before this got the 'h2' pointer
setup */
Expand Down
2 changes: 1 addition & 1 deletion lib/http2.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ CURLcode Curl_http2_switched(struct connectdata *conn,
/* called from http_setup_conn */
void Curl_http2_setup_conn(struct connectdata *conn);
void Curl_http2_setup_req(struct Curl_easy *data);
void Curl_http2_done(struct connectdata *conn, bool premature);
void Curl_http2_done(struct Curl_easy *data, bool premature);
CURLcode Curl_http2_done_sending(struct connectdata *conn);
CURLcode Curl_http2_add_child(struct Curl_easy *parent,
struct Curl_easy *child,
Expand Down
20 changes: 13 additions & 7 deletions lib/multi.c
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,8 @@ static CURLcode multi_done(struct Curl_easy *data,
/* Stop if multi_done() has already been called */
return CURLE_OK;

conn->data = data; /* ensure the connection uses this transfer now */

/* Stop the resolver and free its own resources (but not dns_entry yet). */
Curl_resolver_kill(conn);

Expand Down Expand Up @@ -583,15 +585,17 @@ static CURLcode multi_done(struct Curl_easy *data,

process_pending_handles(data->multi); /* connection / multiplex */

CONN_LOCK(data);
detach_connnection(data);
if(CONN_INUSE(conn)) {
/* Stop if still used. */
CONN_UNLOCK(data);
DEBUGF(infof(data, "Connection still in use %zu, "
"no more multi_done now!\n",
conn->easyq.size));
return CURLE_OK;
}

conn->data = NULL; /* the connection now has no owner */
data->state.done = TRUE; /* called just now! */

if(conn->dns_entry) {
Expand Down Expand Up @@ -634,7 +638,10 @@ static CURLcode multi_done(struct Curl_easy *data,
#endif
) || conn->bits.close
|| (premature && !(conn->handler->flags & PROTOPT_STREAM))) {
CURLcode res2 = Curl_disconnect(data, conn, premature);
CURLcode res2;
conn->bits.close = TRUE; /* forcibly prevents reuse */
CONN_UNLOCK(data);
res2 = Curl_disconnect(data, conn, premature);

/* If we had an error already, make sure we return that one. But
if we got a new error, return that. */
Expand All @@ -651,9 +658,9 @@ static CURLcode multi_done(struct Curl_easy *data,
conn->bits.httpproxy ? conn->http_proxy.host.dispname :
conn->bits.conn_to_host ? conn->conn_to_host.dispname :
conn->host.dispname);

/* the connection is no longer in use by this transfer */
if(Curl_conncache_return_conn(conn)) {
CONN_UNLOCK(data);
if(Curl_conncache_return_conn(data, conn)) {
/* remember the most recently used connection */
data->state.lastconnect = conn;
infof(data, "%s\n", buffer);
Expand Down Expand Up @@ -760,10 +767,8 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi,
vanish with this handle */

/* Remove the association between the connection and the handle */
if(data->conn) {
data->conn->data = NULL;
if(data->conn)
detach_connnection(data);
}

#ifdef USE_LIBPSL
/* Remove the PSL association. */
Expand Down Expand Up @@ -1349,6 +1354,7 @@ static CURLcode multi_do(struct Curl_easy *data, bool *done)

DEBUGASSERT(conn);
DEBUGASSERT(conn->handler);
DEBUGASSERT(conn->data == data);

if(conn->handler->do_it) {
/* generic protocol-specific function pointer set in curl_connect() */
Expand Down
21 changes: 8 additions & 13 deletions lib/url.c
Original file line number Diff line number Diff line change
Expand Up @@ -1082,16 +1082,15 @@ ConnectionExists(struct Curl_easy *data,
check = curr->ptr;
curr = curr->next;

if(check->bits.connect_only)
/* connect-only connections will not be reused */
if(check->bits.connect_only || check->bits.close)
/* connect-only or to-be-closed connections will not be reused */
continue;

multiplexed = CONN_INUSE(check) &&
(bundle->multiuse == BUNDLE_MULTIPLEX);

if(canmultiplex) {
if(check->bits.protoconnstart && check->bits.close)
continue;
;
}
else {
if(multiplexed) {
Expand All @@ -1111,12 +1110,9 @@ ConnectionExists(struct Curl_easy *data,
}
}

if((check->sock[FIRSTSOCKET] == CURL_SOCKET_BAD) ||
check->bits.close) {
if(!check->bits.close)
foundPendingCandidate = TRUE;
/* Don't pick a connection that hasn't connected yet or that is going
to get closed. */
if(check->sock[FIRSTSOCKET] == CURL_SOCKET_BAD) {
foundPendingCandidate = TRUE;
/* Don't pick a connection that hasn't connected yet */
infof(data, "Connection #%ld isn't open enough, can't reuse\n",
check->connection_id);
continue;
Expand Down Expand Up @@ -1194,8 +1190,7 @@ ConnectionExists(struct Curl_easy *data,
already in use so we skip it */
continue;

if(CONN_INUSE(check) && check->data &&
(check->data->multi != needle->data->multi))
if(check->data && (check->data->multi != needle->data->multi))
/* this could be subject for multiplex use, but only if they belong to
* the same multi handle */
continue;
Expand Down Expand Up @@ -1643,6 +1638,7 @@ static struct connectdata *allocate_conn(struct Curl_easy *data)
it may live on without (this specific) Curl_easy */
conn->fclosesocket = data->set.fclosesocket;
conn->closesocket_client = data->set.closesocket_client;
conn->lastused = Curl_now(); /* used now */

return conn;
error:
Expand Down Expand Up @@ -3612,7 +3608,6 @@ static CURLcode create_conn(struct Curl_easy *data,
reuse = FALSE;

infof(data, "We can reuse, but we want a new connection anyway\n");
Curl_conncache_return_conn(conn_temp);
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions tests/data/test1554
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ run 1: foobar and so on fun!
<- Mutex unlock
-> Mutex lock
<- Mutex unlock
-> Mutex lock
<- Mutex unlock
run 1: foobar and so on fun!
-> Mutex lock
<- Mutex unlock
Expand All @@ -47,13 +49,17 @@ run 1: foobar and so on fun!
<- Mutex unlock
-> Mutex lock
<- Mutex unlock
-> Mutex lock
<- Mutex unlock
run 1: foobar and so on fun!
-> Mutex lock
<- Mutex unlock
-> Mutex lock
<- Mutex unlock
-> Mutex lock
<- Mutex unlock
-> Mutex lock
<- Mutex unlock
</datacheck>
</reply>

Expand Down