From a619ba3e38c67772d4b4cd71e8930391ec737c69 Mon Sep 17 00:00:00 2001 From: Florian Fischer <florian.fl.fischer@fau.de> Date: Tue, 9 Mar 2021 12:38:59 +0100 Subject: [PATCH] =?UTF-8?q?[IO]=20make=20the=20lock=20implementation=20pro?= =?UTF-8?q?tecting=20a=20IoContext's=20cq=20configurable=20=EF=BF=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change introduces a new synchronization primitive "PseudoCountingTryLock" which takes an actual lock as template and provides a CountingTryLock interface. By using a PseudoCountingTryLock we don't have to change any synchronization code in IoContext::reapCompletion. Since all PseudoCountingTryLock code is defined in a header the compiler should see our constant return values and hopefully optimize away any check depending on those constant return values. Options: * spin_lock - naive CAS spin lock * mutex - std::mutex * counting_try_lock (default) - our own lightweight special purpose synchronization primitive --- emper/io/IoContext.hpp | 22 +++++++++++++++++-- emper/lib/sync/PseudoCountingTryLock.hpp | 25 +++++++++++++++++++++ emper/lib/sync/SpinLock.hpp | 28 ++++++++++++++++++++++++ meson.build | 3 +++ meson_options.txt | 7 ++++++ 5 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 emper/lib/sync/PseudoCountingTryLock.hpp create mode 100644 emper/lib/sync/SpinLock.hpp diff --git a/emper/io/IoContext.hpp b/emper/io/IoContext.hpp index 100a11a3..f72395cc 100644 --- a/emper/io/IoContext.hpp +++ b/emper/io/IoContext.hpp @@ -18,10 +18,28 @@ #include "emper-config.h" // for EMPER_IO_WORKER_URING_ENTRIES #include "io/Stats.hpp" // for Stats #include "lib/adt/LockedSet.hpp" // for LockedSet -#include "lib/sync/CountingTryLock.hpp" class Fiber; +#ifdef EMPER_IO_CQ_LOCK_COUNTING_TRY_LOCK +#include "lib/sync/CountingTryLock.hpp" +using CqLock = emper::lib::sync::CountingTryLock; + +#elif defined EMPER_IO_CQ_LOCK_MUTEX +#include <mutex> + +#include "lib/sync/PseudoCountingTryLock.hpp" +using CqLock = emper::lib::sync::PseudoCountingTryLock<std::mutex>; + +#elif defined EMPER_IO_CQ_LOCK_SPIN_LOCK +#include "lib/sync/PseudoCountingTryLock.hpp" +#include "lib/sync/SpinLock.hpp" +using CqLock = emper::lib::sync::PseudoCountingTryLock<emper::lib::sync::SpinLock>; + +#else +#error Uknown cq lock implementation +#endif + namespace emper::io { class Future; @@ -40,7 +58,7 @@ class IoContext : public Logger<LogSubsystem::IO> { static thread_local IoContext *workerIo; // TryLock protecting the completion queue of ring. - ALIGN_TO_CACHE_LINE lib::sync::CountingTryLock cq_lock; + ALIGN_TO_CACHE_LINE CqLock cq_lock; struct io_uring ring; // In a worker's IoContext This eventfd is registered with the io_uring to get completion diff --git a/emper/lib/sync/PseudoCountingTryLock.hpp b/emper/lib/sync/PseudoCountingTryLock.hpp new file mode 100644 index 00000000..2b278bad --- /dev/null +++ b/emper/lib/sync/PseudoCountingTryLock.hpp @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright © 2021 Florian Fischer +#pragma once + +namespace emper::lib::sync { + +template <class Lockable> +class PseudoCountingTryLock { + private: + Lockable lock; + + public: + [[nodiscard]] auto try_lock() -> bool { + lock.lock(); + return true; + } + + [[nodiscard]] auto try_lock_or_increment() -> bool { return try_lock(); } + + auto unlock() -> uint32_t { + lock.unlock(); + return 0; + } +}; +} // namespace emper::lib::sync diff --git a/emper/lib/sync/SpinLock.hpp b/emper/lib/sync/SpinLock.hpp new file mode 100644 index 00000000..a0eef94b --- /dev/null +++ b/emper/lib/sync/SpinLock.hpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright © 2021 Florian Fischer +#pragma once + +#include <atomic> + +#include "Common.hpp" + +namespace emper::lib::sync { + +class SpinLock { + private: + std::atomic<bool> locked; + + public: + SpinLock(bool locked = false) : locked(locked) {} + + inline void lock() { + bool isLocked; + do { + isLocked = false; + } while (!locked.compare_exchange_weak(isLocked, true, std::memory_order_acquire, + std::memory_order_relaxed)); + } + + inline void unlock() { locked.store(false, std::memory_order_release); } +}; +} // namespace emper::lib::sync diff --git a/meson.build b/meson.build index a7319441..69f961da 100644 --- a/meson.build +++ b/meson.build @@ -93,6 +93,9 @@ foreach option : io_raw_options conf_data.set('EMPER_IO_' + option.to_upper(), get_option('io_' + option)) endforeach +io_cq_lock_impl = get_option('io_cq_lock_implementation') +conf_data.set('EMPER_IO_CQ_LOCK_' + io_cq_lock_impl.to_upper(), true) + subdir('emper') subdir('tests') subdir('apps') diff --git a/meson_options.txt b/meson_options.txt index d9fcab82..ac769500 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -115,3 +115,10 @@ option( value: false, description: 'Share a common async backend between all io_urings' ) +option( + 'io_cq_lock_implementation', + type: 'combo', + description: 'The lock implementation used to protect a worker IoContext CQ', + choices: ['spin_lock', 'counting_try_lock', 'mutex'], + value: 'counting_try_lock', +) -- GitLab