summaryrefslogtreecommitdiffstats
path: root/src/lib/util/tests/multi_threading_mgr_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/util/tests/multi_threading_mgr_unittest.cc')
-rw-r--r--src/lib/util/tests/multi_threading_mgr_unittest.cc626
1 files changed, 626 insertions, 0 deletions
diff --git a/src/lib/util/tests/multi_threading_mgr_unittest.cc b/src/lib/util/tests/multi_threading_mgr_unittest.cc
new file mode 100644
index 0000000..f2a46d9
--- /dev/null
+++ b/src/lib/util/tests/multi_threading_mgr_unittest.cc
@@ -0,0 +1,626 @@
+// Copyright (C) 2019-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <util/multi_threading_mgr.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::util;
+using namespace isc;
+
+/// @brief Verifies that the default mode is false (MT disabled).
+TEST(MultiThreadingMgrTest, defaultMode) {
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+}
+
+/// @brief Verifies that the mode setter works.
+TEST(MultiThreadingMgrTest, setMode) {
+ // enable MT
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setMode(true));
+ // MT should be enabled
+ EXPECT_TRUE(MultiThreadingMgr::instance().getMode());
+ // disable MT
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setMode(false));
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+}
+
+/// @brief Verifies that accessing the thread pool works.
+TEST(MultiThreadingMgrTest, threadPool) {
+ // get the thread pool
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().getThreadPool());
+}
+
+/// @brief Verifies that the thread pool size setter works.
+TEST(MultiThreadingMgrTest, threadPoolSize) {
+ // default thread count is 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // set thread count to 16
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setThreadPoolSize(16));
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // set thread count to 0
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setThreadPoolSize(0));
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+}
+
+/// @brief Verifies that the packet queue size setter works.
+TEST(MultiThreadingMgrTest, packetQueueSize) {
+ // default queue size is 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPool().getMaxQueueSize(), 0);
+ // set queue size to 16
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setPacketQueueSize(16));
+ // queue size should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 16);
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPool().getMaxQueueSize(), 16);
+ // set queue size to 0
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setPacketQueueSize(0));
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPool().getMaxQueueSize(), 0);
+}
+
+/// @brief Verifies that detecting thread count works.
+TEST(MultiThreadingMgrTest, detectThreadCount) {
+ // detecting thread count should work
+ EXPECT_NE(MultiThreadingMgr::detectThreadCount(), 0);
+}
+
+/// @brief Verifies that apply settings works.
+TEST(MultiThreadingMgrTest, applyConfig) {
+ // get the thread pool
+ auto& thread_pool = MultiThreadingMgr::instance().getThreadPool();
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+ // default thread count is 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // enable MT with 16 threads and queue size 256
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 16, 256));
+ // MT should be enabled
+ EXPECT_TRUE(MultiThreadingMgr::instance().getMode());
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // thread pool should be started
+ EXPECT_EQ(thread_pool.size(), 16);
+ // disable MT
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(false, 16, 256));
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // enable MT with auto scaling
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 0, 0));
+ // MT should be enabled
+ EXPECT_TRUE(MultiThreadingMgr::instance().getMode());
+ // thread count should be detected automatically
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), MultiThreadingMgr::detectThreadCount());
+ // thread pool should be started
+ EXPECT_EQ(thread_pool.size(), MultiThreadingMgr::detectThreadCount());
+ // disable MT
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(false, 0, 0));
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+}
+
+/// @brief Verifies that the critical section flag works.
+TEST(MultiThreadingMgrTest, criticalSectionFlag) {
+ // get the thread pool
+ auto& thread_pool = MultiThreadingMgr::instance().getThreadPool();
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+ // critical section should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().isInCriticalSection());
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // exit critical section
+ EXPECT_THROW(MultiThreadingMgr::instance().exitCriticalSection(), InvalidOperation);
+ // critical section should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().isInCriticalSection());
+ // enter critical section
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().enterCriticalSection());
+ // critical section should be enabled
+ EXPECT_TRUE(MultiThreadingMgr::instance().isInCriticalSection());
+ // enable MT with 16 threads and queue size 256
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(true, 16, 256));
+ // MT should be enabled
+ EXPECT_TRUE(MultiThreadingMgr::instance().getMode());
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // exit critical section
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().exitCriticalSection());
+ // critical section should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().isInCriticalSection());
+ // exit critical section
+ EXPECT_THROW(MultiThreadingMgr::instance().exitCriticalSection(), InvalidOperation);
+ // critical section should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().isInCriticalSection());
+ // disable MT
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().apply(false, 0, 0));
+ // MT should be disabled
+ EXPECT_FALSE(MultiThreadingMgr::instance().getMode());
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+}
+
+/// @brief Verifies that the critical section works.
+TEST(MultiThreadingMgrTest, criticalSection) {
+ // get the thread pool instance
+ auto& thread_pool = MultiThreadingMgr::instance().getThreadPool();
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // apply multi-threading configuration with 16 threads and queue size 256
+ MultiThreadingMgr::instance().apply(true, 16, 256);
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 16);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection inner_cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ }
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ }
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 16);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 16
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 16);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // apply multi-threading configuration with 64 threads and queue size 4
+ MultiThreadingMgr::instance().apply(true, 64, 4);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 64
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 64);
+ // queue size should be 4
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 4);
+ }
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 64);
+ // thread count should be 64
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 64);
+ // queue size should be 4
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 4);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 64
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 64);
+ // queue size should be 4
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 4);
+ // apply multi-threading configuration with 0 threads
+ MultiThreadingMgr::instance().apply(false, 64, 256);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ }
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection inner_cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ }
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ }
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 0);
+ // queue size should be 0
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 0);
+ // use scope to test constructor and destructor
+ {
+ MultiThreadingCriticalSection cs;
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // apply multi-threading configuration with 64 threads
+ MultiThreadingMgr::instance().apply(true, 64, 256);
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+ // thread count should be 64
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 64);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ }
+ // thread count should match
+ EXPECT_EQ(thread_pool.size(), 64);
+ // thread count should be 64
+ EXPECT_EQ(MultiThreadingMgr::instance().getThreadPoolSize(), 64);
+ // queue size should be 256
+ EXPECT_EQ(MultiThreadingMgr::instance().getPacketQueueSize(), 256);
+ // apply multi-threading configuration with 0 threads
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+}
+
+/// @brief Checks that the lock works only when multi-threading is enabled and
+/// only during its lifetime.
+TEST(MultiThreadingLockTest, scope) {
+ // Check that the mutex is unlocked by default at first.
+ std::mutex mutex;
+ ASSERT_TRUE(mutex.try_lock());
+ mutex.unlock();
+
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setMode(false));
+
+ // Check that the lock does not locks the mutex if multi-threading is disabled.
+ {
+ MultiThreadingLock lock(mutex);
+ ASSERT_TRUE(mutex.try_lock());
+ mutex.unlock();
+ }
+
+ // Check that the mutex is still unlocked when the lock goes out of scope.
+ ASSERT_TRUE(mutex.try_lock());
+ mutex.unlock();
+
+ EXPECT_NO_THROW(MultiThreadingMgr::instance().setMode(true));
+
+ // Check that the lock actively locks the mutex if multi-threading is enabled.
+ {
+ MultiThreadingLock lock(mutex);
+ ASSERT_FALSE(mutex.try_lock());
+ }
+
+ // Check that the mutex is unlocked when the lock goes out of scope.
+ ASSERT_TRUE(mutex.try_lock());
+ mutex.unlock();
+}
+
+/// @brief Test fixture for exercised CriticalSection callbacks.
+class CriticalSectionCallbackTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ CriticalSectionCallbackTest() {
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+ }
+
+ /// @brief Destructor.
+ ~CriticalSectionCallbackTest() {
+ MultiThreadingMgr::instance().apply(false, 0, 0);
+ }
+
+ /// @brief A callback that adds the value 1 to invocations lists.
+ void one() {
+ invocations_.push_back(1);
+ }
+
+ /// @brief A callback that adds the value 2 to invocations lists.
+ void two() {
+ invocations_.push_back(2);
+ }
+
+ /// @brief A callback that adds the value 3 to invocations lists.
+ void three() {
+ invocations_.push_back(3);
+ }
+
+ /// @brief A callback that adds the value 4 to invocations lists.
+ void four() {
+ invocations_.push_back(4);
+ }
+
+ /// @brief A callback that throws @ref isc::Exception which is ignored.
+ void ignoredException() {
+ isc_throw(isc::Exception, "ignored");
+ }
+
+ /// @brief A callback that throws @ref isc::MultiThreadingInvalidOperation
+ /// which is propagated to the scope of the
+ /// @ref MultiThreadingCriticalSection constructor.
+ void observedException() {
+ isc_throw(isc::MultiThreadingInvalidOperation, "observed");
+ }
+
+ /// @brief Indicates whether or not the DHCP thread pool is running.
+ ///
+ /// @return True if the pool is running, false otherwise.
+ bool isThreadPoolRunning() {
+ return (MultiThreadingMgr::instance().getThreadPool().size());
+ }
+
+ /// @brief Checks callback invocations over a series of nested
+ /// CriticalSections.
+ ///
+ /// @param entries A vector of the invocation values that should
+ /// be present after entry into the outermost CriticalSection. The
+ /// expected values should be in the order the callbacks were added
+ /// to the MultiThreadingMgr's list of callbacks.
+ /// @param exits A vector of the invocation values that should
+ /// be present after exiting the outermost CriticalSection. The
+ /// expected values should be in the order the callbacks were added
+ /// to the MultiThreadingMgr's list of callbacks.
+ /// @param should_throw The flag indicating if the CriticalSection should
+ /// throw, simulating a dead-lock scenario when a processing thread tries
+ /// to stop the thread pool.
+ void runCriticalSections(std::vector<int> entries, std::vector<int>exits,
+ bool should_throw = false) {
+ // Pool must be running.
+ ASSERT_TRUE(isThreadPoolRunning());
+
+ // Clear the invocations list.
+ invocations_.clear();
+
+ // Use scope to create nested CriticalSections.
+ if (!should_throw) {
+ // Enter a critical section.
+ MultiThreadingCriticalSection cs;
+
+ // Thread pool should be stopped.
+ ASSERT_FALSE(isThreadPoolRunning());
+
+ if (entries.size()) {
+ // We expect entry invocations.
+ ASSERT_EQ(invocations_.size(), entries.size());
+ ASSERT_EQ(invocations_, entries);
+ } else {
+ // We do not expect entry invocations.
+ ASSERT_FALSE(invocations_.size());
+ }
+
+ // Clear the invocations list.
+ invocations_.clear();
+
+ {
+ // Enter another CriticalSection.
+ MultiThreadingCriticalSection inner_cs;
+
+ // Thread pool should still be stopped.
+ ASSERT_FALSE(isThreadPoolRunning());
+
+ // We should not have had any callback invocations.
+ ASSERT_FALSE(invocations_.size());
+ }
+
+ // After exiting inner section, the thread pool should
+ // still be stopped.
+ ASSERT_FALSE(isThreadPoolRunning());
+
+ // We should not have had more callback invocations.
+ ASSERT_FALSE(invocations_.size());
+ } else {
+ ASSERT_THROW(MultiThreadingCriticalSection cs, MultiThreadingInvalidOperation);
+
+ if (entries.size()) {
+ // We expect entry invocations.
+ ASSERT_EQ(invocations_.size(), entries.size());
+ ASSERT_EQ(invocations_, entries);
+ } else {
+ // We do not expect entry invocations.
+ ASSERT_FALSE(invocations_.size());
+ }
+
+ // Clear the invocations list.
+ invocations_.clear();
+ }
+
+ // After exiting the outer section, the thread pool should
+ // match the thread count.
+ ASSERT_TRUE(isThreadPoolRunning());
+
+ if (exits.size()) {
+ // We expect exit invocations.
+ ASSERT_EQ(invocations_, exits);
+ } else {
+ // We do not expect exit invocations.
+ ASSERT_FALSE(invocations_.size());
+ }
+ }
+
+ /// @brief A list of values set by callback invocations.
+ std::vector<int> invocations_;
+};
+
+/// @brief Verifies critical section callback maintenance:
+/// catch invalid pairs, add pairs, remove pairs.
+TEST_F(CriticalSectionCallbackTest, addAndRemove) {
+ auto& mgr = MultiThreadingMgr::instance();
+
+ // Cannot add with a blank name.
+ ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("", [](){}, [](){}, [](){}),
+ BadValue, "CSCallbackSetList - name cannot be empty");
+
+ // Cannot add with an empty check callback.
+ ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("bad", nullptr, [](){}, [](){}),
+ BadValue, "CSCallbackSetList - check callback for bad cannot be empty");
+
+ // Cannot add with an empty exit callback.
+ ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("bad", [](){}, nullptr, [](){}),
+ BadValue, "CSCallbackSetList - entry callback for bad cannot be empty");
+
+ // Cannot add with an empty exit callback.
+ ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("bad", [](){}, [](){}, nullptr),
+ BadValue, "CSCallbackSetList - exit callback for bad cannot be empty");
+
+ // Should be able to add foo.
+ ASSERT_NO_THROW_LOG(mgr.addCriticalSectionCallbacks("foo", [](){}, [](){}, [](){}));
+
+ // Should not be able to add foo twice.
+ ASSERT_THROW_MSG(mgr.addCriticalSectionCallbacks("foo", [](){}, [](){}, [](){}),
+ BadValue, "CSCallbackSetList - callbacks for foo already exist");
+
+ // Should be able to add bar.
+ ASSERT_NO_THROW_LOG(mgr.addCriticalSectionCallbacks("bar", [](){}, [](){}, [](){}));
+
+ // Should be able to remove foo.
+ ASSERT_NO_THROW_LOG(mgr.removeCriticalSectionCallbacks("foo"));
+
+ // Should be able to remove foo twice without issue.
+ ASSERT_NO_THROW_LOG(mgr.removeCriticalSectionCallbacks("foo"));
+
+ // Should be able to remove all without issue.
+ ASSERT_NO_THROW_LOG(mgr.removeAllCriticalSectionCallbacks());
+}
+
+/// @brief Verifies that the critical section callbacks work.
+TEST_F(CriticalSectionCallbackTest, invocations) {
+ // get the thread pool instance
+ auto& thread_pool = MultiThreadingMgr::instance().getThreadPool();
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+
+ // Add two sets of CriticalSection call backs.
+ MultiThreadingMgr::instance().addCriticalSectionCallbacks("oneAndTwo",
+ std::bind(&CriticalSectionCallbackTest::ignoredException, this),
+ std::bind(&CriticalSectionCallbackTest::one, this),
+ std::bind(&CriticalSectionCallbackTest::two, this));
+
+ MultiThreadingMgr::instance().addCriticalSectionCallbacks("threeAndFour",
+ std::bind(&CriticalSectionCallbackTest::ignoredException, this),
+ std::bind(&CriticalSectionCallbackTest::three, this),
+ std::bind(&CriticalSectionCallbackTest::four, this));
+
+ // Apply multi-threading configuration with 16 threads and queue size 256.
+ MultiThreadingMgr::instance().apply(true, 16, 256);
+
+ // Make three passes over nested CriticalSections to ensure
+ // callbacks execute at the appropriate times and we can do
+ // so repeatedly.
+ for (int i = 0; i < 3; ++i) {
+ runCriticalSections({1 ,3}, {4, 2});
+ }
+
+ // Now remove the first set of callbacks.
+ MultiThreadingMgr::instance().removeCriticalSectionCallbacks("oneAndTwo");
+
+ // Retest CriticalSections.
+ runCriticalSections({3}, {4});
+
+ // Now remove the remaining callbacks.
+ MultiThreadingMgr::instance().removeAllCriticalSectionCallbacks();
+
+ // Retest CriticalSections.
+ runCriticalSections({}, {});
+}
+
+/// @brief Verifies that the critical section callbacks work.
+TEST_F(CriticalSectionCallbackTest, invocationsWithExceptions) {
+ // get the thread pool instance
+ auto& thread_pool = MultiThreadingMgr::instance().getThreadPool();
+ // thread pool should be stopped
+ EXPECT_EQ(thread_pool.size(), 0);
+
+ // Apply multi-threading configuration with 16 threads and queue size 256.
+ MultiThreadingMgr::instance().apply(true, 16, 256);
+
+ // Add two sets of CriticalSection call backs.
+ MultiThreadingMgr::instance().addCriticalSectionCallbacks("observed",
+ std::bind(&CriticalSectionCallbackTest::observedException, this),
+ std::bind(&CriticalSectionCallbackTest::one, this),
+ std::bind(&CriticalSectionCallbackTest::two, this));
+
+ MultiThreadingMgr::instance().addCriticalSectionCallbacks("ignored",
+ std::bind(&CriticalSectionCallbackTest::ignoredException, this),
+ std::bind(&CriticalSectionCallbackTest::three, this),
+ std::bind(&CriticalSectionCallbackTest::four, this));
+
+ // Make three passes over nested CriticalSections to ensure
+ // callbacks execute at the appropriate times and we can do
+ // so repeatedly.
+ for (int i = 0; i < 3; ++i) {
+ runCriticalSections({}, {}, true);
+ }
+
+ // Now remove the first set of callbacks.
+ MultiThreadingMgr::instance().removeCriticalSectionCallbacks("observed");
+
+ // Retest CriticalSections.
+ runCriticalSections({3}, {4});
+
+ // Now remove the remaining callbacks.
+ MultiThreadingMgr::instance().removeAllCriticalSectionCallbacks();
+
+ // Retest CriticalSections.
+ runCriticalSections({}, {});
+}