/* Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "rpl_slave_commit_order_manager.h" #include "rpl_rli_pdb.h" // Slave_worker #include "debug_sync.h" // debug_sync_set_action Commit_order_manager::Commit_order_manager(uint32 worker_numbers) : m_rollback_trx(false), m_workers(worker_numbers), queue_head(QUEUE_EOF), queue_tail(QUEUE_EOF) { mysql_mutex_init(key_commit_order_manager_mutex, &m_mutex, NULL); for (uint32 i= 0; i < worker_numbers; i++) { mysql_cond_init(key_commit_order_manager_cond, &m_workers[i].cond); m_workers[i].status= OCS_FINISH; } } Commit_order_manager::~Commit_order_manager() { mysql_mutex_destroy(&m_mutex); for (uint32 i= 0; i < m_workers.size(); i++) { mysql_cond_destroy(&m_workers[i].cond); } } void Commit_order_manager::register_trx(Slave_worker *worker) { DBUG_ENTER("Commit_order_manager::register_trx"); mysql_mutex_lock(&m_mutex); m_workers[worker->id].status= OCS_WAIT; queue_push(worker->id); mysql_mutex_unlock(&m_mutex); DBUG_VOID_RETURN; } /** Waits until it becomes the queue head. @retval false All previous threads succeeded so this thread can go ahead and commit. */ bool Commit_order_manager::wait_for_its_turn(Slave_worker *worker, bool all) { DBUG_ENTER("Commit_order_manager::wait_for_its_turn"); /* When prior transaction fail, current trx should stop and wait for signal to rollback itself */ if ((all || ending_single_stmt_trans(worker->info_thd, all) || m_rollback_trx) && m_workers[worker->id].status == OCS_WAIT) { PSI_stage_info old_stage; mysql_cond_t *cond= &m_workers[worker->id].cond; THD *thd= worker->info_thd; DBUG_PRINT("info", ("Worker %lu is waiting for commit signal", worker->id)); mysql_mutex_lock(&m_mutex); thd->ENTER_COND(cond, &m_mutex, &stage_worker_waiting_for_its_turn_to_commit, &old_stage); while (queue_front() != worker->id) { if (unlikely(worker->found_order_commit_deadlock())) { mysql_mutex_unlock(&m_mutex); thd->EXIT_COND(&old_stage); DBUG_RETURN(true); } mysql_cond_wait(cond, &m_mutex); } mysql_mutex_unlock(&m_mutex); thd->EXIT_COND(&old_stage); m_workers[worker->id].status= OCS_SIGNAL; if (m_rollback_trx) { unregister_trx(worker); DBUG_PRINT("info", ("thd has seen an error signal from old thread")); thd->get_stmt_da()->set_overwrite_status(true); my_error(ER_SLAVE_WORKER_STOPPED_PREVIOUS_THD_ERROR, MYF(0)); } } DBUG_RETURN(m_rollback_trx); } void Commit_order_manager::unregister_trx(Slave_worker *worker) { DBUG_ENTER("Commit_order_manager::unregister_trx"); if (m_workers[worker->id].status == OCS_SIGNAL) { DBUG_PRINT("info", ("Worker %lu is signalling next transaction", worker->id)); mysql_mutex_lock(&m_mutex); DBUG_ASSERT(!queue_empty()); /* Set next manager as the head and signal the trx to commit. */ queue_pop(); if (!queue_empty()) mysql_cond_signal(&m_workers[queue_front()].cond); m_workers[worker->id].status= OCS_FINISH; mysql_mutex_unlock(&m_mutex); } DBUG_VOID_RETURN; } void Commit_order_manager::report_rollback(Slave_worker *worker) { DBUG_ENTER("Commit_order_manager::report_rollback"); (void) wait_for_its_turn(worker, true); /* No worker can set m_rollback_trx unless it is its turn to commit */ m_rollback_trx= true; unregister_trx(worker); DBUG_VOID_RETURN; } void Commit_order_manager::report_deadlock(Slave_worker *worker) { DBUG_ENTER("Commit_order_manager::report_deadlock"); mysql_mutex_lock(&m_mutex); worker->report_order_commit_deadlock(); DBUG_EXECUTE_IF("rpl_fake_cod_deadlock", { const char act[]= "now signal reported_deadlock"; DBUG_ASSERT(!debug_sync_set_action(current_thd, STRING_WITH_LEN(act))); }); mysql_cond_signal(&m_workers[worker->id].cond); mysql_mutex_unlock(&m_mutex); DBUG_VOID_RETURN; }