Uploaded image for project: 'MariaDB Connector/C'
  1. MariaDB Connector/C
  2. CONC-834

`MYSQL_OPT_CONNECT_TIMEOUT` (and read/write waits) never expire when the thread receives periodic signals: EINTR restarts `poll` with the full timeout

    XMLWordPrintable

Details

    • Bug
    • Status: Closed (View Workflow)
    • Major
    • Resolution: Fixed
    • 3.3.19, 3.4.9
    • 3.3.20, 3.4.10
    • Internal
    • 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

      { rc= poll(&p_fd, 1, timeout); }

      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 =

      {0}

      ;
      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)

      { rc= 0; break; }

      }
      }
      }

      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.

      Attachments

        Activity

          People

            georg Georg Richter
            tiandiwonder Shaohua Wang
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Git Integration

                Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.