Details
-
Bug
-
Status: Closed (View Workflow)
-
Major
-
Resolution: Fixed
-
3.3.19, 3.4.9
-
None
-
Linux (any POSIX platform using the `poll` path in `pvio_socket.c`)
Description
`pvio_socket_wait_io_or_timeout()` in `plugins/pvio/pvio_socket.c` restarts `poll()` with the
full original timeout after `EINTR`:
```c
do
while (rc == -1 && errno == EINTR);
```
The deadline is therefore reset by every signal. If the application delivers periodic signals to the thread (a sampling profiler, `setitimer`, GC signals, etc.) with a period that does not exceed the timeout, the poll never expires. For a TCP connect to a host that drops SYN packets (instead of refusing the connection), `mysql_real_connect()` then blocks until the kernel exhausts SYN retransmissions — ~130 s with the default `tcp_syn_retries = 6` — regardless of `MYSQL_OPT_CONNECT_TIMEOUT`. The same loop serves the TLS handshake and the read/write wait paths, so those timeouts are affected identically.
We found this, whose query threads receive a sampling-profiler signal every second (installed without `SA_RESTART`): with MYSQL_OPT_CONNECT_TIMEOUT = 1` a connect to a blackholedaddress consistently took ~135 s instead of 1 s.
How to repeat
Self-contained reproducer (Linux; `192.0.2.1` is RFC 5737 TEST-NET-1, unroutable — SYNs are
dropped, not refused):
```c
#include <mysql.h>
#include <stdio.h>
#include <time.h>
#include <signal.h>
#include <sys/time.h>
static void on_tick(int sig)
{ (void)sig; }int main(void)
{
unsigned timeout = 1;
/* Periodic 100 ms signal, no SA_RESTART - e.g. a sampling profiler */
struct sigaction sa =
;
sa.sa_handler = on_tick;
sigaction(SIGALRM, &sa, NULL);
struct itimerval it = 0, 100000}, {0, 100000;
setitimer(ITIMER_REAL, &it, NULL);
MYSQL *m = mysql_init(NULL);
mysql_options(m, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
struct timespec t0, t1;
clock_gettime(CLOCK_MONOTONIC, &t0);
mysql_real_connect(m, "192.0.2.1", "user", "password", "db", 3306, NULL, 0);
clock_gettime(CLOCK_MONOTONIC, &t1);
printf("elapsed=%.2fs error=%s\n",
(t1.tv_sec - t0.tv_sec) + (t1.tv_nsec - t0.tv_nsec) / 1e9, mysql_error(m));
mysql_close(m);
return 0;
}
```
Suggested fix
Track the deadline and re-poll with the remaining time; report a timeout when the budget is exhausted. We are running the following patch in production (ClickHouse fork):
```c
if (!timeout)
timeout= -1;
/* Restarting poll() with the full timeout after EINTR would reset the deadline;
with a periodic signal (e.g. a sampling profiler) firing more often than the
timeout, the poll would then never expire. Re-poll with the remaining time. */
{
struct timespec start;
int remaining= timeout;
if (timeout > 0)
clock_gettime(CLOCK_MONOTONIC, &start);
for (;![]()
{
rc= poll(&p_fd, 1, remaining);
if (rc != -1 || errno != EINTR)
break;
if (timeout > 0)
{
struct timespec now;
long elapsed_ms;
clock_gettime(CLOCK_MONOTONIC, &now);
elapsed_ms= (now.tv_sec - start.tv_sec) * 1000 + (now.tv_nsec - start.tv_nsec) / 1000000;
remaining= timeout - (int)elapsed_ms;
if (remaining <= 0)
}
}
}
if (rc == 0)
errno= ETIMEDOUT;
```
The Windows `select()` branch is not affected (no EINTR restart loop there). The blocking-connect retry loop in `pvio_socket_internal_connect()` already bounds itself with a wall-clock check.
Related but distinct: CONC-322 discusses EAGAIN/EINPROGRESS classification in the same area; this report is specifically about the EINTR restart resetting the deadline.