Fix MySQL connect_timeout not expiring under query profiler signals

Bump mariadb-connector-c: pvio_socket_wait_io_or_timeout restarted poll
with the full original timeout after EINTR, resetting the deadline on
every signal. ClickHouse query threads receive sampling-profiler timer
signals (query_profiler_real_time_period_ns, default 1 s) without
SA_RESTART, so with a signal period not exceeding connect_timeout the
poll never expired: a connection attempt to a blackholed host (SYN
dropped, e.g. 192.0.2.1) hung until the kernel exhausted SYN
retransmissions (~130 s) regardless of connect_timeout. The connector
now re-polls with the remaining time and reports ETIMEDOUT when the
budget is exhausted. This also bounds the TLS handshake and read/write
waits that go through the same function, and cuts test
04210_show_remote_databases_in_system_tables from ~135 s to ~4 s.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Shaohua Wang
2026-07-06 11:09:30 +00:00
co-authored by Claude Fable 5
parent 24fa6dc366
commit ae45720d80
3 changed files with 39 additions and 1 deletions
@@ -0,0 +1 @@
attach_bounded_by_timeout 1
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# Tags: no-fasttest, no-parallel
# Tag justification:
# no-fasttest: depends on libmysql (MySQL database engine), not built in fast test.
# no-parallel: attaches a MySQL database pointing at an unreachable host; it is visible
# in system.tables, so a concurrent unfiltered scan would also try to connect to it.
CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=../shell_config.sh
. "$CUR_DIR"/../shell_config.sh
# `MYSQL_OPT_CONNECT_TIMEOUT` must bound the TCP connect even when the thread receives
# periodic signals: the sampling query profiler interrupts poll with EINTR, and the
# connector used to restart the poll with the full timeout, resetting the deadline every
# time. With a blackholed host (192.0.2.1, RFC 5737 TEST-NET-1: SYN packets are dropped,
# not refused) the connect then lasted until the kernel exhausted SYN retransmissions
# (~130 s) instead of connect_timeout. The profiler period is set well below
# connect_timeout so the poll is guaranteed to be interrupted several times.
MYSQL_DB="${CLICKHOUSE_DATABASE}_mysql"
ATTACH_QUERY_ID="${CLICKHOUSE_DATABASE}_mysql_timeout_attach_${RANDOM}${RANDOM}"
CLICKHOUSE_CLIENT_QUIET=$(echo "${CLICKHOUSE_CLIENT}" | sed "s/--send_logs_level=${CLICKHOUSE_CLIENT_SERVER_LOGS_LEVEL}/--send_logs_level=fatal/g")
${CLICKHOUSE_CLIENT} -q "DROP DATABASE IF EXISTS ${MYSQL_DB}"
${CLICKHOUSE_CLIENT_QUIET} --query_id "${ATTACH_QUERY_ID}" \
--query_profiler_real_time_period_ns 100000000 \
-q "ATTACH DATABASE ${MYSQL_DB} ENGINE = MySQL('192.0.2.1:3306', 'fake_db', 'user', 'password') SETTINGS connect_timeout = 1, connection_max_tries = 1"
${CLICKHOUSE_CLIENT} -q "
SYSTEM FLUSH LOGS query_log;
SELECT 'attach_bounded_by_timeout', query_duration_ms < 60000 FROM system.query_log
WHERE current_database = currentDatabase() AND query_id = '${ATTACH_QUERY_ID}' AND type = 'QueryFinish';
"
${CLICKHOUSE_CLIENT_QUIET} -q "DROP DATABASE ${MYSQL_DB}"