12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370 |
- /***************************************************************************
- * _ _ ____ _
- * Project ___| | | | _ \| |
- * / __| | | | |_) | |
- * | (__| |_| | _ <| |___
- * \___|\___/|_| \_\_____|
- *
- * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se>
- * Copyright (C) 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.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.
- *
- * SPDX-License-Identifier: curl
- *
- ***************************************************************************/
- #include "curl_setup.h"
- #include <curl/curl.h>
- #include "urldata.h"
- #include "url.h"
- #include "cfilters.h"
- #include "progress.h"
- #include "multiif.h"
- #include "sendf.h"
- #include "conncache.h"
- #include "http_negotiate.h"
- #include "http_ntlm.h"
- #include "share.h"
- #include "sigpipe.h"
- #include "connect.h"
- #include "select.h"
- #include "strcase.h"
- /* The last 3 #include files should be in this order */
- #include "curl_printf.h"
- #include "curl_memory.h"
- #include "memdebug.h"
- #define CPOOL_IS_LOCKED(c) ((c) && (c)->locked)
- #define CPOOL_LOCK(c) \
- do { \
- if((c)) { \
- if(CURL_SHARE_KEEP_CONNECT((c)->share)) \
- Curl_share_lock(((c)->idata), CURL_LOCK_DATA_CONNECT, \
- CURL_LOCK_ACCESS_SINGLE); \
- DEBUGASSERT(!(c)->locked); \
- (c)->locked = TRUE; \
- } \
- } while(0)
- #define CPOOL_UNLOCK(c) \
- do { \
- if((c)) { \
- DEBUGASSERT((c)->locked); \
- (c)->locked = FALSE; \
- if(CURL_SHARE_KEEP_CONNECT((c)->share)) \
- Curl_share_unlock((c)->idata, CURL_LOCK_DATA_CONNECT); \
- } \
- } while(0)
- /* A list of connections to the same destination. */
- struct cpool_bundle {
- struct Curl_llist conns; /* connections in the bundle */
- size_t dest_len; /* total length of destination, including NUL */
- char *dest[1]; /* destination of bundle, allocated to keep dest_len bytes */
- };
- static void cpool_discard_conn(struct cpool *cpool,
- struct Curl_easy *data,
- struct connectdata *conn,
- bool aborted);
- static void cpool_close_and_destroy(struct cpool *cpool,
- struct connectdata *conn,
- struct Curl_easy *data,
- bool do_shutdown);
- static void cpool_run_conn_shutdown(struct Curl_easy *data,
- struct connectdata *conn,
- bool *done);
- static void cpool_run_conn_shutdown_handler(struct Curl_easy *data,
- struct connectdata *conn);
- static CURLMcode cpool_update_shutdown_ev(struct Curl_multi *multi,
- struct Curl_easy *data,
- struct connectdata *conn);
- static void cpool_shutdown_all(struct cpool *cpool,
- struct Curl_easy *data, int timeout_ms);
- static void cpool_close_and_destroy_all(struct cpool *cpool);
- static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool);
- static struct cpool_bundle *cpool_bundle_create(const char *dest,
- size_t dest_len)
- {
- struct cpool_bundle *bundle;
- bundle = calloc(1, sizeof(*bundle) + dest_len);
- if(!bundle)
- return NULL;
- Curl_llist_init(&bundle->conns, NULL);
- bundle->dest_len = dest_len;
- memcpy(bundle->dest, dest, dest_len);
- return bundle;
- }
- static void cpool_bundle_destroy(struct cpool_bundle *bundle)
- {
- DEBUGASSERT(!Curl_llist_count(&bundle->conns));
- free(bundle);
- }
- /* Add a connection to a bundle */
- static void cpool_bundle_add(struct cpool_bundle *bundle,
- struct connectdata *conn)
- {
- DEBUGASSERT(!Curl_node_llist(&conn->cpool_node));
- Curl_llist_append(&bundle->conns, conn, &conn->cpool_node);
- conn->bits.in_cpool = TRUE;
- }
- /* Remove a connection from a bundle */
- static void cpool_bundle_remove(struct cpool_bundle *bundle,
- struct connectdata *conn)
- {
- (void)bundle;
- DEBUGASSERT(Curl_node_llist(&conn->cpool_node) == &bundle->conns);
- Curl_node_remove(&conn->cpool_node);
- conn->bits.in_cpool = FALSE;
- }
- static void cpool_bundle_free_entry(void *freethis)
- {
- cpool_bundle_destroy((struct cpool_bundle *)freethis);
- }
- int Curl_cpool_init(struct cpool *cpool,
- Curl_cpool_disconnect_cb *disconnect_cb,
- struct Curl_multi *multi,
- struct Curl_share *share,
- size_t size)
- {
- DEBUGASSERT(!!multi != !!share); /* either one */
- Curl_hash_init(&cpool->dest2bundle, size, Curl_hash_str,
- Curl_str_key_compare, cpool_bundle_free_entry);
- Curl_llist_init(&cpool->shutdowns, NULL);
- DEBUGASSERT(disconnect_cb);
- if(!disconnect_cb)
- return 1;
- /* allocate a new easy handle to use when closing cached connections */
- cpool->idata = curl_easy_init();
- if(!cpool->idata)
- return 1; /* bad */
- cpool->idata->state.internal = TRUE;
- /* TODO: this is quirky. We need an internal handle for certain
- * operations, but we do not add it to the multi (if there is one).
- * But we give it the multi so that socket event operations can work.
- * Probably better to have an internal handle owned by the multi that
- * can be used for cpool operations. */
- cpool->idata->multi = multi;
- #ifdef DEBUGBUILD
- if(getenv("CURL_DEBUG"))
- cpool->idata->set.verbose = TRUE;
- #endif
- cpool->disconnect_cb = disconnect_cb;
- cpool->idata->multi = cpool->multi = multi;
- cpool->idata->share = cpool->share = share;
- return 0; /* good */
- }
- void Curl_cpool_destroy(struct cpool *cpool)
- {
- if(cpool) {
- if(cpool->idata) {
- cpool_close_and_destroy_all(cpool);
- /* The internal closure handle is special and we need to
- * disconnect it from multi/share before closing it down. */
- cpool->idata->multi = NULL;
- cpool->idata->share = NULL;
- Curl_close(&cpool->idata);
- }
- Curl_hash_destroy(&cpool->dest2bundle);
- cpool->multi = NULL;
- }
- }
- static struct cpool *cpool_get_instance(struct Curl_easy *data)
- {
- if(data) {
- if(CURL_SHARE_KEEP_CONNECT(data->share))
- return &data->share->cpool;
- else if(data->multi_easy)
- return &data->multi_easy->cpool;
- else if(data->multi)
- return &data->multi->cpool;
- }
- return NULL;
- }
- void Curl_cpool_xfer_init(struct Curl_easy *data)
- {
- struct cpool *cpool = cpool_get_instance(data);
- DEBUGASSERT(cpool);
- if(cpool) {
- CPOOL_LOCK(cpool);
- /* the identifier inside the connection cache */
- data->id = cpool->next_easy_id++;
- if(cpool->next_easy_id <= 0)
- cpool->next_easy_id = 0;
- data->state.lastconnect_id = -1;
- /* The closure handle only ever has default timeouts set. To improve the
- 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. */
- cpool->idata->set.timeout = data->set.timeout;
- cpool->idata->set.server_response_timeout =
- data->set.server_response_timeout;
- cpool->idata->set.no_signal = data->set.no_signal;
- CPOOL_UNLOCK(cpool);
- }
- else {
- /* We should not get here, but in a non-debug build, do something */
- data->id = 0;
- data->state.lastconnect_id = -1;
- }
- }
- static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool,
- struct connectdata *conn)
- {
- return Curl_hash_pick(&cpool->dest2bundle,
- conn->destination, conn->destination_len);
- }
- static struct cpool_bundle *
- cpool_add_bundle(struct cpool *cpool, struct connectdata *conn)
- {
- struct cpool_bundle *bundle;
- bundle = cpool_bundle_create(conn->destination, conn->destination_len);
- if(!bundle)
- return NULL;
- if(!Curl_hash_add(&cpool->dest2bundle,
- bundle->dest, bundle->dest_len, bundle)) {
- cpool_bundle_destroy(bundle);
- return NULL;
- }
- return bundle;
- }
- static void cpool_remove_bundle(struct cpool *cpool,
- struct cpool_bundle *bundle)
- {
- if(!cpool)
- return;
- Curl_hash_delete(&cpool->dest2bundle, bundle->dest, bundle->dest_len);
- }
- static struct connectdata *
- cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle);
- int Curl_cpool_check_limits(struct Curl_easy *data,
- struct connectdata *conn)
- {
- struct cpool *cpool = cpool_get_instance(data);
- struct cpool_bundle *bundle;
- size_t dest_limit = 0;
- size_t total_limit = 0;
- int result = CPOOL_LIMIT_OK;
- if(!cpool)
- return CPOOL_LIMIT_OK;
- if(data && data->multi) {
- dest_limit = data->multi->max_host_connections;
- total_limit = data->multi->max_total_connections;
- }
- if(!dest_limit && !total_limit)
- return CPOOL_LIMIT_OK;
- CPOOL_LOCK(cpool);
- if(dest_limit) {
- bundle = cpool_find_bundle(cpool, conn);
- while(bundle && (Curl_llist_count(&bundle->conns) >= dest_limit)) {
- struct connectdata *oldest_idle = NULL;
- /* The bundle is full. Extract the oldest connection that may
- * be removed now, if there is one. */
- oldest_idle = cpool_bundle_get_oldest_idle(bundle);
- if(!oldest_idle)
- break;
- /* disconnect the old conn and continue */
- DEBUGF(infof(data, "Discarding connection #%"
- FMT_OFF_T " from %zu to reach destination "
- "limit of %zu", oldest_idle->connection_id,
- Curl_llist_count(&bundle->conns), dest_limit));
- Curl_cpool_disconnect(data, oldest_idle, FALSE);
- /* in case the bundle was destroyed in disconnect, look it up again */
- bundle = cpool_find_bundle(cpool, conn);
- }
- if(bundle && (Curl_llist_count(&bundle->conns) >= dest_limit)) {
- result = CPOOL_LIMIT_DEST;
- goto out;
- }
- }
- if(total_limit) {
- while(cpool->num_conn >= total_limit) {
- struct connectdata *oldest_idle = cpool_get_oldest_idle(cpool);
- if(!oldest_idle)
- break;
- /* disconnect the old conn and continue */
- DEBUGF(infof(data, "Discarding connection #%"
- FMT_OFF_T " from %zu to reach total "
- "limit of %zu",
- oldest_idle->connection_id, cpool->num_conn, total_limit));
- Curl_cpool_disconnect(data, oldest_idle, FALSE);
- }
- if(cpool->num_conn >= total_limit) {
- result = CPOOL_LIMIT_TOTAL;
- goto out;
- }
- }
- out:
- CPOOL_UNLOCK(cpool);
- return result;
- }
- CURLcode Curl_cpool_add_conn(struct Curl_easy *data,
- struct connectdata *conn)
- {
- CURLcode result = CURLE_OK;
- struct cpool_bundle *bundle = NULL;
- struct cpool *cpool = cpool_get_instance(data);
- DEBUGASSERT(conn);
- DEBUGASSERT(cpool);
- if(!cpool)
- return CURLE_FAILED_INIT;
- CPOOL_LOCK(cpool);
- bundle = cpool_find_bundle(cpool, conn);
- if(!bundle) {
- bundle = cpool_add_bundle(cpool, conn);
- if(!bundle) {
- result = CURLE_OUT_OF_MEMORY;
- goto out;
- }
- }
- cpool_bundle_add(bundle, conn);
- conn->connection_id = cpool->next_connection_id++;
- cpool->num_conn++;
- DEBUGF(infof(data, "Added connection %" FMT_OFF_T ". "
- "The cache now contains %zu members",
- conn->connection_id, cpool->num_conn));
- out:
- CPOOL_UNLOCK(cpool);
- return result;
- }
- static void cpool_remove_conn(struct cpool *cpool,
- struct connectdata *conn)
- {
- struct Curl_llist *list = Curl_node_llist(&conn->cpool_node);
- DEBUGASSERT(cpool);
- if(list) {
- /* The connection is certainly in the pool, but where? */
- struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn);
- if(bundle && (list == &bundle->conns)) {
- cpool_bundle_remove(bundle, conn);
- if(!Curl_llist_count(&bundle->conns))
- cpool_remove_bundle(cpool, bundle);
- conn->bits.in_cpool = FALSE;
- cpool->num_conn--;
- }
- else {
- /* Not in a bundle, already in the shutdown list? */
- DEBUGASSERT(list == &cpool->shutdowns);
- }
- }
- }
- /* This function iterates the entire connection pool and calls the function
- func() with the connection pointer as the first argument and the supplied
- 'param' argument as the other.
- The cpool lock is still held when the callback is called. It needs it,
- so that it can safely continue traversing the lists once the callback
- returns.
- Returns TRUE if the loop was aborted due to the callback's return code.
- Return 0 from func() to continue the loop, return 1 to abort it.
- */
- static bool cpool_foreach(struct Curl_easy *data,
- struct cpool *cpool,
- void *param,
- int (*func)(struct Curl_easy *data,
- struct connectdata *conn, void *param))
- {
- struct Curl_hash_iterator iter;
- struct Curl_hash_element *he;
- if(!cpool)
- return FALSE;
- Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
- he = Curl_hash_next_element(&iter);
- while(he) {
- struct Curl_llist_node *curr;
- struct cpool_bundle *bundle = he->ptr;
- he = Curl_hash_next_element(&iter);
- curr = Curl_llist_head(&bundle->conns);
- while(curr) {
- /* Yes, we need to update curr before calling func(), because func()
- might decide to remove the connection */
- struct connectdata *conn = Curl_node_elem(curr);
- curr = Curl_node_next(curr);
- if(1 == func(data, conn, param)) {
- return TRUE;
- }
- }
- }
- return FALSE;
- }
- /* Return a live connection in the pool or NULL. */
- static struct connectdata *cpool_get_live_conn(struct cpool *cpool)
- {
- struct Curl_hash_iterator iter;
- struct Curl_hash_element *he;
- struct cpool_bundle *bundle;
- struct Curl_llist_node *conn_node;
- Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
- for(he = Curl_hash_next_element(&iter); he;
- he = Curl_hash_next_element(&iter)) {
- bundle = he->ptr;
- conn_node = Curl_llist_head(&bundle->conns);
- if(conn_node)
- return Curl_node_elem(conn_node);
- }
- return NULL;
- }
- /*
- * A connection (already in the pool) has become idle. Do any
- * cleanups in regard to the pool's limits.
- *
- * Return TRUE if idle connection kept in pool, FALSE if closed.
- */
- bool Curl_cpool_conn_now_idle(struct Curl_easy *data,
- struct connectdata *conn)
- {
- unsigned int maxconnects = !data->multi->maxconnects ?
- data->multi->num_easy * 4 : data->multi->maxconnects;
- struct connectdata *oldest_idle = NULL;
- struct cpool *cpool = cpool_get_instance(data);
- bool kept = TRUE;
- conn->lastused = Curl_now(); /* it was used up until now */
- if(cpool && maxconnects) {
- /* may be called form a callback already under lock */
- bool do_lock = !CPOOL_IS_LOCKED(cpool);
- if(do_lock)
- CPOOL_LOCK(cpool);
- if(cpool->num_conn > maxconnects) {
- infof(data, "Connection pool is full, closing the oldest one");
- oldest_idle = cpool_get_oldest_idle(cpool);
- kept = (oldest_idle != conn);
- if(oldest_idle) {
- Curl_cpool_disconnect(cpool->idata, oldest_idle, FALSE);
- }
- }
- if(do_lock)
- CPOOL_UNLOCK(cpool);
- }
- return kept;
- }
- /*
- * This function finds the connection in the connection bundle that has been
- * unused for the longest time.
- */
- static struct connectdata *
- cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle)
- {
- struct Curl_llist_node *curr;
- timediff_t highscore = -1;
- timediff_t score;
- struct curltime now;
- struct connectdata *oldest_idle = NULL;
- struct connectdata *conn;
- now = Curl_now();
- curr = Curl_llist_head(&bundle->conns);
- while(curr) {
- conn = Curl_node_elem(curr);
- if(!CONN_INUSE(conn)) {
- /* Set higher score for the age passed since the connection was used */
- score = Curl_timediff(now, conn->lastused);
- if(score > highscore) {
- highscore = score;
- oldest_idle = conn;
- }
- }
- curr = Curl_node_next(curr);
- }
- return oldest_idle;
- }
- static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool)
- {
- struct Curl_hash_iterator iter;
- struct Curl_llist_node *curr;
- struct Curl_hash_element *he;
- struct connectdata *oldest_idle = NULL;
- struct cpool_bundle *bundle;
- struct curltime now;
- timediff_t highscore =- 1;
- timediff_t score;
- now = Curl_now();
- Curl_hash_start_iterate(&cpool->dest2bundle, &iter);
- for(he = Curl_hash_next_element(&iter); he;
- he = Curl_hash_next_element(&iter)) {
- struct connectdata *conn;
- bundle = he->ptr;
- for(curr = Curl_llist_head(&bundle->conns); curr;
- curr = Curl_node_next(curr)) {
- conn = Curl_node_elem(curr);
- if(CONN_INUSE(conn) || conn->bits.close || conn->connect_only)
- continue;
- /* Set higher score for the age passed since the connection was used */
- score = Curl_timediff(now, conn->lastused);
- if(score > highscore) {
- highscore = score;
- oldest_idle = conn;
- }
- }
- }
- return oldest_idle;
- }
- bool Curl_cpool_find(struct Curl_easy *data,
- const char *destination, size_t dest_len,
- Curl_cpool_conn_match_cb *conn_cb,
- Curl_cpool_done_match_cb *done_cb,
- void *userdata)
- {
- struct cpool *cpool = cpool_get_instance(data);
- struct cpool_bundle *bundle;
- bool result = FALSE;
- DEBUGASSERT(cpool);
- DEBUGASSERT(conn_cb);
- if(!cpool)
- return FALSE;
- CPOOL_LOCK(cpool);
- bundle = Curl_hash_pick(&cpool->dest2bundle, (void *)destination, dest_len);
- if(bundle) {
- struct Curl_llist_node *curr = Curl_llist_head(&bundle->conns);
- while(curr) {
- struct connectdata *conn = Curl_node_elem(curr);
- /* Get next node now. callback might discard current */
- curr = Curl_node_next(curr);
- if(conn_cb(conn, userdata)) {
- result = TRUE;
- break;
- }
- }
- }
- if(done_cb) {
- result = done_cb(result, userdata);
- }
- CPOOL_UNLOCK(cpool);
- return result;
- }
- static void cpool_shutdown_discard_all(struct cpool *cpool)
- {
- struct Curl_llist_node *e = Curl_llist_head(&cpool->shutdowns);
- struct connectdata *conn;
- if(!e)
- return;
- DEBUGF(infof(cpool->idata, "cpool_shutdown_discard_all"));
- while(e) {
- conn = Curl_node_elem(e);
- Curl_node_remove(e);
- DEBUGF(infof(cpool->idata, "discard connection #%" FMT_OFF_T,
- conn->connection_id));
- cpool_close_and_destroy(cpool, conn, NULL, FALSE);
- e = Curl_llist_head(&cpool->shutdowns);
- }
- }
- static void cpool_close_and_destroy_all(struct cpool *cpool)
- {
- struct connectdata *conn;
- int timeout_ms = 0;
- SIGPIPE_VARIABLE(pipe_st);
- DEBUGASSERT(cpool);
- /* Move all connections to the shutdown list */
- sigpipe_init(&pipe_st);
- CPOOL_LOCK(cpool);
- conn = cpool_get_live_conn(cpool);
- while(conn) {
- cpool_remove_conn(cpool, conn);
- sigpipe_apply(cpool->idata, &pipe_st);
- connclose(conn, "kill all");
- cpool_discard_conn(cpool, cpool->idata, conn, FALSE);
- conn = cpool_get_live_conn(cpool);
- }
- CPOOL_UNLOCK(cpool);
- /* Just for testing, run graceful shutdown */
- #ifdef DEBUGBUILD
- {
- char *p = getenv("CURL_GRACEFUL_SHUTDOWN");
- if(p) {
- long l = strtol(p, NULL, 10);
- if(l > 0 && l < INT_MAX)
- timeout_ms = (int)l;
- }
- }
- #endif
- sigpipe_apply(cpool->idata, &pipe_st);
- cpool_shutdown_all(cpool, cpool->idata, timeout_ms);
- /* discard all connections in the shutdown list */
- cpool_shutdown_discard_all(cpool);
- Curl_hostcache_clean(cpool->idata, cpool->idata->dns.hostcache);
- sigpipe_restore(&pipe_st);
- }
- static void cpool_shutdown_destroy_oldest(struct cpool *cpool)
- {
- struct Curl_llist_node *e;
- struct connectdata *conn;
- e = Curl_llist_head(&cpool->shutdowns);
- if(e) {
- SIGPIPE_VARIABLE(pipe_st);
- conn = Curl_node_elem(e);
- Curl_node_remove(e);
- sigpipe_init(&pipe_st);
- sigpipe_apply(cpool->idata, &pipe_st);
- cpool_close_and_destroy(cpool, conn, NULL, FALSE);
- sigpipe_restore(&pipe_st);
- }
- }
- static void cpool_discard_conn(struct cpool *cpool,
- struct Curl_easy *data,
- struct connectdata *conn,
- bool aborted)
- {
- bool done = FALSE;
- DEBUGASSERT(data);
- DEBUGASSERT(cpool);
- DEBUGASSERT(!conn->bits.in_cpool);
- /*
- * If this connection is not marked to force-close, leave it open if there
- * are other users of it
- */
- if(CONN_INUSE(conn) && !aborted) {
- DEBUGF(infof(data, "[CCACHE] not discarding #%" FMT_OFF_T
- " still in use by %zu transfers", conn->connection_id,
- CONN_INUSE(conn)));
- return;
- }
- /* treat the connection as aborted in CONNECT_ONLY situations, we do
- * not know what the APP did with it. */
- if(conn->connect_only)
- aborted = TRUE;
- conn->bits.aborted = aborted;
- /* We do not shutdown dead connections. The term 'dead' can be misleading
- * here, as we also mark errored connections/transfers as 'dead'.
- * If we do a shutdown for an aborted transfer, the server might think
- * it was successful otherwise (for example an ftps: upload). This is
- * not what we want. */
- if(aborted)
- done = TRUE;
- if(!done) {
- /* Attempt to shutdown the connection right away. */
- Curl_attach_connection(data, conn);
- cpool_run_conn_shutdown(data, conn, &done);
- DEBUGF(infof(data, "[CCACHE] shutdown #%" FMT_OFF_T ", done=%d",
- conn->connection_id, done));
- Curl_detach_connection(data);
- }
- if(done) {
- cpool_close_and_destroy(cpool, conn, data, FALSE);
- return;
- }
- /* Add the connection to our shutdown list for non-blocking shutdown
- * during multi processing. */
- if(data->multi && data->multi->max_shutdown_connections > 0 &&
- (data->multi->max_shutdown_connections >=
- (long)Curl_llist_count(&cpool->shutdowns))) {
- DEBUGF(infof(data, "[CCACHE] discarding oldest shutdown connection "
- "due to limit of %ld",
- data->multi->max_shutdown_connections));
- cpool_shutdown_destroy_oldest(cpool);
- }
- if(data->multi && data->multi->socket_cb) {
- DEBUGASSERT(cpool == &data->multi->cpool);
- /* Start with an empty shutdown pollset, so out internal closure handle
- * is added to the sockets. */
- memset(&conn->shutdown_poll, 0, sizeof(conn->shutdown_poll));
- if(cpool_update_shutdown_ev(data->multi, cpool->idata, conn)) {
- DEBUGF(infof(data, "[CCACHE] update events for shutdown failed, "
- "discarding #%" FMT_OFF_T,
- conn->connection_id));
- cpool_close_and_destroy(cpool, conn, data, FALSE);
- return;
- }
- }
- Curl_llist_append(&cpool->shutdowns, conn, &conn->cpool_node);
- DEBUGF(infof(data, "[CCACHE] added #%" FMT_OFF_T
- " to shutdown list of length %zu", conn->connection_id,
- Curl_llist_count(&cpool->shutdowns)));
- }
- void Curl_cpool_disconnect(struct Curl_easy *data,
- struct connectdata *conn,
- bool aborted)
- {
- struct cpool *cpool = cpool_get_instance(data);
- bool do_lock;
- DEBUGASSERT(cpool);
- DEBUGASSERT(data && !data->conn);
- if(!cpool)
- return;
- /* If this connection is not marked to force-close, leave it open if there
- * are other users of it */
- if(CONN_INUSE(conn) && !aborted) {
- DEBUGASSERT(0); /* does this ever happen? */
- DEBUGF(infof(data, "Curl_disconnect when inuse: %zu", CONN_INUSE(conn)));
- return;
- }
- /* This method may be called while we are under lock, e.g. from a
- * user callback in find. */
- do_lock = !CPOOL_IS_LOCKED(cpool);
- if(do_lock)
- CPOOL_LOCK(cpool);
- if(conn->bits.in_cpool) {
- cpool_remove_conn(cpool, conn);
- DEBUGASSERT(!conn->bits.in_cpool);
- }
- /* Run the callback to let it clean up anything it wants to. */
- aborted = cpool->disconnect_cb(data, conn, aborted);
- if(data->multi) {
- /* Add it to the multi's cpool for shutdown handling */
- infof(data, "%s connection #%" FMT_OFF_T,
- aborted ? "closing" : "shutting down", conn->connection_id);
- cpool_discard_conn(&data->multi->cpool, data, conn, aborted);
- }
- else {
- /* No multi available. Make a best-effort shutdown + close */
- infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id);
- cpool_close_and_destroy(NULL, conn, data, !aborted);
- }
- if(do_lock)
- CPOOL_UNLOCK(cpool);
- }
- static void cpool_run_conn_shutdown_handler(struct Curl_easy *data,
- struct connectdata *conn)
- {
- if(!conn->bits.shutdown_handler) {
- if(conn->dns_entry)
- Curl_resolv_unlink(data, &conn->dns_entry);
- /* Cleanup NTLM connection-related data */
- Curl_http_auth_cleanup_ntlm(conn);
- /* Cleanup NEGOTIATE connection-related data */
- Curl_http_auth_cleanup_negotiate(conn);
- if(conn->handler && conn->handler->disconnect) {
- /* This is set if protocol-specific cleanups should be made */
- DEBUGF(infof(data, "connection #%" FMT_OFF_T
- ", shutdown protocol handler (aborted=%d)",
- conn->connection_id, conn->bits.aborted));
- conn->handler->disconnect(data, conn, conn->bits.aborted);
- }
- /* possible left-overs from the async name resolvers */
- Curl_resolver_cancel(data);
- conn->bits.shutdown_handler = TRUE;
- }
- }
- static void cpool_run_conn_shutdown(struct Curl_easy *data,
- struct connectdata *conn,
- bool *done)
- {
- CURLcode r1, r2;
- bool done1, done2;
- /* We expect to be attached when called */
- DEBUGASSERT(data->conn == conn);
- cpool_run_conn_shutdown_handler(data, conn);
- if(conn->bits.shutdown_filters) {
- *done = TRUE;
- return;
- }
- if(!conn->connect_only && Curl_conn_is_connected(conn, FIRSTSOCKET))
- r1 = Curl_conn_shutdown(data, FIRSTSOCKET, &done1);
- else {
- r1 = CURLE_OK;
- done1 = TRUE;
- }
- if(!conn->connect_only && Curl_conn_is_connected(conn, SECONDARYSOCKET))
- r2 = Curl_conn_shutdown(data, SECONDARYSOCKET, &done2);
- else {
- r2 = CURLE_OK;
- done2 = TRUE;
- }
- /* we are done when any failed or both report success */
- *done = (r1 || r2 || (done1 && done2));
- if(*done)
- conn->bits.shutdown_filters = TRUE;
- }
- static CURLcode cpool_add_pollfds(struct cpool *cpool,
- struct curl_pollfds *cpfds)
- {
- CURLcode result = CURLE_OK;
- if(Curl_llist_head(&cpool->shutdowns)) {
- struct Curl_llist_node *e;
- struct easy_pollset ps;
- struct connectdata *conn;
- for(e = Curl_llist_head(&cpool->shutdowns); e;
- e = Curl_node_next(e)) {
- conn = Curl_node_elem(e);
- memset(&ps, 0, sizeof(ps));
- Curl_attach_connection(cpool->idata, conn);
- Curl_conn_adjust_pollset(cpool->idata, &ps);
- Curl_detach_connection(cpool->idata);
- result = Curl_pollfds_add_ps(cpfds, &ps);
- if(result) {
- Curl_pollfds_cleanup(cpfds);
- goto out;
- }
- }
- }
- out:
- return result;
- }
- CURLcode Curl_cpool_add_pollfds(struct cpool *cpool,
- struct curl_pollfds *cpfds)
- {
- CURLcode result;
- CPOOL_LOCK(cpool);
- result = cpool_add_pollfds(cpool, cpfds);
- CPOOL_UNLOCK(cpool);
- return result;
- }
- CURLcode Curl_cpool_add_waitfds(struct cpool *cpool,
- struct curl_waitfds *cwfds)
- {
- CURLcode result = CURLE_OK;
- CPOOL_LOCK(cpool);
- if(Curl_llist_head(&cpool->shutdowns)) {
- struct Curl_llist_node *e;
- struct easy_pollset ps;
- struct connectdata *conn;
- for(e = Curl_llist_head(&cpool->shutdowns); e;
- e = Curl_node_next(e)) {
- conn = Curl_node_elem(e);
- memset(&ps, 0, sizeof(ps));
- Curl_attach_connection(cpool->idata, conn);
- Curl_conn_adjust_pollset(cpool->idata, &ps);
- Curl_detach_connection(cpool->idata);
- result = Curl_waitfds_add_ps(cwfds, &ps);
- if(result)
- goto out;
- }
- }
- out:
- CPOOL_UNLOCK(cpool);
- return result;
- }
- static void cpool_perform(struct cpool *cpool)
- {
- struct Curl_easy *data = cpool->idata;
- struct Curl_llist_node *e = Curl_llist_head(&cpool->shutdowns);
- struct Curl_llist_node *enext;
- struct connectdata *conn;
- struct curltime *nowp = NULL;
- struct curltime now;
- timediff_t next_from_now_ms = 0, ms;
- bool done;
- if(!e)
- return;
- DEBUGASSERT(data);
- DEBUGF(infof(data, "[CCACHE] perform, %zu connections being shutdown",
- Curl_llist_count(&cpool->shutdowns)));
- while(e) {
- enext = Curl_node_next(e);
- conn = Curl_node_elem(e);
- Curl_attach_connection(data, conn);
- cpool_run_conn_shutdown(data, conn, &done);
- DEBUGF(infof(data, "[CCACHE] shutdown #%" FMT_OFF_T ", done=%d",
- conn->connection_id, done));
- Curl_detach_connection(data);
- if(done) {
- Curl_node_remove(e);
- cpool_close_and_destroy(cpool, conn, NULL, FALSE);
- }
- else {
- /* Not done, when does this connection time out? */
- if(!nowp) {
- now = Curl_now();
- nowp = &now;
- }
- ms = Curl_conn_shutdown_timeleft(conn, nowp);
- if(ms && ms < next_from_now_ms)
- next_from_now_ms = ms;
- }
- e = enext;
- }
- if(next_from_now_ms)
- Curl_expire(data, next_from_now_ms, EXPIRE_RUN_NOW);
- }
- void Curl_cpool_multi_perform(struct Curl_multi *multi)
- {
- CPOOL_LOCK(&multi->cpool);
- cpool_perform(&multi->cpool);
- CPOOL_UNLOCK(&multi->cpool);
- }
- /*
- * Close and destroy the connection. Run the shutdown sequence once,
- * of so requested.
- */
- static void cpool_close_and_destroy(struct cpool *cpool,
- struct connectdata *conn,
- struct Curl_easy *data,
- bool do_shutdown)
- {
- bool done;
- /* there must be a connection to close */
- DEBUGASSERT(conn);
- /* it must be removed from the connection pool */
- DEBUGASSERT(!conn->bits.in_cpool);
- /* there must be an associated transfer */
- DEBUGASSERT(data || cpool);
- if(!data)
- data = cpool->idata;
- /* the transfer must be detached from the connection */
- DEBUGASSERT(data && !data->conn);
- Curl_attach_connection(data, conn);
- cpool_run_conn_shutdown_handler(data, conn);
- if(do_shutdown) {
- /* Make a last attempt to shutdown handlers and filters, if
- * not done so already. */
- cpool_run_conn_shutdown(data, conn, &done);
- }
- if(cpool)
- DEBUGF(infof(data, "[CCACHE] closing #%" FMT_OFF_T,
- conn->connection_id));
- else
- DEBUGF(infof(data, "closing connection #%" FMT_OFF_T,
- conn->connection_id));
- Curl_conn_close(data, SECONDARYSOCKET);
- Curl_conn_close(data, FIRSTSOCKET);
- Curl_detach_connection(data);
- Curl_conn_free(data, conn);
- }
- static CURLMcode cpool_update_shutdown_ev(struct Curl_multi *multi,
- struct Curl_easy *data,
- struct connectdata *conn)
- {
- struct easy_pollset ps;
- CURLMcode mresult;
- DEBUGASSERT(data);
- DEBUGASSERT(multi);
- DEBUGASSERT(multi->socket_cb);
- memset(&ps, 0, sizeof(ps));
- Curl_attach_connection(data, conn);
- Curl_conn_adjust_pollset(data, &ps);
- Curl_detach_connection(data);
- mresult = Curl_multi_pollset_ev(multi, data, &ps, &conn->shutdown_poll);
- if(!mresult) /* Remember for next time */
- memcpy(&conn->shutdown_poll, &ps, sizeof(ps));
- return mresult;
- }
- void Curl_cpool_multi_socket(struct Curl_multi *multi,
- curl_socket_t s, int ev_bitmask)
- {
- struct cpool *cpool = &multi->cpool;
- struct Curl_easy *data = cpool->idata;
- struct Curl_llist_node *e;
- struct connectdata *conn;
- bool done;
- (void)ev_bitmask;
- DEBUGASSERT(multi->socket_cb);
- CPOOL_LOCK(cpool);
- e = Curl_llist_head(&cpool->shutdowns);
- while(e) {
- conn = Curl_node_elem(e);
- if(s == conn->sock[FIRSTSOCKET] || s == conn->sock[SECONDARYSOCKET]) {
- Curl_attach_connection(data, conn);
- cpool_run_conn_shutdown(data, conn, &done);
- DEBUGF(infof(data, "[CCACHE] shutdown #%" FMT_OFF_T ", done=%d",
- conn->connection_id, done));
- Curl_detach_connection(data);
- if(done || cpool_update_shutdown_ev(multi, data, conn)) {
- Curl_node_remove(e);
- cpool_close_and_destroy(cpool, conn, NULL, FALSE);
- }
- break;
- }
- e = Curl_node_next(e);
- }
- CPOOL_UNLOCK(cpool);
- }
- #define NUM_POLLS_ON_STACK 10
- static CURLcode cpool_shutdown_wait(struct cpool *cpool, int timeout_ms)
- {
- struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK];
- struct curl_pollfds cpfds;
- CURLcode result;
- Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK);
- result = cpool_add_pollfds(cpool, &cpfds);
- if(result)
- goto out;
- Curl_poll(cpfds.pfds, cpfds.n, CURLMIN(timeout_ms, 1000));
- out:
- Curl_pollfds_cleanup(&cpfds);
- return result;
- }
- static void cpool_shutdown_all(struct cpool *cpool,
- struct Curl_easy *data, int timeout_ms)
- {
- struct connectdata *conn;
- struct curltime started = Curl_now();
- if(!data)
- return;
- (void)data;
- DEBUGF(infof(data, "cpool shutdown all"));
- /* Move all connections into the shutdown queue */
- for(conn = cpool_get_live_conn(cpool); conn;
- conn = cpool_get_live_conn(cpool)) {
- /* Move conn from live set to shutdown or destroy right away */
- DEBUGF(infof(data, "moving connection #%" FMT_OFF_T
- " to shutdown queue", conn->connection_id));
- cpool_remove_conn(cpool, conn);
- cpool_discard_conn(cpool, data, conn, FALSE);
- }
- while(Curl_llist_head(&cpool->shutdowns)) {
- timediff_t timespent;
- int remain_ms;
- cpool_perform(cpool);
- if(!Curl_llist_head(&cpool->shutdowns)) {
- DEBUGF(infof(data, "cpool shutdown ok"));
- break;
- }
- /* wait for activity, timeout or "nothing" */
- timespent = Curl_timediff(Curl_now(), started);
- if(timespent >= (timediff_t)timeout_ms) {
- DEBUGF(infof(data, "cpool shutdown %s",
- (timeout_ms > 0) ? "timeout" : "best effort done"));
- break;
- }
- remain_ms = timeout_ms - (int)timespent;
- if(cpool_shutdown_wait(cpool, remain_ms)) {
- DEBUGF(infof(data, "cpool shutdown all, abort"));
- break;
- }
- }
- /* Due to errors/timeout, we might come here without being done. */
- cpool_shutdown_discard_all(cpool);
- }
- struct cpool_reaper_ctx {
- struct curltime now;
- };
- static int cpool_reap_dead_cb(struct Curl_easy *data,
- struct connectdata *conn, void *param)
- {
- struct cpool_reaper_ctx *rctx = param;
- if(Curl_conn_seems_dead(conn, data, &rctx->now)) {
- /* stop the iteration here, pass back the connection that was pruned */
- Curl_cpool_disconnect(data, conn, FALSE);
- return 1;
- }
- return 0; /* continue iteration */
- }
- /*
- * This function scans the data's connection pool for half-open/dead
- * connections, closes and removes them.
- * The cleanup is done at most once per second.
- *
- * When called, this transfer has no connection attached.
- */
- void Curl_cpool_prune_dead(struct Curl_easy *data)
- {
- struct cpool *cpool = cpool_get_instance(data);
- struct cpool_reaper_ctx rctx;
- timediff_t elapsed;
- if(!cpool)
- return;
- rctx.now = Curl_now();
- CPOOL_LOCK(cpool);
- elapsed = Curl_timediff(rctx.now, cpool->last_cleanup);
- if(elapsed >= 1000L) {
- while(cpool_foreach(data, cpool, &rctx, cpool_reap_dead_cb))
- ;
- cpool->last_cleanup = rctx.now;
- }
- CPOOL_UNLOCK(cpool);
- }
- static int conn_upkeep(struct Curl_easy *data,
- struct connectdata *conn,
- void *param)
- {
- struct curltime *now = param;
- /* TODO, shall we reap connections that return an error here? */
- Curl_conn_upkeep(data, conn, now);
- return 0; /* continue iteration */
- }
- CURLcode Curl_cpool_upkeep(void *data)
- {
- struct cpool *cpool = cpool_get_instance(data);
- struct curltime now = Curl_now();
- if(!cpool)
- return CURLE_OK;
- CPOOL_LOCK(cpool);
- cpool_foreach(data, cpool, &now, conn_upkeep);
- CPOOL_UNLOCK(cpool);
- return CURLE_OK;
- }
- struct cpool_find_ctx {
- curl_off_t id;
- struct connectdata *conn;
- };
- static int cpool_find_conn(struct Curl_easy *data,
- struct connectdata *conn, void *param)
- {
- struct cpool_find_ctx *fctx = param;
- (void)data;
- if(conn->connection_id == fctx->id) {
- fctx->conn = conn;
- return 1;
- }
- return 0;
- }
- struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data,
- curl_off_t conn_id)
- {
- struct cpool *cpool = cpool_get_instance(data);
- struct cpool_find_ctx fctx;
- if(!cpool)
- return NULL;
- fctx.id = conn_id;
- fctx.conn = NULL;
- CPOOL_LOCK(cpool);
- cpool_foreach(cpool->idata, cpool, &fctx, cpool_find_conn);
- CPOOL_UNLOCK(cpool);
- return fctx.conn;
- }
- struct cpool_do_conn_ctx {
- curl_off_t id;
- Curl_cpool_conn_do_cb *cb;
- void *cbdata;
- };
- static int cpool_do_conn(struct Curl_easy *data,
- struct connectdata *conn, void *param)
- {
- struct cpool_do_conn_ctx *dctx = param;
- (void)data;
- if(conn->connection_id == dctx->id) {
- dctx->cb(conn, data, dctx->cbdata);
- return 1;
- }
- return 0;
- }
- void Curl_cpool_do_by_id(struct Curl_easy *data, curl_off_t conn_id,
- Curl_cpool_conn_do_cb *cb, void *cbdata)
- {
- struct cpool *cpool = cpool_get_instance(data);
- struct cpool_do_conn_ctx dctx;
- if(!cpool)
- return;
- dctx.id = conn_id;
- dctx.cb = cb;
- dctx.cbdata = cbdata;
- CPOOL_LOCK(cpool);
- cpool_foreach(data, cpool, &dctx, cpool_do_conn);
- CPOOL_UNLOCK(cpool);
- }
- void Curl_cpool_do_locked(struct Curl_easy *data,
- struct connectdata *conn,
- Curl_cpool_conn_do_cb *cb, void *cbdata)
- {
- struct cpool *cpool = cpool_get_instance(data);
- if(cpool) {
- CPOOL_LOCK(cpool);
- cb(conn, data, cbdata);
- CPOOL_UNLOCK(cpool);
- }
- else
- cb(conn, data, cbdata);
- }
- #if 0
- /* Useful for debugging the connection pool */
- void Curl_cpool_print(struct cpool *cpool)
- {
- struct Curl_hash_iterator iter;
- struct Curl_llist_node *curr;
- struct Curl_hash_element *he;
- if(!cpool)
- return;
- fprintf(stderr, "=Bundle cache=\n");
- Curl_hash_start_iterate(cpool->dest2bundle, &iter);
- he = Curl_hash_next_element(&iter);
- while(he) {
- struct cpool_bundle *bundle;
- struct connectdata *conn;
- bundle = he->ptr;
- fprintf(stderr, "%s -", he->key);
- curr = Curl_llist_head(bundle->conns);
- while(curr) {
- conn = Curl_node_elem(curr);
- fprintf(stderr, " [%p %d]", (void *)conn, conn->refcount);
- curr = Curl_node_next(curr);
- }
- fprintf(stderr, "\n");
- he = Curl_hash_next_element(&iter);
- }
- }
- #endif
|