summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibC/pthread_once.cpp
diff options
context:
space:
mode:
authorTim Schumacher <timschumi@gmx.de>2022-06-12 20:16:06 +0200
committerLinus Groh <mail@linusgroh.de>2022-07-19 11:00:35 +0100
commit2f3b9c49a5fa4e867b77b40191c4221db17f0a16 (patch)
tree3ed0bad36c2816eadd800a96bab38ca7ebb31e31 /Userland/Libraries/LibC/pthread_once.cpp
parente156f79f53d9cfbe238fdb4813609bfb5141c114 (diff)
downloadserenity-2f3b9c49a5fa4e867b77b40191c4221db17f0a16.zip
LibPthread: Move the pthread and semaphore implementation to LibC
This additionally adds some compatibility code to redirect linking attempts for LibPthread to LibC instead.
Diffstat (limited to 'Userland/Libraries/LibC/pthread_once.cpp')
-rw-r--r--Userland/Libraries/LibC/pthread_once.cpp88
1 files changed, 88 insertions, 0 deletions
diff --git a/Userland/Libraries/LibC/pthread_once.cpp b/Userland/Libraries/LibC/pthread_once.cpp
new file mode 100644
index 0000000000..ab435d4639
--- /dev/null
+++ b/Userland/Libraries/LibC/pthread_once.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/Assertions.h>
+#include <AK/Atomic.h>
+#include <AK/Types.h>
+#include <pthread.h>
+#include <serenity.h>
+
+enum State : i32 {
+ INITIAL = PTHREAD_ONCE_INIT,
+ DONE,
+ PERFORMING_NO_WAITERS,
+ PERFORMING_WITH_WAITERS,
+};
+
+// https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_once.html
+int pthread_once(pthread_once_t* self, void (*callback)(void))
+{
+ auto& state = reinterpret_cast<Atomic<State>&>(*self);
+
+ // See what the current state is, and at the same time grab the lock if we
+ // got here first. We need acquire ordering here because if we see
+ // State::DONE, everything we do after that should "happen after" everything
+ // the other thread has done before writing the State::DONE.
+ State state2 = State::INITIAL;
+ bool have_exchanged = state.compare_exchange_strong(
+ state2, State::PERFORMING_NO_WAITERS, AK::memory_order_acquire);
+
+ if (have_exchanged) {
+ // We observed State::INITIAL and we've changed it to
+ // State::PERFORMING_NO_WAITERS, so it's us who should perform the
+ // operation.
+ callback();
+ // Now, record that we're done.
+ state2 = state.exchange(State::DONE, AK::memory_order_release);
+ switch (state2) {
+ case State::INITIAL:
+ case State::DONE:
+ VERIFY_NOT_REACHED();
+ case State::PERFORMING_NO_WAITERS:
+ // The fast path: there's no contention, so we don't have to wake
+ // anyone.
+ break;
+ case State::PERFORMING_WITH_WAITERS:
+ futex_wake(self, INT_MAX);
+ break;
+ }
+
+ return 0;
+ }
+
+ // We did not get there first. Let's see if we have to wait.
+ // state2 contains the observed state.
+ while (true) {
+ switch (state2) {
+ case State::INITIAL:
+ VERIFY_NOT_REACHED();
+ case State::DONE:
+ // Awesome, nothing to do then.
+ return 0;
+ case State::PERFORMING_NO_WAITERS:
+ // We're going to wait for it, but we have to record that we're
+ // waiting and the other thread should wake us up. We need acquire
+ // ordering here for the same reason as above.
+ have_exchanged = state.compare_exchange_strong(
+ state2, State::PERFORMING_WITH_WAITERS, AK::memory_order_acquire);
+ if (!have_exchanged) {
+ // Something has changed already, reevaluate without waiting.
+ continue;
+ }
+ state2 = State::PERFORMING_WITH_WAITERS;
+ [[fallthrough]];
+ case State::PERFORMING_WITH_WAITERS:
+ // Let's wait for it.
+ futex_wait(self, state2, nullptr, 0);
+ // We have been woken up, but that might have been due to a signal
+ // or something, so we have to reevaluate. We need acquire ordering
+ // here for the same reason as above. Hopefully we'll just see
+ // State::DONE this time, but who knows.
+ state2 = state.load(AK::memory_order_acquire);
+ continue;
+ }
+ }
+}