Skip to content

Commit

Permalink
Merge 0fb0a70 into ee8016b
Browse files Browse the repository at this point in the history
  • Loading branch information
bagder committed Nov 4, 2017
2 parents ee8016b + 0fb0a70 commit c173abe
Show file tree
Hide file tree
Showing 15 changed files with 412 additions and 131 deletions.
3 changes: 2 additions & 1 deletion docs/examples/Makefile.inc
Expand Up @@ -33,7 +33,8 @@ check_PROGRAMS = 10-at-a-time anyauthput cookie_interface debug fileupload \
imap-search imap-create imap-delete imap-copy imap-noop imap-ssl \
imap-tls imap-multi url2file sftpget ftpsget postinmemory http2-download \
http2-upload http2-serverpush getredirect ftpuploadfrommem \
ftpuploadresume sslbackend postit2-formadd multi-formadd
ftpuploadresume sslbackend postit2-formadd multi-formadd \
shared-connection-cache

# These examples require external dependencies that may not be commonly
# available on POSIX systems, so don't bother attempting to compile them here.
Expand Down
85 changes: 85 additions & 0 deletions docs/examples/shared-connection-cache.c
@@ -0,0 +1,85 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
/* <DESC>
* Connection cache shared between easy handles with the share inteface
* </DESC>
*/
#include <stdio.h>
#include <curl/curl.h>

static void my_lock(CURL *handle, curl_lock_data data,
curl_lock_access laccess, void *useptr)
{
(void)handle;
(void)data;
(void)laccess;
(void)useptr;
fprintf(stderr, "-> Mutex lock\n");
}

static void my_unlock(CURL *handle, curl_lock_data data, void *useptr)
{
(void)handle;
(void)data;
(void)useptr;
fprintf(stderr, "<- Mutex unlock\n");
}

int main(void)
{
CURL *curl;
CURLcode res;
CURLSH *share;
int i;

share = curl_share_init();
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);

curl_share_setopt(share, CURLSHOPT_LOCKFUNC, my_lock);
curl_share_setopt(share, CURLSHOPT_UNLOCKFUNC, my_unlock);

/* Loop the transfer and cleanup the handle properly every lap. This will
still reuse connections since the pool is in the shared object! */

for(i = 0; i < 3; i++) {
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "https://curl.haxx.se/");

/* use the share object */
curl_easy_setopt(curl, CURLOPT_SHARE, share);

/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
/* Check for errors */
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));

/* always cleanup */
curl_easy_cleanup(curl);
}
}

curl_share_cleanup(share);
return 0;
}
141 changes: 133 additions & 8 deletions lib/conncache.c
Expand Up @@ -31,11 +31,21 @@
#include "multiif.h"
#include "sendf.h"
#include "conncache.h"
#include "share.h"
#include "sigpipe.h"
#include "connect.h"

/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
#include "memdebug.h"

#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)


static void conn_llist_dtor(void *user, void *element)
{
struct connectdata *data = element;
Expand Down Expand Up @@ -109,8 +119,23 @@ static void free_bundle_hash_entry(void *freethis)

int Curl_conncache_init(struct conncache *connc, int size)
{
return Curl_hash_init(&connc->hash, size, Curl_hash_str,
Curl_str_key_compare, free_bundle_hash_entry);
int rc;

/* allocate a new easy handle to use when closing cached connections */
connc->closure_handle = curl_easy_init();
if(!connc->closure_handle)
return 1; /* bad */

rc = Curl_hash_init(&connc->hash, size, Curl_hash_str,
Curl_str_key_compare, free_bundle_hash_entry);
if(rc) {
Curl_close(connc->closure_handle);
connc->closure_handle = NULL;
}
else
connc->closure_handle->state.conn_cache = connc;

return rc;
}

void Curl_conncache_destroy(struct conncache *connc)
Expand Down Expand Up @@ -149,7 +174,9 @@ struct connectbundle *Curl_conncache_find_bundle(struct connectdata *conn,
if(connc) {
char key[128];
hashkey(conn, key, sizeof(key));
CONN_LOCK(conn->data);
bundle = Curl_hash_pick(&connc->hash, key, strlen(key));
CONN_UNLOCK(conn->data);
}

return bundle;
Expand Down Expand Up @@ -206,7 +233,9 @@ CURLcode Curl_conncache_add_conn(struct conncache *connc,
return result;

hashkey(conn, key, sizeof(key));
CONN_LOCK(data);
rc = conncache_add_bundle(data->state.conn_cache, key, new_bundle);
CONN_UNLOCK(data);

if(!rc) {
bundle_destroy(new_bundle);
Expand All @@ -215,12 +244,15 @@ CURLcode Curl_conncache_add_conn(struct conncache *connc,
bundle = new_bundle;
}

CONN_LOCK(data);
result = bundle_add_conn(bundle, conn);
if(result) {
if(new_bundle)
conncache_remove_bundle(data->state.conn_cache, new_bundle);
CONN_UNLOCK(data);
return result;
}
CONN_UNLOCK(data);

conn->connection_id = connc->next_connection_id++;
connc->num_connections++;
Expand All @@ -240,11 +272,11 @@ void Curl_conncache_remove_conn(struct conncache *connc,
/* The bundle pointer can be NULL, since this function can be called
due to a failed connection attempt, before being added to a bundle */
if(bundle) {
CONN_LOCK(conn->data);
bundle_remove_conn(bundle, conn);
if(bundle->num_connections == 0) {
if(bundle->num_connections == 0)
conncache_remove_bundle(connc, bundle);
}

CONN_UNLOCK(conn->data);
if(connc) {
connc->num_connections--;

Expand All @@ -261,7 +293,8 @@ void Curl_conncache_remove_conn(struct conncache *connc,
Return 0 from func() to continue the loop, return 1 to abort it.
*/
void Curl_conncache_foreach(struct conncache *connc,
void Curl_conncache_foreach(struct Curl_easy *data,
struct conncache *connc,
void *param,
int (*func)(struct connectdata *conn, void *param))
{
Expand All @@ -272,6 +305,7 @@ void Curl_conncache_foreach(struct conncache *connc,
if(!connc)
return;

CONN_LOCK(data);
Curl_hash_start_iterate(&connc->hash, &iter);

he = Curl_hash_next_element(&iter);
Expand All @@ -288,14 +322,21 @@ void Curl_conncache_foreach(struct conncache *connc,
struct connectdata *conn = curr->ptr;
curr = curr->next;

if(1 == func(conn, param))
if(1 == func(conn, param)) {
CONN_UNLOCK(data);
return;
}
}
}
CONN_UNLOCK(data);
}

/* Return the first connection found in the cache. Used when closing all
connections */
connections.
NOTE: no locking is done here as this is presumably only done when cleaning
up a cache!
*/
struct connectdata *
Curl_conncache_find_first_connection(struct conncache *connc)
{
Expand All @@ -321,6 +362,90 @@ Curl_conncache_find_first_connection(struct conncache *connc)
return NULL;
}

/*
* This function finds the connection in the connection
* cache that has been unused for the longest time.
*
* Returns the pointer to the oldest idle connection, or NULL if none was
* found.
*/
struct connectdata *
Curl_conncache_oldest_idle(struct Curl_easy *data)
{
struct conncache *bc = data->state.conn_cache;
struct curl_hash_iterator iter;
struct curl_llist_element *curr;
struct curl_hash_element *he;
timediff_t highscore =- 1;
timediff_t score;
struct curltime now;
struct connectdata *conn_candidate = NULL;
struct connectbundle *bundle;

now = Curl_now();

CONN_LOCK(data);
Curl_hash_start_iterate(&bc->hash, &iter);

he = Curl_hash_next_element(&iter);
while(he) {
struct connectdata *conn;

bundle = he->ptr;

curr = bundle->conn_list.head;
while(curr) {
conn = curr->ptr;

if(!conn->inuse) {
/* Set higher score for the age passed since the connection was used */
score = Curl_timediff(now, conn->now);

if(score > highscore) {
highscore = score;
conn_candidate = conn;
}
}
curr = curr->next;
}

he = Curl_hash_next_element(&iter);
}
CONN_UNLOCK(data);

return conn_candidate;
}

void Curl_conncache_close_all_connections(struct conncache *connc)
{
struct connectdata *conn;

conn = Curl_conncache_find_first_connection(connc);
while(conn) {
SIGPIPE_VARIABLE(pipe_st);
conn->data = connc->closure_handle;

sigpipe_ignore(conn->data, &pipe_st);
conn->data->easy_conn = NULL; /* clear the easy handle's connection
pointer */
/* This will remove the connection from the cache */
connclose(conn, "kill all");
(void)Curl_disconnect(conn, FALSE);
sigpipe_restore(&pipe_st);

conn = Curl_conncache_find_first_connection(connc);
}

if(connc->closure_handle) {
SIGPIPE_VARIABLE(pipe_st);
sigpipe_ignore(connc->closure_handle, &pipe_st);

Curl_hostcache_clean(connc->closure_handle,
connc->closure_handle->dns.hostcache);
Curl_close(connc->closure_handle);
sigpipe_restore(&pipe_st);
}
}

#if 0
/* Useful for debugging the connection cache */
Expand Down
10 changes: 8 additions & 2 deletions lib/conncache.h
Expand Up @@ -28,6 +28,8 @@ struct conncache {
size_t num_connections;
long next_connection_id;
struct curltime last_cleanup;
/* handle used for closing cached connections */
struct Curl_easy *closure_handle;
};

#define BUNDLE_NO_MULTIUSE -1
Expand All @@ -41,8 +43,8 @@ struct connectbundle {
struct curl_llist conn_list; /* The connectdata members of the bundle */
};

/* returns 1 on error, 0 is fine */
int Curl_conncache_init(struct conncache *, int size);

void Curl_conncache_destroy(struct conncache *connc);

/* return the correct bundle, to a host or a proxy */
Expand All @@ -55,14 +57,18 @@ CURLcode Curl_conncache_add_conn(struct conncache *connc,
void Curl_conncache_remove_conn(struct conncache *connc,
struct connectdata *conn);

void Curl_conncache_foreach(struct conncache *connc,
void Curl_conncache_foreach(struct Curl_easy *data,
struct conncache *connc,
void *param,
int (*func)(struct connectdata *conn,
void *param));

struct connectdata *
Curl_conncache_find_first_connection(struct conncache *connc);

struct connectdata *
Curl_conncache_oldest_idle(struct Curl_easy *data);
void Curl_conncache_close_all_connections(struct conncache *connc);
void Curl_conncache_print(struct conncache *connc);

#endif /* HEADER_CURL_CONNCACHE_H */
2 changes: 1 addition & 1 deletion lib/connect.c
Expand Up @@ -1225,7 +1225,7 @@ curl_socket_t Curl_getconnectinfo(struct Curl_easy *data,
find.tofind = data->state.lastconnect;
find.found = FALSE;

Curl_conncache_foreach(data->multi_easy?
Curl_conncache_foreach(data, data->multi_easy?
&data->multi_easy->conn_cache:
&data->multi->conn_cache, &find, conn_is_conn);

Expand Down

0 comments on commit c173abe

Please sign in to comment.