Unverified Commit 67c55a26 authored by Daniel Stenberg's avatar Daniel Stenberg
Browse files

share: add support for sharing the connection cache

parent e871ab56
Loading
Loading
Loading
Loading

debug/shared-conn.c

0 → 100644
+68 −0
Original line number Diff line number Diff line
/***************************************************************************
 *                                  _   _ ____  _
 *  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>
 * Two HTTP GET using connection sharing with the share inteface
 * </DESC>
 */
#include <stdio.h>
#include <curl/curl.h>

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

  /* 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, "http://example.com");
      /* example.com is redirected, so we tell libcurl to follow redirection */
      curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);

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

      curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);

      /* 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;
}
+133 −8
Original line number Diff line number Diff line
@@ -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;
@@ -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,
  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)
@@ -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;
@@ -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);
@@ -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++;
@@ -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--;

@@ -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))
{
@@ -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);
@@ -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)
{
@@ -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 */
+8 −2
Original line number Diff line number Diff line
@@ -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
@@ -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 */
@@ -55,7 +57,8 @@ 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));
@@ -63,6 +66,9 @@ void Curl_conncache_foreach(struct conncache *connc,
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 */
+1 −1
Original line number Diff line number Diff line
@@ -1224,7 +1224,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);

+9 −53
Original line number Diff line number Diff line
@@ -326,14 +326,6 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */
  Curl_llist_init(&multi->msglist, multi_freeamsg);
  Curl_llist_init(&multi->pending, multi_freeamsg);

  /* allocate a new easy handle to use when closing cached connections */
  multi->closure_handle = curl_easy_init();
  if(!multi->closure_handle)
    goto error;

  multi->closure_handle->multi = multi;
  multi->closure_handle->state.conn_cache = &multi->conn_cache;

  multi->max_pipeline_length = 5;

  /* -1 means it not set by user, use the default value */
@@ -345,8 +337,6 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */
  Curl_hash_destroy(&multi->sockhash);
  Curl_hash_destroy(&multi->hostcache);
  Curl_conncache_destroy(&multi->conn_cache);
  Curl_close(multi->closure_handle);
  multi->closure_handle = NULL;
  Curl_llist_destroy(&multi->msglist, NULL);
  Curl_llist_destroy(&multi->pending, NULL);

@@ -407,7 +397,10 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi,
    data->dns.hostcachetype = HCACHE_MULTI;
  }

  /* Point to the multi's connection cache */
  /* Point to the shared or multi handle connection cache */
  if(data->share && (data->share->specifier & (1<< CURL_LOCK_DATA_CONNECT)))
    data->state.conn_cache = &data->share->conn_cache;
  else
    data->state.conn_cache = &multi->conn_cache;

  /* This adds the new entry at the 'end' of the doubly-linked circular
@@ -462,8 +455,8 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi,
     state somewhat we clone the timeouts from each added handle so that the
     closure handle always has the same timeouts as the most recently added
     easy handle. */
  multi->closure_handle->set.timeout = data->set.timeout;
  multi->closure_handle->set.server_response_timeout =
  data->state.conn_cache->closure_handle->set.timeout = data->set.timeout;
  data->state.conn_cache->closure_handle->set.server_response_timeout =
    data->set.server_response_timeout;

  update_timer(multi);
@@ -504,7 +497,7 @@ ConnectionDone(struct Curl_easy *data, struct connectdata *conn)
     data->state.conn_cache->num_connections > maxconnects) {
    infof(data, "Connection cache is full, closing the oldest one.\n");

    conn_candidate = Curl_oldest_idle_connection(data);
    conn_candidate = Curl_conncache_oldest_idle(data);

    if(conn_candidate) {
      /* Set the connection's owner correctly */
@@ -2201,36 +2194,12 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles)
  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) {
    SIGPIPE_VARIABLE(pipe_st);
    conn->data = multi->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(&multi->conn_cache);
  }
}

CURLMcode curl_multi_cleanup(struct Curl_multi *multi)
{
  struct Curl_easy *data;
  struct Curl_easy *nextdata;

  if(GOOD_MULTI_HANDLE(multi)) {
    bool restore_pipe = FALSE;
    SIGPIPE_VARIABLE(pipe_st);

    multi->type = 0; /* not good anymore */

    /* Firsrt remove all remaining easy handles */
@@ -2255,18 +2224,7 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi)
    }

    /* Close all the connections in the connection cache */
    close_all_connections(multi);

    if(multi->closure_handle) {
      sigpipe_ignore(multi->closure_handle, &pipe_st);
      restore_pipe = TRUE;

      multi->closure_handle->dns.hostcache = &multi->hostcache;
      Curl_hostcache_clean(multi->closure_handle,
                           multi->closure_handle->dns.hostcache);

      Curl_close(multi->closure_handle);
    }
    Curl_conncache_close_all_connections(&multi->conn_cache);

    Curl_hash_destroy(&multi->sockhash);
    Curl_conncache_destroy(&multi->conn_cache);
@@ -2280,8 +2238,6 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi)
    Curl_pipeline_set_server_blacklist(NULL, &multi->pipelining_server_bl);

    free(multi);
    if(restore_pipe)
      sigpipe_restore(&pipe_st);

    return CURLM_OK;
  }
Loading