mirror of
https://github.com/curl/curl.git
synced 2026-07-21 13:47:16 +03:00
- Adjust pollset during connection shutdown. - Separate the FIRSTSOCKET and SECONDSOCKET check so that one being in connect or shutdown no longer adds poll events for the other. - Fix shutdown state evaluation (as detailed below). - Add a unit test for Curl_conn_adjust_pollset. - Add a client for event testing. - Test that shutdown sockets stay with the socket callback until done. The pollset predicate now reads the connection's own shutdown state rather than going through data->conn, and the EXPIRE_SHUTDOWN arming in cshutdn_perform() works again: next_expire_ms started at 0 and could never be set, so a peer that never sends a close_notify would still park its connection forever, timeout or not. Reported-by: pszemus@users.noreply.github.com Fixes https://github.com/curl/curl/issues/22282 Closes https://github.com/curl/curl/pull/22304
221 lines
6.9 KiB
C
221 lines
6.9 KiB
C
/***************************************************************************
|
|
* _ _ ____ _
|
|
* Project ___| | | | _ \| |
|
|
* / __| | | | |_) | |
|
|
* | (__| |_| | _ <| |___
|
|
* \___|\___/|_| \_\_____|
|
|
*
|
|
* 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 "unitcheck.h"
|
|
|
|
#include "urldata.h"
|
|
#include "cfilters.h"
|
|
#include "curl_trc.h"
|
|
#include "select.h"
|
|
|
|
/* Test the gating in Curl_conn_adjust_pollset(): each filter chain
|
|
* of a connection must only get to adjust the pollset when the transfer
|
|
* wants to send or receive, or when that chain itself is connecting or
|
|
* shutting down. One chain being in connect or shutdown must not add
|
|
* poll events for the other, see #21671. */
|
|
|
|
struct t2606_ctx {
|
|
int calls;
|
|
bool add_socket;
|
|
curl_socket_t sock;
|
|
};
|
|
|
|
static void t2606_cf_destroy(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
(void)data;
|
|
/* ctx is owned by the test case, nothing to free */
|
|
cf->ctx = NULL;
|
|
}
|
|
|
|
static CURLcode t2606_cf_connect(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
bool *done)
|
|
{
|
|
(void)cf;
|
|
(void)data;
|
|
*done = TRUE;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
static CURLcode t2606_cf_adjust_pollset(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
struct easy_pollset *ps)
|
|
{
|
|
struct t2606_ctx *ctx = cf->ctx;
|
|
ctx->calls++;
|
|
if(ctx->add_socket)
|
|
return Curl_pollset_add_in(data, ps, ctx->sock);
|
|
return CURLE_OK;
|
|
}
|
|
|
|
static const struct Curl_cftype t2606_cft = {
|
|
"TEST-PS",
|
|
CF_TYPE_IP_CONNECT,
|
|
CURL_LOG_LVL_NONE,
|
|
t2606_cf_destroy,
|
|
t2606_cf_connect,
|
|
Curl_cf_def_shutdown,
|
|
t2606_cf_adjust_pollset,
|
|
Curl_cf_def_data_pending,
|
|
Curl_cf_def_send,
|
|
Curl_cf_def_recv,
|
|
Curl_cf_def_cntrl,
|
|
Curl_cf_def_conn_is_alive,
|
|
Curl_cf_def_conn_keep_alive,
|
|
Curl_cf_def_query,
|
|
};
|
|
|
|
struct t2606_case {
|
|
int id;
|
|
bool has_c1; /* a SECONDARYSOCKET chain exists */
|
|
bool c0_connected;
|
|
bool c1_connected;
|
|
bool c0_shutdown; /* shutdown started on the chain */
|
|
bool c1_shutdown;
|
|
bool xfer_io; /* the pollset holds a transfer socket already */
|
|
bool c0_adds; /* chain 0 adds a socket when called */
|
|
int exp_c0_calls;
|
|
int exp_c1_calls;
|
|
};
|
|
|
|
static CURLcode t2606_setup(CURL **easy)
|
|
{
|
|
CURLcode result = CURLE_OK;
|
|
|
|
global_init(CURL_GLOBAL_ALL);
|
|
*easy = curl_easy_init();
|
|
if(!*easy) {
|
|
curl_global_cleanup();
|
|
return CURLE_OUT_OF_MEMORY;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void t2606_stop(CURL *easy)
|
|
{
|
|
curl_easy_cleanup(easy);
|
|
curl_global_cleanup();
|
|
}
|
|
|
|
static void t2606_run(struct Curl_easy *data, const struct t2606_case *tc)
|
|
{
|
|
struct connectdata *conn;
|
|
struct Curl_cfilter *cf0 = NULL, *cf1 = NULL;
|
|
struct t2606_ctx ctx0, ctx1;
|
|
struct easy_pollset ps;
|
|
CURLcode result;
|
|
char msg[128];
|
|
|
|
conn = curlx_calloc(1, sizeof(*conn));
|
|
abort_if(!conn, "could not allocate a connection");
|
|
|
|
memset(&ctx0, 0, sizeof(ctx0));
|
|
memset(&ctx1, 0, sizeof(ctx1));
|
|
ctx0.sock = 42;
|
|
ctx0.add_socket = tc->c0_adds;
|
|
ctx1.sock = 43;
|
|
|
|
result = Curl_cf_create(&cf0, &t2606_cft, &ctx0);
|
|
fail_unless(!result, "creating filter chain 0 failed");
|
|
if(!result) {
|
|
Curl_conn_cf_add(data, conn, FIRSTSOCKET, cf0);
|
|
cf0->connected = tc->c0_connected;
|
|
if(tc->c0_shutdown)
|
|
conn->shutdown.start[FIRSTSOCKET] = curlx_now();
|
|
}
|
|
|
|
if(tc->has_c1) {
|
|
result = Curl_cf_create(&cf1, &t2606_cft, &ctx1);
|
|
fail_unless(!result, "creating filter chain 1 failed");
|
|
if(!result) {
|
|
Curl_conn_cf_add(data, conn, SECONDARYSOCKET, cf1);
|
|
cf1->connected = tc->c1_connected;
|
|
if(tc->c1_shutdown)
|
|
conn->shutdown.start[SECONDARYSOCKET] = curlx_now();
|
|
}
|
|
}
|
|
|
|
Curl_pollset_init(&ps);
|
|
if(tc->xfer_io) {
|
|
result = Curl_pollset_add_in(data, &ps, 7);
|
|
fail_unless(!result, "preloading the pollset failed");
|
|
}
|
|
|
|
result = Curl_conn_adjust_pollset(data, conn, &ps);
|
|
|
|
curl_msprintf(msg, "case %d: adjust_pollset failed", tc->id);
|
|
fail_unless(!result, msg);
|
|
curl_msprintf(msg, "case %d: chain 0 called %d times, expected %d",
|
|
tc->id, ctx0.calls, tc->exp_c0_calls);
|
|
fail_unless(ctx0.calls == tc->exp_c0_calls, msg);
|
|
curl_msprintf(msg, "case %d: chain 1 called %d times, expected %d",
|
|
tc->id, ctx1.calls, tc->exp_c1_calls);
|
|
fail_unless(ctx1.calls == tc->exp_c1_calls, msg);
|
|
if(tc->c0_adds && tc->exp_c0_calls) {
|
|
curl_msprintf(msg, "case %d: chain 0 did not add its socket", tc->id);
|
|
fail_unless(ps.n > 0, msg);
|
|
}
|
|
|
|
Curl_pollset_cleanup(&ps);
|
|
Curl_conn_cf_discard_all(data, conn, SECONDARYSOCKET);
|
|
Curl_conn_cf_discard_all(data, conn, FIRSTSOCKET);
|
|
unit_test_abort:
|
|
curlx_free(conn);
|
|
}
|
|
|
|
static CURLcode test_unit2606(const char *arg)
|
|
{
|
|
CURL *easy;
|
|
|
|
UNITTEST_BEGIN(t2606_setup(&easy))
|
|
|
|
static const struct t2606_case TEST_CASES[] = {
|
|
/* both chains connected and quiet, transfer idle: nothing runs */
|
|
{ 1, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, 0, 0 },
|
|
/* transfer wants IO: both chains run */
|
|
{ 2, TRUE, TRUE, TRUE, FALSE, FALSE, TRUE, FALSE, 1, 1 },
|
|
/* chain 0 connecting, chain 1 connected and quiet: only chain 0 */
|
|
{ 3, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, 1, 0 },
|
|
/* chain 1 connecting, chain 0 connected and quiet: only chain 1 */
|
|
{ 4, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, 0, 1 },
|
|
/* chain 0 shutting down, chain 1 connected and quiet: only chain 0 */
|
|
{ 5, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, 1, 0 },
|
|
/* chain 1 shutting down, chain 0 connected and quiet: only chain 1 */
|
|
{ 6, TRUE, TRUE, TRUE, FALSE, TRUE, FALSE, FALSE, 0, 1 },
|
|
/* single connected quiet chain, transfer idle: nothing runs */
|
|
{ 7, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, 0, 0 },
|
|
/* single chain shutting down: runs */
|
|
{ 8, FALSE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, 1, 0 },
|
|
/* a socket added by connecting chain 0 must not run quiet chain 1 */
|
|
{ 9, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, 1, 0 },
|
|
};
|
|
|
|
size_t i;
|
|
|
|
for(i = 0; i < CURL_ARRAYSIZE(TEST_CASES); ++i) {
|
|
t2606_run(easy, &TEST_CASES[i]);
|
|
}
|
|
|
|
UNITTEST_END(t2606_stop(easy))
|
|
}
|