MDEV-38796 Refactor: clean up THD::decide_logging_format() checks#5136
MDEV-38796 Refactor: clean up THD::decide_logging_format() checks#5136bnestere wants to merge 6 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces Global Temporary Tables (GTT) support, implementing the syntax, lifecycle, transaction integration, and replication/binlogging adjustments. Review feedback highlights several critical issues: a debug fprintf statement was left active in thd_binlog_filter_ok(); the in-use check in drop_tmp_table_share() is bypassed when table is NULL; committed in All_tmp_tables_list and binlog_state in THD are uninitialized; thd->query() can return NULL in log_write_error(), potentially causing a crash; and table_ref->table should be passed to drop_tmp_table_share() during truncate table operations instead of NULL.
| bool new_value= !(thd->binlog_state & BINLOG_STATE_FILTER); | ||
| bool old_value= binlog_filter->db_ok(thd->db.str); | ||
| //if (filter_on != old_value_on) | ||
| fprintf(stderr, | ||
| "\n\tDB %s thd_binlog_filter return\n\t\tbinlog_state: %d " | ||
| "\n\t\tlegacy: %d\n", | ||
| thd->db.str, new_value, old_value); | ||
| return new_value; |
| if (table) | ||
| { | ||
| if (tab != table && tab->query_id != 0) | ||
| /* Table might be in use by some outer statement. */ | ||
| All_share_tables_list::Iterator it(share->all_tmp_tables); | ||
| while ((tab= it++)) | ||
| { | ||
| /* Found a table instance in use. This table cannot be be dropped. */ | ||
| my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias.c_ptr()); | ||
| result= true; | ||
| goto end; | ||
| if (tab != table && tab->query_id != 0) | ||
| { | ||
| /* Found a table instance in use. This table cannot be dropped. */ | ||
| my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias.c_ptr()); | ||
| result= true; | ||
| goto end; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
If table is NULL (which happens when called from global_tmp_drop_database or truncate_table), the check for active table instances in use is completely bypassed because of the if (table) guard. This can lead to use-after-free or crashes if any of the global temporary table instances are currently in use by an outer query. The check should be performed regardless of whether table is NULL or not, and if table is NULL, we should check all instances.
/* Table might be in use by some outer statement. */
All_share_tables_list::Iterator it(share->all_tmp_tables);
while ((tab= it++))
{
if (tab != table && tab->query_id != 0)
{
/* Found a table instance in use. This table cannot be dropped. */
my_error(ER_CANT_REOPEN_TABLE, MYF(0),
table ? table->alias.c_ptr() : share->table_name.str);
result= true;
goto end;
}
}| I_P_List_null_counter, | ||
| I_P_List_fast_push_back<TMP_TABLE_SHARE>> | ||
| { | ||
| bool committed; |
|
|
||
| void log_write_error(THD *thd, const char *msg) | ||
| { | ||
| my_error(ER_BINLOG_LOGGING_IMPOSSIBLE, MYF(ME_ERROR_LOG), msg, thd->query()); |
There was a problem hiding this comment.
thd->query() can return NULL if there is no active query (e.g., in background or replication threads). Passing NULL to %s in my_error can lead to undefined behavior or crashes on some platforms. It should be guarded with a null check.
my_error(ER_BINLOG_LOGGING_IMPOSSIBLE, MYF(ME_ERROR_LOG), msg, thd->query() ? thd->query() : "");| { | ||
| TABLE_SHARE *s= table_ref->table->s; | ||
| DBUG_ASSERT(s->tmp_table); | ||
| error= thd->drop_tmp_table_share(NULL, (TMP_TABLE_SHARE *)s, true); |
There was a problem hiding this comment.
| Describe the current state of binary logging for the current statement. | ||
| Includes state of binary logging and also what disables it | ||
| */ | ||
| enum_binlog_state binlog_state; |
cc5acda to
9c78108
Compare
gentle nudge |
|
@ParadoxV5 thanks for the reminder! Also it looks like there are new test failures, so I'll keep it draft until I fix them |
ER_BINLOG_STMT_MODE_AND_ROW_ENGINE is no longer an error, but rather, we
temporarily force ROW logging:
@@ -7259,7 +7262,15 @@ int THD::decide_logging_format(TABLE_LIST
*tables)
wsrep_cs().mode() ==
wsrep::client_state::m_local),1))
{
- my_error((error= ER_BINLOG_STMT_MODE_AND_ROW_ENGINE), MYF(0), "");
+ if (is_current_stmt_binlog_format_stmt())
+ {
+ my_printf_error(ER_BINLOG_STMT_MODE_AND_ROW_ENGINE,
+ "Statement cannot be logged as STATEMENT as at "
+ "least one table is limited to row-based logging."
+ " Switching temporarly to row format",
+ MYF(ME_NOTE));
+ set_current_stmt_binlog_format_row();
+ }
}
Tests are updated that relied on this behavior.
Addresses TODO
- Update binlog_state with BINLOG_STATE_FILTER only when database or filter
is changed. This means we do not have to call binlog_filter->db_ok() for
every statement in decide_binlog_format().
The option BINLOG_STATE_FILTER only applies to filtering the current
database. The name seems over-reaching, and would likely lead to
confusion. Renamed to BINLOG_STATE_FILTER_DB to be clear the option only
applies to the database.
TODO Addressed:
- Remove clear/reset xxx_binlog_local_stmt_filter() as this is now handled
by binlog_state.
TODO addressed
- Remove 'silent' option from mysql_create_db_internal and instead use
tmp_disable_binlog() / reenable_binlog()
Addresses TODO
- Remove all testing of mysql_bin_log.is_open(). Instead test for
binlog_ready() in main code and add testing of is_open() when
trying to commit the binary log. This is needed as currently
mysql_bin_log.is_open() is tested without a mutex which makes
it unreliable.
And also adds tests for the binlog closing for various types of
statements in-between a thd->binlog_ready() check and the commit-time
acquiring of mysql_bin_log.LOCK_log.
Still TODO from the above request:
* WSREP related mysql_bin_log.is_open() changes as a part of MDEV-38865
4c713f0 to
f7a90e8
Compare
This PR sits on top of the binlog state refactor (base branch tip:
MDEV-38865 Simplify testing if binary logging is enabled). Only the topsix commits are new; everything beneath them is the base work. Each commit
addresses one TODO item from MDEV-38796 and is intended to be reviewed on
its own, in commit order. The first commit contains only test updates, the
middle four change server code, and the last is a small build fix.
Commit walkthrough
Test updates for forced row logging (ed7f657). Test changes
only. The base branch changed
ER_BINLOG_STMT_MODE_AND_ROW_ENGINEfroman error into a note: when a statement in STATEMENT mode touches a table
limited to row logging, the server now switches the statement to row
format temporarily instead of failing it. This commit updates
binlog.binlog_spurious_ddl_errorsand thesql_sequencetests thatrelied on the old error.
Update BINLOG_STATE_FILTER at DB change time (ec517d4).
decide_logging_format()used to callbinlog_filter->db_ok()forevery statement. The result is now cached in
binlog_stateandrecomputed only when the default database changes (
THD::set_db()andTHD::reset_db(), via the newupdate_binlog_filter_state());thd_binlog_filter_ok()reads the cached bit. The flag is renamed fromBINLOG_STATE_FILTERtoBINLOG_STATE_FILTER_DB, since it reflectsonly the binlog_do_db/binlog_ignore_db decision on the current default
database. A new comment block in
decide_logging_format()documents thestatement mode filtering semantics. Adds
rpl.rpl_lsu_binlog_filter, achain replication test (three servers) verifying that a middle server
configured with
log_slave_updatesand binlog filters keeps filteredtransactions out of its own binlog.
Remove
*_binlog_local_stmt_filter()(7fe6738). DeletesTHD::m_binlog_filter_stateand its four accessors, since theinformation is fully carried by
binlog_state(BINLOG_STATE_FILTER_DBand
BINLOG_STATE_READONLY). Pure removal, net 52 lines deleted.Remove
silentoption frommysql_create_db_internal(53c847f). Replaces the special
silentparameter with the standardtmp_disable_binlog()/reenable_binlog()mechanism.mysql_create_db_internal()now checksBINLOG_STATE_TMP_DISABLED, andthe one internal caller (
mysql_upgrade_db()) wraps its callaccordingly.
Replace
mysql_bin_log.is_openwithbinlog_ready()(f7a90e8). The widest commit, touching about fifteen files.
mysql_bin_log.is_open()is read without a mutex on main code paths,which makes it unreliable; those sites now use the state cached on the
THD (
thd->binlog_ready_no_wsrep()).is_open()remains only where itis legitimate: at commit time under
LOCK_log, in ddl_log recovery, andat WSREP sites deliberately deferred to MDEV-38865 (each marked with a
TODO: MDEV-38865comment). Adds a DEBUG_SYNC point(
commit_after_binlog_ready_before_LOCK_log) and the new testbinlog.binlog_close_during_commit, which closes the binlog from asecond connection between a statement's
binlog_ready()check and itsacquisition of
LOCK_log, covering five statement types. Thetransaction must still commit in the engine, and the error log must
report the closed binlog once per close event rather than once per
transaction.
Fix Windows compiler error (423acf7). One line: wraps a
binlog_statebit test inMY_TEST()so the comparison againstboolcompiles on Windows.
Notes for review
state, and commit 3 removes the machinery it replaces. Commits 1, 4, 5,
and 6 are independent of that sequence and of each other.
rpl.rpl_lsu_binlog_filter) and 5(
binlog.binlog_close_during_commit). Commit 1 only adapts existingtests to base branch behavior.
is_open()sites (tracked as MDEV-38865), and the CREATE TABLE case inbinlog_close_during_commit, which is commented out pending MDEV-40171.