Create a helper object to wrap polling on multiple file descriptors
This helper API can be used to reliably poll on a dynamically increasing
set of file descriptors, and it provides a functional callback on top
of the C poll() API. As is, it only supports POLLIN as that is what
the Sensor API needs, but more event types can be added
Bug: 158763319
Test: atest --host android.hardware.sensors@2.0-Google-IIO-Subhal_test-MultiPoll
Change-Id: Ifabd1d06b595e93bfaf8e0ffab7b783fbcc73b40
Merged-In: Ifabd1d06b595e93bfaf8e0ffab7b783fbcc73b40
diff --git a/hal/sensors/2.0/Android.bp b/hal/sensors/2.0/Android.bp
index 7f7fd80..0ed62a1 100644
--- a/hal/sensors/2.0/Android.bp
+++ b/hal/sensors/2.0/Android.bp
@@ -18,6 +18,7 @@
vendor: true,
srcs: [
"iio_utils.cpp",
+ "MultiPoll.cpp",
"Sensor.cpp",
"SensorsSubHal.cpp",
"SensorThread.cpp",
@@ -50,3 +51,20 @@
generated_sources: ["sensor_hal_configuration_V1_0"],
generated_headers: ["sensor_hal_configuration_V1_0"],
}
+
+cc_test_host {
+ name: "android.hardware.sensors@2.0-Google-IIO-Subhal_test-MultiPoll",
+ srcs: [
+ "MultiPoll.cpp",
+ "tests/MultiPoll.cpp"
+ ],
+ static_libs: [
+ "libgtest",
+ "android.hardware.sensors@1.0",
+ "android.hardware.sensors@2.0",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+}
diff --git a/hal/sensors/2.0/MultiPoll.cpp b/hal/sensors/2.0/MultiPoll.cpp
new file mode 100644
index 0000000..7014c98
--- /dev/null
+++ b/hal/sensors/2.0/MultiPoll.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include "MultiPoll.h"
+
+namespace android::hardware::sensors::V2_0::subhal::implementation {
+
+MultiPoll::MultiPoll(uint64_t periodMs) : mSamplingPeriodMs(periodMs) {}
+
+void MultiPoll::addDescriptor(int fd) {
+ pollfd pfd{.fd = fd, .events = POLLIN, .revents = 0};
+ std::unique_lock<std::mutex> lck(mDescriptorsMutex);
+ mDescriptors.push_back(pfd);
+}
+
+int MultiPoll::poll(OnPollIn in) {
+ std::vector<pollfd> fds;
+ {
+ // make a copy so you don't need to lock for prolonged periods of time
+ std::unique_lock<std::mutex> lck(mDescriptorsMutex);
+ fds.assign(mDescriptors.begin(), mDescriptors.end());
+ }
+
+ int err = ::poll(&fds[0], fds.size(), mSamplingPeriodMs);
+ if (err < 0) return err;
+
+ for (const auto& fd : fds) {
+ if (fd.revents & POLLIN) {
+ in(fd.fd);
+ }
+ }
+
+ return 0;
+}
+
+} // namespace android::hardware::sensors::V2_0::subhal::implementation
diff --git a/hal/sensors/2.0/MultiPoll.h b/hal/sensors/2.0/MultiPoll.h
new file mode 100644
index 0000000..e6ee889
--- /dev/null
+++ b/hal/sensors/2.0/MultiPoll.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 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 <poll.h>
+#include <functional>
+#include <mutex>
+#include <vector>
+
+namespace android::hardware::sensors::V2_0::subhal::implementation {
+
+class MultiPoll {
+ public:
+ explicit MultiPoll(uint64_t periodMs = 0);
+ ~MultiPoll() = default;
+
+ // TODO(egranata): add support for events other than POLLIN
+ void addDescriptor(int fd);
+ using OnPollIn = std::function<void(int fd)>;
+ int poll(OnPollIn in);
+
+ private:
+ uint64_t mSamplingPeriodMs;
+ std::mutex mDescriptorsMutex;
+ std::vector<pollfd> mDescriptors;
+};
+
+} // namespace android::hardware::sensors::V2_0::subhal::implementation
diff --git a/hal/sensors/2.0/tests/MultiPoll.cpp b/hal/sensors/2.0/tests/MultiPoll.cpp
new file mode 100644
index 0000000..5adef7e
--- /dev/null
+++ b/hal/sensors/2.0/tests/MultiPoll.cpp
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include <gtest/gtest.h>
+#include <unistd.h>
+#include <chrono>
+#include <thread>
+
+#include "MultiPoll.h"
+
+using android::hardware::sensors::V2_0::subhal::implementation::MultiPoll;
+
+// using namespace here is the sanctioned C++ way
+// NOLINTNEXTLINE(build/namespaces)
+using namespace std::chrono_literals;
+
+class PipeHelper {
+ public:
+ PipeHelper() {
+ int pipefd[2] = {0, 0};
+ int err = ::pipe(pipefd);
+ if (err == 0) {
+ mReadFd = pipefd[0];
+ mWriteFd = pipefd[1];
+ } else {
+ mReadFd = mWriteFd = -1;
+ }
+ }
+
+ ~PipeHelper() {
+ close(mReadFd);
+ close(mWriteFd);
+ }
+
+ int readFd() const { return mReadFd; }
+ int writeFd() const { return mWriteFd; }
+
+ explicit operator bool() const { return (mReadFd >= 0) && (mWriteFd >= 0); }
+
+ size_t read(char* buf, size_t n) { return ::read(mReadFd, reinterpret_cast<void*>(buf), n); }
+ size_t write(const char* buf, size_t n) {
+ return ::write(mWriteFd, reinterpret_cast<const void*>(buf), n);
+ }
+
+ private:
+ int mReadFd;
+ int mWriteFd;
+};
+
+TEST(MultiPollTest, EmptyList) {
+ MultiPoll mp(100);
+ bool called = false;
+ MultiPoll::OnPollIn f = [&called](int) -> void { called = true; };
+ mp.poll(f);
+ EXPECT_FALSE(called);
+}
+
+TEST(MultiPollTest, DataAvailable) {
+ MultiPoll mp(100);
+ PipeHelper pe;
+ ASSERT_TRUE(pe);
+
+ mp.addDescriptor(pe.readFd());
+ pe.write("hello", 5);
+ bool called = false;
+ int poll_fd;
+ MultiPoll::OnPollIn f = [&called, &poll_fd](int fd) -> void {
+ called = true;
+ poll_fd = fd;
+ };
+ mp.poll(f);
+ EXPECT_TRUE(called);
+ EXPECT_EQ(poll_fd, pe.readFd());
+}
+
+TEST(MultiPollTest, DataComesUpLater) {
+ MultiPoll mp(120000 /* 2 minutes */);
+ PipeHelper pe;
+ ASSERT_TRUE(pe);
+ mp.addDescriptor(pe.readFd());
+
+ bool called = false;
+ int poll_fd;
+ MultiPoll::OnPollIn f = [&called, &poll_fd](int fd) -> void {
+ called = true;
+ poll_fd = fd;
+ };
+ std::thread pollerThread([&mp, &f]() -> void { mp.poll(f); });
+
+ std::this_thread::sleep_for(100ms);
+ pe.write("hello", 5);
+
+ pollerThread.join();
+ EXPECT_TRUE(called);
+ EXPECT_EQ(poll_fd, pe.readFd());
+}
+
+TEST(MultiPollTest, OneFdHasData) {
+ MultiPoll mp(100);
+ PipeHelper p1;
+ PipeHelper p2;
+
+ mp.addDescriptor(p1.readFd());
+ mp.addDescriptor(p2.readFd());
+
+ int called = 0;
+ MultiPoll::OnPollIn f = [&called](int) -> void { ++called; };
+
+ p1.write("hello", 5);
+ mp.poll(f);
+ EXPECT_EQ(1, called);
+}
+
+TEST(MultiPollTest, TwoFdHaveData) {
+ MultiPoll mp(100);
+ PipeHelper p1;
+ PipeHelper p2;
+
+ mp.addDescriptor(p1.readFd());
+ mp.addDescriptor(p2.readFd());
+
+ int called = 0;
+ int prev_fd = -1;
+ bool repeat_fd = false;
+ MultiPoll::OnPollIn f = [&called, &prev_fd, &repeat_fd](int fd) -> void {
+ ++called;
+ if (prev_fd == fd) repeat_fd = true;
+ prev_fd = fd;
+ };
+
+ p1.write("hello", 5);
+ p2.write("hi", 2);
+ mp.poll(f);
+ EXPECT_EQ(2, called);
+ EXPECT_FALSE(repeat_fd);
+ EXPECT_TRUE(prev_fd == p1.readFd() || prev_fd == p2.readFd());
+}
+
+TEST(MultiPollTest, ZeroWait) {
+ MultiPoll mp(0);
+ PipeHelper pe;
+ ASSERT_TRUE(pe);
+ mp.addDescriptor(pe.readFd());
+
+ bool called = false;
+ int poll_fd;
+ MultiPoll::OnPollIn f = [&called, &poll_fd](int fd) -> void {
+ called = true;
+ poll_fd = fd;
+ };
+ std::thread pollerThread([&mp, &f, &called]() -> void {
+ while (!called) mp.poll(f);
+ });
+
+ std::this_thread::sleep_for(100ms);
+ pe.write("hello", 5);
+
+ pollerThread.join();
+ EXPECT_TRUE(called);
+ EXPECT_EQ(poll_fd, pe.readFd());
+}
+
+TEST(MultiPollTest, AddOneLater) {
+ MultiPoll mp(100);
+ PipeHelper p1;
+ PipeHelper p2;
+
+ mp.addDescriptor(p1.readFd());
+
+ bool called = false;
+ int poll_fd;
+ MultiPoll::OnPollIn f = [&called, &poll_fd](int fd) -> void {
+ called = true;
+ poll_fd = fd;
+ };
+
+ std::thread pollerThread([&mp, &f, &called]() -> void {
+ while (!called) mp.poll(f);
+ });
+
+ std::this_thread::sleep_for(250ms);
+ mp.addDescriptor(p2.readFd());
+ std::this_thread::sleep_for(100ms);
+ p2.write("hello", 5);
+
+ pollerThread.join();
+ EXPECT_TRUE(called);
+ EXPECT_EQ(p2.readFd(), poll_fd);
+}