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