diff --git a/include/tscpp/util/AltMutex.h b/include/tscpp/util/AltMutex.h new file mode 100644 index 00000000000..c7d69eecdb4 --- /dev/null +++ b/include/tscpp/util/AltMutex.h @@ -0,0 +1,110 @@ +/** @file + + An alternative to std::mutex for low contention mutual exclusion. Uses one unsigned int as data. + (sizof(std::mutex) == 40 for gcc on x86.) Locking/unlocking will be very fast when there is no + contention. When there is contention, the latency to get the lock after it is unlocked by another + thread wil likely be longer (on the order of 10ms). If the wait for the lock exceeds a timer tick, + there will likely be unnecessary context switching between threads. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include + +namespace ts +{ +class AltMutex +{ +public: + bool try_lock() noexcept; + + // WARNING: Not recursive. + // + void lock(); + + void unlock() noexcept; + + // Notes: all 3 member functions act as a strong memory fence. The atomic data member suppresses + // copying/moving member functions. + +private: + // This is incremented on locking and unlocking, so it is odd when the lock is locked. + // + std::atomic lock_count{0}; +}; + +inline bool +AltMutex::try_lock() noexcept +{ + unsigned lc = lock_count; + + if (lc & 1) { + // Already locked. + // + return false; + } + + return lock_count.compare_exchange_strong(lc, lc + 1); +} + +inline void +AltMutex::lock() +{ + unsigned tries = 0; + + for (;;) { + if (try_lock()) { + break; + } + + ++tries; + + if (tries < 20) { + std::this_thread::yield(); + + } else { + // Failsafe check for deadlock. + // + ink_release_assert(tries < 50000); + + // This may be a case of priority inversion, sleep to ensure the thread holding the lock can run. + // + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } +} + +inline void +AltMutex::unlock() noexcept +{ + if (lock_count & 1) { + ++lock_count; + + } else { + ink_assert(false); + } +} + +} // end namespace ts diff --git a/include/tscpp/util/Makefile.am b/include/tscpp/util/Makefile.am index 3424e6556d2..6eb24a03a19 100644 --- a/include/tscpp/util/Makefile.am +++ b/include/tscpp/util/Makefile.am @@ -19,6 +19,7 @@ library_includedir=$(includedir)/tscpp/util library_include_HEADERS = \ + AltMutex.h \ IntrusiveDList.h \ LocalBuffer.h \ PostScript.h \ diff --git a/src/tscpp/util/Makefile.am b/src/tscpp/util/Makefile.am index 07351fde45c..0ce14714bc6 100644 --- a/src/tscpp/util/Makefile.am +++ b/src/tscpp/util/Makefile.am @@ -38,6 +38,7 @@ test_tscpputil_CXXFLAGS = -Wno-array-bounds $(AM_CXXFLAGS) test_tscpputil_LDADD = libtscpputil.la test_tscpputil_SOURCES = \ unit_tests/unit_test_main.cc \ + unit_tests/test_AltMutex.cc \ unit_tests/test_LocalBuffer.cc \ unit_tests/test_MemSpan.cc \ unit_tests/test_PostScript.cc \ diff --git a/src/tscpp/util/unit_tests/test_AltMutex.cc b/src/tscpp/util/unit_tests/test_AltMutex.cc new file mode 100644 index 00000000000..0eba230ca6c --- /dev/null +++ b/src/tscpp/util/unit_tests/test_AltMutex.cc @@ -0,0 +1,84 @@ +/** @file + + Unit tests for AltMutex.h. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include + +#include +#include + +namespace +{ +int i{0}; + +ts::AltMutex mtx; + +void +incr_i(int wait_value) +{ + bool done = false; + + do { + mtx.lock(); + + if (i == wait_value) { + ++i; + done = true; + } + + mtx.unlock(); + + } while (!done); +} + +void +thread2() +{ + incr_i(1); +} + +void +thread3() +{ + incr_i(2); +} + +} // end anonymous namespace + +void +_ink_assert(char const *, char const *, int) +{ + std::exit(1); +} + +TEST_CASE("AltMutex", "[AM]") +{ + std::thread t2(thread2); + std::thread t3(thread3); + + incr_i(0); + + t2.join(); + t3.join(); + + REQUIRE(i == 3); +}