Introducing a new persistent connection caching system using "bundles".

A bundle is a list of all persistent connections to the same host.
The connection cache consists of a hash of bundles, with the
hostname as the key.
The benefits may not be obvious, but they are two:

1) Faster search for connections to reuse, since the hash
   lookup only finds connections to the host in question.
2) It lays out the groundworks for an upcoming patch,
   which will introduce multiple HTTP pipelines.

This patch also removes the awkward list of "closure handles",
which were needed to send QUIT commands to the FTP server
when closing a connection.
Now we allocate a separate closure handle and use that
one to close all connections.

This has been tested in a live system for a few weeks, and of
course passes the test suite.
This commit is contained in:
Linus Nielsen Feltzing 2012-12-06 12:12:04 +01:00 коммит произвёл Daniel Stenberg
Родитель ca5f4e2135
Коммит d021f2e8a0
14 изменённых файлов: 894 добавлений и 773 удалений

Просмотреть файл

@ -23,8 +23,9 @@ CSOURCES = file.c timeval.c base64.c hostip.c progress.c formdata.c \
curl_rtmp.c openldap.c curl_gethostname.c gopher.c axtls.c \ curl_rtmp.c openldap.c curl_gethostname.c gopher.c axtls.c \
idn_win32.c http_negotiate_sspi.c cyassl.c http_proxy.c non-ascii.c \ idn_win32.c http_negotiate_sspi.c cyassl.c http_proxy.c non-ascii.c \
asyn-ares.c asyn-thread.c curl_gssapi.c curl_ntlm.c curl_ntlm_wb.c \ asyn-ares.c asyn-thread.c curl_gssapi.c curl_ntlm.c curl_ntlm_wb.c \
curl_ntlm_core.c curl_ntlm_msgs.c curl_sasl.c curl_schannel.c \ curl_ntlm_core.c curl_ntlm_msgs.c curl_sasl.c curl_schannel.c \
curl_multibyte.c curl_darwinssl.c hostcheck.c curl_multibyte.c curl_darwinssl.c hostcheck.c \
bundles.c conncache.c
HHEADERS = arpa_telnet.h netrc.h file.h timeval.h qssl.h hostip.h \ HHEADERS = arpa_telnet.h netrc.h file.h timeval.h qssl.h hostip.h \
progress.h formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h \ progress.h formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h \
@ -42,4 +43,4 @@ HHEADERS = arpa_telnet.h netrc.h file.h timeval.h qssl.h hostip.h \
gopher.h axtls.h cyassl.h http_proxy.h non-ascii.h asyn.h curl_ntlm.h \ gopher.h axtls.h cyassl.h http_proxy.h non-ascii.h asyn.h curl_ntlm.h \
curl_gssapi.h curl_ntlm_wb.h curl_ntlm_core.h curl_ntlm_msgs.h \ curl_gssapi.h curl_ntlm_wb.h curl_ntlm_core.h curl_ntlm_msgs.h \
curl_sasl.h curl_schannel.h curl_multibyte.h curl_darwinssl.h \ curl_sasl.h curl_schannel.h curl_multibyte.h curl_darwinssl.h \
hostcheck.h hostcheck.h bundles.h conncache.h

101
lib/bundles.c Normal file
Просмотреть файл

@ -0,0 +1,101 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 2012, Linus Nielsen Feltzing, <linus@haxx.se>
*
* 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 http://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.
*
***************************************************************************/
#include "setup.h"
#include <curl/curl.h>
#include "urldata.h"
#include "url.h"
#include "progress.h"
#include "multiif.h"
#include "bundles.h"
#include "sendf.h"
#include "rawstr.h"
#include "curl_memory.h"
/* The last #include file should be: */
#include "memdebug.h"
static void conn_llist_dtor(void *user, void *element)
{
struct connectdata *data = element;
(void)user;
data->bundle = NULL;
}
CURLcode Curl_bundle_create(struct SessionHandle *data,
struct connectbundle **cb_ptr)
{
(void)data;
*cb_ptr = malloc(sizeof(struct connectbundle));
if(!*cb_ptr)
return CURLE_OUT_OF_MEMORY;
(*cb_ptr)->num_connections = 0;
(*cb_ptr)->server_supports_pipelining = FALSE;
(*cb_ptr)->conn_list = Curl_llist_alloc((curl_llist_dtor) conn_llist_dtor);
if(!(*cb_ptr)->conn_list)
return CURLE_OUT_OF_MEMORY;
return CURLE_OK;
}
void Curl_bundle_destroy(struct connectbundle *cb_ptr)
{
if(cb_ptr->conn_list)
Curl_llist_destroy(cb_ptr->conn_list, NULL);
Curl_safefree(cb_ptr);
}
/* Add a connection to a bundle */
CURLcode Curl_bundle_add_conn(struct connectbundle *cb_ptr,
struct connectdata *conn)
{
if(!Curl_llist_insert_next(cb_ptr->conn_list, cb_ptr->conn_list->tail, conn))
return CURLE_OUT_OF_MEMORY;
conn->bundle = cb_ptr;
cb_ptr->num_connections++;
return CURLE_OK;
}
/* Remove a connection from a bundle */
int Curl_bundle_remove_conn(struct connectbundle *cb_ptr,
struct connectdata *conn)
{
struct curl_llist_element *curr;
curr = cb_ptr->conn_list->head;
while(curr) {
if(curr->ptr == conn) {
Curl_llist_remove(cb_ptr->conn_list, curr, NULL);
cb_ptr->num_connections--;
conn->bundle = NULL;
return 1; /* we removed a handle */
}
curr = curr->next;
}
return 0;
}

44
lib/bundles.h Normal file
Просмотреть файл

@ -0,0 +1,44 @@
#ifndef __BUNDLES_H
#define __BUNDLES_H
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 2012, Linus Nielsen Feltzing, <linus@haxx.se>
*
* 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 http://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.
*
***************************************************************************/
struct connectbundle {
bool server_supports_pipelining; /* TRUE if server supports pipelining,
set after first response */
size_t num_connections; /* Number of connections in the bundle */
struct curl_llist *conn_list; /* The connectdata members of the bundle */
};
CURLcode Curl_bundle_create(struct SessionHandle *data,
struct connectbundle **cb_ptr);
void Curl_bundle_destroy(struct connectbundle *cb_ptr);
CURLcode Curl_bundle_add_conn(struct connectbundle *cb_ptr,
struct connectdata *conn);
int Curl_bundle_remove_conn(struct connectbundle *cb_ptr,
struct connectdata *conn);
#endif /* __BUNDLES_H */

270
lib/conncache.c Normal file
Просмотреть файл

@ -0,0 +1,270 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 2012, Linus Nielsen Feltzing, <linus@haxx.se>
*
* 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 http://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.
*
***************************************************************************/
#include "setup.h"
#include <curl/curl.h>
#include "urldata.h"
#include "url.h"
#include "progress.h"
#include "multiif.h"
#include "sendf.h"
#include "rawstr.h"
#include "bundles.h"
#include "conncache.h"
#include "curl_memory.h"
/* The last #include file should be: */
#include "memdebug.h"
#define CONNECTION_HASH_SIZE 97
static void free_bundle_hash_entry(void *freethis)
{
struct connectbundle *b = (struct connectbundle *) freethis;
Curl_bundle_destroy(b);
}
struct conncache *Curl_conncache_init(int type)
{
struct conncache *connc;
connc = calloc(1, sizeof(struct conncache));
if(!connc)
return NULL;
connc->hash = Curl_hash_alloc(CONNECTION_HASH_SIZE, Curl_hash_str,
Curl_str_key_compare, free_bundle_hash_entry);
if(!connc->hash) {
free(connc);
return NULL;
}
connc->type = type;
connc->num_connections = 0;
return connc;
}
void Curl_conncache_destroy(struct conncache *connc)
{
Curl_hash_destroy(connc->hash);
free(connc);
}
struct connectbundle *Curl_conncache_find_bundle(struct conncache *connc,
char *hostname)
{
struct connectbundle *bundle = NULL;
if(connc)
bundle = Curl_hash_pick(connc->hash, hostname, strlen(hostname)+1);
return bundle;
}
static bool conncache_add_bundle(struct conncache *connc,
char *hostname,
struct connectbundle *bundle)
{
void *p;
p = Curl_hash_add(connc->hash, hostname, strlen(hostname)+1, bundle);
return p?TRUE:FALSE;
}
static void conncache_remove_bundle(struct conncache *connc,
struct connectbundle *bundle)
{
struct curl_hash_iterator iter;
struct curl_hash_element *he;
if(!connc)
return;
Curl_hash_start_iterate(connc->hash, &iter);
he = Curl_hash_next_element(&iter);
while(he) {
if(he->ptr == bundle) {
/* The bundle is destroyed by the hash destructor function,
free_bundle_hash_entry() */
Curl_hash_delete(connc->hash, he->key, he->key_len);
return;
}
he = Curl_hash_next_element(&iter);
}
}
CURLcode Curl_conncache_add_conn(struct conncache *connc,
struct connectdata *conn)
{
CURLcode result;
struct connectbundle *bundle;
struct SessionHandle *data = conn->data;
bundle = Curl_conncache_find_bundle(data->state.conn_cache,
conn->host.name);
if(!bundle) {
result = Curl_bundle_create(data, &bundle);
if(result != CURLE_OK)
return result;
if(!conncache_add_bundle(data->state.conn_cache,
conn->host.name, bundle))
return CURLE_OUT_OF_MEMORY;
}
result = Curl_bundle_add_conn(bundle, conn);
if(result != CURLE_OK)
return result;
connc->num_connections++;
return CURLE_OK;
}
void Curl_conncache_remove_conn(struct conncache *connc,
struct connectdata *conn)
{
struct connectbundle *bundle = conn->bundle;
/* 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) {
Curl_bundle_remove_conn(bundle, conn);
if(bundle->num_connections == 0) {
conncache_remove_bundle(connc, bundle);
}
connc->num_connections--;
DEBUGF(infof(conn->data, "The cache now contains %d members\n",
connc->num_connections));
}
}
/* This function iterates the entire connection cache and calls the
function func() with the connection pointer as the first argument
and the supplied 'param' argument as the other */
void Curl_conncache_foreach(struct conncache *connc,
void *param,
void (*func)(void *conn, void *param))
{
struct curl_hash_iterator iter;
struct curl_llist_element *curr;
struct curl_hash_element *he;
if(!connc)
return;
Curl_hash_start_iterate(connc->hash, &iter);
he = Curl_hash_next_element(&iter);
while(he) {
struct connectbundle *bundle;
struct connectdata *conn;
bundle = he->ptr;
curr = bundle->conn_list->head;
while(curr) {
/* Yes, we need to update curr before calling func(), because func()
might decide to remove the connection */
conn = curr->ptr;
curr = curr->next;
func(conn, param);
}
he = Curl_hash_next_element(&iter);
}
}
/* Return the first connection found in the cache. Used when closing all
connections */
struct connectdata *
Curl_conncache_find_first_connection(struct conncache *connc)
{
struct curl_hash_iterator iter;
struct curl_llist_element *curr;
struct curl_hash_element *he;
struct connectbundle *bundle;
Curl_hash_start_iterate(connc->hash, &iter);
he = Curl_hash_next_element(&iter);
while(he) {
bundle = he->ptr;
curr = bundle->conn_list->head;
if(curr) {
return curr->ptr;
}
he = Curl_hash_next_element(&iter);
}
return NULL;
}
#if 0
/* Useful for debugging the connection cache */
void Curl_conncache_print(struct conncache *connc)
{
struct curl_hash_iterator iter;
struct curl_llist_element *curr;
struct curl_hash_element *he;
if(!connc)
return;
fprintf(stderr, "=Bundle cache=\n");
Curl_hash_start_iterate(connc->hash, &iter);
he = Curl_hash_next_element(&iter);
while(he) {
struct connectbundle *bundle;
struct connectdata *conn;
bundle = he->ptr;
fprintf(stderr, "%s -", he->key);
curr = bundle->conn_list->head;
while(curr) {
conn = curr->ptr;
fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse);
curr = curr->next;
}
fprintf(stderr, "\n");
he = Curl_hash_next_element(&iter);
}
}
#endif

56
lib/conncache.h Normal file
Просмотреть файл

@ -0,0 +1,56 @@
#ifndef __CONNCACHE_H
#define __CONNCACHE_H
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 2012, Linus Nielsen Feltzing, <linus@haxx.se>
*
* 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 http://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.
*
***************************************************************************/
struct conncache {
struct curl_hash *hash;
enum {
CONNCACHE_PRIVATE, /* used for an easy handle alone */
CONNCACHE_MULTI /* shared within a multi handle */
} type;
size_t num_connections;
};
struct conncache *Curl_conncache_init(int type);
void Curl_conncache_destroy(struct conncache *connc);
struct connectbundle *Curl_conncache_find_bundle(struct conncache *connc,
char *hostname);
CURLcode Curl_conncache_add_conn(struct conncache *connc,
struct connectdata *conn);
void Curl_conncache_remove_conn(struct conncache *connc,
struct connectdata *conn);
void Curl_conncache_foreach(struct conncache *connc,
void *param,
void (*func)(void *, void *));
struct connectdata *
Curl_conncache_find_first_connection(struct conncache *connc);
void Curl_conncache_print(struct conncache *connc);
#endif /* __CONNCACHE_H */

Просмотреть файл

@ -1131,10 +1131,8 @@ curl_socket_t Curl_getconnectinfo(struct SessionHandle *data,
DEBUGASSERT(data); DEBUGASSERT(data);
if((data->state.lastconnect != -1) && if(data->state.lastconnect) {
(data->state.connc->connects[data->state.lastconnect] != NULL)) { struct connectdata *c = data->state.lastconnect;
struct connectdata *c =
data->state.connc->connects[data->state.lastconnect];
if(connp) if(connp)
/* only store this if the caller cares for it */ /* only store this if the caller cares for it */
*connp = c; *connp = c;

Просмотреть файл

@ -70,6 +70,7 @@
#include "curl_rand.h" #include "curl_rand.h"
#include "non-ascii.h" #include "non-ascii.h"
#include "warnless.h" #include "warnless.h"
#include "conncache.h"
#define _MPRINTF_REPLACE /* use our functions only */ #define _MPRINTF_REPLACE /* use our functions only */
#include <curl/mprintf.h> #include <curl/mprintf.h>
@ -526,10 +527,10 @@ CURLcode curl_easy_perform(CURL *curl)
} }
if(!data->state.connc) { if(!data->state.conn_cache) {
/* oops, no connection cache, make one up */ /* Oops, no connection cache, create one */
data->state.connc = Curl_mk_connc(CONNCACHE_PRIVATE, -1L); data->state.conn_cache = Curl_conncache_init(CONNCACHE_PRIVATE);
if(!data->state.connc) if(!data->state.conn_cache)
return CURLE_OUT_OF_MEMORY; return CURLE_OUT_OF_MEMORY;
} }
@ -616,9 +617,9 @@ CURL *curl_easy_duphandle(CURL *incurl)
goto fail; goto fail;
/* the connection cache is setup on demand */ /* the connection cache is setup on demand */
outcurl->state.connc = NULL; outcurl->state.conn_cache = NULL;
outcurl->state.lastconnect = -1; outcurl->state.lastconnect = NULL;
outcurl->progress.flags = data->progress.flags; outcurl->progress.flags = data->progress.flags;
outcurl->progress.callback = data->progress.callback; outcurl->progress.callback = data->progress.callback;
@ -674,11 +675,6 @@ CURL *curl_easy_duphandle(CURL *incurl)
fail: fail:
if(outcurl) { if(outcurl) {
if(outcurl->state.connc &&
(outcurl->state.connc->type == CONNCACHE_PRIVATE)) {
Curl_rm_connc(outcurl->state.connc);
outcurl->state.connc = NULL;
}
curl_slist_free_all(outcurl->change.cookielist); curl_slist_free_all(outcurl->change.cookielist);
outcurl->change.cookielist = NULL; outcurl->change.cookielist = NULL;
Curl_safefree(outcurl->state.headerbuff); Curl_safefree(outcurl->state.headerbuff);

Просмотреть файл

@ -322,34 +322,77 @@ size_t Curl_str_key_compare(void*k1, size_t key1_len, void*k2, size_t key2_len)
return 0; return 0;
} }
void Curl_hash_start_iterate(struct curl_hash *hash,
struct curl_hash_iterator *iter)
{
iter->hash = hash;
iter->slot_index = 0;
iter->current_element = NULL;
}
struct curl_hash_element *
Curl_hash_next_element(struct curl_hash_iterator *iter)
{
int i;
struct curl_hash *h = iter->hash;
/* Get the next element in the current list, if any */
if(iter->current_element)
iter->current_element = iter->current_element->next;
/* If we have reached the end of the list, find the next one */
if(!iter->current_element) {
for(i = iter->slot_index;i < h->slots;i++) {
if(h->table[i]->head) {
iter->current_element = h->table[i]->head;
iter->slot_index = i+1;
break;
}
}
}
if(iter->current_element) {
struct curl_hash_element *he = iter->current_element->ptr;
return he;
}
else {
iter->current_element = NULL;
return NULL;
}
}
#if 0 /* useful function for debugging hashes and their contents */ #if 0 /* useful function for debugging hashes and their contents */
void Curl_hash_print(struct curl_hash *h, void Curl_hash_print(struct curl_hash *h,
void (*func)(void *)) void (*func)(void *))
{ {
int i; struct curl_hash_iterator iter;
struct curl_llist_element *le; struct curl_hash_element *he;
struct curl_llist *list; int last_index = -1;
struct curl_hash_element *he;
if(!h) if(!h)
return; return;
fprintf(stderr, "=Hash dump=\n"); fprintf(stderr, "=Hash dump=\n");
for(i = 0; i < h->slots; i++) { Curl_hash_start_iterate(h, &iter);
list = h->table[i];
le = list->head; /* get first list entry */ he = Curl_hash_next_element(&iter);
if(le) { while(he) {
fprintf(stderr, "index %d:", i); if(iter.slot_index != last_index) {
while(le) { fprintf(stderr, "index %d:", iter.slot_index);
he = le->ptr; if(last_index >= 0) {
if(func) fprintf(stderr, "\n");
func(he->ptr);
else
fprintf(stderr, " [%p]", he->ptr);
le = le->next;
} }
fprintf(stderr, "\n"); last_index = iter.slot_index;
} }
if(func)
func(he->ptr);
else
fprintf(stderr, " [%p]", he->ptr);
he = Curl_hash_next_element(&iter);
} }
fprintf(stderr, "\n");
} }
#endif #endif

Просмотреть файл

@ -62,6 +62,11 @@ struct curl_hash_element {
size_t key_len; size_t key_len;
}; };
struct curl_hash_iterator {
struct curl_hash *hash;
int slot_index;
struct curl_llist_element *current_element;
};
int Curl_hash_init(struct curl_hash *h, int Curl_hash_init(struct curl_hash *h,
int slots, int slots,
@ -89,4 +94,13 @@ size_t Curl_hash_str(void* key, size_t key_length, size_t slots_num);
size_t Curl_str_key_compare(void*k1, size_t key1_len, void*k2, size_t Curl_str_key_compare(void*k1, size_t key1_len, void*k2,
size_t key2_len); size_t key2_len);
void Curl_hash_start_iterate(struct curl_hash *hash,
struct curl_hash_iterator *iter);
struct curl_hash_element *
Curl_hash_next_element(struct curl_hash_iterator *iter);
void Curl_hash_print(struct curl_hash *h,
void (*func)(void *));
#endif #endif

Просмотреть файл

@ -3170,6 +3170,7 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data,
} }
else if(conn->httpversion >= 11 && else if(conn->httpversion >= 11 &&
!conn->bits.close) { !conn->bits.close) {
/* If HTTP version is >= 1.1 and connection is persistent /* If HTTP version is >= 1.1 and connection is persistent
server supports pipelining. */ server supports pipelining. */
DEBUGF(infof(data, DEBUGF(infof(data,

Просмотреть файл

@ -44,6 +44,8 @@
#include "select.h" #include "select.h"
#include "warnless.h" #include "warnless.h"
#include "speedcheck.h" #include "speedcheck.h"
#include "conncache.h"
#include "bundles.h"
#define _MPRINTF_REPLACE /* use our functions only */ #define _MPRINTF_REPLACE /* use our functions only */
#include <curl/mprintf.h> #include <curl/mprintf.h>
@ -97,11 +99,6 @@ typedef enum {
#define GETSOCK_READABLE (0x00ff) #define GETSOCK_READABLE (0x00ff)
#define GETSOCK_WRITABLE (0xff00) #define GETSOCK_WRITABLE (0xff00)
struct closure {
struct closure *next; /* a simple one-way list of structs */
struct SessionHandle *easy_handle;
};
struct Curl_one_easy { struct Curl_one_easy {
/* first, two fields for the linked list of these */ /* first, two fields for the linked list of these */
struct Curl_one_easy *next; struct Curl_one_easy *next;
@ -164,14 +161,16 @@ struct Curl_multi {
/* Whether pipelining is enabled for this multi handle */ /* Whether pipelining is enabled for this multi handle */
bool pipelining_enabled; bool pipelining_enabled;
/* shared connection cache */ /* Shared connection cache (bundles)*/
struct conncache *connc; struct conncache *conn_cache;
/* This handle will be used for closing the cached connections in
curl_multi_cleanup() */
struct SessionHandle *closure_handle;
long maxconnects; /* if >0, a fixed limit of the maximum number of entries long maxconnects; /* if >0, a fixed limit of the maximum number of entries
we're allowed to grow the connection cache to */ we're allowed to grow the connection cache to */
/* list of easy handles kept around for doing nice connection closures */
struct closure *closure;
/* timer callback and user data pointer for the *socket() API */ /* timer callback and user data pointer for the *socket() API */
curl_multi_timer_callback timer_cb; curl_multi_timer_callback timer_cb;
void *timer_userp; void *timer_userp;
@ -179,12 +178,8 @@ struct Curl_multi {
previous callback */ previous callback */
}; };
static void multi_connc_remove_handle(struct Curl_multi *multi,
struct SessionHandle *data);
static void singlesocket(struct Curl_multi *multi, static void singlesocket(struct Curl_multi *multi,
struct Curl_one_easy *easy); struct Curl_one_easy *easy);
static CURLMcode add_closure(struct Curl_multi *multi,
struct SessionHandle *data);
static int update_timer(struct Curl_multi *multi); static int update_timer(struct Curl_multi *multi);
static CURLcode addHandleToSendOrPendPipeline(struct SessionHandle *handle, static CURLcode addHandleToSendOrPendPipeline(struct SessionHandle *handle,
@ -228,7 +223,7 @@ static void multi_freetimeout(void *a, void *b);
static void multistate(struct Curl_one_easy *easy, CURLMstate state) static void multistate(struct Curl_one_easy *easy, CURLMstate state)
{ {
#ifdef DEBUGBUILD #ifdef DEBUGBUILD
long connectindex = -5000; long connection_id = -5000;
#endif #endif
CURLMstate oldstate = easy->state; CURLMstate oldstate = easy->state;
@ -242,12 +237,12 @@ static void multistate(struct Curl_one_easy *easy, CURLMstate state)
if(easy->easy_conn) { if(easy->easy_conn) {
if(easy->state > CURLM_STATE_CONNECT && if(easy->state > CURLM_STATE_CONNECT &&
easy->state < CURLM_STATE_COMPLETED) easy->state < CURLM_STATE_COMPLETED)
connectindex = easy->easy_conn->connectindex; connection_id = easy->easy_conn->connection_id;
infof(easy->easy_handle, infof(easy->easy_handle,
"STATE: %s => %s handle %p; (connection #%ld) \n", "STATE: %s => %s handle %p; (connection #%ld) \n",
statename[oldstate], statename[easy->state], statename[oldstate], statename[easy->state],
(char *)easy, connectindex); (char *)easy, connection_id);
} }
#endif #endif
if(state == CURLM_STATE_COMPLETED) if(state == CURLM_STATE_COMPLETED)
@ -391,7 +386,6 @@ static void multi_freeamsg(void *a, void *b)
(void)b; (void)b;
} }
CURLM *curl_multi_init(void) CURLM *curl_multi_init(void)
{ {
struct Curl_multi *multi = calloc(1, sizeof(struct Curl_multi)); struct Curl_multi *multi = calloc(1, sizeof(struct Curl_multi));
@ -409,8 +403,8 @@ CURLM *curl_multi_init(void)
if(!multi->sockhash) if(!multi->sockhash)
goto error; goto error;
multi->connc = Curl_mk_connc(CONNCACHE_MULTI, -1L); multi->conn_cache = Curl_conncache_init(CONNCACHE_MULTI);
if(!multi->connc) if(!multi->conn_cache)
goto error; goto error;
multi->msglist = Curl_llist_alloc(multi_freeamsg); multi->msglist = Curl_llist_alloc(multi_freeamsg);
@ -431,8 +425,8 @@ CURLM *curl_multi_init(void)
multi->sockhash = NULL; multi->sockhash = NULL;
Curl_hash_destroy(multi->hostcache); Curl_hash_destroy(multi->hostcache);
multi->hostcache = NULL; multi->hostcache = NULL;
Curl_rm_connc(multi->connc); Curl_conncache_destroy(multi->conn_cache);
multi->connc = NULL; multi->conn_cache = NULL;
free(multi); free(multi);
return NULL; return NULL;
@ -443,8 +437,6 @@ CURLMcode curl_multi_add_handle(CURLM *multi_handle,
{ {
struct curl_llist *timeoutlist; struct curl_llist *timeoutlist;
struct Curl_one_easy *easy; struct Curl_one_easy *easy;
struct closure *cl;
struct closure *prev = NULL;
struct Curl_multi *multi = (struct Curl_multi *)multi_handle; struct Curl_multi *multi = (struct Curl_multi *)multi_handle;
struct SessionHandle *data = (struct SessionHandle *)easy_handle; struct SessionHandle *data = (struct SessionHandle *)easy_handle;
@ -462,22 +454,13 @@ CURLMcode curl_multi_add_handle(CURLM *multi_handle,
/* possibly we should create a new unique error code for this condition */ /* possibly we should create a new unique error code for this condition */
return CURLM_BAD_EASY_HANDLE; return CURLM_BAD_EASY_HANDLE;
/* We want the connection cache to have plenty of room. Before we supported /* This is a good time to allocate a fresh easy handle to use when closing
the shared cache every single easy handle had 5 entries in their cache cached connections */
by default. */ if(!multi->closure_handle) {
if(((multi->num_easy + 1) * 4) > multi->connc->num) { multi->closure_handle =
long newmax = (multi->num_easy + 1) * 4; (struct SessionHandle *)curl_easy_init();
Curl_easy_addmulti(easy_handle, multi_handle);
if(multi->maxconnects && (newmax > multi->maxconnects)) multi->closure_handle->state.conn_cache = multi->conn_cache;
/* don't grow beyond the allowed size */
newmax = multi->maxconnects;
if(newmax > multi->connc->num) {
/* we only do this is we can in fact grow the cache */
CURLcode res = Curl_ch_connc(data, multi->connc, newmax);
if(res)
return CURLM_OUT_OF_MEMORY;
}
} }
/* Allocate and initialize timeout list for easy handle */ /* Allocate and initialize timeout list for easy handle */
@ -504,27 +487,6 @@ CURLMcode curl_multi_add_handle(CURLM *multi_handle,
data->state.timeoutlist = timeoutlist; data->state.timeoutlist = timeoutlist;
timeoutlist = NULL; timeoutlist = NULL;
/* Remove handle from the list of 'closure handles' in case it is there */
cl = multi->closure;
while(cl) {
struct closure *next = cl->next;
if(cl->easy_handle == data) {
/* Remove node from list */
free(cl);
if(prev)
prev->next = next;
else
multi->closure = next;
/* removed from closure list now, this might reuse an existing
existing connection but we don't know that at this point */
data->state.shared_conn = NULL;
/* No need to continue, handle can only be present once in the list */
break;
}
prev = cl;
cl = next;
}
/* set the easy handle */ /* set the easy handle */
easy->easy_handle = data; easy->easy_handle = data;
multistate(easy, CURLM_STATE_INIT); multistate(easy, CURLM_STATE_INIT);
@ -548,16 +510,15 @@ CURLMcode curl_multi_add_handle(CURLM *multi_handle,
} }
/* On a multi stack the connection cache, owned by the multi handle, /* On a multi stack the connection cache, owned by the multi handle,
is shared between all easy handles within the multi handle. */ is shared between all easy handles within the multi handle.
if(easy->easy_handle->state.connc && Therefore we free the private connection cache if there is one */
(easy->easy_handle->state.connc->type == CONNCACHE_PRIVATE)) { if(easy->easy_handle->state.conn_cache &&
/* kill old private connection cache */ easy->easy_handle->state.conn_cache->type == CONNCACHE_PRIVATE) {
Curl_rm_connc(easy->easy_handle->state.connc); Curl_conncache_destroy(easy->easy_handle->state.conn_cache);
easy->easy_handle->state.connc = NULL;
} }
/* Point now to this multi's connection cache */ /* Point now to this multi's connection cache */
easy->easy_handle->state.connc = multi->connc; easy->easy_handle->state.conn_cache = multi->conn_cache;
easy->easy_handle->state.connc->type = CONNCACHE_MULTI;
/* This adds the new entry at the 'end' of the doubly-linked circular /* This adds the new entry at the 'end' of the doubly-linked circular
list of Curl_one_easy structs to try and maintain a FIFO queue so list of Curl_one_easy structs to try and maintain a FIFO queue so
@ -701,42 +662,17 @@ CURLMcode curl_multi_remove_handle(CURLM *multi_handle,
Note that this ignores the return code simply because there's Note that this ignores the return code simply because there's
nothing really useful to do with it anyway! */ nothing really useful to do with it anyway! */
(void)Curl_done(&easy->easy_conn, easy->result, premature); (void)Curl_done(&easy->easy_conn, easy->result, premature);
if(easy->easy_conn)
/* the connection is still alive, set back the association to enable
the check below to trigger TRUE */
easy->easy_conn->data = easy->easy_handle;
} }
else else
/* Clear connection pipelines, if Curl_done above was not called */ /* Clear connection pipelines, if Curl_done above was not called */
Curl_getoff_all_pipelines(easy->easy_handle, easy->easy_conn); Curl_getoff_all_pipelines(easy->easy_handle, easy->easy_conn);
} }
/* figure out if the easy handle is used by one or more connections in the if(easy->easy_handle->state.conn_cache->type == CONNCACHE_MULTI) {
cache */
multi_connc_remove_handle(multi, easy->easy_handle);
if(easy->easy_handle->state.connc->type == CONNCACHE_MULTI) {
/* if this was using the shared connection cache we clear the pointer /* if this was using the shared connection cache we clear the pointer
to that since we're not part of that handle anymore */ to that since we're not part of that handle anymore */
easy->easy_handle->state.connc = NULL; easy->easy_handle->state.conn_cache = NULL;
easy->easy_handle->state.lastconnect = NULL;
/* Since we return the connection back to the communal connection pool
we mark the last connection as inaccessible */
easy->easy_handle->state.lastconnect = -1;
/* Modify the connectindex since this handle can't point to the
connection cache anymore.
TODO: consider if this is really what we want. The connection cache
is within the multi handle and that owns the connections so we should
not need to touch connections like this when we just remove an easy
handle...
*/
if(easy->easy_conn && easy_owns_conn &&
(easy->easy_conn->send_pipe->size +
easy->easy_conn->recv_pipe->size == 0))
easy->easy_conn->connectindex = -1;
} }
/* change state without using multistate(), only to make singlesocket() do /* change state without using multistate(), only to make singlesocket() do
@ -745,6 +681,12 @@ CURLMcode curl_multi_remove_handle(CURLM *multi_handle,
singlesocket(multi, easy); /* to let the application know what sockets singlesocket(multi, easy); /* to let the application know what sockets
that vanish with this handle */ that vanish with this handle */
/* Remove the association between the connection and the handle */
if(easy->easy_conn) {
easy->easy_conn->data = NULL;
easy->easy_conn = NULL;
}
Curl_easy_addmulti(easy->easy_handle, NULL); /* clear the association Curl_easy_addmulti(easy->easy_handle, NULL); /* clear the association
to this multi handle */ to this multi handle */
@ -1324,8 +1266,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
case CURLM_STATE_WAITDO: case CURLM_STATE_WAITDO:
/* Wait for our turn to DO when we're pipelining requests */ /* Wait for our turn to DO when we're pipelining requests */
#ifdef DEBUGBUILD #ifdef DEBUGBUILD
infof(data, "Conn %ld send pipe %zu inuse %d athead %d\n", infof(data, "WAITDO: Conn %ld send pipe %zu inuse %d athead %d\n",
easy->easy_conn->connectindex, easy->easy_conn->connection_id,
easy->easy_conn->send_pipe->size, easy->easy_conn->send_pipe->size,
easy->easy_conn->writechannel_inuse?1:0, easy->easy_conn->writechannel_inuse?1:0,
isHandleAtHead(data, isHandleAtHead(data,
@ -1514,8 +1456,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
} }
#ifdef DEBUGBUILD #ifdef DEBUGBUILD
else { else {
infof(data, "Conn %ld recv pipe %zu inuse %d athead %d\n", infof(data, "WAITPERFORM: Conn %ld recv pipe %zu inuse %d athead %d\n",
easy->easy_conn->connectindex, easy->easy_conn->connection_id,
easy->easy_conn->recv_pipe->size, easy->easy_conn->recv_pipe->size,
easy->easy_conn->readchannel_inuse?1:0, easy->easy_conn->readchannel_inuse?1:0,
isHandleAtHead(data, isHandleAtHead(data,
@ -1891,45 +1833,41 @@ CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles)
return returncode; return returncode;
} }
static void close_all_connections(struct Curl_multi *multi)
{
struct connectdata *conn;
conn = Curl_conncache_find_first_connection(multi->conn_cache);
while(conn) {
conn->data = multi->closure_handle;
/* This will remove the connection from the cache */
(void)Curl_disconnect(conn, FALSE);
conn = Curl_conncache_find_first_connection(multi->conn_cache);
}
}
CURLMcode curl_multi_cleanup(CURLM *multi_handle) CURLMcode curl_multi_cleanup(CURLM *multi_handle)
{ {
struct Curl_multi *multi=(struct Curl_multi *)multi_handle; struct Curl_multi *multi=(struct Curl_multi *)multi_handle;
struct Curl_one_easy *easy; struct Curl_one_easy *easy;
struct Curl_one_easy *nexteasy; struct Curl_one_easy *nexteasy;
int i;
struct closure *cl;
struct closure *n;
if(GOOD_MULTI_HANDLE(multi)) { if(GOOD_MULTI_HANDLE(multi)) {
multi->type = 0; /* not good anymore */ multi->type = 0; /* not good anymore */
/* go over all connections that have close actions */ /* Close all the connections in the connection cache */
for(i=0; i< multi->connc->num; i++) { close_all_connections(multi);
if(multi->connc->connects[i] &&
multi->connc->connects[i]->handler->flags & PROTOPT_CLOSEACTION) { Curl_close(multi->closure_handle);
Curl_disconnect(multi->connc->connects[i], FALSE); multi->closure_handle = NULL;
multi->connc->connects[i] = NULL;
}
}
/* now walk through the list of handles we kept around only to be
able to close connections "properly" */
cl = multi->closure;
while(cl) {
cl->easy_handle->state.shared_conn = NULL; /* allow cleanup */
if(cl->easy_handle->state.closed)
/* close handle only if curl_easy_cleanup() already has been called
for this easy handle */
Curl_close(cl->easy_handle);
n = cl->next;
free(cl);
cl= n;
}
Curl_hash_destroy(multi->sockhash); Curl_hash_destroy(multi->sockhash);
multi->sockhash = NULL; multi->sockhash = NULL;
Curl_rm_connc(multi->connc); Curl_conncache_destroy(multi->conn_cache);
multi->connc = NULL; multi->conn_cache = NULL;
/* remove the pending list of messages */ /* remove the pending list of messages */
Curl_llist_destroy(multi->msglist, NULL); Curl_llist_destroy(multi->msglist, NULL);
@ -1947,7 +1885,7 @@ CURLMcode curl_multi_cleanup(CURLM *multi_handle)
} }
/* Clear the pointer to the connection cache */ /* Clear the pointer to the connection cache */
easy->easy_handle->state.connc = NULL; easy->easy_handle->state.conn_cache = NULL;
Curl_easy_addmulti(easy->easy_handle, NULL); /* clear the association */ Curl_easy_addmulti(easy->easy_handle, NULL); /* clear the association */
@ -2803,125 +2741,6 @@ CURLMcode curl_multi_assign(CURLM *multi_handle,
return CURLM_OK; return CURLM_OK;
} }
static void multi_connc_remove_handle(struct Curl_multi *multi,
struct SessionHandle *data)
{
/* a connection in the connection cache pointing to the given 'data' ? */
int i;
for(i=0; i< multi->connc->num; i++) {
struct connectdata * conn = multi->connc->connects[i];
if(conn && conn->data == data) {
/* If this easy_handle was the last one in charge for one or more
connections in the shared connection cache, we might need to keep
this handle around until either A) the connection is closed and
killed properly, or B) another easy_handle uses the connection.
The reason why we need to have a easy_handle associated with a live
connection is simply that some connections will need a handle to get
closed down properly. Currently, the only connections that need to
keep a easy_handle handle around are using FTP(S). Such connections
have the PROT_CLOSEACTION bit set.
Thus, we need to check for all connections in the shared cache that
points to this handle and are using PROT_CLOSEACTION. If there's any,
we need to add this handle to the list of "easy handles kept around
for nice connection closures".
*/
if(conn->handler->flags & PROTOPT_CLOSEACTION) {
/* this handle is still being used by a shared connection and
thus we leave it around for now */
if(add_closure(multi, data) == CURLM_OK)
data->state.shared_conn = multi;
else {
/* out of memory - so much for graceful shutdown */
Curl_disconnect(conn, /* dead_connection */ FALSE);
multi->connc->connects[i] = NULL;
data->state.shared_conn = NULL;
}
}
else {
/* disconect the easy handle from the connection since the connection
will now remain but this easy handle is going */
data->state.shared_conn = NULL;
conn->data = NULL;
}
}
}
}
/* Add the given data pointer to the list of 'closure handles' that are kept
around only to be able to close some connections nicely - just make sure
that this handle isn't already added, like for the cases when an easy
handle is removed, added and removed again... */
static CURLMcode add_closure(struct Curl_multi *multi,
struct SessionHandle *data)
{
struct closure *cl = multi->closure;
struct closure *p = NULL;
bool add = TRUE;
/* Before adding, scan through all the other currently kept handles and see
if there are any connections still referring to them and kill them if
not. */
while(cl) {
struct closure *n;
bool inuse = FALSE;
int i;
for(i=0; i< multi->connc->num; i++) {
if(multi->connc->connects[i] &&
(multi->connc->connects[i]->data == cl->easy_handle)) {
inuse = TRUE;
break;
}
}
n = cl->next;
if(!inuse) {
/* cl->easy_handle is now killable */
/* unmark it as not having a connection around that uses it anymore */
cl->easy_handle->state.shared_conn= NULL;
if(cl->easy_handle->state.closed) {
infof(data, "Delayed kill of easy handle %p\n", cl->easy_handle);
/* close handle only if curl_easy_cleanup() already has been called
for this easy handle */
Curl_close(cl->easy_handle);
}
if(p)
p->next = n;
else
multi->closure = n;
free(cl);
}
else {
if(cl->easy_handle == data)
add = FALSE;
p = cl;
}
cl = n;
}
if(add) {
cl = calloc(1, sizeof(struct closure));
if(!cl)
return CURLM_OUT_OF_MEMORY;
cl->easy_handle = data;
cl->next = multi->closure;
multi->closure = cl;
}
return CURLM_OK;
}
#ifdef DEBUGBUILD #ifdef DEBUGBUILD
void Curl_multi_dump(const struct Curl_multi *multi_handle) void Curl_multi_dump(const struct Curl_multi *multi_handle)
{ {

708
lib/url.c
Просмотреть файл

@ -126,6 +126,8 @@ int curl_win32_idn_to_ascii(const char *in, char **out);
#include "curl_rtmp.h" #include "curl_rtmp.h"
#include "gopher.h" #include "gopher.h"
#include "http_proxy.h" #include "http_proxy.h"
#include "bundles.h"
#include "conncache.h"
#define _MPRINTF_REPLACE /* use our functions only */ #define _MPRINTF_REPLACE /* use our functions only */
#include <curl/mprintf.h> #include <curl/mprintf.h>
@ -135,7 +137,7 @@ int curl_win32_idn_to_ascii(const char *in, char **out);
#include "memdebug.h" #include "memdebug.h"
/* Local static prototypes */ /* Local static prototypes */
static long ConnectionKillOne(struct SessionHandle *data); static bool ConnectionKillOne(struct SessionHandle *data);
static void conn_free(struct connectdata *conn); static void conn_free(struct connectdata *conn);
static void signalPipeClose(struct curl_llist *pipeline, bool pipe_broke); static void signalPipeClose(struct curl_llist *pipeline, bool pipe_broke);
static CURLcode do_init(struct connectdata *conn); static CURLcode do_init(struct connectdata *conn);
@ -262,10 +264,10 @@ static const struct Curl_handler Curl_handler_dummy = {
static void close_connections(struct SessionHandle *data) static void close_connections(struct SessionHandle *data)
{ {
/* Loop through all open connections and kill them one by one */ /* Loop through all open connections and kill them one by one */
long i; bool killed;
do { do {
i = ConnectionKillOne(data); killed = ConnectionKillOne(data);
} while(i != -1L); } while(killed);
} }
void Curl_freeset(struct SessionHandle * data) void Curl_freeset(struct SessionHandle * data)
@ -378,66 +380,6 @@ CURLcode Curl_close(struct SessionHandle *data)
{ {
struct Curl_multi *m = data->multi; struct Curl_multi *m = data->multi;
#ifdef DEBUGBUILD
/* only for debugging, scan through all connections and see if there's a
pipe reference still identifying this handle */
if(data->state.connc && data->state.connc->type == CONNCACHE_MULTI) {
struct conncache *c = data->state.connc;
long i;
struct curl_llist *pipeline;
struct curl_llist_element *curr;
struct connectdata *connptr;
for(i=0; i< c->num; i++) {
connptr = c->connects[i];
if(!connptr)
continue;
pipeline = connptr->send_pipe;
if(pipeline) {
for(curr = pipeline->head; curr; curr=curr->next) {
if(data == (struct SessionHandle *) curr->ptr) {
fprintf(stderr,
"problem we %p are still in send pipe for %p done %d\n",
data, connptr, (int)connptr->bits.done);
}
}
}
pipeline = connptr->recv_pipe;
if(pipeline) {
for(curr = pipeline->head; curr; curr=curr->next) {
if(data == (struct SessionHandle *) curr->ptr) {
fprintf(stderr,
"problem we %p are still in recv pipe for %p done %d\n",
data, connptr, (int)connptr->bits.done);
}
}
}
pipeline = connptr->done_pipe;
if(pipeline) {
for(curr = pipeline->head; curr; curr=curr->next) {
if(data == (struct SessionHandle *) curr->ptr) {
fprintf(stderr,
"problem we %p are still in done pipe for %p done %d\n",
data, connptr, (int)connptr->bits.done);
}
}
}
pipeline = connptr->pend_pipe;
if(pipeline) {
for(curr = pipeline->head; curr; curr=curr->next) {
if(data == (struct SessionHandle *) curr->ptr) {
fprintf(stderr,
"problem we %p are still in pend pipe for %p done %d\n",
data, connptr, (int)connptr->bits.done);
}
}
}
}
}
#endif
Curl_expire(data, 0); /* shut off timers */ Curl_expire(data, 0); /* shut off timers */
if(m) if(m)
@ -457,26 +399,16 @@ CURLcode Curl_close(struct SessionHandle *data)
the multi handle, since that function uses the magic the multi handle, since that function uses the magic
field! */ field! */
if(data->state.connc) { if(data->state.conn_cache) {
if(data->state.conn_cache->type == CONNCACHE_PRIVATE) {
if(data->state.connc->type == CONNCACHE_PRIVATE) {
/* close all connections still alive that are in the private connection /* close all connections still alive that are in the private connection
cache, as we no longer have the pointer left to the shared one. */ cache, as we no longer have the pointer left to the shared one. */
close_connections(data); close_connections(data);
Curl_conncache_destroy(data->state.conn_cache);
/* free the connection cache if allocated privately */ data->state.conn_cache = NULL;
Curl_rm_connc(data->state.connc);
data->state.connc = NULL;
} }
} }
if(data->state.shared_conn) {
/* marked to be used by a pending connection so we can't kill this handle
just yet */
data->state.closed = TRUE;
return CURLE_OK;
}
if(data->dns.hostcachetype == HCACHE_PRIVATE) if(data->dns.hostcachetype == HCACHE_PRIVATE)
Curl_hostcache_destroy(data); Curl_hostcache_destroy(data);
@ -533,124 +465,6 @@ CURLcode Curl_close(struct SessionHandle *data)
return CURLE_OK; return CURLE_OK;
} }
/* create a connection cache of a private or multi type */
struct conncache *Curl_mk_connc(int type,
long amount) /* set -1 to use default */
{
/* It is subject for debate how many default connections to have for a multi
connection cache... */
struct conncache *c;
long default_amount;
long max_amount = (long)(((size_t)INT_MAX) / sizeof(struct connectdata *));
if(type == CONNCACHE_PRIVATE) {
default_amount = (amount < 1L) ? 5L : amount;
}
else {
default_amount = (amount < 1L) ? 10L : amount;
}
if(default_amount > max_amount)
default_amount = max_amount;
c = calloc(1, sizeof(struct conncache));
if(!c)
return NULL;
c->connects = calloc((size_t)default_amount, sizeof(struct connectdata *));
if(!c->connects) {
free(c);
return NULL;
}
c->num = default_amount;
return c;
}
/* Change number of entries of a connection cache */
CURLcode Curl_ch_connc(struct SessionHandle *data,
struct conncache *c,
long newamount)
{
long i;
struct connectdata **newptr;
long max_amount = (long)(((size_t)INT_MAX) / sizeof(struct connectdata *));
if(newamount < 1)
newamount = 1; /* we better have at least one entry */
if(!c) {
/* we get a NULL pointer passed in as connection cache, which means that
there is no cache created for this SessionHandle just yet, we create a
brand new with the requested size.
*/
data->state.connc = Curl_mk_connc(CONNCACHE_PRIVATE, newamount);
if(!data->state.connc)
return CURLE_OUT_OF_MEMORY;
return CURLE_OK;
}
if(newamount < c->num) {
/* Since this number is *decreased* from the existing number, we must
close the possibly open connections that live on the indexes that
are being removed!
NOTE: for conncache_multi cases we must make sure that we only
close handles not in use.
*/
for(i=newamount; i< c->num; i++) {
Curl_disconnect(c->connects[i], /* dead_connection */ FALSE);
c->connects[i] = NULL;
}
/* If the most recent connection is no longer valid, mark it
invalid. */
if(data->state.lastconnect <= newamount)
data->state.lastconnect = -1;
}
if(newamount > 0) {
if(newamount > max_amount)
newamount = max_amount;
newptr = realloc(c->connects, sizeof(struct connectdata *) * newamount);
if(!newptr)
/* we closed a few connections in vain, but so what? */
return CURLE_OUT_OF_MEMORY;
/* nullify the newly added pointers */
for(i=c->num; i<newamount; i++)
newptr[i] = NULL;
c->connects = newptr;
c->num = newamount;
}
/* we no longer support less than 1 as size for the connection cache, and
I'm not sure it ever worked to set it to zero */
return CURLE_OK;
}
/* Free a connection cache. This is called from Curl_close() and
curl_multi_cleanup(). */
void Curl_rm_connc(struct conncache *c)
{
if(!c)
return;
if(c->connects) {
long i;
for(i = 0; i < c->num; ++i) {
conn_free(c->connects[i]);
c->connects[i] = NULL;
}
free(c->connects);
c->connects = NULL;
}
c->num = 0;
free(c);
}
/* /*
* Initialize the UserDefined fields within a SessionHandle. * Initialize the UserDefined fields within a SessionHandle.
* This may be safely called on a new or existing SessionHandle. * This may be safely called on a new or existing SessionHandle.
@ -807,7 +621,7 @@ CURLcode Curl_open(struct SessionHandle **curl)
Curl_convert_init(data); Curl_convert_init(data);
/* most recent connection is not yet defined */ /* most recent connection is not yet defined */
data->state.lastconnect = -1; data->state.lastconnect = NULL;
data->progress.flags |= PGRS_HIDE; data->progress.flags |= PGRS_HIDE;
data->state.current_speed = -1; /* init to negative == impossible */ data->state.current_speed = -1; /* init to negative == impossible */
@ -880,7 +694,7 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
* Set the absolute number of maximum simultaneous alive connection that * Set the absolute number of maximum simultaneous alive connection that
* libcurl is allowed to have. * libcurl is allowed to have.
*/ */
result = Curl_ch_connc(data, data->state.connc, va_arg(param, long)); data->set.maxconnects = va_arg(param, long);
break; break;
case CURLOPT_FORBID_REUSE: case CURLOPT_FORBID_REUSE:
/* /*
@ -2744,14 +2558,9 @@ CURLcode Curl_disconnect(struct connectdata *conn, bool dead_connection)
/* This is set if protocol-specific cleanups should be made */ /* This is set if protocol-specific cleanups should be made */
conn->handler->disconnect(conn, dead_connection); conn->handler->disconnect(conn, dead_connection);
if(-1 != conn->connectindex) {
/* unlink ourselves! */ /* unlink ourselves! */
infof(data, "Closing connection #%ld\n", conn->connectindex); infof(data, "Closing connection %d\n", conn->connection_id);
if(data->state.connc) Curl_conncache_remove_conn(data->state.conn_cache, conn);
/* only clear the table entry if we still know in which cache we
used to be in */
data->state.connc->connects[conn->connectindex] = NULL;
}
#if defined(USE_LIBIDN) #if defined(USE_LIBIDN)
if(conn->host.encalloc) if(conn->host.encalloc)
@ -2938,225 +2747,229 @@ ConnectionExists(struct SessionHandle *data,
struct connectdata *needle, struct connectdata *needle,
struct connectdata **usethis) struct connectdata **usethis)
{ {
long i;
struct connectdata *check; struct connectdata *check;
struct connectdata *chosen = 0; struct connectdata *chosen = 0;
bool canPipeline = IsPipeliningPossible(data, needle); bool canPipeline = IsPipeliningPossible(data, needle);
bool wantNTLM = (data->state.authhost.want==CURLAUTH_NTLM) || bool wantNTLM = (data->state.authhost.want==CURLAUTH_NTLM) ||
(data->state.authhost.want==CURLAUTH_NTLM_WB) ? TRUE : FALSE; (data->state.authhost.want==CURLAUTH_NTLM_WB) ? TRUE : FALSE;
struct connectbundle *bundle;
for(i=0; i< data->state.connc->num; i++) { /* Look up the bundle with all the connections to this
bool match = FALSE; particular host */
bool credentialsMatch = FALSE; bundle = Curl_conncache_find_bundle(data->state.conn_cache,
size_t pipeLen = 0; needle->host.name);
/* if(bundle) {
* Note that if we use a HTTP proxy, we check connections to that struct curl_llist_element *curr;
* proxy and not to the actual remote server.
*/
check = data->state.connc->connects[i];
if(!check)
/* NULL pointer means not filled-in entry */
continue;
pipeLen = check->send_pipe->size + check->recv_pipe->size; infof(data, "Found bundle for host %s: %p\n", needle->host.name, bundle);
if(check->connectindex == -1) { curr = bundle->conn_list->head;
check->connectindex = i; /* Set this appropriately since it might have while(curr) {
been set to -1 when the easy was removed bool match = FALSE;
from the multi */ bool credentialsMatch = FALSE;
} size_t pipeLen;
if(!pipeLen && !check->inuse) { /*
/* The check for a dead socket makes sense only if there are no * Note that if we use a HTTP proxy, we check connections to that
handles in pipeline and the connection isn't already marked in * proxy and not to the actual remote server.
use */ */
bool dead; check = curr->ptr;
if(check->handler->protocol & CURLPROTO_RTSP) curr = curr->next;
/* RTSP is a special case due to RTP interleaving */
dead = Curl_rtsp_connisdead(check);
else
dead = SocketIsDead(check->sock[FIRSTSOCKET]);
if(dead) { pipeLen = check->send_pipe->size + check->recv_pipe->size;
check->data = data;
infof(data, "Connection #%ld seems to be dead!\n", i);
/* disconnect resources */ if(!pipeLen && !check->inuse) {
Curl_disconnect(check, /* dead_connection */ TRUE); /* The check for a dead socket makes sense only if there are no
data->state.connc->connects[i]=NULL; /* nothing here */ handles in pipeline and the connection isn't already marked in
use */
bool dead;
if(check->handler->protocol & CURLPROTO_RTSP)
/* RTSP is a special case due to RTP interleaving */
dead = Curl_rtsp_connisdead(check);
else
dead = SocketIsDead(check->sock[FIRSTSOCKET]);
continue; if(dead) {
} check->data = data;
} infof(data, "Connection %d seems to be dead!\n",
check->connection_id);
if(canPipeline) { /* disconnect resources */
/* Make sure the pipe has only GET requests */ Curl_disconnect(check, /* dead_connection */ TRUE);
struct SessionHandle* sh = gethandleathead(check->send_pipe);
struct SessionHandle* rh = gethandleathead(check->recv_pipe);
if(sh) {
if(!IsPipeliningPossible(sh, check))
continue;
}
else if(rh) {
if(!IsPipeliningPossible(rh, check))
continue; continue;
}
} }
if(canPipeline) {
/* Make sure the pipe has only GET requests */
struct SessionHandle* sh = gethandleathead(check->send_pipe);
struct SessionHandle* rh = gethandleathead(check->recv_pipe);
if(sh) {
if(!IsPipeliningPossible(sh, check))
continue;
}
else if(rh) {
if(!IsPipeliningPossible(rh, check))
continue;
}
#ifdef DEBUGBUILD #ifdef DEBUGBUILD
if(pipeLen > MAX_PIPELINE_LENGTH) { if(pipeLen > MAX_PIPELINE_LENGTH) {
infof(data, "BAD! Connection #%ld has too big pipeline!\n", infof(data, "BAD! Connection #%ld has too big pipeline!\n",
check->connectindex); check->connection_id);
} }
#endif #endif
}
else {
if(pipeLen > 0) {
/* can only happen within multi handles, and means that another easy
handle is using this connection */
continue;
} }
else {
if(pipeLen > 0) {
/* can only happen within multi handles, and means that another easy
handle is using this connection */
continue;
}
if(Curl_resolver_asynch()) { if(Curl_resolver_asynch()) {
/* ip_addr_str[0] is NUL only if the resolving of the name hasn't /* ip_addr_str[0] is NUL only if the resolving of the name hasn't
completed yet and until then we don't re-use this connection */ completed yet and until then we don't re-use this connection */
if(!check->ip_addr_str[0]) { if(!check->ip_addr_str[0]) {
infof(data, infof(data,
"Connection #%ld hasn't finished name resolve, can't reuse\n", "Connection #%ld is still name resolving, can't reuse\n",
check->connectindex); check->connection_id);
continue;
}
}
if((check->sock[FIRSTSOCKET] == CURL_SOCKET_BAD) ||
check->bits.close) {
/* Don't pick a connection that hasn't connected yet or that is going
to get closed. */
infof(data, "Connection #%ld isn't open enough, can't reuse\n",
check->connection_id);
#ifdef DEBUGBUILD
if(check->recv_pipe->size > 0) {
infof(data,
"BAD! Unconnected #%ld has a non-empty recv pipeline!\n",
check->connection_id);
}
#endif
continue; continue;
} }
} }
if((check->sock[FIRSTSOCKET] == CURL_SOCKET_BAD) || check->bits.close) { if((needle->handler->flags&PROTOPT_SSL) !=
/* Don't pick a connection that hasn't connected yet or that is going (check->handler->flags&PROTOPT_SSL))
to get closed. */ /* don't do mixed SSL and non-SSL connections */
infof(data, "Connection #%ld isn't open enough, can't reuse\n", if(!(needle->handler->protocol & check->handler->protocol))
check->connectindex); /* except protocols that have been upgraded via TLS */
#ifdef DEBUGBUILD continue;
if(check->recv_pipe->size > 0) {
infof(data, "BAD! Unconnected #%ld has a non-empty recv pipeline!\n", if(needle->handler->flags&PROTOPT_SSL) {
check->connectindex); if((data->set.ssl.verifypeer != check->verifypeer) ||
} (data->set.ssl.verifyhost != check->verifyhost))
#endif continue;
continue;
} }
}
if((needle->handler->flags&PROTOPT_SSL) != if(needle->bits.proxy != check->bits.proxy)
(check->handler->flags&PROTOPT_SSL)) /* don't do mixed proxy and non-proxy connections */
/* don't do mixed SSL and non-SSL connections */
if(!(needle->handler->protocol & check->handler->protocol))
/* except protocols that have been upgraded via TLS */
continue; continue;
if(needle->handler->flags&PROTOPT_SSL) { if(!canPipeline && check->inuse)
if((data->set.ssl.verifypeer != check->verifypeer) || /* this request can't be pipelined but the checked connection is
(data->set.ssl.verifyhost != check->verifyhost)) already in use so we skip it */
continue; continue;
}
if(needle->bits.proxy != check->bits.proxy) if(needle->localdev || needle->localport) {
/* don't do mixed proxy and non-proxy connections */ /* If we are bound to a specific local end (IP+port), we must not
continue; re-use a random other one, although if we didn't ask for a
particular one we can reuse one that was bound.
if(!canPipeline && check->inuse) This comparison is a bit rough and too strict. Since the input
/* this request can't be pipelined but the checked connection is already parameters can be specified in numerous ways and still end up the
in use so we skip it */ same it would take a lot of processing to make it really accurate.
continue; Instead, this matching will assume that re-uses of bound connections
will most likely also re-use the exact same binding parameters and
if(needle->localdev || needle->localport) { missing out a few edge cases shouldn't hurt anyone very much.
/* If we are bound to a specific local end (IP+port), we must not re-use */
a random other one, although if we didn't ask for a particular one we if((check->localport != needle->localport) ||
can reuse one that was bound. (check->localportrange != needle->localportrange) ||
!check->localdev ||
This comparison is a bit rough and too strict. Since the input !needle->localdev ||
parameters can be specified in numerous ways and still end up the strcmp(check->localdev, needle->localdev))
same it would take a lot of processing to make it really accurate. continue;
Instead, this matching will assume that re-uses of bound connections
will most likely also re-use the exact same binding parameters and
missing out a few edge cases shouldn't hurt anyone very much.
*/
if((check->localport != needle->localport) ||
(check->localportrange != needle->localportrange) ||
!check->localdev ||
!needle->localdev ||
strcmp(check->localdev, needle->localdev))
continue;
}
if(!needle->bits.httpproxy || needle->handler->flags&PROTOPT_SSL ||
(needle->bits.httpproxy && check->bits.httpproxy &&
needle->bits.tunnel_proxy && check->bits.tunnel_proxy &&
Curl_raw_equal(needle->proxy.name, check->proxy.name) &&
(needle->port == check->port))) {
/* The requested connection does not use a HTTP proxy or it uses SSL or
it is a non-SSL protocol tunneled over the same http proxy name and
port number or it is a non-SSL protocol which is allowed to be
upgraded via TLS */
if((Curl_raw_equal(needle->handler->scheme, check->handler->scheme) ||
needle->handler->protocol & check->handler->protocol) &&
Curl_raw_equal(needle->host.name, check->host.name) &&
needle->remote_port == check->remote_port) {
if(needle->handler->flags & PROTOPT_SSL) {
/* This is a SSL connection so verify that we're using the same
SSL options as well */
if(!Curl_ssl_config_matches(&needle->ssl_config,
&check->ssl_config)) {
DEBUGF(infof(data,
"Connection #%ld has different SSL parameters, "
"can't reuse\n",
check->connectindex));
continue;
}
else if(check->ssl[FIRSTSOCKET].state != ssl_connection_complete) {
DEBUGF(infof(data,
"Connection #%ld has not started SSL connect, "
"can't reuse\n",
check->connectindex));
continue;
}
}
if((needle->handler->protocol & CURLPROTO_FTP) ||
((needle->handler->protocol & CURLPROTO_HTTP) && wantNTLM)) {
/* This is FTP or HTTP+NTLM, verify that we're using the same name
and password as well */
if(!strequal(needle->user, check->user) ||
!strequal(needle->passwd, check->passwd)) {
/* one of them was different */
continue;
}
credentialsMatch = TRUE;
}
match = TRUE;
} }
}
else { /* The requested needle connection is using a proxy, if(!needle->bits.httpproxy || needle->handler->flags&PROTOPT_SSL ||
is the checked one using the same host, port and type? */ (needle->bits.httpproxy && check->bits.httpproxy &&
if(check->bits.proxy && needle->bits.tunnel_proxy && check->bits.tunnel_proxy &&
(needle->proxytype == check->proxytype) && Curl_raw_equal(needle->proxy.name, check->proxy.name) &&
(needle->bits.tunnel_proxy == check->bits.tunnel_proxy) && (needle->port == check->port))) {
Curl_raw_equal(needle->proxy.name, check->proxy.name) && /* The requested connection does not use a HTTP proxy or it uses SSL or
needle->port == check->port) { it is a non-SSL protocol tunneled over the same http proxy name and
/* This is the same proxy connection, use it! */ port number or it is a non-SSL protocol which is allowed to be
match = TRUE; upgraded via TLS */
if((Curl_raw_equal(needle->handler->scheme, check->handler->scheme) ||
needle->handler->protocol & check->handler->protocol) &&
Curl_raw_equal(needle->host.name, check->host.name) &&
needle->remote_port == check->remote_port) {
if(needle->handler->flags & PROTOPT_SSL) {
/* This is a SSL connection so verify that we're using the same
SSL options as well */
if(!Curl_ssl_config_matches(&needle->ssl_config,
&check->ssl_config)) {
DEBUGF(infof(data,
"Connection #%ld has different SSL parameters, "
"can't reuse\n",
check->connection_id));
continue;
}
else if(check->ssl[FIRSTSOCKET].state != ssl_connection_complete) {
DEBUGF(infof(data,
"Connection #%ld has not started SSL connect, "
"can't reuse\n",
check->connection_id));
continue;
}
}
if((needle->handler->protocol & CURLPROTO_FTP) ||
((needle->handler->protocol & CURLPROTO_HTTP) && wantNTLM)) {
/* This is FTP or HTTP+NTLM, verify that we're using the same name
and password as well */
if(!strequal(needle->user, check->user) ||
!strequal(needle->passwd, check->passwd)) {
/* one of them was different */
continue;
}
credentialsMatch = TRUE;
}
match = TRUE;
}
}
else { /* The requested needle connection is using a proxy,
is the checked one using the same host, port and type? */
if(check->bits.proxy &&
(needle->proxytype == check->proxytype) &&
(needle->bits.tunnel_proxy == check->bits.tunnel_proxy) &&
Curl_raw_equal(needle->proxy.name, check->proxy.name) &&
needle->port == check->port) {
/* This is the same proxy connection, use it! */
match = TRUE;
}
} }
}
if(match) { if(match) {
chosen = check; chosen = check;
/* If we are not looking for an NTLM connection, we can choose this one /* If we are not looking for an NTLM connection, we can choose this one
immediately. */ immediately. */
if(!wantNTLM) if(!wantNTLM)
break; break;
/* Otherwise, check if this is already authenticating with the right /* Otherwise, check if this is already authenticating with the right
credentials. If not, keep looking so that we can reuse NTLM credentials. If not, keep looking so that we can reuse NTLM
connections if possible. (Especially we must reuse the same connections if possible. (Especially we must reuse the same
connection if partway through a handshake!) */ connection if partway through a handshake!) */
if(credentialsMatch && chosen->ntlm.state != NTLMSTATE_NONE) if(credentialsMatch && chosen->ntlm.state != NTLMSTATE_NONE)
break; break;
}
} }
} }
@ -3170,53 +2983,67 @@ ConnectionExists(struct SessionHandle *data,
return FALSE; /* no matching connecting exists */ return FALSE; /* no matching connecting exists */
} }
/* /*
* This function kills and removes an existing connection in the connection * This function kills and removes an existing connection in the connection
* cache. The connection that has been unused for the longest time. * cache. The connection that has been unused for the longest time.
* *
* Returns -1 if it can't find any unused connection to kill. * Returns FALSE if it can't find any unused connection to kill.
*/ */
static long static bool
ConnectionKillOne(struct SessionHandle *data) ConnectionKillOne(struct SessionHandle *data)
{ {
long i; struct conncache *bc = data->state.conn_cache;
struct connectdata *conn; struct curl_hash_iterator iter;
struct curl_llist_element *curr;
struct curl_hash_element *he;
long highscore=-1; long highscore=-1;
long connindex=-1;
long score; long score;
struct timeval now; struct timeval now;
struct connectdata *conn_candidate = NULL;
struct connectbundle *bundle;
now = Curl_tvnow(); now = Curl_tvnow();
for(i=0; data->state.connc && (i< data->state.connc->num); i++) { Curl_hash_start_iterate(bc->hash, &iter);
conn = data->state.connc->connects[i];
if(!conn || conn->inuse) he = Curl_hash_next_element(&iter);
continue; while(he) {
struct connectdata *conn;
/* Set higher score for the age passed since the connection was used */ bundle = he->ptr;
score = Curl_tvdiff(now, conn->now);
if(score > highscore) { curr = bundle->conn_list->head;
highscore = score; while(curr) {
connindex = i; conn = curr->ptr;
if(!conn->inuse) {
/* Set higher score for the age passed since the connection was used */
score = Curl_tvdiff(now, conn->now);
if(score > highscore) {
highscore = score;
conn_candidate = conn;
}
}
curr = curr->next;
} }
he = Curl_hash_next_element(&iter);
} }
if(connindex >= 0) {
if(conn_candidate) {
/* Set the connection's owner correctly */ /* Set the connection's owner correctly */
conn = data->state.connc->connects[connindex]; conn_candidate->data = data;
conn->data = data;
bundle = conn_candidate->bundle;
/* the winner gets the honour of being disconnected */ /* the winner gets the honour of being disconnected */
(void)Curl_disconnect(conn, /* dead_connection */ FALSE); (void)Curl_disconnect(conn_candidate, /* dead_connection */ FALSE);
/* clean the array entry */ return TRUE;
data->state.connc->connects[connindex] = NULL;
} }
return connindex; /* return the available index or -1 */ return FALSE;
} }
/* this connection can now be marked 'idle' */ /* this connection can now be marked 'idle' */
@ -3234,41 +3061,16 @@ ConnectionDone(struct connectdata *conn)
* The given connection should be unique. That must've been checked prior to * The given connection should be unique. That must've been checked prior to
* this call. * this call.
*/ */
static void ConnectionStore(struct SessionHandle *data, static CURLcode ConnectionStore(struct SessionHandle *data,
struct connectdata *conn) struct connectdata *conn)
{ {
long i; static int connection_id_counter = 0;
for(i=0; i< data->state.connc->num; i++) {
if(!data->state.connc->connects[i])
break;
}
if(i == data->state.connc->num) {
/* there was no room available, kill one */
i = ConnectionKillOne(data);
if(-1 != i)
infof(data, "Connection (#%ld) was killed to make room (holds %ld)\n",
i, data->state.connc->num);
else
infof(data, "This connection did not fit in the connection cache\n");
}
conn->connectindex = i; /* Make the child know where the pointer to this /* Assign a number to the connection for easier tracking in the log
particular data is stored. But note that this -1 output */
if this is not within the cache and this is conn->connection_id = connection_id_counter++;
probably not checked for everywhere (yet). */
conn->inuse = TRUE;
if(-1 != i) {
/* Only do this if a true index was returned, if -1 was returned there
is no room in the cache for an unknown reason and we cannot store
this there.
TODO: make sure we really can work with more handles than positions in return Curl_conncache_add_conn(data->state.conn_cache, conn);
the cache, or possibly we should (allow to automatically) resize the
connection cache when we add more easy handles to a multi handle!
*/
data->state.connc->connects[i] = conn; /* fill in this */
conn->data = data;
}
} }
/* after a TCP connection to the proxy has been verified, this function does /* after a TCP connection to the proxy has been verified, this function does
@ -3318,7 +3120,7 @@ static CURLcode ConnectPlease(struct SessionHandle *data,
infof(data, "About to connect() to %s%s port %ld (#%ld)\n", infof(data, "About to connect() to %s%s port %ld (#%ld)\n",
conn->bits.proxy?"proxy ":"", conn->bits.proxy?"proxy ":"",
hostname, conn->port, conn->connectindex); hostname, conn->port, conn->connection_id);
#else #else
(void)data; (void)data;
#endif #endif
@ -3359,7 +3161,7 @@ void Curl_verboseconnect(struct connectdata *conn)
if(conn->data->set.verbose) if(conn->data->set.verbose)
infof(conn->data, "Connected to %s (%s) port %ld (#%ld)\n", infof(conn->data, "Connected to %s (%s) port %ld (#%ld)\n",
conn->bits.proxy ? conn->proxy.dispname : conn->host.dispname, conn->bits.proxy ? conn->proxy.dispname : conn->host.dispname,
conn->ip_addr_str, conn->port, conn->connectindex); conn->ip_addr_str, conn->port, conn->connection_id);
} }
#endif #endif
@ -3618,7 +3420,7 @@ static struct connectdata *allocate_conn(struct SessionHandle *data)
conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */
conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */ conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */
conn->connectindex = -1; /* no index */ conn->connection_id = -1; /* no ID */
conn->port = -1; /* unknown at this point */ conn->port = -1; /* unknown at this point */
/* Default protocol-independent behavior doesn't support persistent /* Default protocol-independent behavior doesn't support persistent
@ -4898,7 +4700,7 @@ static CURLcode create_conn(struct SessionHandle *data,
urllen=LEAST_PATH_ALLOC; urllen=LEAST_PATH_ALLOC;
/* /*
* We malloc() the buffers below urllen+2 to make room for to possibilities: * We malloc() the buffers below urllen+2 to make room for 2 possibilities:
* 1 - an extra terminating zero * 1 - an extra terminating zero
* 2 - an extra slash (in case a syntax like "www.host.com?moo" is used) * 2 - an extra slash (in case a syntax like "www.host.com?moo" is used)
*/ */
@ -4960,8 +4762,8 @@ static CURLcode create_conn(struct SessionHandle *data,
/* according to rfc3986, allow the query (?foo=bar) /* according to rfc3986, allow the query (?foo=bar)
also on protocols that can't handle it. also on protocols that can't handle it.
cut the string-part after '?' cut the string-part after '?'
*/ */
/* terminate the string */ /* terminate the string */
path_q_sep[0] = 0; path_q_sep[0] = 0;
@ -4975,7 +4777,7 @@ static CURLcode create_conn(struct SessionHandle *data,
if(conn->bits.proxy_user_passwd) { if(conn->bits.proxy_user_passwd) {
result = parse_proxy_auth(data, conn); result = parse_proxy_auth(data, conn);
if(result != CURLE_OK) if(result != CURLE_OK)
return result; return result;
} }
/************************************************************* /*************************************************************
@ -5178,7 +4980,7 @@ static CURLcode create_conn(struct SessionHandle *data,
fix_hostname(data, conn, &conn->host); fix_hostname(data, conn, &conn->host);
infof(data, "Re-using existing connection! (#%ld) with host %s\n", infof(data, "Re-using existing connection! (#%ld) with host %s\n",
conn->connectindex, conn->connection_id,
conn->proxy.name?conn->proxy.dispname:conn->host.dispname); conn->proxy.name?conn->proxy.dispname:conn->host.dispname);
} }
else { else {
@ -5443,12 +5245,8 @@ CURLcode Curl_done(struct connectdata **connp,
state it is for re-using, so we're forced to close it. In a perfect world state it is for re-using, so we're forced to close it. In a perfect world
we can add code that keep track of if we really must close it here or not, we can add code that keep track of if we really must close it here or not,
but currently we have no such detail knowledge. but currently we have no such detail knowledge.
connectindex == -1 here means that the connection has no spot in the
connection cache and thus we must disconnect it here.
*/ */
if(data->set.reuse_forbid || conn->bits.close || premature || if(data->set.reuse_forbid || conn->bits.close || premature) {
(-1 == conn->connectindex)) {
CURLcode res2 = Curl_disconnect(conn, premature); /* close connection */ CURLcode res2 = Curl_disconnect(conn, premature); /* close connection */
/* If we had an error already, make sure we return that one. But /* If we had an error already, make sure we return that one. But
@ -5460,10 +5258,10 @@ CURLcode Curl_done(struct connectdata **connp,
ConnectionDone(conn); /* the connection is no longer in use */ ConnectionDone(conn); /* the connection is no longer in use */
/* remember the most recently used connection */ /* remember the most recently used connection */
data->state.lastconnect = conn->connectindex; data->state.lastconnect = conn;
infof(data, "Connection #%ld to host %s left intact\n", infof(data, "Connection #%ld to host %s left intact\n",
conn->connectindex, conn->connection_id,
conn->bits.httpproxy?conn->proxy.dispname:conn->host.dispname); conn->bits.httpproxy?conn->proxy.dispname:conn->host.dispname);
} }

Просмотреть файл

@ -46,15 +46,6 @@ CURLcode Curl_protocol_doing(struct connectdata *conn, bool *done);
CURLcode Curl_setup_conn(struct connectdata *conn, CURLcode Curl_setup_conn(struct connectdata *conn,
bool *protocol_done); bool *protocol_done);
/* create a connection cache */
struct conncache *Curl_mk_connc(int type, long amount);
/* free a connection cache */
void Curl_rm_connc(struct conncache *c);
/* Change number of entries of a connection cache */
CURLcode Curl_ch_connc(struct SessionHandle *data,
struct conncache *c,
long newamount);
int Curl_protocol_getsock(struct connectdata *conn, int Curl_protocol_getsock(struct connectdata *conn,
curl_socket_t *socks, curl_socket_t *socks,
int numsocks); int numsocks);

Просмотреть файл

@ -795,8 +795,8 @@ struct connectdata {
consideration (== only for pipelining). */ consideration (== only for pipelining). */
/**** Fields set when inited and not modified again */ /**** Fields set when inited and not modified again */
long connectindex; /* what index in the connection cache connects index this long connection_id; /* Contains a unique number to make it easier to
particular struct has */ track the connections in the log output */
/* 'dns_entry' is the particular host we use. This points to an entry in the /* 'dns_entry' is the particular host we use. This points to an entry in the
DNS cache and it will not get pruned while locked. It gets unlocked in DNS cache and it will not get pruned while locked. It gets unlocked in
@ -924,7 +924,6 @@ struct connectdata {
handle */ handle */
bool server_supports_pipelining; /* TRUE if server supports pipelining, bool server_supports_pipelining; /* TRUE if server supports pipelining,
set after first response */ set after first response */
struct curl_llist *send_pipe; /* List of handles waiting to struct curl_llist *send_pipe; /* List of handles waiting to
send on this pipeline */ send on this pipeline */
struct curl_llist *recv_pipe; /* List of handles waiting to read struct curl_llist *recv_pipe; /* List of handles waiting to read
@ -934,7 +933,6 @@ struct connectdata {
struct curl_llist *done_pipe; /* Handles that are finished, but struct curl_llist *done_pipe; /* Handles that are finished, but
still reference this connectdata */ still reference this connectdata */
#define MAX_PIPELINE_LENGTH 5 #define MAX_PIPELINE_LENGTH 5
char* master_buffer; /* The master buffer allocated on-demand; char* master_buffer; /* The master buffer allocated on-demand;
used for pipelining. */ used for pipelining. */
size_t read_pos; /* Current read position in the master buffer */ size_t read_pos; /* Current read position in the master buffer */
@ -1011,6 +1009,8 @@ struct connectdata {
TUNNEL_CONNECT, /* CONNECT has been sent off */ TUNNEL_CONNECT, /* CONNECT has been sent off */
TUNNEL_COMPLETE /* CONNECT response received completely */ TUNNEL_COMPLETE /* CONNECT response received completely */
} tunnel_state[2]; /* two separate ones to allow FTP */ } tunnel_state[2]; /* two separate ones to allow FTP */
struct connectbundle *bundle; /* The bundle we are member of */
}; };
/* The end of connectdata. */ /* The end of connectdata. */
@ -1146,18 +1146,6 @@ struct auth {
be RFC compliant */ be RFC compliant */
}; };
struct conncache {
/* 'connects' will be an allocated array with pointers. If the pointer is
set, it holds an allocated connection. */
struct connectdata **connects;
long num; /* number of entries of the 'connects' array */
enum {
CONNCACHE_PRIVATE, /* used for an easy handle alone */
CONNCACHE_MULTI /* shared within a multi handle */
} type;
};
struct UrlState { struct UrlState {
enum { enum {
Curl_if_none, Curl_if_none,
@ -1165,13 +1153,20 @@ struct UrlState {
Curl_if_multi Curl_if_multi
} used_interface; } used_interface;
struct conncache *connc; /* points to the connection cache this handle /* Points to the connection cache */
uses */ struct conncache *conn_cache;
/* buffers to store authentication data in, as parsed from input options */ /* buffers to store authentication data in, as parsed from input options */
struct timeval keeps_speed; /* for the progress meter really */ struct timeval keeps_speed; /* for the progress meter really */
long lastconnect; /* index of most recent connect or -1 if undefined */ struct connectdata *pending_conn; /* This points to the connection we want
to open when we are waiting in the
CONNECT_PEND state in the multi
interface. This to avoid recreating it
when we enter the CONNECT state again.
*/
struct connectdata *lastconnect; /* The last connection, NULL if undefined */
char *headerbuff; /* allocated buffer to store headers in */ char *headerbuff; /* allocated buffer to store headers in */
size_t headersize; /* size of the allocation */ size_t headersize; /* size of the allocation */
@ -1250,14 +1245,6 @@ struct UrlState {
/* for FTP downloads: how many CRLFs did we converted to LFs? */ /* for FTP downloads: how many CRLFs did we converted to LFs? */
curl_off_t crlf_conversions; curl_off_t crlf_conversions;
#endif #endif
/* If set to non-NULL, there's a connection in a shared connection cache
that uses this handle so we can't kill this SessionHandle just yet but
must keep it around and add it to the list of handles to kill once all
its connections are gone */
void *shared_conn;
bool closed; /* set to TRUE when curl_easy_cleanup() has been called on this
handle, but it is kept around as mentioned for
shared_conn */
char *pathbuffer;/* allocated buffer to store the URL's path part in */ char *pathbuffer;/* allocated buffer to store the URL's path part in */
char *path; /* path to use, points to somewhere within the pathbuffer char *path; /* path to use, points to somewhere within the pathbuffer
area */ area */
@ -1593,6 +1580,8 @@ struct UserDefined {
bool tcp_keepalive; /* use TCP keepalives */ bool tcp_keepalive; /* use TCP keepalives */
long tcp_keepidle; /* seconds in idle before sending keepalive probe */ long tcp_keepidle; /* seconds in idle before sending keepalive probe */
long tcp_keepintvl; /* seconds between TCP keepalive probes */ long tcp_keepintvl; /* seconds between TCP keepalive probes */
size_t maxconnects; /* Max idle connections in the connection cache */
}; };
struct Names { struct Names {