Skip to content

MySQL: Track session-specific system variables changes#5166

Merged
renecannao merged 23 commits into
v3.0from
session-track-system-variable
Apr 22, 2026
Merged

MySQL: Track session-specific system variables changes#5166
renecannao merged 23 commits into
v3.0from
session-track-system-variable

Conversation

@wazir-ahmed

@wazir-ahmed wazir-ahmed commented Oct 16, 2025

Copy link
Copy Markdown
Member
  • Configure session_track_system_variables and session_track_state_change in all backend connections.
  • Utilize notifications from backend servers to capture system variable changes that cannot be handled by MySQL_Set_Stmt_Parser
  • Update both client and server variable maps based on backend responses.

Summary by CodeRabbit

  • New Features

    • Configurable session variable tracking: DISABLED (default), OPTIONAL, ENFORCED with runtime toggle and fast-forward support.
    • Proxy now respects per-server backoff when selecting backend connections and preserves client capability advertising when ENFORCED.
    • Backend-driven session-variable/state setup and extraction integrated into connection flow.
  • Tests

    • New TAP tests covering OPTIONAL/ENFORCED modes, fast-forward paths, and MySQL-version specific behavior.
    • New CI infra and helper scripts for MySQL 5.6 single-instance testing.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @wazir-ahmed, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces comprehensive support for tracking session-specific system variable changes within MySQL backend connections. It establishes a robust mechanism to configure the necessary session tracking settings, process notifications from the backend when variables change, and synchronize these changes across the internal client and server variable states. This enhancement improves the accuracy of ProxySQL's understanding of the backend session state, particularly for variables that are not easily parsed from SQL statements.

Highlights

  • Session Variable Tracking Configuration: Implemented logic to configure session_track_system_variables and session_track_state_change on all backend connections to enable server-side tracking of session variable changes.
  • Backend Notification Processing: Added mechanisms to utilize notifications from backend servers to capture system variable changes that cannot be reliably handled by statement parsing alone.
  • Variable Map Synchronization: Ensured that both client and server internal variable maps are accurately updated based on the session variable changes communicated from the backend responses, maintaining state consistency.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@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 tracking for session-specific system variables in MySQL by leveraging session_track_system_variables and session_track_state_change. The changes include new handlers and logic to set and process these variables, ensuring that changes are captured and synchronized between the client and server. My review focuses on improving code safety, clarity, and performance in the new implementations. I've suggested using safer string comparison functions, reducing variable scopes for better readability, and making minor performance optimizations in string handling. I also pointed out a commented-out line that should be removed.

Comment thread lib/MySQL_Session.cpp Outdated
Comment thread lib/MySQL_Session.cpp Outdated
Comment thread lib/mysql_connection.cpp
@wazir-ahmed
wazir-ahmed force-pushed the session-track-system-variable branch from d817bda to 346030e Compare October 17, 2025 07:19
@wazir-ahmed
wazir-ahmed force-pushed the session-track-system-variable branch 2 times, most recently from a22aa90 to e059a67 Compare November 2, 2025 21:57
@wazir-ahmed
wazir-ahmed force-pushed the session-track-system-variable branch 4 times, most recently from f1f57ac to 7709911 Compare November 11, 2025 07:56
@wazir-ahmed
wazir-ahmed force-pushed the session-track-system-variable branch 3 times, most recently from 826fd70 to c5aed61 Compare November 24, 2025 10:39
@noizu noizu added this to the Release 3.0.4 milestone Nov 25, 2025
@renecannao

Copy link
Copy Markdown
Contributor

PR 5166: Comprehensive Review and Concerns - Detailed Analysis

First, excellent work on implementing session variable tracking! This is a valuable enhancement that addresses real limitations in ProxySQL's session state management. The architectural approach leveraging MySQL's native session tracking capabilities is sound and well-designed.

However, I have identified several critical issues and areas for improvement that should be addressed before merge. This analysis provides detailed technical background for each concern.

🔴 Critical Issues (Blockers)

1. Missing Capability Detection - Protocol Level Compatibility Gap

Problem: The implementation lacks proper capability detection for CLIENT_SESSION_TRACK, unlike the existing GTID tracking.

Technical Background:
In MySQL's client/server protocol, both client and server exchange capability flags during connection handshake. CLIENT_SESSION_TRACK is a capability flag that indicates the server supports MySQL 5.7+ session tracking features. When this flag is not set:

  • Server does not track session variable changes
  • Server cannot send session tracking information in OK packets
  • Attempting to SET session_track_system_variables will likely fail
  • MySQL client library functions like mysql_session_track_get_first() will not work

Current Implementation Gap:

// ✅ GTID tracking correctly checks capability:
bool MySQL_Session::handler_again___verify_backend_session_track_gtids() {
    if ((mybe->server_myds->myconn->mysql->server_capabilities & CLIENT_SESSION_TRACK) == 0) {
        // the backend doesn't support CLIENT_SESSION_TRACK
        return ret; // Gracefully handles unsupported backends
    }
    // ... proceed with configuration
}

// ❌ Variable tracking attempts SET commands without capability check:
bool MySQL_Session::handler_again___verify_backend_session_track_variables() {
    if (mysql_thread___session_track_variables == session_track_variables::DISABLED) {
        return false;
    }
    // ❌ No CLIENT_SESSION_TRACK check - will try to SET on unsupported servers!
    if (mybe->server_myds->myconn->options.session_track_variables_sent == false) {
        // This will fail on MySQL < 5.7, MariaDB, SQLite3, etc.
        NEXT_IMMEDIATE_NEW(SETTING_SESSION_TRACK_VARIABLES);
    }
}

Impact: This will cause connection failures on:

  • MySQL < 5.7: Session tracking was introduced in MySQL 5.7.4
  • MariaDB < 10.2: MariaDB implemented this later than MySQL
  • ProxySQL's own admin interface: Uses SQLite3 backend which doesn't support MySQL protocol extensions
  • sqlite3-server module: SQLite3 backend in ProxySQL
  • Any non-MySQL backend that uses the MySQL Protocol

Fix Required: Add the same capability check that GTID tracking uses before attempting any SET commands.

2. CLIENT_DEPRECATE_EOF Dependency Not Handled

Technical Background:
MySQL session tracking requires two capability flags to work properly:

  1. CLIENT_SESSION_TRACK : Enables session tracking protocol support
  2. CLIENT_DEPRECATE_EOF : Replaces EOF packets with OK packets, required for session tracking data

When CLIENT_DEPRECATE_EOF is not set, MySQL server sends EOF packets instead of OK packets with session tracking data. Session tracking information is only transmitted in OK packets, not EOF packets.

Current State: The implementation assumes backends support session tracking without verifying both required capabilities.

Impact: Even if a server supports CLIENT_SESSION_TRACK, without CLIENT_DEPRECATE_EOF the session tracking data cannot be transmitted to the client. I actually expect that CLIENT_DEPRECATE_EOF is a requirement for CLIENT_SESSION_TRACK.

🟡 Major Concerns

3. TAP Test Modifications Need Explanation

Observation: Two TAP tests were modified to explicitly disable session tracking:

// test_binlog_reader-t.cpp:
MYSQL_QUERY_T(proxysql_admin, "SET mysql-session_track_variables=0");
MYSQL_QUERY_T(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME");

// reg_test_4264-commit_rollback-t.cpp:
MYSQL_QUERY_T(admin, "SET mysql-session_track_variables=0");

Possible Root Causes:

  • Test Failures: Tests likely fail when session tracking is enabled due to the capability issues above
  • State Pollution: The SET commands might affect test state in unexpected ways
  • Variable Behavior Changes: Session tracking changes how variables are managed, potentially breaking test assumptions

Action Required: We need to understand the exact failure modes before merge. These test modifications suggest issues were encountered but worked around them instead of fixing the root cause.

4. Fast Forward Mode Compatibility

Technical Background:
Fast forward mode in ProxySQL provides high-performance query forwarding with minimal protocol overhead. It's designed for scenarios where ProxySQL acts as a transparent proxy with minimal processing.

Session Tracking Impact:

  • Packet Format Changes: Session tracking adds data to OK packets, changing the protocol stream
  • Client Capability Mismatch: Fast forward may assign connections to clients that don't support session tracking

Current Gap: No checking of client capabilities when assigning connections in fast forward mode.

Impact: Clients without session tracking support may receive packets they cannot parse or unexpected protocol overhead.

🔵 Enhancement Opportunities

5. Enhanced Configuration Modes (Production-Ready Design)

Current Limitation: Binary enable/disable (0/1) is too simplistic for production environments with mixed backends.

Proposed Three-Tier Configuration:

enum mode {
    DISABLED = 0,  // Current behavior: feature completely disabled
    OPTIONAL = 1,  // Graceful fallback with logging for mixed environments
    ENFORCED = 2   // Strict mode: fail if requirements cannot be met
};

Detailed Mode Behavior:

OPTIONAL Mode (1):

  • Attempts to enable session tracking on supported backends
  • Logs INFO message when backend doesn't support session tracking
  • Logs WARNING if mysql-enable_server_deprecate_eof is false (required capability)
  • Continues gracefully without session tracking on unsupported backends
  • Use Case: Gradual rollout in mixed-version environments

ENFORCED Mode (2):

  • Automatically enables CLIENT_DEPRECATE_EOF regardless of mysql-enable_server_deprecate_eof setting
  • Fails connection if session_track_system_variables or session_track_state_change cannot be set
  • Logs ERROR and drops backend connection if requirements not met
  • Use Case: Production environments requiring consistent behavior

6. Frontend Variable Propagation (Complete Implementation)

Current Gap: The implementation tracks variables internally for ProxySQL's state management but doesn't propagate changes to clients that support session tracking.

Technical Requirements:

  1. Client Capability Detection: Check if client connection has CLIENT_SESSION_TRACK flag
  2. OK Packet Enhancement: Add session tracking data to OK packets sent to supporting clients

Implementation Complexity:

  • OK packet format changes require careful protocol handling
  • Need to buffer session tracking data from backend and forward to client
  • Must handle cases where client and backend support different levels of tracking
  • session_track_system_variables or session_track_state_change themselves should become tracked variables

📋 Required Testing

7. Comprehensive CI Testing with Feature Enabled

Current State: Tests run with feature disabled (see TAP test modifications above)

Required Testing Matrix:

A. Version Compatibility with mysql-session_track_variables=1 and mysql-session_track_variables=2:

  • MySQL 5.6 (no session tracking support)
  • MySQL 8.0+ (full session tracking support)

B. Protocol-Level Testing:

  • OK packet format validation
  • Client compatibility verification
  • Error handling for unsupported backends

🎯 Recommended Implementation Order

Phase 1 - Critical Fixes (Merge Blockers)

  1. Add CLIENT_SESSION_TRACKING capability detection before attempting SET commands
  2. Add CLIENT_DEPRECATE_EOF dependency handling
  3. Fix TAP test failures - understand root causes instead of disabling feature
  4. Add comprehensive error handling for unsupported backends

Phase 2 - Compatibility Hardening

  1. Fast forward mode compatibility checks
  2. Comprehensive CI testing with feature enabled
  3. Performance baseline measurement

Phase 3 - Production Enhancements

  1. Implement 0/1/2 configuration modes
  2. Frontend variable propagation
  3. Performance optimization for high-frequency workloads
  4. Add metrics and monitoring for session tracking usage

📝 Additional Technical Implementation Details

Error Handling Strategy Example:

bool MySQL_Session::handler_again___verify_backend_session_track_variables() {
    if (mysql_thread___session_track_variables == session_track_variables::DISABLED) {
        return false;
    }

    // Check if backend supports session tracking
    if ((mybe->server_myds->myconn->mysql->server_capabilities & CLIENT_SESSION_TRACK) == 0) {
        if (mysql_thread___session_track_variables == ENFORCED) {
            proxy_error("Backend %s:%d does not support required session tracking capabilities (CLIENT_SESSION_TRACK)",
                       mybe->host, mybe->port);
            return true; // Trigger connection failure
        } else if (mysql_thread___session_track_variables == OPTIONAL) {
            proxy_info("Backend %s:%d does not support session tracking, continuing without it",
                      mybe->host, mybe->port);
            return false; // Skip session tracking setup
        }
        return false;
    }

    // Check CLIENT_DEPRECATE_EOF requirement
    if ((mybe->server_myds->myconn->mysql->server_capabilities & CLIENT_DEPRECATE_EOF) == 0) {
        if (mysql_thread___session_track_variables == ENFORCED) {
            proxy_error("Backend %s:%d does not support CLIENT_DEPRECATE_EOF required for session tracking",
                       mybe->host, mybe->port);
            return true; // Trigger connection failure
        } else if (mysql_thread___session_track_variables == OPTIONAL) {
            proxy_info("Backend %s:%d does not support CLIENT_DEPRECATE_EOF, session tracking will be limited",
                      mybe->host, mybe->port);
            // Could proceed with limited functionality or skip entirely
        }
    }

    // Proceed with session tracking setup...
}

Summary

This PR implements valuable functionality that addresses real production needs. However, the current implementation has critical gaps that could cause connection failures in production environments.

Critical Issues Summary:

  1. Protocol Capability Gap: Missing CLIENT_SESSION_TRACK detection will cause failures on older MySQL/MariaDB versions
  2. Dependency Gap: Missing CLIENT_DEPRECATE_EOF handling

Recommendation:

  • Address critical issues first before merge consideration
  • Implement enhanced configuration modes for production robustness
  • Add comprehensive testing across different MySQL versions and configurations
  • Consider Phase 2 enhancements (frontend propagation) as follow-up PRs

The architectural foundation is solid and the approach is sound - we just need to harden the implementation for production use across diverse backend environments.

@wazir-ahmed
wazir-ahmed force-pushed the session-track-system-variable branch from eb5a10c to 3f1c89f Compare December 11, 2025 08:54
Comment thread lib/MySQL_Session.cpp
Comment thread lib/mysql_connection.cpp
Comment thread lib/mysql_connection.cpp
@wazir-ahmed
wazir-ahmed force-pushed the session-track-system-variable branch from 3f1c89f to 47f344f Compare December 17, 2025 08:21
wazir-ahmed and others added 5 commits December 22, 2025 12:13
- Add new mysql variable `mysql-session_track_variables`.
- Configure `session_track_system_variables` and `session_track_state_change`
  on backend connections if the mysql variable is enabled.
- Utilize notifications from backend servers to capture system variable changes
  that cannot be handled by `MySQL_Set_Stmt_Parser`
- Update both client and server variable maps based on backend responses.
- TAP test to verify this patch.

Signed-off-by: Wazir Ahmed <wazir@proxysql.com>
…acking

Add detailed architectural documentation for PR 5166 session variable tracking:

- High-level architecture overview in session_track_variables struct explaining the 3-phase workflow
- Detailed documentation for handler_rc0_Process_Variables explaining the core processing workflow
- Technical implementation details for MySQL_Connection::get_variables protocol interface
- Configuration logic documentation for handler_again___verify_backend_session_track_variables
- Added inline comments explaining why session tracking is needed and performance considerations

This documentation provides a complete understanding of how MySQL session variable tracking integrates with ProxySQL's existing state machine and leverages MySQL's native session tracking capabilities.
Signed-off-by: Wazir Ahmed <wazir@proxysql.com>
Signed-off-by: Wazir Ahmed <wazir@proxysql.com>
Signed-off-by: Wazir Ahmed <wazir@proxysql.com>
@wazir-ahmed
wazir-ahmed force-pushed the session-track-system-variable branch from 415dc07 to 6e6a9bb Compare December 22, 2025 06:45
@renecannao

Copy link
Copy Markdown
Contributor

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jan 11, 2026

Copy link
Copy Markdown
✅ Actions performed

Full review triggered.

Signed-off-by: Wazir Ahmed <wazir@proxysql.com>
@mirostauder

Copy link
Copy Markdown
Contributor

retest this please

Signed-off-by: René Cannaò <rene@proxysql.com>
@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🧹 Nitpick comments (3)
include/MySQL_Thread.h (1)

374-384: Naming collision: struct session_track_variables shares its identifier with variables.session_track_variables (int).

The struct tag and the integer field share the same name, which compiles but creates ambiguous-looking sites:

if (mysql_thread___session_track_variables == session_track_variables::DISABLED)

Here the left-hand side is an int variable and the right-hand side resolves to the struct type — readable only once the pattern is known. Consider eliminating the ambiguity:

Suggested alternative — direct enum class
-struct session_track_variables {
-    enum mode {
-        DISABLED = 0,
-        OPTIONAL,
-        ENFORCED
-    };
-};
+enum class session_track_variables_mode {
+    DISABLED = 0,
+    OPTIONAL,
+    ENFORCED
+};

Then update the int field declaration and all comparison sites to use session_track_variables_mode::DISABLED etc. The thread-local mysql_thread___session_track_variables (an int) still stores the underlying integer; static_cast<session_track_variables_mode>(...) makes conversions explicit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/MySQL_Thread.h` around lines 374 - 384, The struct tag
session_track_variables conflicts with the int variable
variables.session_track_variables and makes comparisons like
mysql_thread___session_track_variables == session_track_variables::DISABLED
ambiguous; rename the type to a clear enum (e.g. session_track_variables_mode)
and make it an enum class or plain enum with named constants, update the field
declaration (variables.session_track_variables) uses to store the underlying int
(keep mysql_thread___session_track_variables as int) and replace all comparison
sites (e.g. mysql_thread___session_track_variables ==
session_track_variables::DISABLED) to use session_track_variables_mode::DISABLED
with explicit static_casts where converting between int and the enum; update any
switch/case or function signatures referencing session_track_variables to the
new enum name.
lib/mysql_connection.cpp (1)

3150-3150: Parameter variables shadows the class member Variable variables[].

The parameter std::unordered_map<string, string>& variables has the same identifier as the class member Variable variables[SQL_NAME_LAST_HIGH_WM]. Inside the method body, variables[var_name] = val; resolves to the parameter due to shadowing — functionally correct because the types are unambiguous, but confusing for readers who may conflate the two.

♻️ Suggested rename
-bool MySQL_Connection::get_variables(std::unordered_map<string, string>& variables) {
+bool MySQL_Connection::get_variables(std::unordered_map<string, string>& out_vars) {
     ...
-                variables[var_name] = val;
+                out_vars[var_name] = val;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mysql_connection.cpp` at line 3150, The method
MySQL_Connection::get_variables shadows the class member Variable variables[] by
using the same name for the parameter std::unordered_map<string, string>&
variables; rename the parameter (for example to out_variables or variables_map)
and update all uses inside get_variables to refer to the new name (e.g.,
out_variables[var_name] = val;) so there is no ambiguity with the class member
Variable variables[], keeping class member access unchanged.
lib/MySQL_Thread.cpp (1)

2683-2684: Use enum bounds to avoid drift.

Hard‑coding 0..2 risks mismatching the enum if new modes are added later. Consider tying the max to the enum constant.

♻️ Proposed tweak
-		VariablesPointers_int["session_track_variables"]   = make_tuple(&variables.session_track_variables,   0, 2, false);
+		VariablesPointers_int["session_track_variables"]   = make_tuple(
+			&variables.session_track_variables,
+			static_cast<int>(session_track_variables::DISABLED),
+			static_cast<int>(session_track_variables::ENFORCED),
+			false
+		);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/MySQL_Thread.cpp` around lines 2683 - 2684, The hard-coded range "0..2"
for VariablesPointers_int["session_track_variables"] risks drifting if the
session tracking enum changes; update the max bound to derive from the enum
constant (e.g., use the enum's MAX/COUNT value or SESSION_TRACK_MODE_COUNT) and
replace the literal 2 with that enum-based constant when constructing the tuple
for VariablesPointers_int and ensure any code using
variables.session_track_variables uses the same enum bound for consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@include/MySQL_Thread.h`:
- Around line 380-382: The comment on ENFORCED in include/MySQL_Thread.h
incorrectly states "connection fails" — update the comment to reflect that
unsupported session tracking causes the connection to be returned/destroyed and
the server backoff logic is applied (see handle_session_track_capabilities in
lib/MySQL_Session.cpp and server_backoff_time) so no client-visible error is
raised and the session will be retried on a compatible server; change wording to
explicitly mention "returned to pool or destroyed and server backoff applied"
instead of "connection fails".

In `@lib/mysql_connection.cpp`:
- Around line 900-903: The ENFORCED guard (check of
mysql_thread___session_track_variables == session_track_variables::ENFORCED
which sets mysql->options.client_flag |= CLIENT_DEPRECATE_EOF) is being silently
overridden by the fast-forward code that clears CLIENT_DEPRECATE_EOF; move the
ENFORCED handling so it runs after the fast-forward block (or merge its logic
into that block) so ENFORCED can actually take effect, and if you must keep the
current ordering instead log a proxy_warning when session_fast_forward is true
and ENFORCED was requested but cannot be honored; update references to
mysql->options.client_flag and CLIENT_DEPRECATE_EOF accordingly.
- Around line 3160-3184: The code currently sets ret = true unconditionally
after mysql_session_track_get_first even if no name/value pairs were stored or
the last name is unpaired; change it so ret is set only when at least one
complete pair was inserted (e.g., check variables.empty() or that expect_value
== false before assigning ret = true), ensuring
mysql_session_track_get_next/expect_value/var_name/val logic remains the same so
unpaired trailing names are not treated as a successful result.

---

Duplicate comments:
In `@include/MySQL_HostGroups_Manager.h`:
- Around line 238-244: The field server_backoff_time in
MySQL_HostGroups_Manager.h has a data race because it is written by
MySQL_Session::handle_session_track_capabilities() and read concurrently by
MyHGC::get_random_MySrvC() and code in MySQL_Thread.cpp; change its type to
std::atomic<unsigned long long>, add `#include` <atomic>, and update all accesses
(writes in handle_session_track_capabilities and reads in get_random_MySrvC /
MySQL_Thread.cpp) to use atomic store/load (or fetch/store with appropriate
memory_order) so updates are synchronized and the race is eliminated.

In `@include/MySQL_Thread.h`:
- Around line 646-655: The doc comment for the int member
session_track_variables incorrectly references a non-existent enum constant
ENABLED; update the text in the comment above session_track_variables to
reference the correct enum values (DISABLED (0), OPTIONAL (1), ENFORCED (2)) and
replace the phrase "When set to session_track_variables::ENABLED (1)" with the
appropriate wording (e.g., "When set to session_track_variables::OPTIONAL (1) or
session_track_variables::ENFORCED (2)" or specify the intended behavior for
OPTIONAL vs ENFORCED) so it matches the actual enum constants DISABLED,
OPTIONAL, and ENFORCED.

In `@lib/MySQL_Session.cpp`:
- Around line 1976-2004: The retry flags session_track_variables_sent and
session_track_state_sent are being set unconditionally in
handler_again___verify_backend_session_track_variables which can block retries
after soft SQL errors; modify the flow so that if the subsequent SET operations
(SETTING_SESSION_TRACK_VARIABLES / SETTING_SESSION_TRACK_STATE dispatched via
NEXT_IMMEDIATE_NEW) fail with a soft error the flags are cleared/reset and
previous state restored (undo session_track_*_sent and call
set_previous_status_mode3 if needed) so retries remain possible; ensure the
reset logic is invoked wherever soft SQL errors are handled for those settings
(the error/rollback path that handles SETTING_SESSION_TRACK_VARIABLES or
SETTING_SESSION_TRACK_STATE) and use the same option fields and helper functions
(myconn->options.session_track_variables_sent,
myconn->options.session_track_state_sent, set_previous_status_mode3) to revert
state.

---

Nitpick comments:
In `@include/MySQL_Thread.h`:
- Around line 374-384: The struct tag session_track_variables conflicts with the
int variable variables.session_track_variables and makes comparisons like
mysql_thread___session_track_variables == session_track_variables::DISABLED
ambiguous; rename the type to a clear enum (e.g. session_track_variables_mode)
and make it an enum class or plain enum with named constants, update the field
declaration (variables.session_track_variables) uses to store the underlying int
(keep mysql_thread___session_track_variables as int) and replace all comparison
sites (e.g. mysql_thread___session_track_variables ==
session_track_variables::DISABLED) to use session_track_variables_mode::DISABLED
with explicit static_casts where converting between int and the enum; update any
switch/case or function signatures referencing session_track_variables to the
new enum name.

In `@lib/mysql_connection.cpp`:
- Line 3150: The method MySQL_Connection::get_variables shadows the class member
Variable variables[] by using the same name for the parameter
std::unordered_map<string, string>& variables; rename the parameter (for example
to out_variables or variables_map) and update all uses inside get_variables to
refer to the new name (e.g., out_variables[var_name] = val;) so there is no
ambiguity with the class member Variable variables[], keeping class member
access unchanged.

In `@lib/MySQL_Thread.cpp`:
- Around line 2683-2684: The hard-coded range "0..2" for
VariablesPointers_int["session_track_variables"] risks drifting if the session
tracking enum changes; update the max bound to derive from the enum constant
(e.g., use the enum's MAX/COUNT value or SESSION_TRACK_MODE_COUNT) and replace
the literal 2 with that enum-based constant when constructing the tuple for
VariablesPointers_int and ensure any code using
variables.session_track_variables uses the same enum bound for consistency.

Comment thread include/MySQL_Thread.h
Comment thread lib/mysql_connection.cpp
Comment thread lib/mysql_connection.cpp
Signed-off-by: Wazir Ahmed <wazir@proxysql.com>
Signed-off-by: Wazir Ahmed <wazir@proxysql.com>
- Keep `CLIENT_DEPRECATE_EOF` enabled during frontend handshake when
`session_track_variables=ENFORCED`.
- Avoid unsafe mysql_error() calls in session-track TAP tests.

Signed-off-by: Wazir Ahmed <wazir@proxysql.com>
@wazir-ahmed

wazir-ahmed commented Apr 20, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai Do a full review of this PR. Report if you find any new issues. Don't repeat previous review comments.

@coderabbitai

coderabbitai Bot commented Apr 20, 2026

Copy link
Copy Markdown

@wazir-ahmed, I'll trigger a fresh full review of the PR. While doing so, here are new issues I've identified from analyzing the PR changes that are not covered in @renecannao's existing review:


🔴 New Critical Issues

1. server_backoff_time Data Race (Thread Safety)
MySrvC::server_backoff_time is a plain unsigned long long written from session-handling threads (via handle_session_track_capabilities) and read concurrently inside MyHGC::get_random_MySrvC and get_MyConn_local — without any visible mutex protection. This is a data race under C++ memory model and can cause undefined behavior in high-concurrency scenarios. It should use std::atomic<unsigned long long> or be protected by the existing HG lock.

2. CLIENT_DEPRECATE_EOF Forced on Frontend Clients in ENFORCED Mode
lib/MySQL_Protocol.cpp now advertises CLIENT_DEPRECATE_EOF to frontend clients when session_track_variables == ENFORCED, even when mysql-enable_client_deprecate_eof is false. This can:

  • Break clients that do not support CLIENT_DEPRECATE_EOF (e.g., older connectors)
  • Override the explicit operator intent of leaving mysql-enable_client_deprecate_eof=false
  • Create protocol-level incompatibility with compliant clients that did not negotiate this capability

This should at minimum log a warning, and ideally the ENFORCED mode's capability forcing should be opt-in with clear documentation.


🟡 New Major Concerns

3. Duplicate get_variables() Implementation
The AI summary explicitly notes "a second occurrence of a similar get_variables implementation in another location" in lib/mysql_connection.cpp. Duplicated implementation blocks for the same method risk diverging over time and should be consolidated.

4. Duplicate Backoff Check in Two Locations
The server_backoff_time check appears independently in both MyHGC::get_random_MySrvC (lib/MyHGC.cpp) and get_MyConn_local (lib/MySQL_Thread.cpp). Any future change to backoff semantics would need to be applied to both places — a maintenance footprint concern.

5. Undefined Backoff Duration and Reset
There is no visible definition of how long the backoff period lasts or a clear code path that resets server_backoff_time back to 0 once the server is retried or confirmed capable. Without this, ENFORCED mode could permanently exclude servers from selection after a single capability failure.

6. session_track_variables.h Uses innodb_lock_wait_timeout
The test helper in session_track_variables.h uses innodb_lock_wait_timeout as the variable to track, which requires InnoDB to be active. This variable is:

  • Absent or immutable in some cloud MySQL configurations (e.g., Aurora)
  • Not universally representative — session tracking should ideally be verified with a less engine-specific variable (e.g., time_zone, sql_mode)

7. Naming Ambiguity: Struct and Field Share the Same Name
In lib/MySQL_Thread.cpp, the constructor sets:

variables.session_track_variables = session_track_variables::DISABLED;

Here session_track_variables names both the struct type and the int field — this compiles due to the variables. prefix, but it is confusing and error-prone. Renaming the struct (e.g., SessionTrackVariables or session_track_vars_mode) would improve clarity.


🔵 New Enhancement Observations

8. No SAVE TO DISK or Persistence Path Documented
The new mysql-session_track_variables variable is wired into get_variables_list and refresh_variables, but there's no evidence in the summaries of it being included in the SAVE MYSQL VARIABLES TO DISK path. If this variable is not persisted, it will reset to DISABLED after a ProxySQL restart, which is surprising behavior for operators who configure ENFORCED mode.

9. MySQL 5.6 Test Groups Include OPTIONAL/ENFORCED
In test/tap/groups/groups.json, both mysql-session_track_variables_optional-t and mysql-session_track_variables_enforced-t include "mysql56-single-g4". Given MySQL 5.6 has no session tracking support, running ENFORCED mode tests against it is correct for negative testing, but the test should explicitly assert the correct error code (9001) rather than silently passing or failing.


Triggering a full automated review now for any additional findings from the full diff.


🧠 Learnings used
Learnt from: yuji-hatakeyama
Repo: sysown/proxysql PR: 5548
File: lib/mysql_connection.cpp:1837-1843
Timestamp: 2026-03-26T16:39:02.446Z
Learning: In ProxySQL's lib/mysql_connection.cpp, `SHOW WARNINGS` detection for both `update_warning_count_from_connection()` and the `add_eof()` call in `ASYNC_USE_RESULT_CONT` intentionally uses `myds->sess->CurrentQuery.QueryParserArgs.digest_text` (comment-stripped digest text). This means the fix/feature does not work when `mysql-query_digests_keep_comment=1` (digest_text contains comments) or `mysql-query_digests=0` (digest_text is unavailable) — these configurations are explicitly excluded from the regression test for `reg_test_5306-show_warnings_with_comment-t`. This design is consistent across the codebase and is an accepted, documented limitation.
✅ Actions performed

Full review triggered.

@coderabbitai

coderabbitai Bot commented Apr 20, 2026

Copy link
Copy Markdown
🧩 Analysis chain

@wazir-ahmed, I'll conduct a thorough review of the PR code to identify any new issues not previously raised. Let me examine the implementation details.


🏁 Script executed:

#!/bin/bash
# Examine the core implementation files

echo "=== lib/MySQL_Session.cpp - new methods ==="
rg -n "handle_session_track_capabilities|handler_again___verify_backend_session_variables|handler_again___status_SETTING_SESSION_TRACK_VARIABLES|handler_again___status_SETTING_SESSION_TRACK_STATE|handler_rc0_Process_Variables|SETTING_SESSION_TRACK" lib/MySQL_Session.cpp -A 30 -B 2

echo ""
echo "=== lib/mysql_connection.cpp - get_variables ==="
rg -n "get_variables|session_track_variables_sent|session_track_state_sent|session_track_state_change|session_track_system_variables" lib/mysql_connection.cpp -A 5 -B 2

echo ""
echo "=== lib/MySQL_Protocol.cpp - deprecate_eof changes ==="
rg -n "CLIENT_DEPRECATE_EOF|session_track_variables|ENFORCED" lib/MySQL_Protocol.cpp -A 5 -B 2

echo ""
echo "=== include/MySQL_Thread.h - session_track_variables struct ==="
rg -n "session_track_variables|DISABLED|OPTIONAL|ENFORCED" include/MySQL_Thread.h -A 3 -B 2

echo ""
echo "=== lib/MySQL_Thread.cpp - variable registration ==="
rg -n "session_track_variables" lib/MySQL_Thread.cpp -A 5 -B 2

echo ""
echo "=== lib/MyHGC.cpp - server_backoff_time ==="
rg -n "server_backoff_time" lib/MyHGC.cpp -A 5 -B 5

echo ""
echo "=== include/MySQL_HostGroups_Manager.h - server_backoff_time ==="
rg -n "server_backoff_time" include/MySQL_HostGroups_Manager.h -A 3 -B 3

Length of output: 22540


🏁 Script executed:

#!/bin/bash
echo "=== handle_session_track_capabilities full function ==="
rg -n "handle_session_track_capabilities" lib/MySQL_Session.cpp -A 80 -B 2 | head -150

echo ""
echo "=== server_backoff_time assignment - race condition check ==="
rg -n "server_backoff_time" lib/MySQL_Session.cpp -A 10 -B 5

echo ""
echo "=== get_variables full implementation ==="
sed -n '/bool MySQL_Connection::get_variables/,/^}/p' lib/mysql_connection.cpp | head -60

echo ""
echo "=== connect_start_SetClientFlag - ENFORCED mode CLIENT_DEPRECATE_EOF ==="
rg -n "connect_start_SetClientFlag|CLIENT_DEPRECATE_EOF|session_track_variables" lib/mysql_connection.cpp -A 5 -B 3 | head -100

echo ""
echo "=== session_track_variables.h - test utilities ==="
cat test/tap/tests/session_track_variables.h | head -300

echo ""
echo "=== set_session_variable - stored procedure ==="
rg -n "set_session_variable|innodb_lock_wait_timeout|stored proc" test/tap/tests/session_track_variables.h -A 20 -B 2

echo ""
echo "=== Check handler_rc0_Process_Variables for variables outside SQL_NAME_LAST_HIGH_WM ==="
rg -n "SQL_NAME_LAST_HIGH_WM\|SQL_NAME_LAST_LOW_WM\|idx.*SQL_NAME" lib/MySQL_Session.cpp -A 5 -B 2

echo ""
echo "=== Check if session_track_variables_sent flag is reset when it should be ==="
rg -n "session_track_variables_sent|session_track_state_sent" lib/mysql_connection.cpp lib/MySQL_Session.cpp -A 3 -B 2

Length of output: 29631


🏁 Script executed:

#!/bin/bash
echo "=== CONNECTING_SERVER state - where handle_session_track_capabilities is called relative to async_connect ==="
rg -n "ASYNC_IDLE\|async_state_machine\|async_connect\|handle_session_track_cap" lib/MySQL_Session.cpp -B 3 -A 5 | head -120

echo ""
echo "=== server_backoff_time - check if atomic or protected ==="
rg -n "server_backoff_time\|atomic" include/MySQL_HostGroups_Manager.h -A 2 -B 2

echo ""
echo "=== get_random_MySrvC locking ==="
rg -n "get_random_MySrvC\|rdlock\|wrlock\|lock_guard\|mutex" lib/MyHGC.cpp -A 3 -B 3 | head -60

echo ""
echo "=== get_MyConn_local - server_backoff_time ==="
rg -n "server_backoff_time\|get_MyConn_local" lib/MySQL_Thread.cpp -A 5 -B 5

echo ""
echo "=== handler_rc0_Process_Variables called - check if gated by mode ==="
rg -n "handler_rc0_Process_Variables\|session_track_variables" lib/MySQL_Session.cpp -A 3 -B 3 | grep -A3 -B3 "Process_Variables"

echo ""
echo "=== OPTIONAL mode logging check ==="
rg -n "proxy_info\|proxy_warning\|proxy_error\|OPTIONAL\|session_track_variables" lib/MySQL_Session.cpp | grep -i "info\|warn\|error\|optional" | head -20

echo ""
echo "=== set_session_variable function - does it set set_value? ==="
sed -n '/inline int set_session_variable/,/^}/p' test/tap/tests/session_track_variables.h

echo ""
echo "=== handler_again___status_SETTING_GENERIC_VARIABLE error handling ==="
rg -n "SETTING_GENERIC_VARIABLE" lib/MySQL_Session.cpp -A 25 -B 2 | head -60

echo ""
echo "=== ENFORCED mode backoff writes to parent (shared object) - thread safety ==="
rg -n "be_conn->parent->server_backoff_time\|be_conn\.parent\->server_backoff" lib/MySQL_Session.cpp -A 3 -B 3

Length of output: 3739


🏁 Script executed:

#!/bin/bash
echo "=== server_backoff_time type and atomic usage ==="
grep -n "server_backoff_time\|atomic" include/MySQL_HostGroups_Manager.h

echo ""
echo "=== get_random_MySrvC lock usage ==="
sed -n '/MySrvC.*get_random_MySrvC/,/^}/p' lib/MyHGC.cpp | head -50

echo ""
echo "=== CONNECTING_SERVER pre-handshake capability check ==="
sed -n '/handler_again___status_CONNECTING_SERVER/,/async_connect/p' lib/MySQL_Session.cpp | head -60

echo ""
echo "=== handler_rc0_Process_Variables mode gate check ==="
grep -n "handler_rc0_Process_Variables\|session_track_variables" lib/MySQL_Session.cpp | grep -A2 -B2 "Process_Var" | head -30

echo ""
echo "=== check handler_rc0_Process_Variables gate ==="
sed -n '/handler_rc0_Process_Variables/p' lib/MySQL_Session.cpp

echo ""
echo "=== OPTIONAL mode no logging ==="
sed -n '/handler_again___verify_backend_session_track_variables/,/^}/p' lib/MySQL_Session.cpp | head -40

echo ""
echo "=== session_track_variables_sent set before success ==="
grep -n "session_track_variables_sent\|session_track_state_sent\|NEXT_IMMEDIATE_NEW\|SETTING_SESSION_TRACK" lib/MySQL_Session.cpp | head -30

echo ""
echo "=== session_track_system_variables wildcard - any performance warning? ==="
grep -rn "session_track_system_variables\|\\\*\|wildcard\|performance" include/MySQL_Thread.h lib/MySQL_Thread.cpp | head -20

echo ""
echo "=== get_MyConn_local backoff check ==="
grep -n "server_backoff_time" lib/MySQL_Thread.cpp

Length of output: 10173


@wazir-ahmed, here is my full review of PR #5166. I've focused exclusively on issues not previously raised by @renecannao in the existing comment thread.


New Issues Found

🔴 Critical: Thread Safety — server_backoff_time is Not Atomic

File: include/MySQL_HostGroups_Manager.h (Line 215) / lib/MySQL_Session.cpp (Line ~9766)

server_backoff_time is declared as a plain unsigned long long:

unsigned long long server_backoff_time;

It is written from a session thread inside handle_session_track_capabilities():

be_conn->parent->server_backoff_time = thread->curtime + (30 * 1000000);

And read concurrently from potentially different threads in:

  • MyHGC::get_random_MySrvC() (lib/MyHGC.cpp, line 42): if (mysrvc->server_backoff_time > sess->thread->curtime)
  • MySQL_Thread::get_MyConn_local() (lib/MySQL_Thread.cpp, line 6396): if (c->parent->server_backoff_time > curtime)

This is an unsynchronized read-write across threads — a data race, which is undefined behavior in C++. The <atomic> header is already included in the file; the fix is straightforward:

// include/MySQL_HostGroups_Manager.h
std::atomic<unsigned long long> server_backoff_time { 0 };

// lib/MySrvC.cpp constructor - initialization already sets to 0, no change needed
// reads/writes automatically use relaxed/acquire-release atomics as appropriate

🟡 Major: handler_rc0_Process_Variables Not Gated by Mode — Runs on Every Query

File: lib/MySQL_Session.cpp (Line 6404)

handler_rc0_Process_Variables(myconn) is called unconditionally in the hot query processing path on every rc == 0 response:

handler_rc0_Process_Variables(myconn);  // called for EVERY query, regardless of mode

Inside, it calls myconn->get_variables() which iterates SESSION_TRACK_SYSTEM_VARIABLES data every time SERVER_SESSION_STATE_CHANGED is set. SERVER_SESSION_STATE_CHANGED can be set for reasons unrelated to system variable tracking (e.g., schema tracking, GTID tracking), which means this function does meaningful work on non-variable-tracking state change events even when session_track_variables == DISABLED.

Fix: Gate the call by mode:

if (mysql_thread___session_track_variables != session_track_variables::DISABLED) {
    handler_rc0_Process_Variables(myconn);
}

🟡 Major: Missing Null Guard for mysql in handler_again___verify_backend_session_track_variables()

File: lib/MySQL_Session.cpp (Lines ~1998–2001)

Unlike handle_session_track_capabilities(), which carefully null-guards mybe->server_myds->myconn->mysql before accessing it, handler_again___verify_backend_session_track_variables() directly dereferences mysql->server_capabilities without any null check:

// ❌ No null guard here — potential null pointer dereference
if ((mybe->server_myds->myconn->mysql->server_capabilities & CLIENT_SESSION_TRACKING) == 0
    || (mybe->server_myds->myconn->mysql->server_capabilities & CLIENT_DEPRECATE_EOF) == 0) {
    return false;
}

Compare with the pattern used safely in handle_session_track_capabilities():

if (mybe->server_myds->myconn->mysql == NULL) {
    return true;
}

The same guard should be added at the top of handler_again___verify_backend_session_track_variables().


🟡 Major: OPTIONAL Mode Silently Fails with No Log — Zero Observability

File: lib/MySQL_Session.cpp (Lines ~1998–2004)

When session_track_variables = OPTIONAL (1) and either the backend lacks CLIENT_SESSION_TRACKING/CLIENT_DEPRECATE_EOF, or mysql-enable_server_deprecate_eof is false, session tracking is silently skipped:

if ((mybe->server_myds->myconn->mysql->server_capabilities & CLIENT_SESSION_TRACKING) == 0
    || (mybe->server_myds->myconn->mysql->server_capabilities & CLIENT_DEPRECATE_EOF) == 0) {
    return false;  // ❌ Silent — no log
}
if (!mysql_thread___enable_server_deprecate_eof && mode != session_track_variables::ENFORCED) {
    return false;  // ❌ Silent — no log, very common misconfiguration
}

A user who sets mysql-session_track_variables=1 without mysql-enable_server_deprecate_eof=true will see the variable set but get no tracking and no indication in any log. This is a common misconfiguration scenario with no visibility. A proxy_info() or proxy_warning() should be emitted (rate-limited, similar to how other capability mismatches are logged).


🟡 Major: session_track_system_variables='*' Performance Impact Not Documented

File: lib/MySQL_Session.cpp (Line ~2823) / include/MySQL_Thread.h

The implementation hardcodes session_track_system_variables = '*' (wildcard — track ALL system variables):

ret = handler_again___status_SETTING_GENERIC_VARIABLE(_rc, (char *)"session_track_system_variables", "*", false);

With *, the backend will include every changed system variable in every OK packet response. In sessions with high variable churn (many SET statements, stored procedures like the test's set_innodb_lock_wait_timeout), this can significantly inflate OK packet payloads and add CPU overhead in mysql_session_track_get_first/next iteration. There is no warning about this in the variable documentation in include/MySQL_Thread.h. Consider either:

  1. Documenting this performance caveat clearly, or
  2. Allowing configuration of which variables to track instead of hardcoding *

🔵 Minor: set_session_variable() Documents set_value as Output Parameter but Never Sets It

File: test/tap/tests/session_track_variables.h (Lines ~111–130)

The function is documented as:

@param[out] set_value The actual value of innodb_lock_wait_timeout after the procedure call.

But the implementation never assigns set_value — it stays at -1 after the function returns. The actual value is obtained by a separate select_sess_var_value() call. The comment is misleading for anyone reading test_session_variables_ff() expecting set_value to be populated by set_session_variable. Either fix the doc or restructure so the function actually retrieves the value it sets.


🔵 Minor: commented-out Alternative Backoff Duration Left in Production Code

File: lib/MySQL_Session.cpp (Line ~9765)

// be_conn->parent->server_backoff_time = thread->curtime + (600 * 1000000); // 10 minutes
be_conn->parent->server_backoff_time = thread->curtime + (30 * 1000000); // 30 seconds

This left-over commented alternative (10 minutes vs. 30 seconds) suggests the backoff duration was not finalized. The hardcoded 30-second value should either be made configurable or the comment explaining the rationale for 30 seconds over 10 minutes should be added and the dead code removed.


Summary of new blockers:

Severity Issue File
🔴 server_backoff_time not atomic — data race across threads MySQL_HostGroups_Manager.h, MySQL_Session.cpp
🟡 handler_rc0_Process_Variables called unconditionally in hot path MySQL_Session.cpp:6404
🟡 Missing null guard in handler_again___verify_backend_session_track_variables MySQL_Session.cpp
🟡 OPTIONAL mode silent failure with no log output MySQL_Session.cpp
🟡 session_track_system_variables='*' performance impact undocumented MySQL_Session.cpp, MySQL_Thread.h
🔵 set_session_variable() misleading set_value out-param documentation session_track_variables.h
🔵 Commented-out alternative backoff value left in code MySQL_Session.cpp

🧠 Learnings used
Learnt from: yuji-hatakeyama
Repo: sysown/proxysql PR: 5548
File: lib/mysql_connection.cpp:1837-1843
Timestamp: 2026-03-26T16:39:02.446Z
Learning: In ProxySQL's lib/mysql_connection.cpp, `SHOW WARNINGS` detection for both `update_warning_count_from_connection()` and the `add_eof()` call in `ASYNC_USE_RESULT_CONT` intentionally uses `myds->sess->CurrentQuery.QueryParserArgs.digest_text` (comment-stripped digest text). This means the fix/feature does not work when `mysql-query_digests_keep_comment=1` (digest_text contains comments) or `mysql-query_digests=0` (digest_text is unavailable) — these configurations are explicitly excluded from the regression test for `reg_test_5306-show_warnings_with_comment-t`. This design is consistent across the codebase and is an accepted, documented limitation.
✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai 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.

Actionable comments posted: 13

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/infra/control/run-tests-isolated.bash (1)

73-94: ⚠️ Potential issue | 🟠 Major

Populate INFRAS_TO_CHECK after deriving INFRA_TYPE.

For mysql56-single* groups without an infras.lst, line 75 captures an empty INFRA_TYPE before line 82 derives it. That leaves INFRAS_TO_CHECK empty, so backend verification at lines 131-153 is skipped.

Proposed fix
 # Derive INFRA_TYPE from TAP_GROUP if not set
 # This is simple and deterministic - matches group naming convention
 if [ -z "${INFRA_TYPE}" ]; then
     case "${TAP_GROUP}" in
         mysql56-single*) export INFRA_TYPE="${DEFAULT_MYSQL_INFRA:-infra-dbdeployer-mysql56-single}" ;;
@@
     if [ -n "${INFRA_TYPE}" ]; then
         echo ">>> Derived INFRA_TYPE from TAP_GROUP: ${INFRA_TYPE}"
     fi
 fi
+
+# If no list found, use the derived INFRA_TYPE as single requirement.
+if [ -z "${INFRAS_TO_CHECK}" ]; then
+    INFRAS_TO_CHECK="${INFRA_TYPE}"
+fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/infra/control/run-tests-isolated.bash` around lines 73 - 94,
INFRAS_TO_CHECK is only initialized before INFRA_TYPE may be derived from
TAP_GROUP, causing INFRAS_TO_CHECK to stay empty for groups like mysql56-single*
and skip backend checks; after the case block that sets INFRA_TYPE (the block
referencing TAP_GROUP and values like mysql56-single*, mysql84*, etc.), add
logic to populate INFRAS_TO_CHECK from INFRA_TYPE when INFRAS_TO_CHECK is empty
(i.e., if INFRAS_TO_CHECK is unset/null and INFRA_TYPE is set, assign
INFRAS_TO_CHECK=INFRA_TYPE) so subsequent backend verification uses the derived
infra.
♻️ Duplicate comments (8)
include/MySQL_Thread.h (1)

374-384: ⚠️ Potential issue | 🟡 Minor

Clarify ENFORCED: unsupported backends are backed off, not simply failed.

Both comments still say the connection “fails” when required capabilities are missing. The current flow rejects/returns the backend connection and applies temporary server backoff, so the docs should avoid implying an immediate client-visible hard failure.

Suggested wording
-		// Enforce session tracking; connection fails if backend does
-		// not support CLIENT_DEPRECATE_EOF and CLIENT_SESSION_TRACKING
+		// Enforce session tracking; backend connections that lack
+		// CLIENT_DEPRECATE_EOF or CLIENT_SESSION_TRACKING are rejected
+		// and the server is put into temporary backoff.
 		ENFORCED
-		 * - ENFORCED (2): Enforce session tracking; connection fails if backend
-		 *   does not support CLIENT_DEPRECATE_EOF and CLIENT_SESSION_TRACKING.
+		 * - ENFORCED (2): Enforce session tracking; backend connections that
+		 *   do not support CLIENT_DEPRECATE_EOF and CLIENT_SESSION_TRACKING
+		 *   are rejected and the server is put into temporary backoff.

Also applies to: 656-670

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/MySQL_Thread.h` around lines 374 - 384, The comment for
session_track_variables::mode::ENFORCED is misleading: it says the connection
"fails" when backends lack CLIENT_DEPRECATE_EOF and CLIENT_SESSION_TRACKING, but
the code actually rejects that backend and applies a temporary server backoff
rather than an immediate client-visible hard failure; update the comment on enum
mode (struct session_track_variables) for ENFORCED to state that unsupported
backends are rejected and the server applies a temporary backoff (not an instant
client-visible failure), and make the same wording change in the duplicate
comment block elsewhere (the other session tracking enum comment).
test/tap/tests/reg_test_4264-commit_rollback-t.cpp (1)

1156-1160: ⚠️ Potential issue | 🟠 Major

Don’t mask session-tracking traffic by disabling the feature in this regression test.

reg_test_4264 should establish its counter baseline after connection/session-track setup, or subtract known internal setup queries, so it still validates transaction routing with the new feature enabled.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/tests/reg_test_4264-commit_rollback-t.cpp` around lines 1156 - 1160,
This test currently disables session-tracking (via the MYSQL_QUERY_T calls
setting mysql-default_session_track_gtids and mysql-session_track_variables and
LOAD MYSQL VARIABLES TO RUNTIME), which masks the behavior under test; either
remove those statements so session-tracking remains enabled, or move them so
they run before establishing the test's transaction-routing baseline (or adjust
the baseline calculation to subtract these known internal setup queries). Update
the reg_test_4264 test flow around the MYSQL_QUERY_T(admin, ...) calls and the
point where the baseline is captured so the session/connection tracking is
active when you establish the counter baseline used to validate routing.
include/MySQL_HostGroups_Manager.h (1)

209-215: ⚠️ Potential issue | 🟠 Major

Declare server_backoff_time as atomic.

This timestamp is read from hostgroup selection/cache paths and written by session handling, so the plain unsigned long long keeps the data race already noted in prior review.

Proposed fix
-	unsigned long long server_backoff_time;
+	std::atomic<unsigned long long> server_backoff_time { 0 };
#!/bin/bash
# Confirm all reads/writes can be migrated to atomic load/store.
rg -n -C3 'server_backoff_time\s*(=|>|\.load|\.store)' --type=cpp --type=h

As per coding guidelines, use pthread mutexes for synchronization and std::atomic<> for counters.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/MySQL_HostGroups_Manager.h` around lines 209 - 215, Declare
server_backoff_time as std::atomic<unsigned long long> and include <atomic>;
update all producers/consumers to use .store()/ .load() (e.g., uses in
MyHGC::get_random_MySrvC and the session handling code that writes the
timestamp) instead of raw reads/writes, choosing an appropriate memory_order
(memory_order_relaxed is acceptable for simple timestamp updates unless stronger
ordering is required); scan the repo for direct accesses to server_backoff_time
and replace them with atomic ops so reads/writes are race-free.
lib/MyHGC.cpp (1)

41-43: ⚠️ Potential issue | 🟠 Major

Guard sess and make the backoff timestamp atomic.

The new check dereferences sess->thread unconditionally, while this function already has nullable-session guards later. Also, the server_backoff_time read is still unsynchronized; that part was already flagged in prior review.

Proposed fix
-				if (mysrvc->server_backoff_time > sess->thread->curtime)
+				if (sess && mysrvc->server_backoff_time.load(std::memory_order_relaxed) > sess->thread->curtime)
 					continue;

And update the field declaration:

-	unsigned long long server_backoff_time;
+	std::atomic<unsigned long long> server_backoff_time { 0 };
#!/bin/bash
# Verify nullable-session hints and all server_backoff_time access sites.
rg -n -C3 'sess &&|get_random_MySrvC|server_backoff_time\s*(=|>|\.load|\.store)' --type=cpp --type=h

As per coding guidelines, use pthread mutexes for synchronization and std::atomic<> for counters.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/MyHGC.cpp` around lines 41 - 43, The code dereferences sess->thread
unconditionally when checking mysrvc->server_backoff_time; update the check to
first ensure sess is non-null and sess->thread is non-null before accessing
curtime, and replace the unsynchronized plain access to
mysrvc->server_backoff_time with a thread-safe read (e.g., change the field in
the MySrvC struct to std::atomic<time_t> server_backoff_time or protect
reads/writes with the appropriate pthread mutex), then use
server_backoff_time.load() (or the mutex-protected read) in the if condition;
update all other access sites to server_backoff_time accordingly and follow the
project guideline to use pthread mutexes for compound operations and
std::atomic<> for simple counters/flags.
test/infra/infra-mysql56-single/docker-compose-destroy.bash (1)

6-8: ⚠️ Potential issue | 🟠 Major

Same INFRA_ID preservation gap as the dbdeployer destroy script.

docker-compose-init.bash for this infra preserves a caller-provided INFRA_ID across .env sourcing; this destroy script does not, so a .env-defined INFRA_ID can override the caller's and make COMPOSE_PROJECT diverge from what init created, causing docker compose down -v to no-op and leave the real cluster/volumes behind. Apply the same SAVED_INFRA_ID preservation pattern used in docker-compose-init.bash.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/infra/infra-mysql56-single/docker-compose-destroy.bash` around lines 6 -
8, The destroy script fails to preserve a caller-provided INFRA_ID before
sourcing .env, so a .env INFRA_ID can override the caller and make
COMPOSE_PROJECT mismatch; mimic the pattern from docker-compose-init.bash by
saving the existing INFRA_ID into a temporary SAVED_INFRA_ID (or similar) before
the "set -a; . .env; set +a" block and restore INFRA_ID from that saved variable
after sourcing, then compute/export COMPOSE_PROJECT="${INFRA}-${INFRA_ID}" so it
matches the init-created project and docker compose down -v targets the correct
resources.
lib/mysql_connection.cpp (2)

3177-3200: ⚠️ Potential issue | 🟡 Minor

Return success only after storing a complete variable pair.

Line 3200 reports success even if get_first() returned only a variable name and no value was inserted. Move ret = true into the completed-pair branch so callers do not process an empty/incomplete tracking map.

Proposed fix
 				if (expect_value) {
 					val = string(data, length);
 					variables[var_name] = val;
+					ret = true;
 					// got a value in this iteration
 					// in the next iteration, we have to expect a variable_name
 					expect_value = false;
@@
-
-			ret = true;
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mysql_connection.cpp` around lines 3177 - 3200, The code currently sets
ret = true unconditionally after calling mysql_session_track_get_first, which
can indicate success even if no value was paired with the variable name; modify
the loop handling mysql_session_track_get_next so that ret is only set to true
when a complete name/value pair has been stored into the variables map (i.e.,
move the assignment of ret = true into the branch where you assign
variables[var_name] = val after constructing val and before toggling
expect_value), and ensure ret remains false if get_first() returns a name but no
subsequent value was read by mysql_session_track_get_next.

904-920: ⚠️ Potential issue | 🟠 Major

ENFORCED mode is still cleared for fast-forward backend connects.

Line 906 sets CLIENT_DEPRECATE_EOF, but Line 915 clears it for fast-forward sessions and only restores it when the frontend/client capability match succeeds. That means ENFORCED can still be silently bypassed for fast-forward connections.

Proposed direction
 				if (c->options.client_flag & CLIENT_DEPRECATE_EOF) {
 					if (c->options.server_capabilities & CLIENT_DEPRECATE_EOF) {
 						mysql->options.client_flag |= CLIENT_DEPRECATE_EOF;
 					}
 				}
+				if (mysql_thread___session_track_variables == session_track_variables::ENFORCED
+					&& !(mysql->options.client_flag & CLIENT_DEPRECATE_EOF)) {
+					proxy_warning("session_track_variables=ENFORCED but CLIENT_DEPRECATE_EOF "
+						"could not be enabled for fast_forward connection\n");
+				}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mysql_connection.cpp` around lines 904 - 920, The ENFORCED setting can be
bypassed for fast-forward connections because code clears CLIENT_DEPRECATE_EOF
for myds->sess->session_fast_forward then conditionally restores it from the
backend client; change this so that when mysql_thread___session_track_variables
== session_track_variables::ENFORCED you never clear CLIENT_DEPRECATE_EOF for
fast-forward backends. Concretely, in the fast-forward branch (check using
myds->sess->session_fast_forward and MySQL_Connection *c =
myds->sess->client_myds->myconn) add a guard so mysql->options.client_flag &=
~(CLIENT_DEPRECATE_EOF) is skipped if mysql_thread___session_track_variables ==
session_track_variables::ENFORCED, or alternatively set CLIENT_DEPRECATE_EOF
unconditionally when ENFORCED before touching it; reference the symbols
mysql_thread___session_track_variables, session_track_variables::ENFORCED,
myds->sess->session_fast_forward, mysql->options.client_flag and
CLIENT_DEPRECATE_EOF.
lib/MySQL_Session.cpp (1)

2685-2705: ⚠️ Potential issue | 🟠 Major

Keep optional session-track setup errors non-fatal.

The bounds check prevents the OOB write, but session_track_system_variables / session_track_state_change are still not entries in mysql_tracked_variables[]. If a backend returns a permitted setup error for these variables, idx remains the sentinel and this block falls through to the generic client ERR path. In OPTIONAL mode that turns a compatibility fallback into a user-visible query failure.

Handle these two setup variables explicitly: free the async result, clear the corresponding *_sent flag or mark tracking unsupported for this backend, and resume the previous status without touching var_absent.

Also applies to: 2820-2831

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/MySQL_Session.cpp` around lines 2685 - 2705, When a permitted setup error
occurs for session_track_system_variables or session_track_state_change, do not
treat it as a generic fatal client error; instead detect these two variable
names (session_track_system_variables and session_track_state_change) in the
error handling around mysql_tracked_variables lookup and, if matched, call
myds->myconn->async_free_result(), call
myconn->compute_unknown_transaction_status(), clear the appropriate
"<variable>_sent" flag (or set a tracking-unsupported flag for this backend)
rather than setting myconn->var_absent[idx], and then restore previous_status
with previous_status.pop() + NEXT_IMMEDIATE_NEW(st) and set myds->revents |=
POLLOUT and myds->DSS = STATE_MARIADB_GENERIC so processing resumes; ensure this
logic is applied in the same places as the var_absent handling (including the
other occurrence at the 2820–2831 block).
🟡 Minor comments (8)
test/infra/infra-mysql56-single/.env-1-7 (1)

1-7: ⚠️ Potential issue | 🟡 Minor

Remove or relocate this .env file; it will not be loaded by the mysql56-single test group.

The test runner uses infra-dbdeployer-mysql56-single (set in run-tests-isolated.bash:82 for mysql56-single* groups), so this .env in infra-mysql56-single will be ignored. The infra-dbdeployer-mysql56-single directory already has its own .env with complete configuration. If these variables are needed only for infra-mysql56-single, clarify its role; otherwise, this file should be removed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/infra/infra-mysql56-single/.env` around lines 1 - 7, This .env contains
variables (MYSQL_VERSION, PREFIX, WHG,
TAP_REG_TEST_3549_AUTOCOMMIT_TRACKING___MYSQL_SERVER_HOSTGROUP) that will be
ignored by the mysql56-single test group; remove this file from
infra-mysql56-single or move these variables into the
infra-dbdeployer-mysql56-single/.env (where the test runner actually loads env)
or a shared config location if they are needed by multiple groups, ensuring you
keep the same variable names and values when relocating.
test/tap/groups/mysql56-single/env.sh-5-7 (1)

5-7: ⚠️ Potential issue | 🟡 Minor

Add a ShellCheck source directive for the dynamic include.

This avoids SC1090 for the intentionally dynamic infra .env source.

Proposed fix
 if [ -n "${WORKSPACE}" ]; then
     _INFRA_ENV="${WORKSPACE}/test/infra/${DEFAULT_MYSQL_INFRA}/.env"
+    # shellcheck source=test/infra/infra-dbdeployer-mysql56-single/.env
     [ -f "${_INFRA_ENV}" ] && . "${_INFRA_ENV}"
 fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/groups/mysql56-single/env.sh` around lines 5 - 7, Add a ShellCheck
source directive to silence SC1090 for the dynamic include: insert a comment
like "# shellcheck source=/dev/null" immediately above the line that sources
"${_INFRA_ENV}" (the block using WORKSPACE, DEFAULT_MYSQL_INFRA and _INFRA_ENV)
so ShellCheck knows the runtime-driven file include is intentional.
test/infra/infra-mysql56-single/docker-compose.yml-11-12 (1)

11-12: ⚠️ Potential issue | 🟡 Minor

Data and log volumes bind-mounted to the same host path.

Both /var/lib/mysql (datadir) and /var/log/mysql (error/general/slow log dir per my.cnf) are mapped to the same host directory ${INFRA_LOGS_PATH}/${COMPOSE_PROJECT}/mysql1. MySQL will create log files next to tablespace files, configure_ssl.sh writes ca.pem/server-*.pem into the same tree, and any cleanup of logs risks touching the datadir. Please split them into distinct subdirectories, e.g. .../mysql1/data and .../mysql1/log.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/infra/infra-mysql56-single/docker-compose.yml` around lines 11 - 12, The
compose file mounts both /var/lib/mysql and /var/log/mysql to the same host path
(${INFRA_LOGS_PATH}/${COMPOSE_PROJECT}/mysql1); change the mounts to use
distinct host subdirectories so data and logs/certs are separated (e.g., map
${INFRA_LOGS_PATH}/${COMPOSE_PROJECT}/mysql1/data -> /var/lib/mysql and
${INFRA_LOGS_PATH}/${COMPOSE_PROJECT}/mysql1/log -> /var/log/mysql) to avoid
MySQL and configure_ssl.sh (which writes ca.pem/server-*.pem) placing logs or
certificates into the datadir.
test/infra/infra-mysql56-single/bin/docker-proxy-post.bash-14-18 (1)

14-18: ⚠️ Potential issue | 🟡 Minor

Dead error branch: set -e aborts before $? is checked.

With set -e at the top of the script, a failure of the docker exec … <<SQL heredoc exits the script immediately, so line 18's else branch never runs — errors will surface only as bare non-zero exit without the intended message. Either drop set -e around this call, capture the status into a variable before the if, or use if ! docker exec ...; then ...; fi:

Proposed change
-docker exec -i "${PROXY_CONTAINER}" mysql -uadmin -padmin -h127.0.0.1 -P6032 <<SQL
-${SQL_CONTENT}
-SQL
-
-if [ $? -eq 0 ]; then echo "Cluster ${INFRA} registered in ProxySQL."; else echo "ERROR: ProxySQL configuration FAILED for ${INFRA}"; exit 1; fi
+if docker exec -i "${PROXY_CONTAINER}" mysql -uadmin -padmin -h127.0.0.1 -P6032 <<SQL
+${SQL_CONTENT}
+SQL
+then
+    echo "Cluster ${INFRA} registered in ProxySQL."
+else
+    echo "ERROR: ProxySQL configuration FAILED for ${INFRA}"
+    exit 1
+fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/infra/infra-mysql56-single/bin/docker-proxy-post.bash` around lines 14 -
18, The current docker exec heredoc invocation (docker exec -i
"${PROXY_CONTAINER}" mysql -uadmin -padmin -h127.0.0.1 -P6032 <<SQL ...
${SQL_CONTENT} SQL) is run under set -e so failures abort before the subsequent
if [$?] check; change the block to reliably handle failure by either (a)
capturing the command result into a variable immediately after the heredoc
(rc=$?) and then testing rc in the if/else that prints "Cluster ${INFRA}
registered..." or the error message, or (b) replacing the heredoc invocation
with an if/then construct (if docker exec -i "${PROXY_CONTAINER}" mysql ...
<<SQL; then echo "Cluster ${INFRA} registered in ProxySQL."; else echo "ERROR:
ProxySQL configuration FAILED for ${INFRA}"; exit 1; fi) so the error branch
runs even with set -e; reference the docker exec heredoc, ${PROXY_CONTAINER},
${SQL_CONTENT}, and ${INFRA} when applying the change.
test/infra/infra-dbdeployer-mysql56-single/bin/docker-proxy-post.bash-11-12 (1)

11-12: ⚠️ Potential issue | 🟡 Minor

Load the SQL template relative to the script directory.

Line 11 ignores SCRIPT_DIR, so invoking this hook from any directory other than the infra root reads the wrong path or fails before configuring ProxySQL.

Proposed fix
-SQL_TEMPLATE=$(cat ./conf/proxysql/infra-config.sql)
+SQL_TEMPLATE=$(cat "${SCRIPT_DIR}/../conf/proxysql/infra-config.sql")
 SQL_CONTENT=$(eval "echo \"${SQL_TEMPLATE}\"")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/infra/infra-dbdeployer-mysql56-single/bin/docker-proxy-post.bash` around
lines 11 - 12, The SQL template is being read with a relative path
(SQL_TEMPLATE) which ignores SCRIPT_DIR; update the SQL_TEMPLATE assignment to
load the file using SCRIPT_DIR (e.g., point to
"${SCRIPT_DIR}/conf/proxysql/infra-config.sql") so the script works when invoked
from any CWD, and ensure SCRIPT_DIR is defined (or computed from the script
location like dirname of BASH_SOURCE) before SQL_TEMPLATE is evaluated; leave
SQL_CONTENT evaluation as-is.
test/tap/tests/session_track_variables.h-99-130 (1)

99-130: ⚠️ Potential issue | 🟡 Minor

Docstring does not match implementation.

The doc on line 108 says set_value is "The actual value of innodb_lock_wait_timeout after the procedure call", but the function body only assigns set_value = -1 at line 112 and never queries the server to populate it. Callers must invoke select_sess_var_value() separately (which test_session_variables_ff does at line 227). Either:

  • Call select_sess_var_value() inside set_session_variable() before returning, or
  • Update the doc to say set_value is reset to -1 and the caller must retrieve the actual value.

Misleading docs on shared test helpers tend to cause subtle test flakes down the line.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/tests/session_track_variables.h` around lines 99 - 130, The
docstring for set_session_variable is incorrect because the function never
populates set_value; modify set_session_variable(MYSQL* proxy, int& set_value)
to call select_sess_var_value(proxy, "innodb_lock_wait_timeout", set_value) (or
the existing helper used elsewhere) after the CALL
test.set_innodb_lock_wait_timeout(), check its return and propagate EXIT_FAILURE
on error (otherwise leave/return EXIT_SUCCESS), so callers no longer need to
separately invoke select_sess_var_value; update or remove the initial set_value
= -1 behavior as appropriate and keep the docstring consistent with this
behavior.
test/tap/tests/session_track_variables.h-164-201 (1)

164-201: ⚠️ Potential issue | 🟡 Minor

Unhandled JSON exceptions will abort the test process.

nlohmann::json::parse(row[0]) at line 180 throws on malformed input, and std::stoi(...) at lines 187 and 196 throws on empty/non-numeric strings. Without a try/catch, any anomaly in the PROXYSQL INTERNAL SESSION output kills the test with an uninformative abort rather than producing a proper TAP failure. Consider wrapping the JSON access in try { ... } catch (const std::exception& e) { diag("JSON parse error: %s", e.what()); ... return EXIT_FAILURE; } so tests report a clean failure.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/tests/session_track_variables.h` around lines 164 - 201, Wrap the
JSON parsing and numeric conversions inside extract_sess_var_proxy_internal in a
try/catch block: catch std::exception (or const std::exception& e) around
nlohmann::json::parse(row[0]) and the std::stoi(...) calls for
backend["conn"]["innodb_lock_wait_timeout"] and
j_session["conn"]["innodb_lock_wait_timeout"], call diag("JSON parse/convert
error: %s", e.what()), ensure mysql_free_result(result) is called before
returning, and return EXIT_FAILURE instead of allowing exceptions to propagate
and abort the test.
test/infra/infra-dbdeployer-mysql56-single/docker-compose-init.bash-102-102 (1)

102-102: ⚠️ Potential issue | 🟡 Minor

Brittle hardcoded container name.

CONTAINER="${COMPOSE_PROJECT}-dbdeployer1-1" hardcodes the Compose V2 dash-style naming convention and replica index 1. This breaks if:

  • Docker Compose V1 (underscore separator) is used (the script still supports docker-compose at line 31).
  • COMPOSE_COMPATIBILITY=1 is set or service name includes a project prefix override.

You already resolve container names via $COMPOSE_CMD -p "${COMPOSE_PROJECT}" ps --format '{{.Name}}' at line 86 — reuse that approach.

🔧 Proposed fix
-CONTAINER="${COMPOSE_PROJECT}-dbdeployer1-1"
+CONTAINER=$($COMPOSE_CMD -p "${COMPOSE_PROJECT}" ps --format '{{.Name}}' dbdeployer1 | head -n1)
+if [ -z "${CONTAINER}" ]; then
+    echo "ERROR: Could not resolve dbdeployer1 container name"
+    exit 1
+fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/infra/infra-dbdeployer-mysql56-single/docker-compose-init.bash` at line
102, The script currently hardcodes
CONTAINER="${COMPOSE_PROJECT}-dbdeployer1-1", which is brittle; instead derive
CONTAINER from the Compose ps output you already generate with $COMPOSE_CMD -p
"${COMPOSE_PROJECT}" ps --format '{{.Name}}' (used earlier) — filter that output
for the dbdeployer service (e.g., match "dbdeployer" or the service base name)
and assign the matching entry to CONTAINER so it works with both Compose V1/V2
and compatibility mode; update the assignment of CONTAINER to use that
ps-derived value and remove the hardcoded string.
🧹 Nitpick comments (6)
test/infra/infra-mysql56-single/conf/mysql/mysql1/my.cnf (1)

26-31: Enabling the general log plus slow-log-without-indexes will be very noisy.

general_log=1 writes every statement executed against this test MySQL, and log_queries_not_using_indexes=1 combined with long_query_time=2 will spam the slow log from many TAP tests. On longer CI runs this can fill ${INFRA_LOGS_PATH} quickly and perceptibly slow query throughput. Consider leaving general_log=0 by default and flipping it on only when debugging a specific failure.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/infra/infra-mysql56-single/conf/mysql/mysql1/my.cnf` around lines 26 -
31, The configuration enables both general_log and noisy slow-query logging
which will flood logs; change general_log=1 to general_log=0 by default in the
MySQL config and keep slow_query_log and log_queries_not_using_indexes as-is (or
increase long_query_time) so normal TAP runs aren’t spammed; ensure the change
is made to the my.cnf entry containing general_log, slow_query_log_file,
log_queries_not_using_indexes and long_query_time so general query logging is
only enabled manually when debugging.
test/infra/infra-mysql56-single/bin/docker-proxy-post.bash (1)

11-12: eval "echo ..." on the SQL template is fragile and unsafe.

Passing the contents of infra-config.sql through eval means any `...` or $(...) that ends up in the SQL (now or in future edits — e.g. a password containing $, a comment with backticks) will be executed as a shell command, and any literal \, ", ! etc. must also be shell-escaped. Prefer envsubst (or a whitelist of variables) for pure variable substitution:

Proposed change
-SQL_TEMPLATE=$(cat ./conf/proxysql/infra-config.sql)
-SQL_CONTENT=$(eval "echo \"${SQL_TEMPLATE}\"")
+SQL_CONTENT=$(envsubst '${INFRA} ${INFRA_ID} ${WHG} ${ROOT_PASSWORD}' \
+    < ./conf/proxysql/infra-config.sql)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/infra/infra-mysql56-single/bin/docker-proxy-post.bash` around lines 11 -
12, The current code reads infra-config.sql into SQL_TEMPLATE and then uses
unsafe eval in SQL_CONTENT (SQL_TEMPLATE and SQL_CONTENT) which will execute any
shell metacharacters; instead replace the eval "echo ..." step with a safe
variable substitution approach such as piping the template through envsubst or
using a controlled whitelist-based replacement: load the file into SQL_TEMPLATE
as before, then run envsubst on that content (or implement explicit parameter
replacement for known variables) to produce SQL_CONTENT, ensuring
infra-config.sql is not executed by the shell.
test/infra/infra-dbdeployer-mysql56-single/docker/Dockerfile (1)

22-31: Consider verifying the MySQL tarball checksum.

The MySQL 5.6.49 tarball is downloaded over the network and consumed without any integrity check. A network MITM or a future URL/redirect change would silently produce a compromised image. Since the version is pinned via ARG, pinning the SHA256 alongside it is cheap and makes the build reproducible.

Proposed change
 ARG DBDEPLOYER_VERSION=2.2.1
 ARG MYSQL_TARBALL_URL=https://dev.mysql.com/get/Downloads/MySQL-5.6/mysql-5.6.49-linux-glibc2.12-x86_64.tar.gz
+ARG MYSQL_TARBALL_SHA256=<sha256-of-5.6.49-tarball>
 ...
     && curl -fsSL -o /root/downloads/mysql-5.6.49-linux-glibc2.12-x86_64.tar.gz "${MYSQL_TARBALL_URL}" \
+    && echo "${MYSQL_TARBALL_SHA256}  /root/downloads/mysql-5.6.49-linux-glibc2.12-x86_64.tar.gz" | sha256sum -c - \
     && dbdeployer unpack /root/downloads/mysql-5.6.49-linux-glibc2.12-x86_64.tar.gz \
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/infra/infra-dbdeployer-mysql56-single/docker/Dockerfile` around lines 22
- 31, Add a pinned SHA256 variable and validate the downloaded tarball before
unpacking: introduce an ARG or ENV like MYSQL_TARBALL_SHA256 and after
downloading via curl (the current use of MYSQL_TARBALL_URL) compute the tarball
checksum (e.g., sha256sum) and compare it to MYSQL_TARBALL_SHA256, failing the
build if it does not match; only proceed to call dbdeployer unpack on successful
verification and remove the tarball on mismatch/after verification. Reference
the existing symbols MYSQL_TARBALL_URL, the downloaded file path
/root/downloads/mysql-5.6.49-linux-glibc2.12-x86_64.tar.gz, and the dbdeployer
unpack invocation to locate where to insert the check.
test/tap/tests/mysql-session_track_variables_ff_mysql56-t.cpp (1)

82-98: Magic error code 9001 and direct proxy->options manipulation.

Two nits worth addressing:

  1. Line 84 — directly writing proxy->options.client_flag |= CLIENT_DEPRECATE_EOF; touches internal client-library struct fields. The supported API is to pass client flags via the last argument of mysql_real_connect():

    mysql_real_connect(proxy, cl.host, "sbtest4", "sbtest4", NULL, cl.port, NULL, CLIENT_DEPRECATE_EOF);

    This is more portable across libmysqlclient/libmariadb versions.

  2. Line 95error_code == 9001 is a magic number for the ProxySQL-generated "backend connection unavailable" error. If the error code is redefined, this test breaks silently with a misleading diff. Consider referencing a named constant (e.g., ER_PROXYSQL_NO_BACKEND_CONNECTION or equivalent) or adding a comment citing the ProxySQL source where 9001 is defined.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/tests/mysql-session_track_variables_ff_mysql56-t.cpp` around lines
82 - 98, Don't write into the internal client struct via
proxy->options.client_flag; instead pass CLIENT_DEPRECATE_EOF through the
mysql_real_connect() client_flag parameter (update the mysql_real_connect call
that currently passes 0 as the last arg). Also avoid the magic literal 9001:
replace the direct comparison error_code == 9001 with a named constant or at
minimum add a clear comment referencing the ProxySQL source/name (e.g.,
ER_PROXYSQL_NO_BACKEND_CONNECTION) and use that named symbol where available so
the check in the ok(...) assertion refers to the constant instead of the
hardcoded 9001.
include/MySQL_Session.h (1)

246-274: Remove PR-specific markers from doc comments before merge.

The === PR 5166: Backend Configuration Entry Point === and === PR 5166: Variable Processing Workflow === annotations (also at line 359) embed transient PR metadata into permanent header documentation. This noise obscures the intent and ages poorly—git history is the right place for PR provenance. Consider trimming these blocks to a concise purpose statement and moving operational detail to the .cpp implementation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/MySQL_Session.h` around lines 246 - 274, Remove the transient PR
annotations (e.g., "=== PR 5166: Backend Configuration Entry Point ===" and the
similar marker at the other doc block around line 359) from the header comment
for the session-tracking function in include/MySQL_Session.h; replace them with
a concise purpose sentence for the function (e.g., "Configure session variable
tracking on backend connections") and move the operational/configuration details
(state machine steps, configuration logic) into the corresponding .cpp
implementation comments (or trim them to a brief summary in the header),
ensuring the header only documents the API contract and return semantics for the
function that orchestrates session tracking.
lib/MySQL_Session.cpp (1)

6077-6091: Mirror charset/collation side effects when applying backend notifications.

The SET parser keeps collation_connection and character_set_connection in sync, but this backend-notification path only updates the notified index. If the server reports only one side of that implicit change, ProxySQL’s client/server maps diverge and later backend verification can issue unnecessary or wrong charset resets.

Suggested direction
 				if (ci) {
 					value = std::to_string(ci->nr);
 				}
 
+				if (variable == "collation_connection") {
+					mysql_variables.client_set_value(this, SQL_CHARACTER_SET_CONNECTION, value.c_str());
+					mysql_variables.server_set_value(this, SQL_CHARACTER_SET_CONNECTION, value.c_str());
+				} else if (variable == "character_set_connection") {
+					mysql_variables.client_set_value(this, SQL_COLLATION_CONNECTION, value.c_str());
+					mysql_variables.server_set_value(this, SQL_COLLATION_CONNECTION, value.c_str());
+				}
+
-				mysql_variables.client_set_value(this, idx, value);
+				mysql_variables.client_set_value(this, idx, value.c_str());
 				mysql_variables.server_set_value(this, idx, value.c_str());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/MySQL_Session.cpp` around lines 6077 - 6091, The backend-notification
path only updates the notified variable index, causing client/server maps to
diverge for charset/collation pairs; update the mirrored counterpart when
handling "collation_connection" or any "character_set_*" change. After resolving
ci with proxysql_find_charset_name or proxysql_find_charset_collate, determine
the paired variable's index (the complementary "collation_connection" or the
appropriate
"character_set_connection"/"character_set_client"/"character_set_results"/"character_set_database"
mapping) and call mysql_variables.client_set_value(...) and
mysql_variables.server_set_value(...) for both the original idx and the paired
index so both sides stay in sync. Use the same value conversion
(std::to_string(ci->nr)) for the mirrored update and ensure you only mirror when
ci is non-null.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6c675f26-decd-4e52-8517-6d31f80a4091

📥 Commits

Reviewing files that changed from the base of the PR and between 872479a and 8d94919.

📒 Files selected for processing (45)
  • include/MySQL_HostGroups_Manager.h
  • include/MySQL_Session.h
  • include/MySQL_Thread.h
  • include/mysql_connection.h
  • include/proxysql_structs.h
  • lib/MyHGC.cpp
  • lib/MySQL_Protocol.cpp
  • lib/MySQL_Session.cpp
  • lib/MySQL_Thread.cpp
  • lib/MySrvC.cpp
  • lib/mysql_connection.cpp
  • test/infra/control/run-tests-isolated.bash
  • test/infra/infra-dbdeployer-mysql56-single/.env
  • test/infra/infra-dbdeployer-mysql56-single/bin/docker-mysql-post.bash
  • test/infra/infra-dbdeployer-mysql56-single/bin/docker-proxy-post.bash
  • test/infra/infra-dbdeployer-mysql56-single/conf/proxysql/infra-config.sql
  • test/infra/infra-dbdeployer-mysql56-single/docker-compose-destroy.bash
  • test/infra/infra-dbdeployer-mysql56-single/docker-compose-init.bash
  • test/infra/infra-dbdeployer-mysql56-single/docker-compose.yml
  • test/infra/infra-dbdeployer-mysql56-single/docker/Dockerfile
  • test/infra/infra-dbdeployer-mysql56-single/docker/build.sh
  • test/infra/infra-dbdeployer-mysql56-single/docker/entrypoint.sh
  • test/infra/infra-mysql56-single/.env
  • test/infra/infra-mysql56-single/bin/docker-mysql-post.bash
  • test/infra/infra-mysql56-single/bin/docker-proxy-post.bash
  • test/infra/infra-mysql56-single/conf/mysql/Dockerfile
  • test/infra/infra-mysql56-single/conf/mysql/docker-entrypoint.sh
  • test/infra/infra-mysql56-single/conf/mysql/mysql1/my.cnf
  • test/infra/infra-mysql56-single/conf/mysql/ssl/configure_ssl.sh
  • test/infra/infra-mysql56-single/conf/proxysql/infra-config.sql
  • test/infra/infra-mysql56-single/docker-compose-destroy.bash
  • test/infra/infra-mysql56-single/docker-compose-init.bash
  • test/infra/infra-mysql56-single/docker-compose.yml
  • test/tap/groups/groups.json
  • test/tap/groups/mysql56-single/env.sh
  • test/tap/groups/mysql56-single/infras.lst
  • test/tap/groups/mysql56-single/pre-proxysql.bash
  • test/tap/tap/utils.h
  • test/tap/tests/mysql-session_track_variables_enforced-t.cpp
  • test/tap/tests/mysql-session_track_variables_ff-t.cpp
  • test/tap/tests/mysql-session_track_variables_ff_mysql56-t.cpp
  • test/tap/tests/mysql-session_track_variables_optional-t.cpp
  • test/tap/tests/reg_test_4264-commit_rollback-t.cpp
  • test/tap/tests/session_track_variables.h
  • test/tap/tests/test_binlog_reader-t.cpp

Comment thread lib/MySQL_Session.cpp
Comment thread lib/MySQL_Thread.cpp Outdated
Comment thread lib/MySrvC.cpp Outdated
Comment thread test/tap/tests/mysql-session_track_variables_enforced-t.cpp
Comment thread test/tap/tests/mysql-session_track_variables_ff-t.cpp
Comment thread test/tap/tests/mysql-session_track_variables_ff-t.cpp
Comment thread test/tap/tests/mysql-session_track_variables_optional-t.cpp Outdated
Comment thread test/tap/tests/test_binlog_reader-t.cpp

@coderabbitai coderabbitai 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.

Actionable comments posted: 19

♻️ Duplicate comments (12)
lib/MyHGC.cpp (1)

41-43: ⚠️ Potential issue | 🟠 Major

Use an atomic/locked read for the backoff timestamp.

server_backoff_time can be updated by another session thread while this selection loop reads it. Keep the backoff check, but make the member atomic and use .load(std::memory_order_relaxed) here, or guard all reads/writes with the same mutex. As per coding guidelines, **/*.{cpp,h,hpp} should “Use pthread mutexes for synchronization and std::atomic<> for counters.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/MyHGC.cpp` around lines 41 - 43, The backoff check reads
mysrvc->server_backoff_time concurrently without synchronization; make
server_backoff_time a std::atomic<int64_t> (or appropriate integer type) in the
service struct/header and include <atomic>, then change the read here to
mysrvc->server_backoff_time.load(std::memory_order_relaxed) (or alternatively
protect both reads/writes with the same pthread mutex if you prefer mutexing);
update any writes to use .store(...) or atomic ops so all accesses are
consistently synchronized.
test/tap/tests/reg_test_4264-commit_rollback-t.cpp (1)

1156-1160: ⚠️ Potential issue | 🟠 Major

Don’t mask the new session-tracking traffic in this regression test.

This test depends on exact query/connection deltas, but disabling mysql-session_track_variables globally sidesteps the behavior added by this PR. Prefer taking baselines after connection-init/session-track setup, or explicitly subtract/ignore the internal setup queries, so transaction routing is still validated with tracking enabled.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/tests/reg_test_4264-commit_rollback-t.cpp` around lines 1156 - 1160,
The test currently disables session tracking by issuing MYSQL_QUERY_T(admin,
"SET mysql-session_track_variables=0") which masks the new session-tracking
traffic; remove or avoid that SET and instead collect the baseline after
performing connection-init/session-track setup (or explicitly subtract/ignore
the known internal setup queries like "LOAD MYSQL VARIABLES TO RUNTIME" and any
connection-init commands) so the test can validate transaction routing with
session tracking enabled; update the test around the MYSQL_QUERY_T calls (e.g.,
the block containing MYSQL_QUERY_T(admin, "SET
mysql-auto_increment_delay_multiplex=0"), MYSQL_QUERY_T(admin, "SET
mysql-default_session_track_gtids='OFF'"), MYSQL_QUERY_T(admin, "SET
mysql-session_track_variables=0"), and MYSQL_QUERY_T(admin, "LOAD MYSQL
VARIABLES TO RUNTIME")) to either not set mysql-session_track_variables to 0 or
to take baselines after setup and explicitly ignore the setup queries in the
delta comparison.
lib/MySrvC.cpp (1)

34-34: ⚠️ Potential issue | 🟠 Major

Make server_backoff_time thread-safe before using it across session threads.

This field is written from session handling and read during server/connection selection, but the backing member is still a plain unsigned long long; initialize/store/load it through std::atomic<unsigned long long> or guard all accesses with the hostgroup lock. As per coding guidelines, **/*.{cpp,h,hpp} should “Use pthread mutexes for synchronization and std::atomic<> for counters.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/MySrvC.cpp` at line 34, The field server_backoff_time is accessed by
session threads and the server/connection selection path but is a plain unsigned
long long; make it thread-safe by changing the backing member to
std::atomic<unsigned long long> (or, if you prefer locking, ensure every
read/write is protected by the existing hostgroup mutex), then update all uses
(writes in the session handling code and reads in the server/connection
selection code) to use atomic store/load semantics or to acquire the hostgroup
lock before accessing server_backoff_time; search for the symbol
server_backoff_time and the session/selection code paths to update all accesses
consistently.
test/tap/tests/test_binlog_reader-t.cpp (1)

464-465: ⚠️ Potential issue | 🟠 Major

Restore the global session-tracking setting in cleanup.

This disables mysql-session_track_variables at runtime but never restores the previous value, so later tests in the same ProxySQL instance can inherit the modified behavior. Save the original value before changing it and restore it at the cleanup: path after the assertions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/tests/test_binlog_reader-t.cpp` around lines 464 - 465, Before
calling MYSQL_QUERY_T(proxysql_admin, "SET mysql-session_track_variables=0"),
query and store the current value (e.g., run MYSQL_QUERY_T(proxysql_admin,
"SELECT @@mysql-session_track_variables") and save into a variable) and then
after the test assertions in the cleanup: block restore it by executing
MYSQL_QUERY_T(proxysql_admin, "SET
mysql-session_track_variables=<saved_value>"); ensure the saved value is used
even if the test fails so cleanup: always resets the global setting.
include/MySQL_Thread.h (1)

380-382: ⚠️ Potential issue | 🟡 Minor

Update ENFORCED docs to describe backoff/retry behavior, not hard failure.

The implementation path uses backend rejection/backoff for unsupported servers; “connection fails” reads like a client-visible hard failure and is misleading.

Proposed fix
-		// Enforce session tracking; connection fails if backend does
-		// not support CLIENT_DEPRECATE_EOF and CLIENT_SESSION_TRACKING
+		// Enforce session tracking; backends that do not support
+		// CLIENT_DEPRECATE_EOF and CLIENT_SESSION_TRACKING are rejected
+		// and put in temporary backoff.
-		 * - ENFORCED (2): Enforce session tracking; connection fails if backend
-		 *   does not support CLIENT_DEPRECATE_EOF and CLIENT_SESSION_TRACKING.
+		 * - ENFORCED (2): Enforce session tracking; unsupported backends are
+		 *   rejected and put in temporary backoff.

Verification:

#!/bin/bash
# Verify comments match the implementation behavior around capability handling and backoff.
rg -n -C4 'handle_session_track_capabilities|server_backoff_time|connection fails if backend|temporary backoff' include lib

Also applies to: 663-664

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/MySQL_Thread.h` around lines 380 - 382, Update the ENFORCED comment
to reflect that unsupported server session-tracking capabilities trigger a
client-side backoff/retry rather than an immediate hard failure: change wording
around the ENFORCED marker in include/MySQL_Thread.h to describe that when
CLIENT_DEPRECATE_EOF or CLIENT_SESSION_TRACKING are rejected the implementation
uses server_backoff_time and handle_session_track_capabilities to perform a
temporary backoff and retry connection attempts, not a fatal client-visible
failure; reference those symbols (handle_session_track_capabilities,
server_backoff_time, ENFORCED) in the docstring so the behavior matches the
code.
include/MySQL_HostGroups_Manager.h (1)

209-215: ⚠️ Potential issue | 🟠 Major

Make server_backoff_time atomic before sharing it across session threads.

This field is read during host/cached-connection selection (lib/MyHGC.cpp, lib/MySQL_Thread.cpp) and is written by session handling, so the plain unsigned long long introduces an unsynchronized data race. Use std::atomic<unsigned long long> and update call sites to load()/store().

Proposed fix
-	unsigned long long server_backoff_time;
+	std::atomic<unsigned long long> server_backoff_time { 0 };

Then update readers/writers, for example:

mysrvc->server_backoff_time.load(std::memory_order_relaxed)
mysrvc->server_backoff_time.store(backoff_until, std::memory_order_relaxed)

Verification:

#!/bin/bash
# Verify all server_backoff_time access sites are atomic load/store or otherwise lock-protected.
rg -n -C3 '\bserver_backoff_time\b' include lib

As per coding guidelines, use pthread mutexes for synchronization and std::atomic<> for counters.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/MySQL_HostGroups_Manager.h` around lines 209 - 215, Change the plain
field server_backoff_time to std::atomic<unsigned long long> in the class
declaration and update every access site to use atomic load/store (e.g. in
MyHGC::get_random_MySrvC, lib/MyHGC.cpp, lib/MySQL_Thread.cpp and any session
handling code that writes it) instead of direct reads/writes; use
mysrvc->server_backoff_time.load(std::memory_order_relaxed) for readers and
mysrvc->server_backoff_time.store(backoff_until, std::memory_order_relaxed) for
writers (or stronger ordering where logically required) and ensure no remaining
unsynchronized accesses remain.
test/infra/infra-dbdeployer-mysql56-single/docker-compose-destroy.bash (1)

4-8: ⚠️ Potential issue | 🟠 Major

Mirror init’s INFRA_ID handling before destroying the compose project.

docker-compose-init.bash preserves a caller-provided INFRA_ID; this script should do the same before deriving COMPOSE_PROJECT, otherwise down -v can target the wrong project and leave the real infra running.

Proposed fix
-pushd $(dirname $0) &>/dev/null
+pushd "$(dirname "$0")" &>/dev/null
 trap 'popd &>/dev/null' EXIT
-set -a; . .env; set +a
+if [ ! -f .env ]; then echo "Error: .env not found"; exit 1; fi
+SAVED_INFRA_ID="${INFRA_ID}"
+set -a; . .env; set +a
+[ -n "${SAVED_INFRA_ID}" ] && export INFRA_ID="${SAVED_INFRA_ID}"
 export INFRA=${PWD##*/}
 export COMPOSE_PROJECT="${INFRA}-${INFRA_ID}"

Verification:

#!/bin/bash
# Compare init/destroy project derivation. Expect both scripts to preserve caller-provided INFRA_ID
# before computing COMPOSE_PROJECT.
rg -n -C2 'SAVED_INFRA_ID|INFRA_ID|COMPOSE_PROJECT|set -a|pushd' \
  test/infra/infra-dbdeployer-mysql56-single/docker-compose-init.bash \
  test/infra/infra-dbdeployer-mysql56-single/docker-compose-destroy.bash
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/infra/infra-dbdeployer-mysql56-single/docker-compose-destroy.bash`
around lines 4 - 8, The destroy script must mirror init's INFRA_ID handling: if
a caller provided INFRA_ID, preserve it across sourcing .env before deriving
COMPOSE_PROJECT. Before "set -a; . .env; set +a" save any existing INFRA_ID
(e.g., SAVED_INFRA_ID="$INFRA_ID"), then source .env, then if SAVED_INFRA_ID is
non-empty restore INFRA_ID from it, and only after that compute
COMPOSE_PROJECT="${INFRA}-${INFRA_ID}" so docker-compose down -v targets the
same project as init.
test/tap/tests/mysql-session_track_variables_enforced-t.cpp (1)

16-20: ⚠️ Potential issue | 🟠 Major

Guard cleanup handles so failed reconnect still resets ENFORCED mode.

When Line 61 returns NULL, cleanup(admin, proxy) still runs and Line 19 closes proxy unconditionally. Keep the admin reset, but only close non-null handles.

Proposed fix
 int cleanup(MYSQL* admin, MYSQL* proxy) {
-	MYSQL_QUERY_T(admin, "SET mysql-session_track_variables=0");
-	MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME");
-	mysql_close(proxy);
-	mysql_close(admin);
+	if (admin) {
+		MYSQL_QUERY_T(admin, "SET mysql-session_track_variables=0");
+		MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME");
+	}
+	if (proxy) {
+		mysql_close(proxy);
+	}
+	if (admin) {
+		mysql_close(admin);
+	}
 	return EXIT_SUCCESS;
 }

Also applies to: 61-65

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/tests/mysql-session_track_variables_enforced-t.cpp` around lines 16
- 20, The cleanup function currently unconditionally calls mysql_close on both
proxy and admin which can crash when proxy is NULL after a failed reconnect;
update the cleanup(MYSQL* admin, MYSQL* proxy) routine to always execute the
admin reset queries (MYSQL_QUERY_T(admin, "SET
mysql-session_track_variables=0"); and MYSQL_QUERY_T(admin, "LOAD MYSQL
VARIABLES TO RUNTIME");) but only call mysql_close(proxy) if proxy is non-NULL
and only call mysql_close(admin) if admin is non-NULL, guarding both closes with
null checks to avoid closing null pointers.
lib/MySQL_Thread.cpp (1)

6394-6397: ⚠️ Potential issue | 🟠 Major

Keep server_backoff_time access synchronized.

This read still races with writes from the session path unless server_backoff_time has been converted to an atomic or guarded accessor everywhere. Use one synchronization strategy for the declaration, writes, and this read. Based on learnings, Applies to **/*.{cpp,h,hpp} : Use pthread mutexes for synchronization and std::atomic<> for counters.

Proposed atomic fix
-		if (c->parent->server_backoff_time > curtime)
+		if (c->parent->server_backoff_time.load(std::memory_order_acquire) > curtime)
 			continue;

And update the backing field/write sites consistently:

-	unsigned long long server_backoff_time;
+	std::atomic<unsigned long long> server_backoff_time {0};
-	be_conn->parent->server_backoff_time = thread->curtime + (30 * 1000000);
+	be_conn->parent->server_backoff_time.store(
+		thread->curtime + (30 * 1000000),
+		std::memory_order_release
+	);

Run this read-only check to verify all access sites use the same synchronization model:

#!/bin/bash
set -euo pipefail

# Expectation: declaration is std::atomic<...> or all reads/writes go through a mutex-protected accessor.
rg -n -C3 '\bserver_backoff_time\b' \
  include/MySQL_HostGroups_Manager.h \
  lib/MySQL_Thread.cpp \
  lib/MySQL_Session.cpp \
  lib/MyHGC.cpp

# Expectation: no direct plain assignments/reads remain if the field is atomic.
rg -n -C2 '\bserver_backoff_time\s*(=|\.store|\.load)\b|server_backoff_time\s*[<>!=]' \
  include/MySQL_HostGroups_Manager.h \
  lib/MySQL_Thread.cpp \
  lib/MySQL_Session.cpp \
  lib/MyHGC.cpp
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/MySQL_Thread.cpp` around lines 6394 - 6397, The read of
server_backoff_time (seen at c->parent->server_backoff_time in
MySQL_Thread::...) races with writers; make server_backoff_time access
consistent by choosing one strategy (preferred: protect the field with a pthread
mutex on the host/group object and add guarded accessor methods, or convert the
declaration to std::atomic<time_t> and update all writes/reads to use
.store/.load). Update the declaration in MySQL_HostGroups_Manager (or the host
struct/class that defines server_backoff_time), change all write sites in
MySQL_Session.cpp and MyHGC.cpp to use the chosen synchronization (mutex lock +
accessor or atomic store), and change this read in MySQL_Thread.cpp to use the
same accessor or atomic load; then run the provided grep-based verification
script to ensure no remaining unsynchronized direct accesses.
lib/MySQL_Session.cpp (3)

2006-2016: ⚠️ Potential issue | 🟡 Minor

Set the session-track “sent” flags only after the SET succeeds.

session_track_variables_sent and session_track_state_sent are flipped before the async SET completes. If a soft error/retry path returns to the previous status, the next verification skips the SET forever for that connection.

🛠️ Suggested direction

Move these flags to the success path of the corresponding SETTING_SESSION_TRACK_* handlers, or clear the specific flag before popping back to previous_status on retryable/ignored errors.

- mybe->server_myds->myconn->options.session_track_variables_sent = true;
  set_previous_status_mode3();
  NEXT_IMMEDIATE_NEW(SETTING_SESSION_TRACK_VARIABLES);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/MySQL_Session.cpp` around lines 2006 - 2016, The flags
session_track_variables_sent and session_track_state_sent are currently set
before the async SET completes; change this so the flags are only set on
successful completion of the corresponding async SETs (i.e., move the
assignments out of the current block and into the success/commit path of the
SETTING_SESSION_TRACK_VARIABLES and SETTING_SESSION_TRACK_STATE handlers),
and/or ensure any retry/pop-to-previous_status paths (invocations of
set_previous_status_mode3() / NEXT_IMMEDIATE_NEW) clear the appropriate flag
before returning so a failed/soft-retry does not permanently skip the SET for
that connection.

1940-1942: ⚠️ Potential issue | 🟠 Major

Use negotiated CLIENT_DEPRECATE_EOF, not advertised server capabilities.

These checks still use mysql->server_capabilities for CLIENT_DEPRECATE_EOF. For pooled/reused backend connections, validate be_conn->options.client_flag & CLIENT_DEPRECATE_EOF so ENFORCED/GTID/session-variable tracking only runs when EOF deprecation was actually negotiated. Keep CLIENT_SESSION_TRACKING against server-advertised capabilities.

🔎 Read-only verification
#!/bin/bash
# Verify where backend negotiated flags are copied and where session tracking still checks advertised capabilities.
# Expectation: CLIENT_DEPRECATE_EOF checks for backend session tracking should use options.client_flag.
rg -n -C3 --type=cpp --type=h \
  'options\.client_flag\s*=\s*mysql->client_flag|server_capabilities.*CLIENT_DEPRECATE_EOF|CLIENT_DEPRECATE_EOF.*server_capabilities|CLIENT_SESSION_TRACKING' \
  lib include
🛠️ Suggested direction
- if ((mybe->server_myds->myconn->mysql->server_capabilities & CLIENT_SESSION_TRACKING) == 0
-     || (mybe->server_myds->myconn->mysql->server_capabilities & CLIENT_DEPRECATE_EOF) == 0) {
+ MySQL_Connection* be_conn = mybe->server_myds->myconn;
+ if ((be_conn->mysql->server_capabilities & CLIENT_SESSION_TRACKING) == 0
+     || (be_conn->options.client_flag & CLIENT_DEPRECATE_EOF) == 0) {

Also applies to: 1998-2004, 9751-9757

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/MySQL_Session.cpp` around lines 1940 - 1942, The check is using the
server's advertised server_capabilities for CLIENT_DEPRECATE_EOF but must use
the negotiated client flags on the backend connection; change the second
conditional to test be_conn->options.client_flag & CLIENT_DEPRECATE_EOF (leave
CLIENT_SESSION_TRACKING check against mysql->server_capabilities) and ensure
mysql_thread___enable_server_deprecate_eof is still respected; update the
analogous checks at the other occurrences (around the blocks identified near
lines ~1998-2004 and ~9751-9757) so ENFORCED/GTID/session-variable tracking only
runs when CLIENT_DEPRECATE_EOF was actually negotiated on be_conn.

2678-2706: ⚠️ Potential issue | 🟠 Major

Keep permitted session_track_* setup errors non-fatal even when they are not tracked variables.

The bounds check avoids the out-of-bounds write, but session_track_system_variables and session_track_state_change are still not in mysql_tracked_variables. For 1193/permitted tracking errors, idx remains out of range and execution falls through to sending an ERR packet to the client, which breaks OPTIONAL fallback.

🛠️ Suggested direction
- if (idx >= 0 && idx < SQL_NAME_LAST_HIGH_WM) {
+ const bool known_tracked_variable = idx >= 0 && idx < SQL_NAME_LAST_HIGH_WM;
+ const bool ignorable_session_track_error = is_perm_track_err(myerr, var_name)
+     || strcasecmp(var_name, "session_track_system_variables") == 0
+     || strcasecmp(var_name, "session_track_state_change") == 0;
+
+ if (known_tracked_variable || ignorable_session_track_error) {
+     if (known_tracked_variable) {
          myconn->var_absent[idx] = true;
+     }

      myds->myconn->async_free_result();
      myconn->compute_unknown_transaction_status();

Also applies to: 2820-2831

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/MySQL_Session.cpp` around lines 2678 - 2706, The code currently only
handles permitted tracking errors (is_perm_track_err(var_name)) if the variable
is found in mysql_tracked_variables, causing an ERR to be sent when a permitted
session_track_* error refers to a variable that isn't tracked; fix by treating
permitted tracking errors as non-fatal even when idx is not within [0,
SQL_NAME_LAST_HIGH_WM): after computing idx, if is_perm_track_err(myerr,
var_name) and idx is out of bounds, do not send an error—perform the same
non-fatal flow (set myds->revents |= POLLOUT, set myds->DSS =
STATE_MARIADB_GENERIC, pop previous_status and call NEXT_IMMEDIATE_NEW) without
touching myconn->var_absent to avoid out-of-bounds writes; use
mysql_tracked_variables, is_perm_track_err, myconn->var_absent,
SQL_NAME_LAST_HIGH_WM, myds->revents, myds->DSS, previous_status, and
NEXT_IMMEDIATE_NEW to locate and implement the change.
🧹 Nitpick comments (7)
include/mysql_connection.h (1)

269-298: Add explicit #include <unordered_map> for clarity and self-contained inclusion.

The new get_variables() method at line 298 exposes std::unordered_map in the public API. While this type is available through a transitive include via cpp.h, adding an explicit include makes the dependency clear and improves header self-containment.

🛠️ Proposed fix
 `#include` "proxysql.h"
 `#include` "cpp.h"
+#include <unordered_map>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/mysql_connection.h` around lines 269 - 298, Add an explicit `#include`
<unordered_map> to the header so the public API type used by
get_variables(std::unordered_map<std::string, std::string>&) is directly
visible; modify include/mysql_connection.h to include <unordered_map> near other
standard headers so the declaration of bool get_variables(...) is self-contained
and does not rely on transitive includes like cpp.h.
test/tap/groups/mysql56-single/env.sh (1)

5-8: Use [[ instead of [ and suppress SC1090 for the dynamic .env source.

The dynamic source on line 7 triggers ShellCheck SC1090. Replace single brackets with [[, and add a # shellcheck source=/dev/null directive to silence the warning. These are bash best practices for this context.

Proposed fix
-if [ -n "${WORKSPACE}" ]; then
+if [[ -n "${WORKSPACE}" ]]; then
     _INFRA_ENV="${WORKSPACE}/test/infra/${DEFAULT_MYSQL_INFRA}/.env"
-    [ -f "${_INFRA_ENV}" ] && . "${_INFRA_ENV}"
+    if [[ -f "${_INFRA_ENV}" ]]; then
+        # shellcheck source=/dev/null
+        . "${_INFRA_ENV}"
+    fi
 fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/groups/mysql56-single/env.sh` around lines 5 - 8, Replace the POSIX
test brackets and silence ShellCheck for the dynamic env source: change the
conditionals using [ -n "${WORKSPACE}" ] and [ -f "${_INFRA_ENV}" ] to bash
conditional syntax [[ -n "${WORKSPACE}" ]] and [[ -f "${_INFRA_ENV}" ]], and add
a directive comment "# shellcheck source=/dev/null" immediately before the
dynamic source line that evaluates . "${_INFRA_ENV}" (referencing variables
WORKSPACE, _INFRA_ENV, DEFAULT_MYSQL_INFRA) so SC1090 is suppressed.
test/infra/infra-mysql56-single/bin/docker-proxy-post.bash (1)

11-12: Use the resolved script directory and avoid eval for the SQL template.

Line 11 ignores SCRIPT_DIR, so this script depends on the caller’s working directory. Line 12 also evaluates the SQL as shell code; prefer reading the file via an absolute script-relative path and only substituting known variables if needed.

Proposed hardening
-SQL_TEMPLATE=$(cat ./conf/proxysql/infra-config.sql)
-SQL_CONTENT=$(eval "echo \"${SQL_TEMPLATE}\"")
+SQL_TEMPLATE_PATH="${SCRIPT_DIR}/../conf/proxysql/infra-config.sql"
+SQL_CONTENT=$(cat "${SQL_TEMPLATE_PATH}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/infra/infra-mysql56-single/bin/docker-proxy-post.bash` around lines 11 -
12, The current code reads the SQL template relative to the CWD and uses eval to
expand it; change it to load the file using the resolved SCRIPT_DIR (use the
SCRIPT_DIR variable) to build an absolute path for
./conf/proxysql/infra-config.sql, and replace the use of eval for SQL_CONTENT
with a safe read/templating approach (e.g., read the file contents into
SQL_TEMPLATE and perform controlled variable substitution with a safe tool or
parameter expansion rather than eval) so SQL_TEMPLATE and SQL_CONTENT no longer
depend on the caller's working directory or execute arbitrary shell code.
test/infra/infra-mysql56-single/bin/docker-mysql-post.bash (1)

88-94: Optional: align CA extraction with the dbdeployer variant.

The sibling script uses docker exec ... cat ca.pem | tee -a ... while this one uses docker cp ... - | tar -Ox | tee -a .... Both work, but unifying on docker exec cat is simpler, avoids the tar-stream dependency, and keeps the two post-scripts symmetric.

♻️ Suggested change
-    docker cp "${CONTAINER}:/var/lib/mysql/ca.pem" - | tar -Ox | sudo tee -a "${DB_BUNDLE}" | sudo tee -a "${CA_BUNDLE}" >/dev/null
+    docker exec "${CONTAINER}" cat /var/lib/mysql/ca.pem | sudo tee -a "${DB_BUNDLE}" | sudo tee -a "${CA_BUNDLE}" >/dev/null
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/infra/infra-mysql56-single/bin/docker-mysql-post.bash` around lines 88 -
94, Replace the tar-stream-based CA extraction with a simpler docker exec/cat
pipeline used by the sibling script: instead of using docker cp
"${CONTAINER}:/var/lib/mysql/ca.pem" - | tar -Ox | sudo tee -a "${DB_BUNDLE}" |
sudo tee -a "${CA_BUNDLE}", run docker exec "${CONTAINER}" cat
/var/lib/mysql/ca.pem and pipe its output into sudo tee -a "${DB_BUNDLE}" and
sudo tee -a "${CA_BUNDLE}" (preserving the existing sudo chmod 666 on
"${DB_BUNDLE}" and "${CA_BUNDLE}" and the surrounding if test -f
/var/lib/mysql/ca.pem check).
test/tap/groups/groups.json (1)

95-98: Missing MySQL 8.x test-group coverage for new session-tracking tests.

mysql-session_track_variables_enforced-t and mysql-session_track_variables_optional-t are registered only for legacy-g4 and mysql56-single-g1, and mysql-session_track_variables_ff* only for legacy-g4 / mysql56-single-g1. Most comparable tests in this file (e.g., deprecate_eof_cache-t, eof_conn_options_check-t) also run on mysql84-g4/mysql90-g4/mysql95-g4. Given that ENFORCED mode specifically depends on CLIENT_SESSION_TRACK + CLIENT_DEPRECATE_EOF which differ across MySQL 5.7/8.0/8.4/9.x, running against those groups would catch protocol-level regressions the 5.6 and legacy backends cannot exercise.

♻️ Suggested coverage expansion
-  "mysql-session_track_variables_enforced-t" : [ "legacy-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4","mysql56-single-g1" ],
-  "mysql-session_track_variables_ff-t" : [ "legacy-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4" ],
-  "mysql-session_track_variables_ff_mysql56-t" : [ "mysql56-single-g1" ],
-  "mysql-session_track_variables_optional-t" : [ "legacy-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4","mysql56-single-g1" ],
+  "mysql-session_track_variables_enforced-t" : [ "legacy-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4","mysql84-g4","mysql90-g4","mysql95-g4","mysql56-single-g1" ],
+  "mysql-session_track_variables_ff-t" : [ "legacy-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4","mysql84-g4","mysql90-g4","mysql95-g4" ],
+  "mysql-session_track_variables_ff_mysql56-t" : [ "mysql56-single-g1" ],
+  "mysql-session_track_variables_optional-t" : [ "legacy-g4","mysql-auto_increment_delay_multiplex=0-g4","mysql-multiplexing=false-g4","mysql-query_digests=0-g4","mysql-query_digests_keep_comment=1-g4","mysql84-g4","mysql90-g4","mysql95-g4","mysql56-single-g1" ],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/groups/groups.json` around lines 95 - 98, The test groups for
session-tracking lack MySQL 8+ coverage; update the arrays for
"mysql-session_track_variables_enforced-t",
"mysql-session_track_variables_optional-t", and
"mysql-session_track_variables_ff-t" to also include "mysql84-g4", "mysql90-g4",
and "mysql95-g4" (keep "mysql-session_track_variables_ff_mysql56-t" as-is for
mysql56-specific runs). This ensures ENFORCED/OPTIONAL/FF variants run against
MySQL 8.x/9.x groups to catch protocol-level regressions.
include/MySQL_Session.h (1)

246-304: Keep header comments durable and concise.

The declarations look fine, but the new header docs include PR-specific labels and detailed workflow narration that will age quickly. Prefer a short API contract here and keep implementation workflow in the .cpp or design notes.

Also applies to: 356-384

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/MySQL_Session.h` around lines 246 - 304, The header comments are too
verbose and include PR-specific workflow; shorten them to a concise API contract
for the declared functions (e.g.,
handler_again___verify_backend_session_track_variables,
handler_again___verify_backend_multi_statement,
handler_again___verify_backend_user_schema,
handler_again___verify_multiple_variables,
handler_again___status_SETTING_INIT_CONNECT,
handler_again___status_SETTING_LDAP_USER_VARIABLE,
handler_again___status_SETTING_SQL_MODE,
handler_again___status_SETTING_SESSION_TRACK_GTIDS,
handler_again___status_SETTING_SESSION_TRACK_VARIABLES,
handler_again___status_SETTING_SESSION_TRACK_STATE): remove PR labels,
step-by-step state machine narration and implementation details, leaving a one-
or two-sentence description of purpose, parameters, and return semantics; move
any detailed workflow or rationale into the .cpp implementation or design notes.
test/tap/tests/session_track_variables.h (1)

111-228: Hardcoded innodb_lock_wait_timeout limits portability.

Every helper (set_session_variable, select_sess_var_value, extract_sess_var_proxy_internal, and test_session_variables_ff) is pinned to innodb_lock_wait_timeout. As already raised in the PR discussion, that variable is InnoDB-specific and may not behave identically on Aurora or MyRocks deployments. Parameterise the variable name (or pick a server-generic one like wait_timeout / net_read_timeout that is writable at session scope) so the same helper can be reused across infra targets without reimplementation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/tests/session_track_variables.h` around lines 111 - 228, The helpers
are hardcoded to "innodb_lock_wait_timeout", breaking portability; update
set_session_variable, select_sess_var_value, extract_sess_var_proxy_internal,
and test_session_variables_ff to accept a variable-name parameter (e.g., const
std::string& var_name or const char* var_name) and use that name when building
the CREATE PROCEDURE, CALL, SELECT @@..., and when extracting JSON keys from
PROXYSQL INTERNAL SESSION and mysql packet tracking; keep current behavior by
allowing a default value (e.g., "wait_timeout") when none is provided so tests
can run against non-InnoDB backends without duplicating logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/MySQL_Session.cpp`:
- Around line 9764-9767: The write to backend-wide server_backoff_time from
session logic (see MySrvC::server_backoff_time being set via
be_conn->parent->server_backoff_time = thread->curtime + (30 * 1000000)) must be
made thread-safe to avoid races with concurrent hostgroup/server reads; either
change MySrvC::server_backoff_time to an atomic type (e.g.,
std::atomic<uint64_t>) and use atomic stores/loads where referenced, or protect
both writes and reads with the existing hostgroup/server pthread mutex around
the assignment and all accesses. Also replace the magic constant (30 * 1000000)
and the stale 10-minute comment with a named constexpr (e.g.,
BACKOFF_MICROSECONDS) so the intention is clear and maintainable; update any
code that reads server_backoff_time to use atomic load or hold the mutex
accordingly.

In `@test/infra/infra-dbdeployer-mysql56-single/bin/docker-mysql-post.bash`:
- Around line 5-8: The script currently only sources .env before expanding
COMPOSE_PROJECT, INFRA_LOGS_PATH and ROOT_PASSWORD, so CONTAINER and BUNDLE_DIR
can be empty; update the startup sequence to first source the isolated env file
(.env.isolated.${INFRA_ID}) if it exists (falling back to .env) before any use
of COMPOSE_PROJECT, INFRA_LOGS_PATH or ROOT_PASSWORD, ensuring variables used to
build CONTAINER and BUNDLE_DIR and passed as -p"${ROOT_PASSWORD}" are populated;
reference the variable names CONTAINER, BUNDLE_DIR, COMPOSE_PROJECT,
INFRA_LOGS_PATH and ROOT_PASSWORD when adding the conditional source.

In `@test/infra/infra-dbdeployer-mysql56-single/bin/docker-proxy-post.bash`:
- Around line 14-18: The current docker exec invocation that runs mysql (the
here-doc block invoking ${SQL_CONTENT}) can abort the script under set -e before
the failure echo runs; move the docker exec mysql command into the conditional
check (i.e., run docker exec -i "${PROXY_CONTAINER}" mysql ... <<SQL inside an
if ...; then echo success in the then branch and the error/fail and exit 1 in
the else branch) so the failure branch becomes reachable; update the conditional
that references $? to instead test the command result directly around the docker
exec invocation (use the same ${PROXY_CONTAINER}, ${SQL_CONTENT}, and ${INFRA}
symbols).
- Around line 11-12: The current expansion uses eval on SQL_TEMPLATE then
assigns to SQL_CONTENT which is unsafe; instead, read the template via a
script-relative path (resolve with dirname "$0") into SQL_TEMPLATE and perform a
bounded, non-evaluating substitution to produce SQL_CONTENT (e.g. run the
template through envsubst with an explicit variable whitelist like
'${ROOT_PASSWORD}' or use a sed/awk replacement for specific placeholders) so
shell syntax is not executed and credentials with $, backticks or backslashes
are preserved; update the SQL_TEMPLATE/SQL_CONTENT assignment to use this safe
substitution approach rather than eval "echo ...".

In `@test/infra/infra-dbdeployer-mysql56-single/conf/proxysql/infra-config.sql`:
- Line 8: The DELETE statement targeting mysql_servers uses LIKE 'test server'
which is misleading and won't match variants; update the WHERE clause on the
comment column to either use equality (WHERE comment = 'test server') if you
only mean that exact value, or add the intended wildcard(s) (e.g. WHERE comment
LIKE 'test server%' or LIKE '%test server%') so variants like 'test
server-${INFRA}' are captured; modify the DELETE FROM mysql_servers ... WHERE
comment ... accordingly.

In `@test/infra/infra-dbdeployer-mysql56-single/docker/Dockerfile`:
- Around line 22-35: The Dockerfile hard-codes 5.6.49 in the downloaded filename
and ENV PATH which will break if MYSQL_TARBALL_URL points to a different
version; update the Dockerfile so the MySQL version is derived and used
consistently: introduce or use an ARG (e.g., MYSQL_VERSION) or parse the tarball
basename from MYSQL_TARBALL_URL before calling dbdeployer unpack (references:
the RUN line that invokes curl and dbdeployer unpack and the ENV
PATH="/root/opt/mysql/5.6.49/bin:${PATH}"), then replace the hard-coded 5.6.49
with the variable (e.g., /root/opt/mysql/${MYSQL_VERSION}/bin) so the created
installation path and PATH env match the downloaded tarball.

In `@test/infra/infra-dbdeployer-mysql56-single/docker/entrypoint.sh`:
- Around line 26-57: Detect if SANDBOX_DIR exists and, instead of re-running
dbdeployer deploy single, start the existing sandbox by invoking its generated
start script (use ${SANDBOX_DIR}/start) and skip deploy; then determine the
correct root password for already-initialized instances and set
DBDEPLOYER_ROOT_PASS accordingly (try connecting with the current
DBDEPLOYER_ROOT_PASS first, and if that fails, read the stored credential/config
created by dbdeployer inside ${SANDBOX_DIR}—e.g. sandbox credential or
my.sandbox.cnf/log files—and update MYSQL_CMD to use the discovered password);
finally, keep the readiness loop but use the adjusted MYSQL_CMD so restarts are
idempotent and work with existing sandboxes.
- Around line 16-24: The script currently only checks for /root/certs/ca.pem
before skipping certificate creation; change the existence check to require the
full SSL set (ca.pem, server-key.pem, server-cert.pem) so generation runs if any
of those files are missing. Update the conditional around the openssl generation
block in entrypoint.sh to test all three files (ca.pem, server-key.pem,
server-cert.pem) and only skip when all exist, and ensure the subsequent chmod
/root/certs/server-key.pem remains executed after generation.

In `@test/infra/infra-mysql56-single/bin/docker-proxy-post.bash`:
- Around line 14-18: The current docker exec invocation with here-doc will abort
the script under set -e before the subsequent conditional runs, so change the
logic to test the command directly: run the docker exec mysql -uadmin -padmin
-h127.0.0.1 -P6032 <<SQL ${SQL_CONTENT} SQL inside an if statement (e.g. if
docker exec ... <<SQL ...; then ...) so the success and failure branches (echo
"Cluster ${INFRA} registered in ProxySQL." and the ERROR/exit branch referencing
${INFRA}) are executed reliably; adjust to reference the existing variables
PROXY_CONTAINER, SQL_CONTENT and INFRA so the error message is reachable under
set -e.

In `@test/infra/infra-mysql56-single/docker-compose-destroy.bash`:
- Line 4: The pushd call uses an unquoted command substitution pushd $(dirname
$0) which can break on spaces or glob characters; update the invocation to quote
both the command substitution and the $0 argument so dirname receives a quoted
$0 and pushd receives a quoted result (i.e., use quoted forms around dirname and
the substitution) to prevent word-splitting and globbing when resolving the
script directory.

In `@test/infra/infra-mysql56-single/docker-compose.yml`:
- Around line 9-12: The docker-compose volumes mount both /var/lib/mysql and
/var/log/mysql to the same host path, mixing data and logs; update the volumes
under the volumes: block so /var/lib/mysql maps to a dedicated data directory
(e.g. ${INFRA_LOGS_PATH}/${COMPOSE_PROJECT}/mysql1/data) and /var/log/mysql maps
to a separate logs directory (e.g.
${INFRA_LOGS_PATH}/${COMPOSE_PROJECT}/mysql1/logs), leaving the my.cnf mount
unchanged; adjust references to the environment variables INFRA_LOGS_PATH and
COMPOSE_PROJECT in the docker-compose.yml volumes section for mysql1 so data and
logs are stored in distinct subdirectories.

In `@test/tap/tap/utils.h`:
- Around line 184-190: The fprintf in the MYSQL_QUERY_T macro uses the wrong
format for mysql_errno(): change the "%d" specifier to "%u" so the call in
MYSQL_QUERY_T (which logs mysql_errno(mysql) and mysql_error(mysql) after
mysql_query_t fails) prints the unsigned int correctly; locate the MYSQL_QUERY_T
macro and update the format string in the fprintf accordingly.

In `@test/tap/tests/mysql-session_track_variables_ff_mysql56-t.cpp`:
- Around line 82-84: Null-check the result of mysql_init() before accessing
proxy->options.client_flag: after calling mysql_init(NULL) assign to proxy and
immediately test if (proxy == NULL) and handle the error (e.g., emit a TAP
diagnostic or gracefully abort the test) instead of dereferencing proxy; update
the code around the mysql_init call and the CLIENT_DEPRECATE_EOF line so the
option is only set when proxy is non-NULL to avoid the segfault in mysql_init.
- Around line 90-97: The test currently asserts an exact ProxySQL error code
9001 immediately after calling mysql_query(proxy, ...) which is fragile and
lacks a timeout; update the test to set a read timeout on the proxy MYSQL handle
(use MYSQL_OPT_READ_TIMEOUT on proxy) before calling mysql_query to avoid hangs,
and replace the hardcoded 9001 check with a robust assertion that either (a)
uses a named constant from a shared test helper (e.g.
PROXY_ERR_FAST_FORWARD_UNSUPPORTED) or (b) accepts any ProxySQL-side error in
the 9xxx range (error_code >= 9000 && error_code < 10000) while printing the
actual code in the diag message; reference mysql_query, mysql_errno,
mysql_error, proxy, and MYSQL_OPT_READ_TIMEOUT when making these changes.
- Around line 28-64: The test function test_ff_optional_mysql56 leaks runtime
state on early returns: if mysql_real_connect or test_session_variables_ff fails
the function returns before the cleanup that resets mysql_users.fast_forward and
mysql-session_track_variables; change control flow so both early-error paths
jump to a shared cleanup/exit label (or use a small RAII-style guard) that
always executes the same cleanup SQL (the UPDATE/LOAD and SET/LOAD sequence
currently in the end of test_ff_optional_mysql56) before closing proxy and
returning, apply the same fix to test_ff_enforced_mysql56 early returns, and
update main() to wrap the calls to test_ff_optional_mysql56 and
test_ff_enforced_mysql56 with ok(...) assertions so their return values produce
TAP failures instead of being ignored.

In `@test/tap/tests/mysql-session_track_variables_ff-t.cpp`:
- Around line 34-40: Ensure mysql_init(NULL) is null-checked before
dereferencing proxy->options.client_flag: after calling mysql_init() and
assigning to proxy, test if proxy == NULL and if so print an error (e.g., using
fprintf with mysql_error(NULL) or a descriptive message) and return
EXIT_FAILURE; only then modify proxy->options.client_flag to set
CLIENT_DEPRECATE_EOF so you avoid a potential segfault in the test when
mysql_init fails.

In `@test/tap/tests/session_track_variables.h`:
- Around line 139-153: The helper select_sess_var_value currently always returns
EXIT_SUCCESS even when mysql_store_result returns NULL or when mysql_fetch_row
yields no rows, leaving set_value at -1 and masking failures; update
select_sess_var_value to check the result of mysql_store_result and
mysql_fetch_row and return EXIT_FAILURE (or a non-zero error code) when result
is NULL or no row is fetched, only setting set_value from atoi(row[0]) and
returning EXIT_SUCCESS when a valid row was retrieved; reference
MYSQL_QUERY_T(proxy, ...), mysql_store_result(proxy), mysql_fetch_row(result),
and set_value in your changes.
- Around line 164-201: In extract_sess_var_proxy_internal replace the
inconsistent MYSQL_QUERY call with the standard MYSQL_QUERY_T macro used by
other helpers, and wrap the nlohmann::json::parse(row[0]) call in a
try/catch(nlohmann::json::exception&) block so that if parsing fails (or row[0]
is null/malformed) you catch the exception, mysql_free_result(result) if needed
and return EXIT_FAILURE instead of letting the exception abort the test process.
- Around line 111-130: The helper set_session_variable initializes set_value to
-1 but never writes the actual innodb_lock_wait_timeout, causing a
docstring/behaviour mismatch; fix by either (A) populating set_value after the
CALL by running a query (e.g., SELECT @@session.innodb_lock_wait_timeout or
invoke/select_sess_var_value) and parsing the result into set_value, or (B)
update the function's docstring/param comment to state that set_value is left as
-1 and the caller must call select_sess_var_value to obtain the real value;
reference set_session_variable and select_sess_var_value when applying the
change.

---

Duplicate comments:
In `@include/MySQL_HostGroups_Manager.h`:
- Around line 209-215: Change the plain field server_backoff_time to
std::atomic<unsigned long long> in the class declaration and update every access
site to use atomic load/store (e.g. in MyHGC::get_random_MySrvC, lib/MyHGC.cpp,
lib/MySQL_Thread.cpp and any session handling code that writes it) instead of
direct reads/writes; use
mysrvc->server_backoff_time.load(std::memory_order_relaxed) for readers and
mysrvc->server_backoff_time.store(backoff_until, std::memory_order_relaxed) for
writers (or stronger ordering where logically required) and ensure no remaining
unsynchronized accesses remain.

In `@include/MySQL_Thread.h`:
- Around line 380-382: Update the ENFORCED comment to reflect that unsupported
server session-tracking capabilities trigger a client-side backoff/retry rather
than an immediate hard failure: change wording around the ENFORCED marker in
include/MySQL_Thread.h to describe that when CLIENT_DEPRECATE_EOF or
CLIENT_SESSION_TRACKING are rejected the implementation uses server_backoff_time
and handle_session_track_capabilities to perform a temporary backoff and retry
connection attempts, not a fatal client-visible failure; reference those symbols
(handle_session_track_capabilities, server_backoff_time, ENFORCED) in the
docstring so the behavior matches the code.

In `@lib/MyHGC.cpp`:
- Around line 41-43: The backoff check reads mysrvc->server_backoff_time
concurrently without synchronization; make server_backoff_time a
std::atomic<int64_t> (or appropriate integer type) in the service struct/header
and include <atomic>, then change the read here to
mysrvc->server_backoff_time.load(std::memory_order_relaxed) (or alternatively
protect both reads/writes with the same pthread mutex if you prefer mutexing);
update any writes to use .store(...) or atomic ops so all accesses are
consistently synchronized.

In `@lib/MySQL_Session.cpp`:
- Around line 2006-2016: The flags session_track_variables_sent and
session_track_state_sent are currently set before the async SET completes;
change this so the flags are only set on successful completion of the
corresponding async SETs (i.e., move the assignments out of the current block
and into the success/commit path of the SETTING_SESSION_TRACK_VARIABLES and
SETTING_SESSION_TRACK_STATE handlers), and/or ensure any
retry/pop-to-previous_status paths (invocations of set_previous_status_mode3() /
NEXT_IMMEDIATE_NEW) clear the appropriate flag before returning so a
failed/soft-retry does not permanently skip the SET for that connection.
- Around line 1940-1942: The check is using the server's advertised
server_capabilities for CLIENT_DEPRECATE_EOF but must use the negotiated client
flags on the backend connection; change the second conditional to test
be_conn->options.client_flag & CLIENT_DEPRECATE_EOF (leave
CLIENT_SESSION_TRACKING check against mysql->server_capabilities) and ensure
mysql_thread___enable_server_deprecate_eof is still respected; update the
analogous checks at the other occurrences (around the blocks identified near
lines ~1998-2004 and ~9751-9757) so ENFORCED/GTID/session-variable tracking only
runs when CLIENT_DEPRECATE_EOF was actually negotiated on be_conn.
- Around line 2678-2706: The code currently only handles permitted tracking
errors (is_perm_track_err(var_name)) if the variable is found in
mysql_tracked_variables, causing an ERR to be sent when a permitted
session_track_* error refers to a variable that isn't tracked; fix by treating
permitted tracking errors as non-fatal even when idx is not within [0,
SQL_NAME_LAST_HIGH_WM): after computing idx, if is_perm_track_err(myerr,
var_name) and idx is out of bounds, do not send an error—perform the same
non-fatal flow (set myds->revents |= POLLOUT, set myds->DSS =
STATE_MARIADB_GENERIC, pop previous_status and call NEXT_IMMEDIATE_NEW) without
touching myconn->var_absent to avoid out-of-bounds writes; use
mysql_tracked_variables, is_perm_track_err, myconn->var_absent,
SQL_NAME_LAST_HIGH_WM, myds->revents, myds->DSS, previous_status, and
NEXT_IMMEDIATE_NEW to locate and implement the change.

In `@lib/MySQL_Thread.cpp`:
- Around line 6394-6397: The read of server_backoff_time (seen at
c->parent->server_backoff_time in MySQL_Thread::...) races with writers; make
server_backoff_time access consistent by choosing one strategy (preferred:
protect the field with a pthread mutex on the host/group object and add guarded
accessor methods, or convert the declaration to std::atomic<time_t> and update
all writes/reads to use .store/.load). Update the declaration in
MySQL_HostGroups_Manager (or the host struct/class that defines
server_backoff_time), change all write sites in MySQL_Session.cpp and MyHGC.cpp
to use the chosen synchronization (mutex lock + accessor or atomic store), and
change this read in MySQL_Thread.cpp to use the same accessor or atomic load;
then run the provided grep-based verification script to ensure no remaining
unsynchronized direct accesses.

In `@lib/MySrvC.cpp`:
- Line 34: The field server_backoff_time is accessed by session threads and the
server/connection selection path but is a plain unsigned long long; make it
thread-safe by changing the backing member to std::atomic<unsigned long long>
(or, if you prefer locking, ensure every read/write is protected by the existing
hostgroup mutex), then update all uses (writes in the session handling code and
reads in the server/connection selection code) to use atomic store/load
semantics or to acquire the hostgroup lock before accessing server_backoff_time;
search for the symbol server_backoff_time and the session/selection code paths
to update all accesses consistently.

In `@test/infra/infra-dbdeployer-mysql56-single/docker-compose-destroy.bash`:
- Around line 4-8: The destroy script must mirror init's INFRA_ID handling: if a
caller provided INFRA_ID, preserve it across sourcing .env before deriving
COMPOSE_PROJECT. Before "set -a; . .env; set +a" save any existing INFRA_ID
(e.g., SAVED_INFRA_ID="$INFRA_ID"), then source .env, then if SAVED_INFRA_ID is
non-empty restore INFRA_ID from it, and only after that compute
COMPOSE_PROJECT="${INFRA}-${INFRA_ID}" so docker-compose down -v targets the
same project as init.

In `@test/tap/tests/mysql-session_track_variables_enforced-t.cpp`:
- Around line 16-20: The cleanup function currently unconditionally calls
mysql_close on both proxy and admin which can crash when proxy is NULL after a
failed reconnect; update the cleanup(MYSQL* admin, MYSQL* proxy) routine to
always execute the admin reset queries (MYSQL_QUERY_T(admin, "SET
mysql-session_track_variables=0"); and MYSQL_QUERY_T(admin, "LOAD MYSQL
VARIABLES TO RUNTIME");) but only call mysql_close(proxy) if proxy is non-NULL
and only call mysql_close(admin) if admin is non-NULL, guarding both closes with
null checks to avoid closing null pointers.

In `@test/tap/tests/reg_test_4264-commit_rollback-t.cpp`:
- Around line 1156-1160: The test currently disables session tracking by issuing
MYSQL_QUERY_T(admin, "SET mysql-session_track_variables=0") which masks the new
session-tracking traffic; remove or avoid that SET and instead collect the
baseline after performing connection-init/session-track setup (or explicitly
subtract/ignore the known internal setup queries like "LOAD MYSQL VARIABLES TO
RUNTIME" and any connection-init commands) so the test can validate transaction
routing with session tracking enabled; update the test around the MYSQL_QUERY_T
calls (e.g., the block containing MYSQL_QUERY_T(admin, "SET
mysql-auto_increment_delay_multiplex=0"), MYSQL_QUERY_T(admin, "SET
mysql-default_session_track_gtids='OFF'"), MYSQL_QUERY_T(admin, "SET
mysql-session_track_variables=0"), and MYSQL_QUERY_T(admin, "LOAD MYSQL
VARIABLES TO RUNTIME")) to either not set mysql-session_track_variables to 0 or
to take baselines after setup and explicitly ignore the setup queries in the
delta comparison.

In `@test/tap/tests/test_binlog_reader-t.cpp`:
- Around line 464-465: Before calling MYSQL_QUERY_T(proxysql_admin, "SET
mysql-session_track_variables=0"), query and store the current value (e.g., run
MYSQL_QUERY_T(proxysql_admin, "SELECT @@mysql-session_track_variables") and save
into a variable) and then after the test assertions in the cleanup: block
restore it by executing MYSQL_QUERY_T(proxysql_admin, "SET
mysql-session_track_variables=<saved_value>"); ensure the saved value is used
even if the test fails so cleanup: always resets the global setting.

---

Nitpick comments:
In `@include/mysql_connection.h`:
- Around line 269-298: Add an explicit `#include` <unordered_map> to the header so
the public API type used by get_variables(std::unordered_map<std::string,
std::string>&) is directly visible; modify include/mysql_connection.h to include
<unordered_map> near other standard headers so the declaration of bool
get_variables(...) is self-contained and does not rely on transitive includes
like cpp.h.

In `@include/MySQL_Session.h`:
- Around line 246-304: The header comments are too verbose and include
PR-specific workflow; shorten them to a concise API contract for the declared
functions (e.g., handler_again___verify_backend_session_track_variables,
handler_again___verify_backend_multi_statement,
handler_again___verify_backend_user_schema,
handler_again___verify_multiple_variables,
handler_again___status_SETTING_INIT_CONNECT,
handler_again___status_SETTING_LDAP_USER_VARIABLE,
handler_again___status_SETTING_SQL_MODE,
handler_again___status_SETTING_SESSION_TRACK_GTIDS,
handler_again___status_SETTING_SESSION_TRACK_VARIABLES,
handler_again___status_SETTING_SESSION_TRACK_STATE): remove PR labels,
step-by-step state machine narration and implementation details, leaving a one-
or two-sentence description of purpose, parameters, and return semantics; move
any detailed workflow or rationale into the .cpp implementation or design notes.

In `@test/infra/infra-mysql56-single/bin/docker-mysql-post.bash`:
- Around line 88-94: Replace the tar-stream-based CA extraction with a simpler
docker exec/cat pipeline used by the sibling script: instead of using docker cp
"${CONTAINER}:/var/lib/mysql/ca.pem" - | tar -Ox | sudo tee -a "${DB_BUNDLE}" |
sudo tee -a "${CA_BUNDLE}", run docker exec "${CONTAINER}" cat
/var/lib/mysql/ca.pem and pipe its output into sudo tee -a "${DB_BUNDLE}" and
sudo tee -a "${CA_BUNDLE}" (preserving the existing sudo chmod 666 on
"${DB_BUNDLE}" and "${CA_BUNDLE}" and the surrounding if test -f
/var/lib/mysql/ca.pem check).

In `@test/infra/infra-mysql56-single/bin/docker-proxy-post.bash`:
- Around line 11-12: The current code reads the SQL template relative to the CWD
and uses eval to expand it; change it to load the file using the resolved
SCRIPT_DIR (use the SCRIPT_DIR variable) to build an absolute path for
./conf/proxysql/infra-config.sql, and replace the use of eval for SQL_CONTENT
with a safe read/templating approach (e.g., read the file contents into
SQL_TEMPLATE and perform controlled variable substitution with a safe tool or
parameter expansion rather than eval) so SQL_TEMPLATE and SQL_CONTENT no longer
depend on the caller's working directory or execute arbitrary shell code.

In `@test/tap/groups/groups.json`:
- Around line 95-98: The test groups for session-tracking lack MySQL 8+
coverage; update the arrays for "mysql-session_track_variables_enforced-t",
"mysql-session_track_variables_optional-t", and
"mysql-session_track_variables_ff-t" to also include "mysql84-g4", "mysql90-g4",
and "mysql95-g4" (keep "mysql-session_track_variables_ff_mysql56-t" as-is for
mysql56-specific runs). This ensures ENFORCED/OPTIONAL/FF variants run against
MySQL 8.x/9.x groups to catch protocol-level regressions.

In `@test/tap/groups/mysql56-single/env.sh`:
- Around line 5-8: Replace the POSIX test brackets and silence ShellCheck for
the dynamic env source: change the conditionals using [ -n "${WORKSPACE}" ] and
[ -f "${_INFRA_ENV}" ] to bash conditional syntax [[ -n "${WORKSPACE}" ]] and [[
-f "${_INFRA_ENV}" ]], and add a directive comment "# shellcheck
source=/dev/null" immediately before the dynamic source line that evaluates .
"${_INFRA_ENV}" (referencing variables WORKSPACE, _INFRA_ENV,
DEFAULT_MYSQL_INFRA) so SC1090 is suppressed.

In `@test/tap/tests/session_track_variables.h`:
- Around line 111-228: The helpers are hardcoded to "innodb_lock_wait_timeout",
breaking portability; update set_session_variable, select_sess_var_value,
extract_sess_var_proxy_internal, and test_session_variables_ff to accept a
variable-name parameter (e.g., const std::string& var_name or const char*
var_name) and use that name when building the CREATE PROCEDURE, CALL, SELECT
@@..., and when extracting JSON keys from PROXYSQL INTERNAL SESSION and mysql
packet tracking; keep current behavior by allowing a default value (e.g.,
"wait_timeout") when none is provided so tests can run against non-InnoDB
backends without duplicating logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a55a68a1-9cf9-4e36-a2e3-176cc3787514

📥 Commits

Reviewing files that changed from the base of the PR and between 872479a and 8d94919.

📒 Files selected for processing (45)
  • include/MySQL_HostGroups_Manager.h
  • include/MySQL_Session.h
  • include/MySQL_Thread.h
  • include/mysql_connection.h
  • include/proxysql_structs.h
  • lib/MyHGC.cpp
  • lib/MySQL_Protocol.cpp
  • lib/MySQL_Session.cpp
  • lib/MySQL_Thread.cpp
  • lib/MySrvC.cpp
  • lib/mysql_connection.cpp
  • test/infra/control/run-tests-isolated.bash
  • test/infra/infra-dbdeployer-mysql56-single/.env
  • test/infra/infra-dbdeployer-mysql56-single/bin/docker-mysql-post.bash
  • test/infra/infra-dbdeployer-mysql56-single/bin/docker-proxy-post.bash
  • test/infra/infra-dbdeployer-mysql56-single/conf/proxysql/infra-config.sql
  • test/infra/infra-dbdeployer-mysql56-single/docker-compose-destroy.bash
  • test/infra/infra-dbdeployer-mysql56-single/docker-compose-init.bash
  • test/infra/infra-dbdeployer-mysql56-single/docker-compose.yml
  • test/infra/infra-dbdeployer-mysql56-single/docker/Dockerfile
  • test/infra/infra-dbdeployer-mysql56-single/docker/build.sh
  • test/infra/infra-dbdeployer-mysql56-single/docker/entrypoint.sh
  • test/infra/infra-mysql56-single/.env
  • test/infra/infra-mysql56-single/bin/docker-mysql-post.bash
  • test/infra/infra-mysql56-single/bin/docker-proxy-post.bash
  • test/infra/infra-mysql56-single/conf/mysql/Dockerfile
  • test/infra/infra-mysql56-single/conf/mysql/docker-entrypoint.sh
  • test/infra/infra-mysql56-single/conf/mysql/mysql1/my.cnf
  • test/infra/infra-mysql56-single/conf/mysql/ssl/configure_ssl.sh
  • test/infra/infra-mysql56-single/conf/proxysql/infra-config.sql
  • test/infra/infra-mysql56-single/docker-compose-destroy.bash
  • test/infra/infra-mysql56-single/docker-compose-init.bash
  • test/infra/infra-mysql56-single/docker-compose.yml
  • test/tap/groups/groups.json
  • test/tap/groups/mysql56-single/env.sh
  • test/tap/groups/mysql56-single/infras.lst
  • test/tap/groups/mysql56-single/pre-proxysql.bash
  • test/tap/tap/utils.h
  • test/tap/tests/mysql-session_track_variables_enforced-t.cpp
  • test/tap/tests/mysql-session_track_variables_ff-t.cpp
  • test/tap/tests/mysql-session_track_variables_ff_mysql56-t.cpp
  • test/tap/tests/mysql-session_track_variables_optional-t.cpp
  • test/tap/tests/reg_test_4264-commit_rollback-t.cpp
  • test/tap/tests/session_track_variables.h
  • test/tap/tests/test_binlog_reader-t.cpp

Comment thread lib/MySQL_Session.cpp
Comment thread test/tap/tests/mysql-session_track_variables_ff_mysql56-t.cpp
Comment thread test/tap/tests/mysql-session_track_variables_ff-t.cpp
Comment thread test/tap/tests/session_track_variables.h Outdated
Comment thread test/tap/tests/session_track_variables.h Outdated
Comment thread test/tap/tests/session_track_variables.h Outdated
wazir-ahmed and others added 6 commits April 20, 2026 19:57
Signed-off-by: Wazir Ahmed <wazir@proxysql.com>
Signed-off-by: Wazir Ahmed <wazir@proxysql.com>
…te hot-path read

Context
-------
PR #5166 introduced a per-'MySrvC' atomic timestamp that prevents the server
from being chosen for new connections for a 30-second window. The timestamp
is written in exactly one place, 'MySQL_Session::handle_session_track_capabilities()',
and only when 'mysql-session_track_variables' is in ENFORCED mode and the
backend is missing 'CLIENT_SESSION_TRACKING' or 'CLIENT_DEPRECATE_EOF'.

The field was originally named 'server_backoff_time' and its only semantics
were that ENFORCED-mode capability mismatches temporarily exclude the server
from selection. Two issues with the original shape:

  1. The name does not hint that the scope is session-tracking capability
     enforcement. A reader of 'MyHGC::get_random_MySrvC' or
     'MySQL_Thread::get_MyConn_local' had no way to tell that this field is
     tied to one specific feature and not, say, a generic health backoff.

  2. The atomic load was unconditional in both hot selection loops. Even
     though the atomic is relaxed (effectively a 'mov' on x86), per-iteration
     atomic reads inhibit hoisting and CSE that the compiler can otherwise
     apply to thread-local integer reads. 'get_random_MySrvC' runs the loop
     over every server in the hostgroup on every backend-connection
     acquisition, so this is the sort of place where small per-iteration
     costs accumulate.

Changes
-------
* Rename the field:
    MySrvC::server_backoff_time  ->  MySrvC::session_track_backoff_until
  Naming it after the feature makes it obvious that the field is specific
  to session-tracking enforcement and should not be overloaded for unrelated
  purposes (e.g. connect errors, monitor failures). If a future change needs
  a generic backoff mechanism, a new field -- or a rename at that point --
  keeps intent clear rather than piggy-backing on this one silently.

* Gate the atomic load on mode:
    In both 'MyHGC::get_random_MySrvC()' (lib/MyHGC.cpp) and
    'MySQL_Thread::get_MyConn_local()' (lib/MySQL_Thread.cpp), the relaxed
    load is skipped entirely when 'mysql_thread___session_track_variables'
    is not ENFORCED. In 'get_MyConn_local' the check is computed once before
    the loop; in 'get_random_MySrvC' the check is inside the loop but is a
    thread-local int read, which is free.

    Correctness note: switching out of ENFORCED at runtime makes any stale
    'session_track_backoff_until' values in the pool immediately ignored,
    which is the desired behavior -- servers rejected while ENFORCED was
    active become eligible again the moment the operator relaxes the mode,
    matching how ProxySQL handles other runtime-variable-dependent state.
    No explicit reset is required.

* Updated the field documentation in include/MySQL_HostGroups_Manager.h
  with the monotonic-timestamp unit, the list of sites that read/write it,
  the explicit scope (ENFORCED mode only), and the reset policy (no explicit
  reset; deadline ages out by comparison against curtime).

No functional change for DISABLED / OPTIONAL configurations (the default for
all installations). ENFORCED mode behaviour is unchanged.
…untime backoff

Context
-------
PR #5166 introduced a three-mode 'mysql-session_track_variables' variable
(DISABLED / OPTIONAL / ENFORCED) whose behavior silently depends on two
other MySQL capability toggles and on the capabilities of each backend:

  * In OPTIONAL mode the feature is a no-op when
    'mysql-enable_server_deprecate_eof=false' or when the backend does not
    advertise CLIENT_DEPRECATE_EOF / CLIENT_SESSION_TRACKING. The operator
    sees the variable set to 1, but 'PROXYSQL INTERNAL SESSION' shows no
    tracked variables and there is no indication why.

  * In ENFORCED mode ProxySQL forces CLIENT_DEPRECATE_EOF on both the client
    and server sides of the protocol even if the operator explicitly set
    'mysql-enable_client_deprecate_eof=false' / 'mysql-enable_server_deprecate_eof=false'.
    This is a protocol-visible behavior change for clients that did not
    negotiate CLIENT_DEPRECATE_EOF, and it was previously silent.

  * In ENFORCED mode backends that cannot honor tracking (missing
    CLIENT_SESSION_TRACKING or CLIENT_DEPRECATE_EOF) are placed in a
    30-second selection backoff via 'MySrvC::session_track_backoff_until'.
    The server is NOT shunned and no connect-error counter is incremented,
    so in a mixed-version hostgroup an incompatible backend sits idle
    indefinitely with no log trail.

Changes
-------
* lib/Admin_FlushVariables.cpp: add a cross-variable validator at the tail
  of 'ProxySQL_Admin::flush_mysql_variables___database_to_runtime()', right
  next to the existing 'mysql-use_tcp_keepalive' warning. It runs once per
  'LOAD MYSQL VARIABLES TO RUNTIME' and only when the feature is actually
  requested (mode != DISABLED). Emitted warnings:
    - ENFORCED + !enable_client_deprecate_eof -> "will force CLIENT_DEPRECATE_EOF
      on frontend connections; older clients may be affected"
    - ENFORCED + !enable_server_deprecate_eof -> equivalent for backends
    - OPTIONAL + !enable_server_deprecate_eof -> "has no effect while
      mysql-enable_server_deprecate_eof=false; backend session tracking will
      be skipped"
  The checks use the global 'GloMTH->get_variable_int()' view (same pattern
  as the TCP keepalive warning above it), and deliberately do not name any
  backend -- this is a global-configuration warning; per-backend capability
  mismatches are handled separately below.

* lib/MySQL_Session.cpp: in 'handle_session_track_capabilities()', at the
  point where the code stores a new 'session_track_backoff_until' deadline
  for an incompatible backend, add a 'proxy_warning' naming the backend's
  address:port and the reason. Because this path only runs on new
  connection establishment -- and is itself gated by the 30-second backoff
  on the selection path -- the warning is naturally rate-limited to at most
  once per 30 seconds per backend. In a mixed fleet that rate is just
  enough for an operator to notice the incompatible server without drowning
  the log.

Behaviour
---------
* DISABLED mode (the default): no new log output; validator is skipped
  entirely. The hot selection path was already skipping the atomic read in
  the previous commit, so DISABLED remains free of new work.

* OPTIONAL / ENFORCED: at most three warnings at 'LOAD MYSQL VARIABLES TO
  RUNTIME' time (cross-variable validator) plus one periodic warning per
  incompatible backend (ENFORCED path only). No per-query log output.
…sql_mode coverage

Context
-------
The four session_track_variables TAP tests previously hard-coded a single
variable, 'innodb_lock_wait_timeout', for their round-trip assertion. That
only exercised the integer path in
'MySQL_Session::handler_rc0_Process_Variables', leaving the string-value
path (and the character-set special case elsewhere in the same function)
untested. The stored-procedure helper was also named generically
('set_session_variable') while its body was pinned to InnoDB's lock wait
timeout -- confusing for readers.

Changes
-------
* test/tap/tests/session_track_variables.h:
    - Introduce 'struct tracked_var_spec { name, set_expr; }' so the helpers
      can exercise arbitrary session variables from a single
      spec-driven code path.
    - 'set_session_variable()' now takes a spec, builds a per-variable
      stored procedure name ('test.set_session_track_<name>') so multiple
      specs can coexist without stepping on each other, and guarantees
      that the CALL is the last statement on the connection before the
      tracker is read (any query in between would reset the session
      tracker state).
    - Add 'drop_session_variable_proc()' for optional teardown. Kept
      separate from the set path so tests that need to preserve the CALL
      as the last statement can skip cleanup.
    - Switch the whole value surface from 'int' to 'std::string':
      'extract_sess_var_mysql_pkt', 'select_sess_var_value', and
      'extract_sess_var_proxy_internal' all return strings, which is how
      session tracking delivers values on the wire and matches both
      integer and string variables without encoding assumptions.
    - Provide a default set, 'session_track_default_vars', with three
      representative cases:
        * 'innodb_lock_wait_timeout' (integer; original coverage)
        * 'time_zone' (string, portable small value-space)
        * 'sql_mode' (string, representative of production usage)
      All three 'set_expr' values are pure server-side SQL (no client-side
      randomization), so a CALL alone produces a fresh value per run.

* test/tap/tests/mysql-session_track_variables_optional-t.cpp,
  test/tap/tests/mysql-session_track_variables_enforced-t.cpp,
  test/tap/tests/mysql-session_track_variables_ff-t.cpp,
  test/tap/tests/mysql-session_track_variables_ff_mysql56-t.cpp:
    - Iterate over 'session_track_default_vars' and emit one TAP 'ok' per
      (spec[, mode]) combination.
    - Adjust plans accordingly. The ENFORCED-on-5.6 case still emits a
      single 'ok' because the backend is rejected before any per-variable
      tracking is attempted, so multiplying by the spec count there would
      be misleading.
    - Compare values as strings using the new string-returning helpers.

The broadened coverage does not change what 'handler_rc0_Process_Variables'
or 'handle_session_track_capabilities' do; it only exercises more of their
existing behavior, notably the non-integer value path and the
'proxysql_find_charset_*' branches that activate for character-set
variables (not exercised by these three specs but now reachable through
the same harness for follow-up tests).
Context
-------
PR #5166 introduced a new TAP group 'mysql56-single-g1' backed by
'test/infra/infra-dbdeployer-mysql56-single/' (a dbdeployer-based MySQL 5.6
fixture). Four new tests register against this group in
'test/tap/groups/groups.json':

  - mysql-session_track_variables_enforced-t
  - mysql-session_track_variables_ff_mysql56-t
  - mysql-session_track_variables_optional-t

Without a dedicated CI workflow these tests are never exercised
automatically: the generic 'CI-taptests-groups' plumbing hardcodes
'infradb: [ mysql57 ]' in its matrix, and no existing 'CI-mysql*' caller
routes to the 'mysql56-single-g1' group.

Changes
-------
This commit adds the v3.0-branch half of the standard caller/reusable
pair, following the architecture documented in 'doc/GH-Actions/README.md'
(section "The dedicated-reusable pattern"). The file is modelled on
'CI-mysql84-g1.yml' verbatim -- only the workflow name and the
'uses:' target change. Like every other 'CI-*.yml' caller on v3.0:

  - Triggers on 'workflow_run' after 'CI-trigger' completes successfully
    (so the build/test caches are guaranteed populated) plus manual
    'workflow_dispatch'.
  - Uses 'concurrency.cancel-in-progress: true' to cancel any in-flight
    run when a new push lands on the same branch.
  - Delegates all real work to 'ci-mysql56-single-g1.yml' on the
    'GH-Actions' branch.

The reusable half of the pair -- 'ci-mysql56-single-g1.yml' on the
'GH-Actions' branch -- is added in a separate commit on that branch.
Until both halves are present this caller will fail with
"workflow not found". That is the expected landing order per the
project's CI workflow: the reusable ships first on 'GH-Actions', then
the caller ships on v3.0, matching how 'CI-mysql84-g1.yml' was
introduced (see 'doc/GH-Actions/README.md' section "Adding a new test
group end-to-end").
@renecannao

Copy link
Copy Markdown
Contributor

@wazir-ahmed quick question on the test infra layout. This PR ships two parallel MySQL 5.6 fixtures:

  • test/infra/infra-dbdeployer-mysql56-single/ — dbdeployer-based, wired up as the default by test/tap/groups/mysql56-single/env.sh, infras.lst, and the run-tests-isolated.bash case statement.
  • test/infra/infra-mysql56-single/ — a Docker-Compose / Dockerfile variant. Grepping the tree, nothing outside its own directory references it.

We're moving away from Docker-Compose for single-instance MySQL fixtures in favor of dbdeployer. If the Docker variant is not serving a purpose I'm missing, it looks like a candidate for removal in this PR (or a follow-up) — can you confirm whether it's safe to drop? If it's kept as an intentional alternative, a note in test/infra/README.md explaining when to use each would help future readers.

Signed-off-by: Wazir Ahmed <wazir@proxysql.com>
@wazir-ahmed

Copy link
Copy Markdown
Member Author

@renecannao I have removed the dockerized infra. We now have only one mysql56-single infra which is based on dbdeployer.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
Image 3 Security Hotspots
Image B Maintainability Rating on New Code (required ≥ A)
Image E Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

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

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.

Track session-specific system variables using session_track_system_variables

4 participants