From 1430f50caaf2a71ec0f490a488aec5a5329eb936 Mon Sep 17 00:00:00 2001 From: Rahim Kanji Date: Wed, 13 May 2026 14:37:48 +0500 Subject: [PATCH 1/2] prevent poll_timeout corruption from stale pause_until tune_timeout_for_session_needs_pause computed (pause_until - curtime) as unsigned subtraction without checking if pause_until was still in the future. When pause_until <= curtime (stale), the result underflowed to ~1.8e19, which was then assigned to a signed int poll_timeout, becoming a large negative integer. The downstream ttw calculation at PgSQL_Thread.cpp:3210 compares this against (unsigned int)pgsql_thread___poll_timeout. Signed-to-unsigned promotion turned the negative value into a huge unsigned, the comparison took the wrong branch, and ttw fell back to the default poll_timeout (2000 ms). The worker thread then blocked in poll() for up to 2 seconds, freezing all sessions on that thread. --- lib/Base_Thread.cpp | 21 +++++++++++++++++---- lib/MySQL_Session.cpp | 3 ++- lib/PgSQL_Session.cpp | 2 ++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/Base_Thread.cpp b/lib/Base_Thread.cpp index 263ac1004f..ee7102b984 100644 --- a/lib/Base_Thread.cpp +++ b/lib/Base_Thread.cpp @@ -347,10 +347,23 @@ void Base_Thread::tune_timeout_for_myds_needs_pause(DS * myds) { template void Base_Thread::tune_timeout_for_session_needs_pause(DS * myds) { T* thr = static_cast(this); - if (thr->mypolls.poll_timeout==0 || (myds->sess->pause_until - curtime < thr->mypolls.poll_timeout) ) { - thr->mypolls.poll_timeout= myds->sess->pause_until - curtime; - proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 7, "Session=%p , poll_timeout=%u , pause_until=%llu , curtime=%llu\n", myds->sess, thr->mypolls.poll_timeout, myds->sess->pause_until, curtime); - } + + if (myds->sess->pause_until > curtime) { + // Future pause: align poll_timeout to the pause expiration. + if (thr->mypolls.poll_timeout == 0 || (myds->sess->pause_until - curtime < thr->mypolls.poll_timeout)) { + thr->mypolls.poll_timeout = myds->sess->pause_until - curtime; + proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 7, "Session=%p , poll_timeout=%u , pause_until=%llu , curtime=%llu\n", myds->sess, thr->mypolls.poll_timeout, + myds->sess->pause_until, curtime); + } + } + /* Do we need to immediately wake up poll() because of an already expired pause? + else { + // pause_until > 0 (caller checked) but <= curtime: pause has already expired. + // Wake poll() immediately rather than computing (pause_until - curtime) + if (thr->mypolls.poll_timeout == 0 || thr->mypolls.poll_timeout > 1) { + thr->mypolls.poll_timeout = 1; + } + }*/ } template diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 957c1c25a0..5f49f65747 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -2997,6 +2997,7 @@ bool MySQL_Session::handler_again___status_CONNECTING_SERVER(int *_rc) { st=previous_status.top(); previous_status.pop(); + pause_until = 0; NEXT_IMMEDIATE_NEW(st); } assert(st==status); @@ -3023,7 +3024,7 @@ bool MySQL_Session::handler_again___status_CONNECTING_SERVER(int *_rc) { st=previous_status.top(); previous_status.pop(); myds->wait_until=0; - + pause_until = 0; if (handle_session_track_capabilities() == false) { previous_status.push(st); pause_until = thread->curtime + mysql_thread___connect_retries_delay * 1000; diff --git a/lib/PgSQL_Session.cpp b/lib/PgSQL_Session.cpp index c63ac7c432..04577360e6 100644 --- a/lib/PgSQL_Session.cpp +++ b/lib/PgSQL_Session.cpp @@ -1634,6 +1634,7 @@ bool PgSQL_Session::handler_again___status_CONNECTING_SERVER(int* _rc) { if (mybe->server_myds->myconn->async_state_machine == ASYNC_IDLE) { st = previous_status.top(); previous_status.pop(); + pause_until = 0; NEXT_IMMEDIATE_NEW(st); } assert(st == status); @@ -1671,6 +1672,7 @@ bool PgSQL_Session::handler_again___status_CONNECTING_SERVER(int* _rc) { st = previous_status.top(); previous_status.pop(); myds->wait_until = 0; + pause_until = 0; if (session_fast_forward) { // we have a successful connection and session_fast_forward enabled // set DSS=STATE_SLEEP or it will believe it have to use MARIADB client library From 9887933574655154e8bc639983504a63549e8d85 Mon Sep 17 00:00:00 2001 From: Rahim Kanji Date: Tue, 19 May 2026 12:31:51 +0500 Subject: [PATCH 2/2] Remove comment --- lib/Base_Thread.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/Base_Thread.cpp b/lib/Base_Thread.cpp index ee7102b984..efd814153a 100644 --- a/lib/Base_Thread.cpp +++ b/lib/Base_Thread.cpp @@ -348,22 +348,19 @@ template void Base_Thread::tune_timeout_for_session_needs_pause(DS * myds) { T* thr = static_cast(this); + // Only adjust poll_timeout if the pause is still in the future. If pause_until + // is stale (already <= curtime), computing (pause_until - curtime) as unsigned + // would underflow to ~1.8e19 and corrupt poll_timeout, making poll() fall back + // to the default timeout (~2s) and stalling the worker thread. The stale-pause + // case is handled by check_timing_out_session (AfterPoll) and the handler-entry + // pause checks, so doing nothing here is correct. if (myds->sess->pause_until > curtime) { - // Future pause: align poll_timeout to the pause expiration. if (thr->mypolls.poll_timeout == 0 || (myds->sess->pause_until - curtime < thr->mypolls.poll_timeout)) { thr->mypolls.poll_timeout = myds->sess->pause_until - curtime; - proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 7, "Session=%p , poll_timeout=%u , pause_until=%llu , curtime=%llu\n", myds->sess, thr->mypolls.poll_timeout, + proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 7, "Session=%p , poll_timeout=%u , pause_until=%llu , curtime=%llu\n", myds->sess, thr->mypolls.poll_timeout, myds->sess->pause_until, curtime); } - } - /* Do we need to immediately wake up poll() because of an already expired pause? - else { - // pause_until > 0 (caller checked) but <= curtime: pause has already expired. - // Wake poll() immediately rather than computing (pause_until - curtime) - if (thr->mypolls.poll_timeout == 0 || thr->mypolls.poll_timeout > 1) { - thr->mypolls.poll_timeout = 1; - } - }*/ + } } template