Skip to content

Guard tune_timeout_for_session_needs_pause from stale pause_until - #5784

Open
rahim-kanji wants to merge 4 commits into
v3.0from
v3.0_fix-stale-pause-until
Open

Guard tune_timeout_for_session_needs_pause from stale pause_until#5784
rahim-kanji wants to merge 4 commits into
v3.0from
v3.0_fix-stale-pause-until

Conversation

@rahim-kanji

@rahim-kanji rahim-kanji commented May 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

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, which was then assigned to a signed int poll_timeout, becoming a large negative integer.

The downstream ttw calculation 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.

Summary by CodeRabbit

  • Bug Fixes
    • Clear stale session pause state while advancing connection-handshake steps, preventing unnecessary delays when establishing new connections.
    • Preserve existing shorter poll timeouts when applying a future pause, preventing expired or irrelevant pauses from corrupting scheduling.
    • Improve connection-pool attempt tracking during PostgreSQL connection acquisition for more accurate connection handling.

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 76a484ce-85b8-4889-a1bd-16915208e8ac

📥 Commits

Reviewing files that changed from the base of the PR and between ee1793d and 8caa4cc.

📒 Files selected for processing (3)
  • lib/Base_Thread.cpp
  • lib/MySQL_Session.cpp
  • lib/PgSQL_Session.cpp

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

📜 Recent review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: run / trigger
  • GitHub Check: build
  • GitHub Check: lint
  • GitHub Check: lint
🧰 Additional context used
📓 Path-based instructions (1)
Class names must use `PascalCase` with protocol prefixes such as `MySQL_`, `PgSQL_`, and `ProxySQL_`.

📄 CodeRabbit inference engine (CLAUDE.md)

Files:

  • lib/Base_Thread.cpp
  • lib/PgSQL_Session.cpp
  • lib/MySQL_Session.cpp
🪛 Cppcheck (2.21.0)
lib/Base_Thread.cpp

[warning] 46-46: If memory allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfMemory)

🔇 Additional comments (3)
lib/Base_Thread.cpp (1)

14-15: LGTM!

Also applies to: 37-58, 256-339, 428-439

lib/MySQL_Session.cpp (1)

3016-3016: LGTM!

Also applies to: 3043-3043, 7861-7861

lib/PgSQL_Session.cpp (1)

1637-1637: LGTM!

Also applies to: 1675-1675, 5425-5425


📝 Walkthrough

Walkthrough

Three files refine pause scheduling. Base_Thread.cpp guards timeout calculations against expired pauses. MySQL and PgSQL connection handlers clear pause_until. The PgSQL pool-fetch path records failed pool attempts.

Changes

Pause handling across polling and session connections

Layer / File(s) Summary
Poll timeout calculation guard
lib/Base_Thread.cpp
Base_Thread::tune_timeout_for_session_needs_pause computes a timeout only when pause_until > curtime. It updates poll_timeout only when it is unset or larger than the new timeout.
Connection transition state updates
lib/MySQL_Session.cpp, lib/PgSQL_Session.cpp
The session handlers clear pause_until after status restoration and after successful connection setup. The PgSQL pool-fetch path records whether the pool attempt failed.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 8caa4

This change prevents expired connection-retry pauses from delaying worker polling and clears stale pause state after successful backend connections for MySQL and PostgreSQL. No current merge-blocking risk remains.

Poem

A rabbit reads each line,
The patch grows clear beneath the moon,
Small changes hop in place,
Tests guard the garden path,
Reviews bloom before the dawn.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: preventing stale pause_until values from affecting tune_timeout_for_session_needs_pause.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch v3.0_fix-stale-pause-until

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces resets for pause_until during server connection status changes in MySQL and PostgreSQL sessions and refactors the timeout tuning logic in Base_Thread.cpp. Review feedback highlights that the commented-out logic for expired pauses should be enabled to prevent thread blocking. Additionally, the reviewer pointed out a unit mismatch where microsecond values were being assigned to a millisecond-based timeout and provided a code suggestion to correct the calculation and ensure proper polling behavior.

Comment thread lib/Base_Thread.cpp
Comment on lines +351 to 367
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 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;
}
}*/
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The logic for handling expired pauses should be enabled, and a unit mismatch in the timeout calculation needs to be addressed:

  1. Expired Pauses: The commented-out else block should be active. If pause_until <= curtime, the session is ready for processing. By not updating poll_timeout to a small value (like 1ms), the thread may block for the default timeout (2000ms), causing the freeze described in the PR summary.
  2. Unit Mismatch: pause_until - curtime is in microseconds, but poll_timeout is compared downstream against a millisecond value (2000 ms). If poll_timeout is set to microseconds, the comparison timeout < 2000 will fail for any pause longer than 2ms, causing the tuning to be ignored. The difference should be converted to milliseconds.
	if (myds->sess->pause_until > curtime) {
		// Future pause: align poll_timeout to the pause expiration.
		// Convert microseconds to milliseconds (ceiling division)
		unsigned int timeout_ms = (myds->sess->pause_until - curtime + 999) / 1000;
		if (thr->mypolls.poll_timeout == 0 || timeout_ms < (unsigned int)thr->mypolls.poll_timeout) {
			thr->mypolls.poll_timeout = timeout_ms;
			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);
		}
	} 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;
		}
	}

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.
@rahim-kanji
rahim-kanji force-pushed the v3.0_fix-stale-pause-until branch from 81f4798 to 1430f50 Compare May 13, 2026 10:02
@renecannao
renecannao marked this pull request as ready for review May 25, 2026 21:33
@sonarqubecloud

Copy link
Copy Markdown

@renecannao

Copy link
Copy Markdown
Contributor

Stale-pause analysis — the condition is reachable, but low severity

Reviewing whether pause_until <= curtime can actually reach tune_timeout_for_session_needs_pause against current v3.0.

Entry guard (lib/Base_Thread.cpp:611):

if (unlikely(myds->sess->pause_until > 0)) {
    tune_timeout_for_session_needs_pause<T>(myds);
}

So the question reduces to: can a session reach BeforePoll with pause_until > 0 and pause_until <= curtime?

Yes — there is a real lifecycle gap

In MySQL_Session::handler_again___status_CONNECTING_SERVER (lib/MySQL_Session.cpp):

// 2994: connect attempt fails → set a FUTURE pause and return
if (mybe->server_myds->myconn==NULL) {
    pause_until = thread->curtime + mysql_thread___connect_retries_delay*1000;  // future
    *_rc=1;
    return false;
}
...
// 3008: NEXT iteration — connection is now ASYNC_IDLE (succeeded)
if (mybe->server_myds->myconn->async_state_machine==ASYNC_IDLE) {
    st = previous_status.top(); previous_status.pop();
    NEXT_IMMEDIATE_NEW(st);   // == set_status(st); return true;   ← pause_until NOT cleared
}

NEXT_IMMEDIATE_NEW (lib/MySQL_Session.cpp:1814) is set_status(st); return true; — it returns to the caller without re-entering the state machine, so the pause_until set for the retry delay at 2994/3010 is never cleared on the success transition. The mirror path exists in PgSQL_Session.cpp:~1555-1638.

The usual "AfterPoll clears it via the handler" reasoning does not cover this case, because the handler's own terminal transition is precisely what leaves pause_until stale — which is exactly what the pause_until = 0 additions in this PR target.

But the impact is smaller than the PR description claims

The PR's "huge unsigned → poll() frozen" chain is not quite what happens, because mypolls.poll_timeout is declared int (signed) in include/MySQL_Thread.h:601:

  1. (pause_until - curtime) underflows to ~1.8e19 (unsigned long long).
  2. Assigned to int poll_timeout → implementation-defined (typically a large negative on common platforms).
  3. At run_ComputePollTimeout (lib/MySQL_Thread.cpp:3582):
    int ttw = (mypolls.poll_timeout
               ? (mypolls.poll_timeout/1000 < (unsigned int) mysql_thread___poll_timeout
                  ? mypolls.poll_timeout/1000 : mysql_thread___poll_timeout)
               : mysql_thread___poll_timeout);
    The signed poll_timeout/1000 is promoted to unsigned for the comparison → becomes huge → the comparison takes the wrong branch → ttw falls back to the default mysql_thread___poll_timeout (~2000ms).

Real-world effect: a backend connect-retry that succeeds at the retry-delay boundary leaves a stale pause_until, causing the next poll() on that worker to block for up to ~2s instead of waking immediately for the ready session. A transient latency hiccup on one worker thread — recoverable, not a hard hang that "freezes all sessions."

Verdict

  • The bug is real and reachable (concrete path above), so the guard + pause_until = 0 resets are legitimate hardening.
  • But I'd downgrade the severity from the PR's framing: it is a transient up-to-2s stall, not an indefinite freeze.
  • Keeping this PR open as low-priority hardening. No rebase performed in this pass.

@renecannao renecannao mentioned this pull request Jul 8, 2026
@gitar-bot

gitar-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

@sonarqubecloud

sonarqubecloud Bot commented Sep 3, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
E Maintainability Rating on New Code (required ≥ A)
D Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 27.05%. Comparing base (7ddb3dc) to head (8caa4cc).
⚠️ Report is 1398 commits behind head on v3.0.

Files with missing lines Patch % Lines
lib/Base_Thread.cpp 0.00% 4 Missing ⚠️
lib/MySQL_Session.cpp 0.00% 2 Missing ⚠️
lib/PgSQL_Session.cpp 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             v3.0    #5784      +/-   ##
==========================================
- Coverage   31.51%   27.05%   -4.46%     
==========================================
  Files         435      159     -276     
  Lines      142709    82755   -59954     
  Branches    12374    22530   +10156     
==========================================
- Hits        44968    22387   -22581     
+ Misses      89287    54067   -35220     
+ Partials     8454     6301    -2153     
Flag Coverage Δ
simulation-tests 27.03% <0.00%> (?)
tap-legacy-clickhouse-g1 ?
tap-legacy-g1 ?
tap-legacy-g2 ?
tap-legacy-g3 ?
tap-legacy-g4 ?
tap-legacy-g5 ?
tap-legacy-g6 ?
tap-legacy-g7 ?
tap-legacy-g8 ?
tap-legacy-g9 ?
tap-mariadb10-galera-g1 ?
tap-mariadb10-galera-g2 ?
tap-mariadb10-galera-g3 ?
tap-mariadb10-galera-g4 ?
tap-mariadb10-galera-g5 ?
tap-mariadb10-galera-g6 ?
tap-mariadb10-galera-g7 ?
tap-mariadb10-galera-g8 ?
tap-mariadb10-galera-g9 ?
tap-mysql56-single-g1 ?
tap-mysql84-g1 ?
tap-mysql84-g2 ?
tap-mysql84-g3 ?
tap-mysql84-g4 ?
tap-mysql84-g5 ?
tap-mysql84-g6 ?
tap-mysql84-g7 ?
tap-mysql84-g8 ?
tap-mysql84-g9 ?
tap-mysql84-gr-g1 ?
tap-mysql84-gr-g2 ?
tap-mysql84-gr-g3 ?
tap-mysql84-gr-g4 ?
tap-mysql84-gr-g5 ?
tap-mysql84-gr-g6 ?
tap-mysql84-gr-g7 ?
tap-mysql84-gr-g8 ?
tap-mysql84-gr-g9 ?
unit-tests ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants