Description:
The bug happens if `HAVE_GTID_NEXT_LIST` macro is specified, and if `thd->owned_gtid_set` has more than one gtid.
It cause mysqld to crash at `update_gtids_impl` function in the commit stage of binlog commiting.
This is because the `prev_sidno` in the follow code has not correct value, cause to `sid_lock` to locked repeatly;
And `sid_lock` also forget to be unlocked.
#ifdef HAVE_GTID_NEXT_LIST
rpl_sidno prev_sidno= 0;
Gtid_set::Gtid_iterator git(&thd->owned_gtid_set);
Gtid g= git.get();
while (g.sidno != 0)
{
if (g.sidno != prev_sidno)
sid_locks.lock(g.sidno);
owned_gtids.remove_gtid(g);
git.next();
g= git.get();
if (is_commit)
executed_gtids._add_gtid(g);
}
if (is_commit && !thd->owned_gtid_set.is_empty())
thd->rpl_thd_ctx.session_gtids_ctx().
notify_after_gtid_executed_update(thd);
thd->variables.gtid_next.set_undefined();
thd->owned_gtid.dbug_print(NULL,
"set owned_gtid (clear; old was gtid_set) "
"in update_gtids_impl");
thd->clear_owned_gtids();
#else
How to repeat:
1. compile mysqld with `HAVE_GTID_NEXT_LIST`
2. let `thd->owned_gtid_set` has more than one gtid
3. execute some sql statement that generate binlog
Suggested fix:
#ifdef HAVE_GTID_NEXT_LIST
rpl_sidno prev_sidno= 0;
Gtid_set::Gtid_iterator git(&thd->owned_gtid_set);
Gtid g= git.get();
while (g.sidno != 0)
{
if (g.sidno != prev_sidno)
sid_locks.lock(g.sidno);
owned_gtids.remove_gtid(g);
prev_sidno = g.sidno; // correct it
if (is_commit) {
executed_gtids._add_gtid(g);
if (thd->slave_thread && opt_bin_log && !opt_log_slave_updates)
{
lost_gtids._add_gtid(g);
gtids_only_in_table._add_gtid(g);
}
}
git.next();
g= git.get();
}
if (is_commit && !thd->owned_gtid_set.is_empty())
thd->rpl_thd_ctx.session_gtids_ctx().
notify_after_gtid_executed_update(thd);
broadcast_owned_sidnos(thd);
unlock_owned_sidnos(thd);
thd->clear_owned_gtids(); // unlock the sid
if (thd->variables.gtid_next.type == GTID_GROUP)
{
DBUG_ASSERT(!more_transactions_with_same_gtid_next);
thd->variables.gtid_next.set_undefined();
}
#else
Description: The bug happens if `HAVE_GTID_NEXT_LIST` macro is specified, and if `thd->owned_gtid_set` has more than one gtid. It cause mysqld to crash at `update_gtids_impl` function in the commit stage of binlog commiting. This is because the `prev_sidno` in the follow code has not correct value, cause to `sid_lock` to locked repeatly; And `sid_lock` also forget to be unlocked. #ifdef HAVE_GTID_NEXT_LIST rpl_sidno prev_sidno= 0; Gtid_set::Gtid_iterator git(&thd->owned_gtid_set); Gtid g= git.get(); while (g.sidno != 0) { if (g.sidno != prev_sidno) sid_locks.lock(g.sidno); owned_gtids.remove_gtid(g); git.next(); g= git.get(); if (is_commit) executed_gtids._add_gtid(g); } if (is_commit && !thd->owned_gtid_set.is_empty()) thd->rpl_thd_ctx.session_gtids_ctx(). notify_after_gtid_executed_update(thd); thd->variables.gtid_next.set_undefined(); thd->owned_gtid.dbug_print(NULL, "set owned_gtid (clear; old was gtid_set) " "in update_gtids_impl"); thd->clear_owned_gtids(); #else How to repeat: 1. compile mysqld with `HAVE_GTID_NEXT_LIST` 2. let `thd->owned_gtid_set` has more than one gtid 3. execute some sql statement that generate binlog Suggested fix: #ifdef HAVE_GTID_NEXT_LIST rpl_sidno prev_sidno= 0; Gtid_set::Gtid_iterator git(&thd->owned_gtid_set); Gtid g= git.get(); while (g.sidno != 0) { if (g.sidno != prev_sidno) sid_locks.lock(g.sidno); owned_gtids.remove_gtid(g); prev_sidno = g.sidno; // correct it if (is_commit) { executed_gtids._add_gtid(g); if (thd->slave_thread && opt_bin_log && !opt_log_slave_updates) { lost_gtids._add_gtid(g); gtids_only_in_table._add_gtid(g); } } git.next(); g= git.get(); } if (is_commit && !thd->owned_gtid_set.is_empty()) thd->rpl_thd_ctx.session_gtids_ctx(). notify_after_gtid_executed_update(thd); broadcast_owned_sidnos(thd); unlock_owned_sidnos(thd); thd->clear_owned_gtids(); // unlock the sid if (thd->variables.gtid_next.type == GTID_GROUP) { DBUG_ASSERT(!more_transactions_with_same_gtid_next); thd->variables.gtid_next.set_undefined(); } #else