Skip to content

MDEV-39074 trans_rollback_stmt(THD *): Assertion `! thd->in_sub_stmt' failed. - #5696

Open
midenok wants to merge 1 commit into
10.11from
10.11-midenok-MDEV-39074
Open

midenok wants to merge 1 commit into
10.11from
10.11-midenok-MDEV-39074

Conversation

@midenok

@midenok midenok commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

slave_close_thread_tables() unconditionally called trans_commit_stmt()/trans_rollback_stmt(), which assert !thd->in_sub_stmt. A BINLOG statement with malformed base64 payload executed from an AFTER INSERT trigger hits this: on decode failure, mysql_client_binlog_statement() sets thd->is_error() and calls slave_close_thread_tables() from within the trigger's sub-statement.

Guard it with spcont/in_sub_stmt, deferring cleanup to the enclosing top-level statement. Other callers run only from the top-level SQL slave applier thread, so this doesn't change their behavior.

1. BINLOG statement executed from a trigger, SF or SP is disabled by the patch

Row events (Rows_log_event) open their target table via a one-shot check that only fires at the top of a fresh statement. Inside a trigger, the table is never opened this way. It may be fixed by reusing query_tables, but:

  • find_locked_table() matched only by table name -- could return the TABLE instance the enclosing statement was actively writing through, not an idle one. Reusing it would require pre-saving its state: record[0]/bitmaps/handler/etc. (will deprecate ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG)
  • set_stmt_row_injection()/set_time() calls mutated thd->lex, which at that point is main_lex -- shared with the enclosing statement, not something safe to touch.

Statement events (Query_log_event) run the embedded query via mysql_parse(): it bundles lex_start(), reset_for_next_command() and parse_sql() as one unit meant for a genuinely new top-level statement, not a one-off nested parse.

Calling parse_sql() directly instead avoids that, but then we own everything mysql_parse() was doing for us: a private LEX and a private Query_arena (or allocations land on main_lex/whatever arena is currently active, shared with the enclosing statement), plus calling mysql_execute_command() ourselves afterwards.

In any case, DML for query_tables cannot be done due to ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG reasons explained above.

2. PS for BINLOG statement still works and needs a leak fix for statement events

A BINLOG statement decoding to a Query_log_event, executed via PREPARE/EXECUTE, leaked its nested query's allocations onto the PS's own persistent arena: thd->stmt_arena pointed at it while mysql_parse() ran the decoded query. Second EXECUTE asserted on ROOT_FLAG_READ_ONLY, since PROTECT_STATEMENT_MEMROOT marks that arena read-only after a successful execution.

The fix redirects thd->stmt_arena to thd itself for the duration of the nested mysql_parse() call, so stmt_arena->is_conventional() reads true and activate_stmt_arena_if_needed() (called e.g. from save_leaf_tables()) never redirects allocations to the PS's arena in the first place.

Harmless for what it's protecting: leaf_tables_exec is normally cached on the persistent arena so a repeatedly-executed statement's SELECT_LEX doesn't rebuild it every time, but our SELECT_LEX is torn down and reparsed fresh (due to mysql_parse() semantics) on every EXECUTE, so there's nothing to cache here regardless of which arena is used.

Initial attempt for more fixes was made by PR #5692 (now closed as too complex for stable branches)

Copilot AI lite review requested due to automatic review settings September 18, 2026 10:44

Copilot AI 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.

🟡 Changes recommended

Nested BINLOG events can still mutate the retained format-description cache before rejection.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

This PR hardens BINLOG execution in stored-program contexts and fixes prepared-statement arena handling.

Changes:

  • Defers nested replication cleanup.
  • Adds stored-program event checks and PS arena isolation.
  • Adds regression coverage.
File summaries
File Summary
sql/rpl_rli.cc Defers transaction and table cleanup in sub-statements.
sql/log_event_server.cc Guards nested event execution and PS parsing.
mysql-test/suite/binlog/t/binlog_base64_flag.test Adds regression scenarios.
mysql-test/suite/binlog/r/binlog_base64_flag.result Records expected results.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread sql/log_event_server.cc Outdated

Copilot AI 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.

🟡 Changes recommended

The rejection occurs after validation has already mutated the retained format-description state.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread sql/sql_binlog.cc Outdated

Copilot AI 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.

🔵 Needs a closer look

It changes low-level replication cleanup and prepared-statement memory ownership, requiring final human validation.

Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread mysql-test/suite/binlog/t/binlog_base64_flag.test Outdated
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

… failed.

slave_close_thread_tables() unconditionally called
trans_commit_stmt()/trans_rollback_stmt(), which assert
!thd->in_sub_stmt. A BINLOG statement with malformed base64 payload
executed from an AFTER INSERT trigger hits this: on decode failure,
mysql_client_binlog_statement() sets thd->is_error() and calls
slave_close_thread_tables() from within the trigger's sub-statement.

Guard it with spcont/in_sub_stmt, deferring cleanup to the
enclosing top-level statement. Other callers run only from the
top-level SQL slave applier thread, so this doesn't change their
behavior.

1. BINLOG statement executed from a trigger, SF or SP is disabled
by the patch:

Row events (Rows_log_event) open their target table via a
one-shot check that only fires at the top of a fresh statement.
Inside a trigger, the table is never opened this way. It may be
fixed by reusing query_tables, but:

  - find_locked_table() matched only by table name -- could return
    the TABLE instance the enclosing statement was actively writing
    through, not an idle one. Reusing it would require pre-saving its
    state: record[0]/bitmaps/handler/etc. (will deprecate
    ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG)

  - set_stmt_row_injection()/set_time() calls mutated thd->lex, which
    at that point is main_lex -- shared with the enclosing statement,
    not something safe to touch.

Statement events (Query_log_event) run the embedded query via
mysql_parse(): it bundles lex_start(), reset_for_next_command() and
parse_sql() as one unit meant for a genuinely new top-level statement,
not a one-off nested parse.

Calling parse_sql() directly instead avoids that, but then we own
everything mysql_parse() was doing for us: a private LEX and a
private Query_arena (or allocations land on main_lex/whatever arena
is currently active, shared with the enclosing statement), plus
calling mysql_execute_command() ourselves afterwards.

In any case, DML for query_tables cannot be done due to
ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG reasons explained above.

2. PS for BINLOG statement still works and needs a leak fix for
statement events:

A BINLOG statement decoding to a Query_log_event, executed via
PREPARE/EXECUTE, leaked its nested query's allocations onto the PS's
own persistent arena: thd->stmt_arena pointed at it while mysql_parse()
ran the decoded query. Second EXECUTE asserted on ROOT_FLAG_READ_ONLY,
since PROTECT_STATEMENT_MEMROOT marks that arena read-only after a
successful execution.

The fix redirects thd->stmt_arena to thd itself for the duration of
the nested mysql_parse() call, so stmt_arena->is_conventional() reads
true and activate_stmt_arena_if_needed() (called e.g. from
save_leaf_tables()) never redirects allocations to the PS's arena in
the first place.

Harmless for what it's protecting: leaf_tables_exec is normally cached
on the persistent arena so a repeatedly-executed statement's
SELECT_LEX doesn't rebuild it every time, but our SELECT_LEX is torn
down and reparsed fresh (due to mysql_parse() semantics) on every
EXECUTE, so there's nothing to cache here regardless of which arena is
used.
@midenok
midenok force-pushed the 10.11-midenok-MDEV-39074 branch from ee4c250 to 4d95881 Compare September 18, 2026 12:43
@midenok
midenok requested a balanced review from Copilot September 18, 2026 12:43

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@midenok
midenok requested review from sanja-byelkin and a balanced review from Copilot September 18, 2026 12:45

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants