mediautils: Add jthread impl
std::jthread isn't available, so add a util which implements the
essential subset of its behavior.
Test: jthread_tests
Bug: 355498020
Flag: EXEMPT new utility
Change-Id: I73e02aa53ee056bc3eb23ea432725e2ec55c8a46
diff --git a/media/utils/include/mediautils/jthread.h b/media/utils/include/mediautils/jthread.h
new file mode 100644
index 0000000..17532a4
--- /dev/null
+++ b/media/utils/include/mediautils/jthread.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed 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 <atomic>
+#include <thread>
+#include <utility>
+
+namespace android::mediautils {
+
+namespace impl {
+class stop_source;
+/**
+ * Const view on stop source, which the running thread uses and an interface
+ * for cancellation.
+ */
+class stop_token {
+ public:
+ stop_token(const stop_source& source) : stop_source_(source) {}
+ bool stop_requested() const;
+
+ private:
+ const stop_source& stop_source_;
+};
+
+class stop_source {
+ public:
+ stop_token get_token() { return stop_token{*this}; }
+ bool stop_requested() const { return cancellation_signal_.load(); }
+ bool request_stop() {
+ auto f = false;
+ return cancellation_signal_.compare_exchange_strong(f, true);
+ }
+
+ private:
+ std::atomic_bool cancellation_signal_ = false;
+};
+
+inline bool stop_token::stop_requested() const {
+ return stop_source_.stop_requested();
+}
+} // namespace impl
+
+using stop_token = impl::stop_token;
+/**
+ * Just a jthread, since std::jthread is still experimental in our toolchain.
+ * Implements a subset of essential functionality (co-op cancellation and join on dtor).
+ * If jthread gets picked up, usage can be cut over.
+ */
+class jthread {
+ public:
+ /**
+ * Construct/launch and thread with a callable which consumes a stop_token.
+ * The callable must be cooperatively cancellable via stop_token::stop_requested(), and will be
+ * automatically stopped then joined on destruction.
+ * Example:
+ * jthread([](stop_token stok) {
+ * while(!stok.stop_requested) {
+ * // do work
+ * }
+ * }
+ */
+ template <typename F>
+ jthread(F&& f) : stop_source_{}, thread_{std::forward<F>(f), stop_source_.get_token()} {}
+
+ ~jthread() {
+ stop_source_.request_stop();
+ thread_.join();
+ }
+
+ bool request_stop() { return stop_source_.request_stop(); }
+
+ private:
+ // order matters
+ impl::stop_source stop_source_;
+ std::thread thread_;
+};
+} // namespace android::mediautils
diff --git a/media/utils/tests/Android.bp b/media/utils/tests/Android.bp
index a68569a..ff11b42 100644
--- a/media/utils/tests/Android.bp
+++ b/media/utils/tests/Android.bp
@@ -237,3 +237,11 @@
"shared_memory_allocator_tests.cpp",
],
}
+
+cc_test {
+ name: "jthread_tests",
+ defaults: ["libmediautils_tests_defaults"],
+ srcs: [
+ "jthread_tests.cpp",
+ ],
+}
diff --git a/media/utils/tests/jthread_tests.cpp b/media/utils/tests/jthread_tests.cpp
new file mode 100644
index 0000000..ed77c27
--- /dev/null
+++ b/media/utils/tests/jthread_tests.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed 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.
+ */
+
+#define LOG_TAG "jthread_tests"
+
+#include <mediautils/jthread.h>
+
+#include <gtest/gtest.h>
+
+#include <atomic>
+
+using namespace android::mediautils;
+
+namespace {
+TEST(jthread_tests, dtor) {
+ std::atomic_int x = 0;
+ std::atomic_bool is_stopped = false;
+ {
+ auto jt = jthread([&](stop_token stok) {
+ while (!stok.stop_requested()) {
+ if (x.load() < std::numeric_limits<int>::max())
+ x++;
+ }
+ is_stopped = true;
+ });
+ while (x.load() < 1000)
+ ;
+ }
+ // Check we triggered a stop on dtor
+ ASSERT_TRUE(is_stopped.load());
+ // Check we actually ran
+ ASSERT_GE(x.load(), 1000);
+}
+TEST(jthread_tests, request_stop) {
+ std::atomic_int x = 0;
+ std::atomic_bool is_stopped = false;
+ auto jt = jthread([&](stop_token stok) {
+ while (!stok.stop_requested()) {
+ if (x.load() < std::numeric_limits<int>::max())
+ x++;
+ }
+ is_stopped = true;
+ });
+ while (x.load() < 1000)
+ ;
+ // request stop manually
+ ASSERT_TRUE(jt.request_stop());
+ // busy loop till thread acks
+ while (!is_stopped.load())
+ ;
+ // Check we triggered a stop on dtor
+ ASSERT_TRUE(is_stopped.load());
+ // Check we actually ran
+ ASSERT_GE(x.load(), 1000);
+}
+
+} // namespace