/* Copyright (c) 2013, 2017, 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, version 2.0, as published by the Free Software Foundation. This program is also distributed with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have included with MySQL. 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, version 2.0, 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 */ /** This is a unit test written to demonstrate a race issue in debug_sync functionality which resulted in wait thread getting timed out. This happens because we use a single global variable to identify the thread that has signalled. Before the wait could process the signal from the signal thread, another signal thread can overwrite this global signal value thus making the wait thread to lose this signal and timeout waiting for a signal that it has already lost. */ #include #include #include "my_dbug.h" #include "my_inttypes.h" #include "sql/debug_sync.h" #include "unittest/gunit/test_utils.h" #include "unittest/gunit/thread_utils.h" #if defined(ENABLED_DEBUG_SYNC) extern uchar *debug_sync_value_ptr(THD *thd); namespace debug_sync_unittest { using my_testing::Mock_error_handler; using my_testing::Server_initializer; using thread::Notification; using thread::Thread; class DebugSyncTest : public ::testing::Test { protected: DebugSyncTest() {} void SetUp() { // set debug sync timeout of 60 seconds. opt_debug_sync_timeout = 60; debug_sync_init(); } void TearDown() { debug_sync_end(); opt_debug_sync_timeout = 0; } }; /* Set up a debug sync action thread. Depending on type of action, the thread serves different purpose. In the case where it involves wait_for action, it serves the purpose of a waiting thread that wait at a 'sync_point' in it's thread of control for named signal event. The debug sync action could be a signal event, in which case it assumes the role of signalling thread that signals an event identified by signal name to wake a waiting thread. */ class DebugSyncThread : public Thread { public: DebugSyncThread(Notification *go, bool wait_thread, std::string action) : m_go(go), m_wait_thread(wait_thread), m_action(action) {} virtual void run() { m_initializer.SetUp(); m_thd = m_initializer.thd(); m_go->wait_for_notification(); if (m_wait_thread) { std::string sync_point("sync_point "); std::string action_str = sync_point + m_action; Mock_error_handler error_handler(m_thd, 0); debug_sync_set_action(m_thd, action_str.c_str(), action_str.length()); wait_point(); // The above should not generate any warnings. EXPECT_EQ(0, error_handler.handle_called()); } else { debug_sync_set_action(m_thd, m_action.c_str(), m_action.length()); } m_initializer.TearDown(); } private: Notification *m_go; Server_initializer m_initializer; THD *m_thd; // THD context required to hold debug_sync context. bool m_wait_thread; // indicate if it is a wait thread. std::string m_action; // indicate type of action WAIT_FOR or SIGNAL. void wait_point() { DEBUG_SYNC(m_thd, "sync_point"); } }; /* Start a wait thread waiting for signal X1 and two signal threads one signalling event X1 and other signalling an event X2. Notify all of them to start at once. */ TEST_F(DebugSyncTest, DebugSyncSignalWaitTests) { Notification go[3]; /* wait thread */ DebugSyncThread wait_thread(&go[0], true, std::string("WAIT_FOR X1")); /* signal thread */ DebugSyncThread signal_thread1(&go[1], false, std::string("now signal X1")); DebugSyncThread signal_thread2(&go[2], false, std::string("now signal X2")); wait_thread.start(); signal_thread1.start(); signal_thread2.start(); // Notify all threads go[0].notify(); go[1].notify(); go[2].notify(); // Wait for all threads to finish. wait_thread.join(); signal_thread1.join(); signal_thread2.join(); } // Test debug_sync_value_ptr TEST_F(DebugSyncTest, DebugSyncValuesTest) { std::string action; Server_initializer server_initializer; THD *thd; server_initializer.SetUp(); thd = server_initializer.thd(); // Ensure that we have a empty signal list at startup. EXPECT_STREQ("ON - signals: ''", reinterpret_cast(debug_sync_value_ptr(thd))); // Set up signalling actions and ensure the signals list reflect it. action = "now signal x1"; debug_sync_set_action(thd, action.c_str(), action.length()); EXPECT_STREQ("ON - signals: 'x1'", reinterpret_cast(debug_sync_value_ptr(thd))); action = "now signal x2"; debug_sync_set_action(thd, action.c_str(), action.length()); EXPECT_STREQ("ON - signals: 'x1,x2'", reinterpret_cast(debug_sync_value_ptr(thd))); action = "now signal x3"; debug_sync_set_action(thd, action.c_str(), action.length()); EXPECT_STREQ("ON - signals: 'x1,x2,x3'", reinterpret_cast(debug_sync_value_ptr(thd))); // Ensure the signal list is empty after reset. action = "reset"; debug_sync_set_action(thd, action.c_str(), action.length()); EXPECT_STREQ("ON - signals: ''", reinterpret_cast(debug_sync_value_ptr(thd))); server_initializer.TearDown(); } } // namespace debug_sync_unittest #endif