mirror of
https://github.com/curl/curl.git
synced 2026-04-14 23:51:42 +03:00
shutdowns: split shutdown handling from connection pool
Further testing with timeouts in event based processing revealed that our current shutdown handling in the connection pool was not clear enough. Graceful shutdowns can only happen inside a multi handle and it was confusing to track in the code which situation actually applies. It seems better to split the shutdown handling off and have that code always be part of a multi handle. Add `cshutdn.[ch]` with its own struct to maintain connections being shut down. A `cshutdn` always belongs to a multi handle and uses that for socket/timeout monitoring. The `cpool`, which can be part of a multi or share, either passes connections to a `cshutdn` or terminates them with a one-time, best effort. Add an `admin` easy handle to each multi and share. This is used to perform all maintenance operations where no "real" easy handle is available. This solves the problem that the multi admin handle requires some additional initialisation (e.g. timeout list). The share needs its admin handle as it is often cleaned up when no other transfer or multi handle exists any more. But we need a `data` in almost every call. Fix file:// handling of errors when adding a new connection to the pool. Changes in `curl` itself: - for parallel transfers, do not set a connection pool in the share, rely on the multi's connection pool instead. While not a requirement for the new `cshutdn` to work, this is a) helpful in testing to trigger graceful shutdowns b) a broader code coverage of libcurl via the curl tool - on test_event with uv, cleanup the multi handle before returning from parallel_event(). The uv struct is on the stack, cleanup of the multi later will crash when it tries to register sockets. This is a "eat your own dogfood" related fix. Closes #16508
This commit is contained in:
parent
3afa47b627
commit
df672695e5
23 changed files with 1144 additions and 955 deletions
|
|
@ -123,6 +123,7 @@ LIB_CFILES = \
|
|||
cf-socket.c \
|
||||
cfilters.c \
|
||||
conncache.c \
|
||||
cshutdn.c \
|
||||
connect.c \
|
||||
content_encoding.c \
|
||||
cookie.c \
|
||||
|
|
@ -260,6 +261,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;
|
||||
|
|
|
|||
|
|
@ -755,6 +755,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;
|
||||
|
|
@ -1135,6 +1136,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…
Add table
Add a link
Reference in a new issue