Merge aa3ede6d27 into b4834a7d6d
This commit is contained in:
commit
a182327b35
@ -123,6 +123,7 @@ LIB_CFILES = \
|
||||
cf-socket.c \
|
||||
cfilters.c \
|
||||
conncache.c \
|
||||
cshutdn.c \
|
||||
connect.c \
|
||||
content_encoding.c \
|
||||
cookie.c \
|
||||
@ -259,6 +260,7 @@ LIB_HFILES = \
|
||||
cf-socket.h \
|
||||
cfilters.h \
|
||||
conncache.h \
|
||||
cshutdn.h \
|
||||
connect.h \
|
||||
content_encoding.h \
|
||||
cookie.h \
|
||||
|
||||
@ -202,7 +202,7 @@ CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done)
|
||||
if(!Curl_shutdown_started(data, sockindex)) {
|
||||
CURL_TRC_M(data, "shutdown start on%s connection",
|
||||
sockindex ? " secondary" : "");
|
||||
Curl_shutdown_start(data, sockindex, &now);
|
||||
Curl_shutdown_start(data, sockindex, 0, &now);
|
||||
}
|
||||
else {
|
||||
timeout_ms = Curl_shutdown_timeleft(data->conn, sockindex, &now);
|
||||
|
||||
1184
lib/conncache.c
1184
lib/conncache.c
File diff suppressed because it is too large
Load Diff
@ -35,6 +35,19 @@ struct Curl_waitfds;
|
||||
struct Curl_multi;
|
||||
struct Curl_share;
|
||||
|
||||
/**
|
||||
* Terminate the connection, e.g. close and destroy.
|
||||
* If the connection is in a cpool, remove it.
|
||||
* If a `cshutdn` is available (e.g. data has a multi handle),
|
||||
* pass the connection to that for controlled shutdown.
|
||||
* Otherwise terminate it right away.
|
||||
* Takes ownership of `conn`.
|
||||
* `data` should not be attached to a connection.
|
||||
*/
|
||||
void Curl_conn_terminate(struct Curl_easy *data,
|
||||
struct connectdata *conn,
|
||||
bool aborted);
|
||||
|
||||
/**
|
||||
* Callback invoked when disconnecting connections.
|
||||
* @param data transfer last handling the connection, not attached
|
||||
@ -54,12 +67,11 @@ struct cpool {
|
||||
curl_off_t next_connection_id;
|
||||
curl_off_t next_easy_id;
|
||||
struct curltime last_cleanup;
|
||||
struct Curl_llist shutdowns; /* The connections being shut down */
|
||||
struct Curl_easy *idata; /* internal handle used for discard */
|
||||
struct Curl_multi *multi; /* != NULL iff pool belongs to multi */
|
||||
struct Curl_easy *idata; /* internal handle for maintenance */
|
||||
struct Curl_share *share; /* != NULL iff pool belongs to share */
|
||||
Curl_cpool_disconnect_cb *disconnect_cb;
|
||||
BIT(locked);
|
||||
BIT(initialised);
|
||||
};
|
||||
|
||||
/* Init the pool, pass multi only if pool is owned by it.
|
||||
@ -67,7 +79,7 @@ struct cpool {
|
||||
*/
|
||||
int Curl_cpool_init(struct cpool *cpool,
|
||||
Curl_cpool_disconnect_cb *disconnect_cb,
|
||||
struct Curl_multi *multi,
|
||||
struct Curl_easy *idata,
|
||||
struct Curl_share *share,
|
||||
size_t size);
|
||||
|
||||
@ -78,14 +90,13 @@ void Curl_cpool_destroy(struct cpool *connc);
|
||||
* Assigns `data->id`. */
|
||||
void Curl_cpool_xfer_init(struct Curl_easy *data);
|
||||
|
||||
/**
|
||||
* Get the connection with the given id from the transfer's pool.
|
||||
*/
|
||||
/* Get the connection with the given id from `data`'s conn pool. */
|
||||
struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data,
|
||||
curl_off_t conn_id);
|
||||
|
||||
CURLcode Curl_cpool_add_conn(struct Curl_easy *data,
|
||||
struct connectdata *conn) WARN_UNUSED_RESULT;
|
||||
/* Add the connection to the pool. */
|
||||
CURLcode Curl_cpool_add(struct Curl_easy *data,
|
||||
struct connectdata *conn) WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Return if the pool has reached its configured limits for adding
|
||||
@ -131,17 +142,6 @@ bool Curl_cpool_find(struct Curl_easy *data,
|
||||
bool Curl_cpool_conn_now_idle(struct Curl_easy *data,
|
||||
struct connectdata *conn);
|
||||
|
||||
/**
|
||||
* Remove the connection from the pool and tear it down.
|
||||
* If `aborted` is FALSE, the connection will be shut down first
|
||||
* before closing and destroying it.
|
||||
* If the shutdown is not immediately complete, the connection
|
||||
* will be placed into the pool's shutdown queue.
|
||||
*/
|
||||
void Curl_cpool_disconnect(struct Curl_easy *data,
|
||||
struct connectdata *conn,
|
||||
bool aborted);
|
||||
|
||||
/**
|
||||
* This function scans the data's connection pool for half-open/dead
|
||||
* connections, closes and removes them.
|
||||
@ -178,22 +178,4 @@ void Curl_cpool_do_locked(struct Curl_easy *data,
|
||||
struct connectdata *conn,
|
||||
Curl_cpool_conn_do_cb *cb, void *cbdata);
|
||||
|
||||
/**
|
||||
* Add sockets and POLLIN/OUT flags for connections handled by the pool.
|
||||
*/
|
||||
CURLcode Curl_cpool_add_pollfds(struct cpool *connc,
|
||||
struct curl_pollfds *cpfds);
|
||||
unsigned int Curl_cpool_add_waitfds(struct cpool *connc,
|
||||
struct Curl_waitfds *cwfds);
|
||||
|
||||
void Curl_cpool_setfds(struct cpool *cpool,
|
||||
fd_set *read_fd_set, fd_set *write_fd_set,
|
||||
int *maxfd);
|
||||
|
||||
/**
|
||||
* Run connections on socket. If socket is CURL_SOCKET_TIMEOUT, run
|
||||
* maintenance on all connections.
|
||||
*/
|
||||
void Curl_cpool_multi_perform(struct Curl_multi *multi, curl_socket_t s);
|
||||
|
||||
#endif /* HEADER_CURL_CONNCACHE_H */
|
||||
|
||||
@ -161,7 +161,7 @@ timediff_t Curl_timeleft(struct Curl_easy *data,
|
||||
}
|
||||
|
||||
void Curl_shutdown_start(struct Curl_easy *data, int sockindex,
|
||||
struct curltime *nowp)
|
||||
int timeout_ms, struct curltime *nowp)
|
||||
{
|
||||
struct curltime now;
|
||||
|
||||
@ -171,8 +171,13 @@ void Curl_shutdown_start(struct Curl_easy *data, int sockindex,
|
||||
nowp = &now;
|
||||
}
|
||||
data->conn->shutdown.start[sockindex] = *nowp;
|
||||
data->conn->shutdown.timeout_ms = (data->set.shutdowntimeout > 0) ?
|
||||
data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS;
|
||||
data->conn->shutdown.timeout_ms = (timeout_ms >= 0) ?
|
||||
(unsigned int)timeout_ms :
|
||||
((data->set.shutdowntimeout > 0) ?
|
||||
data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS);
|
||||
if(data->conn->shutdown.timeout_ms)
|
||||
Curl_expire_ex(data, nowp, data->conn->shutdown.timeout_ms,
|
||||
EXPIRE_SHUTDOWN);
|
||||
}
|
||||
|
||||
timediff_t Curl_shutdown_timeleft(struct connectdata *conn, int sockindex,
|
||||
|
||||
@ -45,7 +45,7 @@ timediff_t Curl_timeleft(struct Curl_easy *data,
|
||||
#define DEFAULT_SHUTDOWN_TIMEOUT_MS (2 * 1000)
|
||||
|
||||
void Curl_shutdown_start(struct Curl_easy *data, int sockindex,
|
||||
struct curltime *nowp);
|
||||
int timeout_ms, struct curltime *nowp);
|
||||
|
||||
/* return how much time there is left to shutdown the connection at
|
||||
* sockindex. Returns 0 if there is no limit or shutdown has not started. */
|
||||
|
||||
566
lib/cshutdn.c
Normal file
566
lib/cshutdn.c
Normal file
@ -0,0 +1,566 @@
|
||||
/***************************************************************************
|
||||
* _ _ ____ _
|
||||
* 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 "multi_ev.h"
|
||||
#include "sendf.h"
|
||||
#include "cshutdn.h"
|
||||
#include "http_negotiate.h"
|
||||
#include "http_ntlm.h"
|
||||
#include "sigpipe.h"
|
||||
#include "connect.h"
|
||||
#include "select.h"
|
||||
#include "strcase.h"
|
||||
#include "strparse.h"
|
||||
|
||||
/* The last 3 #include files should be in this order */
|
||||
#include "curl_printf.h"
|
||||
#include "curl_memory.h"
|
||||
#include "memdebug.h"
|
||||
|
||||
|
||||
static void cshutdn_run_conn_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) {
|
||||
/* Some disconnect handlers do a blocking wait on server responses.
|
||||
* FTP/IMAP/SMTP and SFTP are among them. When using the internal
|
||||
* handle, set an overall short timeout so we do not hang for the
|
||||
* default 120 seconds. */
|
||||
if(data->state.internal) {
|
||||
data->set.timeout = DEFAULT_SHUTDOWN_TIMEOUT_MS;
|
||||
(void)Curl_pgrsTime(data, TIMER_STARTOP);
|
||||
}
|
||||
|
||||
/* 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));
|
||||
/* There are protocol handlers that block on retrieving
|
||||
* server responses here (FTP). Set a short timeout. */
|
||||
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 cshutdn_run_once(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);
|
||||
|
||||
cshutdn_run_conn_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;
|
||||
}
|
||||
|
||||
void Curl_cshutdn_run_once(struct Curl_easy *data,
|
||||
struct connectdata *conn,
|
||||
bool *done)
|
||||
{
|
||||
DEBUGASSERT(!data->conn);
|
||||
Curl_attach_connection(data, conn);
|
||||
cshutdn_run_once(data, conn, done);
|
||||
CURL_TRC_M(data, "[SHUTDOWN] shutdown, done=%d", *done);
|
||||
Curl_detach_connection(data);
|
||||
}
|
||||
|
||||
|
||||
void Curl_cshutdn_terminate(struct Curl_easy *data,
|
||||
struct connectdata *conn,
|
||||
bool do_shutdown)
|
||||
{
|
||||
struct Curl_easy *admin = data;
|
||||
bool done;
|
||||
|
||||
/* there must be a connection to close */
|
||||
DEBUGASSERT(conn);
|
||||
/* it must be removed from the connection pool */
|
||||
DEBUGASSERT(!conn->bits.in_cpool);
|
||||
/* the transfer must be detached from the connection */
|
||||
DEBUGASSERT(data && !data->conn);
|
||||
|
||||
/* If we can obtain an internal admin handle, use that to attach
|
||||
* and terminate the connection. Some protocol will try to mess with
|
||||
* `data` during shutdown and we do not want that with a `data` from
|
||||
* the application. */
|
||||
if(data->multi && data->multi->admin)
|
||||
admin = data->multi->admin;
|
||||
|
||||
Curl_attach_connection(admin, conn);
|
||||
|
||||
cshutdn_run_conn_handler(admin, conn);
|
||||
if(do_shutdown) {
|
||||
/* Make a last attempt to shutdown handlers and filters, if
|
||||
* not done so already. */
|
||||
cshutdn_run_once(admin, conn, &done);
|
||||
}
|
||||
CURL_TRC_M(admin, "[SHUTDOWN] closing connection");
|
||||
Curl_conn_close(admin, SECONDARYSOCKET);
|
||||
Curl_conn_close(admin, FIRSTSOCKET);
|
||||
Curl_detach_connection(admin);
|
||||
|
||||
if(data->multi)
|
||||
Curl_multi_ev_conn_done(data->multi, data, conn);
|
||||
Curl_conn_free(admin, conn);
|
||||
|
||||
if(data->multi) {
|
||||
CURL_TRC_M(data, "[SHUTDOWN] trigger multi connchanged");
|
||||
Curl_multi_connchanged(data->multi);
|
||||
}
|
||||
}
|
||||
|
||||
static void cshutdn_destroy_oldest(struct cshutdn *cshutdn,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
struct Curl_llist_node *e;
|
||||
struct connectdata *conn;
|
||||
|
||||
e = Curl_llist_head(&cshutdn->list);
|
||||
if(e) {
|
||||
SIGPIPE_VARIABLE(pipe_st);
|
||||
conn = Curl_node_elem(e);
|
||||
Curl_node_remove(e);
|
||||
sigpipe_init(&pipe_st);
|
||||
sigpipe_apply(data, &pipe_st);
|
||||
Curl_cshutdn_terminate(data, conn, FALSE);
|
||||
sigpipe_restore(&pipe_st);
|
||||
}
|
||||
}
|
||||
|
||||
#define NUM_POLLS_ON_STACK 10
|
||||
|
||||
static CURLcode cshutdn_wait(struct cshutdn *cshutdn,
|
||||
struct Curl_easy *data,
|
||||
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 = Curl_cshutdn_add_pollfds(cshutdn, data, &cpfds);
|
||||
if(result)
|
||||
goto out;
|
||||
|
||||
Curl_poll(cpfds.pfds, cpfds.n, CURLMIN(timeout_ms, 1000));
|
||||
|
||||
out:
|
||||
Curl_pollfds_cleanup(&cpfds);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static void cshutdn_perform(struct cshutdn *cshutdn,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list);
|
||||
struct Curl_llist_node *enext;
|
||||
struct connectdata *conn;
|
||||
struct curltime *nowp = NULL;
|
||||
struct curltime now;
|
||||
timediff_t next_expire_ms = 0, ms;
|
||||
bool done;
|
||||
|
||||
if(!e)
|
||||
return;
|
||||
|
||||
CURL_TRC_M(data, "[SHUTDOWN] perform on %zu connections",
|
||||
Curl_llist_count(&cshutdn->list));
|
||||
while(e) {
|
||||
enext = Curl_node_next(e);
|
||||
conn = Curl_node_elem(e);
|
||||
Curl_cshutdn_run_once(data, conn, &done);
|
||||
if(done) {
|
||||
Curl_node_remove(e);
|
||||
Curl_cshutdn_terminate(data, conn, FALSE);
|
||||
}
|
||||
else {
|
||||
/* idata has one timer list, but maybe more than one connection.
|
||||
* Set EXPIRE_SHUTDOWN to the smallest time left for all. */
|
||||
if(!nowp) {
|
||||
now = Curl_now();
|
||||
nowp = &now;
|
||||
}
|
||||
ms = Curl_conn_shutdown_timeleft(conn, nowp);
|
||||
if(ms && ms < next_expire_ms)
|
||||
next_expire_ms = ms;
|
||||
}
|
||||
e = enext;
|
||||
}
|
||||
|
||||
if(next_expire_ms)
|
||||
Curl_expire_ex(data, nowp, next_expire_ms, EXPIRE_SHUTDOWN);
|
||||
}
|
||||
|
||||
|
||||
static void cshutdn_terminate_all(struct cshutdn *cshutdn,
|
||||
struct Curl_easy *data,
|
||||
int timeout_ms)
|
||||
{
|
||||
struct curltime started = Curl_now();
|
||||
struct Curl_llist_node *e;
|
||||
SIGPIPE_VARIABLE(pipe_st);
|
||||
|
||||
DEBUGASSERT(cshutdn);
|
||||
DEBUGASSERT(data);
|
||||
|
||||
CURL_TRC_M(data, "[SHUTDOWN] shutdown all");
|
||||
sigpipe_init(&pipe_st);
|
||||
sigpipe_apply(data, &pipe_st);
|
||||
|
||||
while(Curl_llist_head(&cshutdn->list)) {
|
||||
timediff_t timespent;
|
||||
int remain_ms;
|
||||
|
||||
cshutdn_perform(cshutdn, data);
|
||||
|
||||
if(!Curl_llist_head(&cshutdn->list)) {
|
||||
CURL_TRC_M(data, "[SHUTDOWN] shutdown finished cleanly");
|
||||
break;
|
||||
}
|
||||
|
||||
/* wait for activity, timeout or "nothing" */
|
||||
timespent = Curl_timediff(Curl_now(), started);
|
||||
if(timespent >= (timediff_t)timeout_ms) {
|
||||
CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, %s",
|
||||
(timeout_ms > 0) ? "timeout" : "best effort done");
|
||||
break;
|
||||
}
|
||||
|
||||
remain_ms = timeout_ms - (int)timespent;
|
||||
if(cshutdn_wait(cshutdn, data, remain_ms)) {
|
||||
CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, aborted");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Terminate any remaining. */
|
||||
e = Curl_llist_head(&cshutdn->list);
|
||||
while(e) {
|
||||
struct connectdata *conn = Curl_node_elem(e);
|
||||
Curl_node_remove(e);
|
||||
Curl_cshutdn_terminate(data, conn, FALSE);
|
||||
e = Curl_llist_head(&cshutdn->list);
|
||||
}
|
||||
DEBUGASSERT(!Curl_llist_count(&cshutdn->list));
|
||||
|
||||
Curl_hostcache_clean(data, data->dns.hostcache);
|
||||
|
||||
sigpipe_restore(&pipe_st);
|
||||
}
|
||||
|
||||
|
||||
int Curl_cshutdn_init(struct cshutdn *cshutdn,
|
||||
struct Curl_multi *multi)
|
||||
{
|
||||
DEBUGASSERT(multi);
|
||||
cshutdn->multi = multi;
|
||||
Curl_llist_init(&cshutdn->list, NULL);
|
||||
cshutdn->initialised = TRUE;
|
||||
return 0; /* good */
|
||||
}
|
||||
|
||||
|
||||
void Curl_cshutdn_destroy(struct cshutdn *cshutdn,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
if(cshutdn->initialised && data) {
|
||||
int timeout_ms = 0;
|
||||
/* Just for testing, run graceful shutdown */
|
||||
#ifdef DEBUGBUILD
|
||||
{
|
||||
const char *p = getenv("CURL_GRACEFUL_SHUTDOWN");
|
||||
if(p) {
|
||||
curl_off_t l;
|
||||
if(!Curl_str_number(&p, &l, INT_MAX))
|
||||
timeout_ms = (int)l;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
CURL_TRC_M(data, "[SHUTDOWN] destroy, %zu connections, timeout=%dms",
|
||||
Curl_llist_count(&cshutdn->list), timeout_ms);
|
||||
cshutdn_terminate_all(cshutdn, data, timeout_ms);
|
||||
}
|
||||
cshutdn->multi = NULL;
|
||||
}
|
||||
|
||||
size_t Curl_cshutdn_count(struct Curl_easy *data)
|
||||
{
|
||||
if(data && data->multi) {
|
||||
struct cshutdn *csd = &data->multi->cshutdn;
|
||||
return Curl_llist_count(&csd->list);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t Curl_cshutdn_dest_count(struct Curl_easy *data,
|
||||
const char *destination)
|
||||
{
|
||||
if(data && data->multi) {
|
||||
struct cshutdn *csd = &data->multi->cshutdn;
|
||||
size_t n = 0;
|
||||
struct Curl_llist_node *e = Curl_llist_head(&csd->list);
|
||||
while(e) {
|
||||
struct connectdata *conn = Curl_node_elem(e);
|
||||
if(!strcmp(destination, conn->destination))
|
||||
++n;
|
||||
e = Curl_node_next(e);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static CURLMcode cshutdn_update_ev(struct cshutdn *cshutdn,
|
||||
struct Curl_easy *data,
|
||||
struct connectdata *conn)
|
||||
{
|
||||
CURLMcode mresult;
|
||||
|
||||
DEBUGASSERT(cshutdn);
|
||||
DEBUGASSERT(cshutdn->multi->socket_cb);
|
||||
|
||||
Curl_attach_connection(data, conn);
|
||||
mresult = Curl_multi_ev_assess_conn(cshutdn->multi, data, conn);
|
||||
Curl_detach_connection(data);
|
||||
return mresult;
|
||||
}
|
||||
|
||||
|
||||
void Curl_cshutdn_add(struct cshutdn *cshutdn,
|
||||
struct connectdata *conn,
|
||||
size_t conns_in_pool)
|
||||
{
|
||||
struct Curl_easy *data = cshutdn->multi->admin;
|
||||
size_t max_total = (cshutdn->multi->max_total_connections > 0) ?
|
||||
(size_t)cshutdn->multi->max_total_connections : 0;
|
||||
|
||||
/* Add the connection to our shutdown list for non-blocking shutdown
|
||||
* during multi processing. */
|
||||
if(max_total > 0 && (max_total <=
|
||||
(conns_in_pool + Curl_llist_count(&cshutdn->list)))) {
|
||||
CURL_TRC_M(data, "[SHUTDOWN] discarding oldest shutdown connection "
|
||||
"due to connection limit of %zu", max_total);
|
||||
cshutdn_destroy_oldest(cshutdn, data);
|
||||
}
|
||||
|
||||
if(cshutdn->multi->socket_cb) {
|
||||
if(cshutdn_update_ev(cshutdn, data, conn)) {
|
||||
CURL_TRC_M(data, "[SHUTDOWN] update events failed, discarding #%"
|
||||
FMT_OFF_T, conn->connection_id);
|
||||
Curl_cshutdn_terminate(data, conn, FALSE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Curl_llist_append(&cshutdn->list, conn, &conn->cshutdn_node);
|
||||
CURL_TRC_M(data, "[SHUTDOWN] added #%" FMT_OFF_T
|
||||
" to shutdowns, now %zu conns in shutdown",
|
||||
conn->connection_id, Curl_llist_count(&cshutdn->list));
|
||||
}
|
||||
|
||||
|
||||
static void cshutdn_multi_socket(struct cshutdn *cshutdn,
|
||||
struct Curl_easy *data,
|
||||
curl_socket_t s)
|
||||
{
|
||||
struct Curl_llist_node *e;
|
||||
struct connectdata *conn;
|
||||
bool done;
|
||||
|
||||
DEBUGASSERT(cshutdn->multi->socket_cb);
|
||||
e = Curl_llist_head(&cshutdn->list);
|
||||
while(e) {
|
||||
conn = Curl_node_elem(e);
|
||||
if(s == conn->sock[FIRSTSOCKET] || s == conn->sock[SECONDARYSOCKET]) {
|
||||
Curl_cshutdn_run_once(data, conn, &done);
|
||||
if(done || cshutdn_update_ev(cshutdn, data, conn)) {
|
||||
Curl_node_remove(e);
|
||||
Curl_cshutdn_terminate(data, conn, FALSE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
e = Curl_node_next(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Curl_cshutdn_perform(struct cshutdn *cshutdn,
|
||||
struct Curl_easy *data,
|
||||
curl_socket_t s)
|
||||
{
|
||||
if((s == CURL_SOCKET_TIMEOUT) || (!cshutdn->multi->socket_cb))
|
||||
cshutdn_perform(cshutdn, data);
|
||||
else
|
||||
cshutdn_multi_socket(cshutdn, data, s);
|
||||
}
|
||||
|
||||
/* return fd_set info about the shutdown connections */
|
||||
void Curl_cshutdn_setfds(struct cshutdn *cshutdn,
|
||||
struct Curl_easy *data,
|
||||
fd_set *read_fd_set, fd_set *write_fd_set,
|
||||
int *maxfd)
|
||||
{
|
||||
if(Curl_llist_head(&cshutdn->list)) {
|
||||
struct Curl_llist_node *e;
|
||||
|
||||
for(e = Curl_llist_head(&cshutdn->list); e;
|
||||
e = Curl_node_next(e)) {
|
||||
struct easy_pollset ps;
|
||||
unsigned int i;
|
||||
struct connectdata *conn = Curl_node_elem(e);
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
Curl_attach_connection(data, conn);
|
||||
Curl_conn_adjust_pollset(data, conn, &ps);
|
||||
Curl_detach_connection(data);
|
||||
|
||||
for(i = 0; i < ps.num; i++) {
|
||||
#if defined(__DJGPP__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Warith-conversion"
|
||||
#endif
|
||||
if(ps.actions[i] & CURL_POLL_IN)
|
||||
FD_SET(ps.sockets[i], read_fd_set);
|
||||
if(ps.actions[i] & CURL_POLL_OUT)
|
||||
FD_SET(ps.sockets[i], write_fd_set);
|
||||
#if defined(__DJGPP__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
if((ps.actions[i] & (CURL_POLL_OUT | CURL_POLL_IN)) &&
|
||||
((int)ps.sockets[i] > *maxfd))
|
||||
*maxfd = (int)ps.sockets[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* return information about the shutdown connections */
|
||||
unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn,
|
||||
struct Curl_easy *data,
|
||||
struct Curl_waitfds *cwfds)
|
||||
{
|
||||
unsigned int need = 0;
|
||||
|
||||
if(Curl_llist_head(&cshutdn->list)) {
|
||||
struct Curl_llist_node *e;
|
||||
struct easy_pollset ps;
|
||||
struct connectdata *conn;
|
||||
|
||||
for(e = Curl_llist_head(&cshutdn->list); e;
|
||||
e = Curl_node_next(e)) {
|
||||
conn = Curl_node_elem(e);
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
Curl_attach_connection(data, conn);
|
||||
Curl_conn_adjust_pollset(data, conn, &ps);
|
||||
Curl_detach_connection(data);
|
||||
|
||||
need += Curl_waitfds_add_ps(cwfds, &ps);
|
||||
}
|
||||
}
|
||||
return need;
|
||||
}
|
||||
|
||||
CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn,
|
||||
struct Curl_easy *data,
|
||||
struct curl_pollfds *cpfds)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
if(Curl_llist_head(&cshutdn->list)) {
|
||||
struct Curl_llist_node *e;
|
||||
struct easy_pollset ps;
|
||||
struct connectdata *conn;
|
||||
|
||||
for(e = Curl_llist_head(&cshutdn->list); e;
|
||||
e = Curl_node_next(e)) {
|
||||
conn = Curl_node_elem(e);
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
Curl_attach_connection(data, conn);
|
||||
Curl_conn_adjust_pollset(data, conn, &ps);
|
||||
Curl_detach_connection(data);
|
||||
|
||||
result = Curl_pollfds_add_ps(cpfds, &ps);
|
||||
if(result) {
|
||||
Curl_pollfds_cleanup(cpfds);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
104
lib/cshutdn.h
Normal file
104
lib/cshutdn.h
Normal file
@ -0,0 +1,104 @@
|
||||
#ifndef HEADER_CURL_CSHUTDN_H
|
||||
#define HEADER_CURL_CSHUTDN_H
|
||||
/***************************************************************************
|
||||
* _ _ ____ _
|
||||
* Project ___| | | | _ \| |
|
||||
* / __| | | | |_) | |
|
||||
* | (__| |_| | _ <| |___
|
||||
* \___|\___/|_| \_\_____|
|
||||
*
|
||||
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
|
||||
* Copyright (C) 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 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/curl.h>
|
||||
#include "timeval.h"
|
||||
|
||||
struct connectdata;
|
||||
struct Curl_easy;
|
||||
struct curl_pollfds;
|
||||
struct Curl_waitfds;
|
||||
struct Curl_multi;
|
||||
struct Curl_share;
|
||||
|
||||
/* Run the shutdown of the connection once.
|
||||
* Will shortly attach/detach `data` to `conn` while doing so.
|
||||
* `done` will be set TRUE if any error was encountered or if
|
||||
* the connection was shut down completely. */
|
||||
void Curl_cshutdn_run_once(struct Curl_easy *data,
|
||||
struct connectdata *conn,
|
||||
bool *done);
|
||||
|
||||
/* Terminates the connection, e.g. closes and destroys it.
|
||||
* If `run_shutdown` is TRUE, the shutdown will be run once before
|
||||
* terminating it.
|
||||
* Takes ownership of `conn`. */
|
||||
void Curl_cshutdn_terminate(struct Curl_easy *data,
|
||||
struct connectdata *conn,
|
||||
bool run_shutdown);
|
||||
|
||||
/* A `cshutdown` is always owned by a multi handle to maintain
|
||||
* the connections to be shut down. It registers timers and
|
||||
* sockets to monitor via the multi handle. */
|
||||
struct cshutdn {
|
||||
struct Curl_llist list; /* connections being shut down */
|
||||
struct Curl_multi *multi; /* the multi owning this */
|
||||
BIT(initialised);
|
||||
};
|
||||
|
||||
/* Init as part of the given multi handle. */
|
||||
int Curl_cshutdn_init(struct cshutdn *cshutdn,
|
||||
struct Curl_multi *multi);
|
||||
|
||||
/* Terminate all remaining connections and free resources. */
|
||||
void Curl_cshutdn_destroy(struct cshutdn *cshutdn,
|
||||
struct Curl_easy *data);
|
||||
|
||||
/* Number of connections being shut down. */
|
||||
size_t Curl_cshutdn_count(struct Curl_easy *data);
|
||||
|
||||
/* Number of connections to the destination being shut down. */
|
||||
size_t Curl_cshutdn_dest_count(struct Curl_easy *data,
|
||||
const char *destination);
|
||||
|
||||
/* Add a connection to have it shut down. Will terminate the oldest
|
||||
* connection when total connection limit of multi is being reached. */
|
||||
void Curl_cshutdn_add(struct cshutdn *cshutdn,
|
||||
struct connectdata *conn,
|
||||
size_t conns_in_pool);
|
||||
|
||||
/* Add sockets and POLLIN/OUT flags for connections being shut down. */
|
||||
CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn,
|
||||
struct Curl_easy *data,
|
||||
struct curl_pollfds *cpfds);
|
||||
|
||||
unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn,
|
||||
struct Curl_easy *data,
|
||||
struct Curl_waitfds *cwfds);
|
||||
|
||||
void Curl_cshutdn_setfds(struct cshutdn *cshutdn,
|
||||
struct Curl_easy *data,
|
||||
fd_set *read_fd_set, fd_set *write_fd_set,
|
||||
int *maxfd);
|
||||
|
||||
/* Run shut down connections using socket. If socket is CURL_SOCKET_TIMEOUT,
|
||||
* run maintenance on all connections. */
|
||||
void Curl_cshutdn_perform(struct cshutdn *cshutdn,
|
||||
struct Curl_easy *data,
|
||||
curl_socket_t s);
|
||||
|
||||
#endif /* HEADER_CURL_CSHUTDN_H */
|
||||
@ -770,7 +770,7 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events)
|
||||
Curl_detach_connection(data);
|
||||
s = Curl_getconnectinfo(data, &c);
|
||||
if((s != CURL_SOCKET_BAD) && c) {
|
||||
Curl_cpool_disconnect(data, c, TRUE);
|
||||
Curl_conn_terminate(data, c, TRUE);
|
||||
}
|
||||
DEBUGASSERT(!data->conn);
|
||||
}
|
||||
|
||||
@ -587,8 +587,9 @@ static CURLcode ftp_readresp(struct Curl_easy *data,
|
||||
}
|
||||
#endif
|
||||
|
||||
/* store the latest code for later retrieval */
|
||||
data->info.httpcode = code;
|
||||
/* store the latest code for later retrieval, except during shutdown */
|
||||
if(!data->conn->proto.ftpc.shutdown)
|
||||
data->info.httpcode = code;
|
||||
|
||||
if(ftpcode)
|
||||
*ftpcode = code;
|
||||
@ -3131,6 +3132,8 @@ static CURLcode ftp_block_statemach(struct Curl_easy *data,
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
while(ftpc->state != FTP_STOP) {
|
||||
if(ftpc->shutdown)
|
||||
CURL_TRC_FTP(data, "in shutdown, waiting for server response");
|
||||
result = Curl_pp_statemach(data, pp, TRUE, TRUE /* disconnecting */);
|
||||
if(result)
|
||||
break;
|
||||
@ -4042,6 +4045,7 @@ static CURLcode ftp_quit(struct Curl_easy *data, struct connectdata *conn)
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
if(conn->proto.ftpc.ctl_valid) {
|
||||
CURL_TRC_FTP(data, "sending QUIT to close session");
|
||||
result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", "QUIT");
|
||||
if(result) {
|
||||
failf(data, "Failure sending QUIT command: %s",
|
||||
@ -4081,6 +4085,7 @@ static CURLcode ftp_disconnect(struct Curl_easy *data,
|
||||
ftp_quit() will check the state of ftp->ctl_valid. If it is ok it
|
||||
will try to send the QUIT command, otherwise it will just return.
|
||||
*/
|
||||
ftpc->shutdown = TRUE;
|
||||
if(dead_connection)
|
||||
ftpc->ctl_valid = FALSE;
|
||||
|
||||
|
||||
@ -160,6 +160,7 @@ struct ftp_conn {
|
||||
BIT(cwdfail); /* set TRUE if a CWD command fails, as then we must prevent
|
||||
caching the current directory */
|
||||
BIT(wait_data_conn); /* this is set TRUE if data connection is waited */
|
||||
BIT(shutdown); /* connection is being shutdown, e.g. QUIT */
|
||||
};
|
||||
|
||||
#define DEFAULT_ACCEPT_TIMEOUT 60000 /* milliseconds == one minute */
|
||||
|
||||
@ -1450,7 +1450,7 @@ CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_done)
|
||||
|
||||
if(result) {
|
||||
Curl_detach_connection(data);
|
||||
Curl_cpool_disconnect(data, conn, TRUE);
|
||||
Curl_conn_terminate(data, conn, TRUE);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
62
lib/multi.c
62
lib/multi.c
@ -227,8 +227,23 @@ struct Curl_multi *Curl_multi_handle(size_t ev_hashsize, /* event hash */
|
||||
Curl_hash_init(&multi->proto_hash, 23,
|
||||
Curl_hash_str, Curl_str_key_compare, ph_freeentry);
|
||||
|
||||
multi->admin = curl_easy_init();
|
||||
if(!multi->admin)
|
||||
goto error;
|
||||
/* Initialize admin handle to operate inside this multi */
|
||||
multi->admin->multi = multi;
|
||||
multi->admin->state.internal = TRUE;
|
||||
Curl_llist_init(&multi->admin->state.timeoutlist, NULL);
|
||||
#ifdef DEBUGBUILD
|
||||
if(getenv("CURL_DEBUG"))
|
||||
multi->admin->set.verbose = TRUE;
|
||||
#endif
|
||||
|
||||
if(Curl_cshutdn_init(&multi->cshutdn, multi))
|
||||
goto error;
|
||||
|
||||
if(Curl_cpool_init(&multi->cpool, Curl_on_disconnect,
|
||||
multi, NULL, chashsize))
|
||||
multi->admin, NULL, chashsize))
|
||||
goto error;
|
||||
|
||||
if(Curl_ssl_scache_create(sesssize, 2, &multi->ssl_scache))
|
||||
@ -264,7 +279,13 @@ error:
|
||||
Curl_hash_destroy(&multi->proto_hash);
|
||||
Curl_hash_destroy(&multi->hostcache);
|
||||
Curl_cpool_destroy(&multi->cpool);
|
||||
Curl_cshutdn_destroy(&multi->cshutdn, multi->admin);
|
||||
Curl_ssl_scache_destroy(multi->ssl_scache);
|
||||
if(multi->admin) {
|
||||
multi->admin->multi = NULL;
|
||||
Curl_close(&multi->admin);
|
||||
}
|
||||
|
||||
free(multi);
|
||||
return NULL;
|
||||
}
|
||||
@ -396,6 +417,15 @@ CURLMcode curl_multi_add_handle(CURLM *m, CURL *d)
|
||||
Curl_cpool_xfer_init(data);
|
||||
multi_warn_debug(multi, data);
|
||||
|
||||
/* The admin handle only ever has default timeouts set. To improve the
|
||||
state somewhat we clone the timeouts from each added handle so that the
|
||||
admin handle always has the same timeouts as the most recently added
|
||||
easy handle. */
|
||||
multi->admin->set.timeout = data->set.timeout;
|
||||
multi->admin->set.server_response_timeout =
|
||||
data->set.server_response_timeout;
|
||||
multi->admin->set.no_signal = data->set.no_signal;
|
||||
|
||||
CURL_TRC_M(data, "added, transfers=%u", multi->num_easy);
|
||||
return CURLM_OK;
|
||||
}
|
||||
@ -475,7 +505,7 @@ static void multi_done_locked(struct connectdata *conn,
|
||||
conn->bits.close, mdctx->premature,
|
||||
Curl_conn_is_multiplex(conn, FIRSTSOCKET));
|
||||
connclose(conn, "disconnecting");
|
||||
Curl_cpool_disconnect(data, conn, mdctx->premature);
|
||||
Curl_conn_terminate(data, conn, mdctx->premature);
|
||||
}
|
||||
else {
|
||||
/* the connection is no longer in use by any transfer */
|
||||
@ -684,7 +714,7 @@ CURLMcode curl_multi_remove_handle(CURLM *m, CURL *d)
|
||||
curl_socket_t s;
|
||||
s = Curl_getconnectinfo(data, &c);
|
||||
if((s != CURL_SOCKET_BAD) && c) {
|
||||
Curl_cpool_disconnect(data, c, TRUE);
|
||||
Curl_conn_terminate(data, c, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1032,7 +1062,8 @@ CURLMcode curl_multi_fdset(CURLM *m,
|
||||
}
|
||||
}
|
||||
|
||||
Curl_cpool_setfds(&multi->cpool, read_fd_set, write_fd_set, &this_max_fd);
|
||||
Curl_cshutdn_setfds(&multi->cshutdn, multi->admin,
|
||||
read_fd_set, write_fd_set, &this_max_fd);
|
||||
|
||||
*max_fd = this_max_fd;
|
||||
|
||||
@ -1068,7 +1099,7 @@ CURLMcode curl_multi_waitfds(CURLM *m,
|
||||
need += Curl_waitfds_add_ps(&cwfds, &ps);
|
||||
}
|
||||
|
||||
need += Curl_cpool_add_waitfds(&multi->cpool, &cwfds);
|
||||
need += Curl_cshutdn_add_waitfds(&multi->cshutdn, multi->admin, &cwfds);
|
||||
|
||||
if(need != cwfds.n && ufds) {
|
||||
result = CURLM_OUT_OF_MEMORY;
|
||||
@ -1146,7 +1177,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi,
|
||||
}
|
||||
}
|
||||
|
||||
if(Curl_cpool_add_pollfds(&multi->cpool, &cpfds)) {
|
||||
if(Curl_cshutdn_add_pollfds(&multi->cshutdn, multi->admin, &cpfds)) {
|
||||
result = CURLM_OUT_OF_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
@ -2492,7 +2523,7 @@ statemachine_end:
|
||||
We do not have to do this in every case block above where a
|
||||
failure is detected */
|
||||
Curl_detach_connection(data);
|
||||
Curl_cpool_disconnect(data, conn, dead_connection);
|
||||
Curl_conn_terminate(data, conn, dead_connection);
|
||||
}
|
||||
}
|
||||
else if(data->mstate == MSTATE_CONNECT) {
|
||||
@ -2581,7 +2612,7 @@ CURLMcode curl_multi_perform(CURLM *m, int *running_handles)
|
||||
pointer now */
|
||||
n = Curl_node_next(e);
|
||||
|
||||
if(data && data != multi->cpool.idata) {
|
||||
if(data && data != multi->admin) {
|
||||
/* connection pool handle is processed below */
|
||||
sigpipe_apply(data, &pipe_st);
|
||||
result = multi_runsingle(multi, &now, data);
|
||||
@ -2590,8 +2621,8 @@ CURLMcode curl_multi_perform(CURLM *m, int *running_handles)
|
||||
}
|
||||
}
|
||||
|
||||
sigpipe_apply(multi->cpool.idata, &pipe_st);
|
||||
Curl_cpool_multi_perform(multi, CURL_SOCKET_TIMEOUT);
|
||||
sigpipe_apply(multi->admin, &pipe_st);
|
||||
Curl_cshutdn_perform(&multi->cshutdn, multi->admin, CURL_SOCKET_TIMEOUT);
|
||||
sigpipe_restore(&pipe_st);
|
||||
|
||||
if(multi_ischanged(m, TRUE))
|
||||
@ -2690,6 +2721,11 @@ CURLMcode curl_multi_cleanup(CURLM *m)
|
||||
}
|
||||
|
||||
Curl_cpool_destroy(&multi->cpool);
|
||||
Curl_cshutdn_destroy(&multi->cshutdn, multi->admin);
|
||||
if(multi->admin) {
|
||||
multi->admin->multi = NULL;
|
||||
Curl_close(&multi->admin);
|
||||
}
|
||||
|
||||
multi->magic = 0; /* not good anymore */
|
||||
|
||||
@ -2855,7 +2891,7 @@ static CURLMcode multi_run_expired(struct multi_run_ctx *mrc)
|
||||
continue;
|
||||
|
||||
(void)add_next_timeout(mrc->now, multi, data);
|
||||
if(data == multi->cpool.idata) {
|
||||
if(data == multi->admin) {
|
||||
mrc->run_cpool = TRUE;
|
||||
continue;
|
||||
}
|
||||
@ -2931,8 +2967,8 @@ static CURLMcode multi_socket(struct Curl_multi *multi,
|
||||
|
||||
out:
|
||||
if(mrc.run_cpool) {
|
||||
sigpipe_apply(multi->cpool.idata, &mrc.pipe_st);
|
||||
Curl_cpool_multi_perform(multi, s);
|
||||
sigpipe_apply(multi->admin, &mrc.pipe_st);
|
||||
Curl_cshutdn_perform(&multi->cshutdn, multi->admin, s);
|
||||
}
|
||||
sigpipe_restore(&mrc.pipe_st);
|
||||
|
||||
|
||||
@ -27,11 +27,13 @@
|
||||
#include "llist.h"
|
||||
#include "hash.h"
|
||||
#include "conncache.h"
|
||||
#include "cshutdn.h"
|
||||
#include "multi_ev.h"
|
||||
#include "psl.h"
|
||||
#include "socketpair.h"
|
||||
|
||||
struct connectdata;
|
||||
struct Curl_easy;
|
||||
|
||||
struct Curl_message {
|
||||
struct Curl_llist_node list;
|
||||
@ -99,6 +101,8 @@ struct Curl_multi {
|
||||
struct Curl_llist msgsent; /* in MSGSENT */
|
||||
curl_off_t next_easy_mid; /* next multi-id for easy handle added */
|
||||
|
||||
struct Curl_easy *admin; /* internal easy handle for admin operations */
|
||||
|
||||
/* callback function and user data pointer for the *socket() API */
|
||||
curl_socket_callback socket_cb;
|
||||
void *socket_userp;
|
||||
@ -140,8 +144,8 @@ struct Curl_multi {
|
||||
* the multi handle is cleaned up (see Curl_hash_add2()).*/
|
||||
struct Curl_hash proto_hash;
|
||||
|
||||
/* Shared connection cache (bundles)*/
|
||||
struct cpool cpool;
|
||||
struct cshutdn cshutdn; /* connection shutdown handling */
|
||||
struct cpool cpool; /* connection pool (bundles) */
|
||||
|
||||
long max_host_connections; /* if >0, a fixed limit of the maximum number
|
||||
of connections per host */
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
|
||||
#include "urldata.h"
|
||||
#include "cfilters.h"
|
||||
#include "connect.h"
|
||||
#include "sendf.h"
|
||||
#include "select.h"
|
||||
#include "progress.h"
|
||||
@ -74,6 +75,11 @@ timediff_t Curl_pp_state_timeout(struct Curl_easy *data,
|
||||
timeout_ms = CURLMIN(timeout_ms, timeout2_ms);
|
||||
}
|
||||
|
||||
if(disconnecting) {
|
||||
timediff_t total_left_ms = Curl_timeleft(data, NULL, FALSE);
|
||||
timeout_ms = CURLMIN(timeout_ms, CURLMAX(total_left_ms, 0));
|
||||
}
|
||||
|
||||
return timeout_ms;
|
||||
}
|
||||
|
||||
@ -96,6 +102,7 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data,
|
||||
return CURLE_OPERATION_TIMEDOUT; /* already too little time */
|
||||
}
|
||||
|
||||
DEBUGF(infof(data, "pp_statematch, timeout=%" FMT_TIMEDIFF_T, timeout_ms));
|
||||
if(block) {
|
||||
interval_ms = 1000; /* use 1 second timeout intervals */
|
||||
if(timeout_ms < interval_ms)
|
||||
@ -135,6 +142,8 @@ CURLcode Curl_pp_statemach(struct Curl_easy *data,
|
||||
}
|
||||
else if(rc)
|
||||
result = pp->statemachine(data, data->conn);
|
||||
else if(disconnecting)
|
||||
return CURLE_OPERATION_TIMEDOUT;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
15
lib/share.c
15
lib/share.c
@ -47,6 +47,16 @@ curl_share_init(void)
|
||||
share->magic = CURL_GOOD_SHARE;
|
||||
share->specifier |= (1 << CURL_LOCK_DATA_SHARE);
|
||||
Curl_init_dnscache(&share->hostcache, 23);
|
||||
share->admin = curl_easy_init();
|
||||
if(!share->admin) {
|
||||
free(share);
|
||||
return NULL;
|
||||
}
|
||||
share->admin->state.internal = TRUE;
|
||||
#ifdef DEBUGBUILD
|
||||
if(getenv("CURL_DEBUG"))
|
||||
share->admin->set.verbose = TRUE;
|
||||
#endif
|
||||
}
|
||||
|
||||
return share;
|
||||
@ -125,9 +135,9 @@ curl_share_setopt(CURLSH *sh, CURLSHoption option, ...)
|
||||
|
||||
case CURL_LOCK_DATA_CONNECT:
|
||||
/* It is safe to set this option several times on a share. */
|
||||
if(!share->cpool.idata) {
|
||||
if(!share->cpool.initialised) {
|
||||
if(Curl_cpool_init(&share->cpool, Curl_on_disconnect,
|
||||
NULL, share, 103))
|
||||
share->admin, share, 103))
|
||||
res = CURLSHE_NOMEM;
|
||||
}
|
||||
break;
|
||||
@ -257,6 +267,7 @@ curl_share_cleanup(CURLSH *sh)
|
||||
#endif
|
||||
|
||||
Curl_psl_destroy(&share->psl);
|
||||
Curl_close(&share->admin);
|
||||
|
||||
if(share->unlockfunc)
|
||||
share->unlockfunc(NULL, CURL_LOCK_DATA_SHARE, share->clientdata);
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
#include "urldata.h"
|
||||
#include "conncache.h"
|
||||
|
||||
struct Curl_easy;
|
||||
struct Curl_ssl_scache;
|
||||
|
||||
#define CURL_GOOD_SHARE 0x7e117a1e
|
||||
@ -48,6 +49,7 @@ struct Curl_share {
|
||||
curl_lock_function lockfunc;
|
||||
curl_unlock_function unlockfunc;
|
||||
void *clientdata;
|
||||
struct Curl_easy *admin;
|
||||
struct cpool cpool;
|
||||
struct Curl_hash hostcache;
|
||||
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)
|
||||
|
||||
18
lib/url.c
18
lib/url.c
@ -1209,7 +1209,7 @@ static bool url_match_conn(struct connectdata *conn, void *userdata)
|
||||
}
|
||||
else if(Curl_conn_seems_dead(conn, data, NULL)) {
|
||||
/* removed and disconnect. Do not treat as aborted. */
|
||||
Curl_cpool_disconnect(data, conn, FALSE);
|
||||
Curl_conn_terminate(data, conn, FALSE);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@ -3540,14 +3540,12 @@ static CURLcode create_conn(struct Curl_easy *data,
|
||||
/* Setup a "faked" transfer that will do nothing */
|
||||
if(!result) {
|
||||
Curl_attach_connection(data, conn);
|
||||
result = Curl_cpool_add_conn(data, conn);
|
||||
if(result)
|
||||
goto out;
|
||||
result = Curl_cpool_add(data, conn);
|
||||
if(!result) {
|
||||
/* Setup whatever necessary for a resumed transfer */
|
||||
result = setup_range(data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Setup whatever necessary for a resumed transfer
|
||||
*/
|
||||
result = setup_range(data);
|
||||
if(result) {
|
||||
DEBUGASSERT(conn->handler->done);
|
||||
/* we ignore the return code for the protocol-specific DONE */
|
||||
@ -3684,7 +3682,7 @@ static CURLcode create_conn(struct Curl_easy *data,
|
||||
}
|
||||
|
||||
Curl_attach_connection(data, conn);
|
||||
result = Curl_cpool_add_conn(data, conn);
|
||||
result = Curl_cpool_add(data, conn);
|
||||
if(result)
|
||||
goto out;
|
||||
}
|
||||
@ -3823,7 +3821,7 @@ CURLcode Curl_connect(struct Curl_easy *data,
|
||||
/* We are not allowed to return failure with memory left allocated in the
|
||||
connectdata struct, free those here */
|
||||
Curl_detach_connection(data);
|
||||
Curl_cpool_disconnect(data, conn, TRUE);
|
||||
Curl_conn_terminate(data, conn, TRUE);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@ -753,6 +753,7 @@ struct ldapconninfo;
|
||||
*/
|
||||
struct connectdata {
|
||||
struct Curl_llist_node cpool_node; /* conncache lists */
|
||||
struct Curl_llist_node cshutdn_node; /* cshutdn list */
|
||||
|
||||
curl_closesocket_callback fclosesocket; /* function closing the socket(s) */
|
||||
void *closesocket_client;
|
||||
@ -1133,6 +1134,7 @@ typedef enum {
|
||||
EXPIRE_QUIC,
|
||||
EXPIRE_FTP_ACCEPT,
|
||||
EXPIRE_ALPN_EYEBALLS,
|
||||
EXPIRE_SHUTDOWN,
|
||||
EXPIRE_LAST /* not an actual timer, used as a marker only */
|
||||
} expire_id;
|
||||
|
||||
|
||||
@ -1853,7 +1853,7 @@ CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data,
|
||||
if(cf->cft == &Curl_cft_ssl) {
|
||||
bool done;
|
||||
CURL_TRC_CF(data, cf, "shutdown and remove SSL, start");
|
||||
Curl_shutdown_start(data, sockindex, NULL);
|
||||
Curl_shutdown_start(data, sockindex, 0, NULL);
|
||||
result = vtls_shutdown_blocking(cf, data, send_shutdown, &done);
|
||||
Curl_shutdown_clear(data, sockindex);
|
||||
if(!result && !done) /* blocking failed? */
|
||||
|
||||
@ -2589,6 +2589,10 @@ static int cb_socket(CURL *easy, curl_socket_t s, int action,
|
||||
int events = 0;
|
||||
(void)easy;
|
||||
|
||||
#if DEBUG_UV
|
||||
fprintf(tool_stderr, "parallel_event: cb_socket, fd=%d, action=%x, p=%p\n",
|
||||
(int)s, action, socketp);
|
||||
#endif
|
||||
switch(action) {
|
||||
case CURL_POLL_IN:
|
||||
case CURL_POLL_OUT:
|
||||
@ -2678,12 +2682,26 @@ static CURLcode parallel_event(struct parastate *s)
|
||||
}
|
||||
}
|
||||
|
||||
result = s->result;
|
||||
|
||||
/* Make sure to return some kind of error if there was a multi problem */
|
||||
if(s->mcode) {
|
||||
result = (s->mcode == CURLM_OUT_OF_MEMORY) ? CURLE_OUT_OF_MEMORY :
|
||||
/* The other multi errors should never happen, so return
|
||||
something suitably generic */
|
||||
CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
}
|
||||
|
||||
/* We need to cleanup the multi here, since the uv context lives on the
|
||||
* stack and will be gone. multi_cleanup can triggere events! */
|
||||
curl_multi_cleanup(s->multi);
|
||||
|
||||
#if DEBUG_UV
|
||||
fprintf(tool_stderr, "DONE parallel_event -> %d, mcode=%d, %d running, "
|
||||
"%d more\n",
|
||||
s->result, s->mcode, uv.s->still_running, s->more_transfers);
|
||||
result, s->mcode, uv.s->still_running, s->more_transfers);
|
||||
#endif
|
||||
return s->result;
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -2787,7 +2805,7 @@ static CURLcode parallel_transfers(struct GlobalConfig *global,
|
||||
#ifdef DEBUGBUILD
|
||||
if(global->test_event_based)
|
||||
#ifdef USE_LIBUV
|
||||
result = parallel_event(s);
|
||||
return parallel_event(s);
|
||||
#else
|
||||
errorf(global, "Testing --parallel event-based requires libuv");
|
||||
#endif
|
||||
@ -3228,7 +3246,9 @@ CURLcode operate(struct GlobalConfig *global, int argc, argv_item_t argv[])
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE,
|
||||
CURL_LOCK_DATA_SSL_SESSION);
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
|
||||
/* Running parallel, use the multi connection cache */
|
||||
if(!global->parallel)
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS);
|
||||
|
||||
|
||||
@ -67,8 +67,6 @@ run 1: foobar and so on fun!
|
||||
-> Mutex lock SHARE
|
||||
<- Mutex unlock SHARE
|
||||
-> Mutex lock SHARE
|
||||
-> Mutex lock CONNECT
|
||||
<- Mutex unlock CONNECT
|
||||
<- Mutex unlock SHARE
|
||||
</datacheck>
|
||||
</reply>
|
||||
|
||||
@ -105,7 +105,7 @@ class TestShutdown:
|
||||
r = curl.http_download(urls=[url], alpn_proto=proto)
|
||||
r.check_response(http_status=200, count=count)
|
||||
shutdowns = [line for line in r.trace_lines
|
||||
if re.match(r'.*\[CPOOL\] shutdown, done=1', line)]
|
||||
if re.match(r'.*\[SHUTDOWN\] shutdown, done=1', line)]
|
||||
assert len(shutdowns) == count, f'{shutdowns}'
|
||||
|
||||
# run downloads with CURLOPT_FORBID_REUSE set, meaning *we* close
|
||||
@ -128,7 +128,7 @@ class TestShutdown:
|
||||
])
|
||||
r.check_exit_code(0)
|
||||
shutdowns = [line for line in r.trace_lines
|
||||
if re.match(r'.*CPOOL\] shutdown, done=1', line)]
|
||||
if re.match(r'.*SHUTDOWN\] shutdown, done=1', line)]
|
||||
assert len(shutdowns) == count, f'{shutdowns}'
|
||||
|
||||
# run event-based downloads with CURLOPT_FORBID_REUSE set, meaning *we* close
|
||||
@ -153,7 +153,7 @@ class TestShutdown:
|
||||
r.check_response(http_status=200, count=count)
|
||||
# check that we closed all connections
|
||||
closings = [line for line in r.trace_lines
|
||||
if re.match(r'.*CPOOL\] closing', line)]
|
||||
if re.match(r'.*SHUTDOWN\] closing', line)]
|
||||
assert len(closings) == count, f'{closings}'
|
||||
# check that all connection sockets were removed from event
|
||||
removes = [line for line in r.trace_lines
|
||||
@ -178,5 +178,5 @@ class TestShutdown:
|
||||
r.check_response(http_status=200, count=2)
|
||||
# check connection cache closings
|
||||
shutdowns = [line for line in r.trace_lines
|
||||
if re.match(r'.*CPOOL\] shutdown, done=1', line)]
|
||||
if re.match(r'.*SHUTDOWN\] shutdown, done=1', line)]
|
||||
assert len(shutdowns) == 1, f'{shutdowns}'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user