diff options
author | mukesh agrawal <quiche@google.com> | 2016-10-27 17:39:18 -0700 |
---|---|---|
committer | mukesh agrawal <quiche@google.com> | 2016-11-15 11:12:35 -0800 |
commit | 960c8a88c83c55c21a3286628591a41d2fb42231 (patch) | |
tree | afe37b9a1fe111663b225442fb6514ca5c11f7d4 | |
parent | 3b1d20a5720a5fe6b0a1eccd577b0d2447139efc (diff) | |
download | wifilogd-960c8a88c83c55c21a3286628591a41d2fb42231.tar.gz |
Os: add Nanosleep()
Add Nanosleep(), so that upper layers of wifilogd can
briefly suspend execution after a transient error.
Bug: 32454491
Test: ./runtest.sh (on angler)
Change-Id: I85e95ddf4139ae10ab95a40c46ed2a424e2a28ad
-rw-r--r-- | os.cpp | 24 | ||||
-rw-r--r-- | os.h | 5 | ||||
-rw-r--r-- | raw_os.cpp | 4 | ||||
-rw-r--r-- | raw_os.h | 4 | ||||
-rw-r--r-- | tests/mock_os.h | 1 | ||||
-rw-r--r-- | tests/mock_raw_os.h | 2 | ||||
-rw-r--r-- | tests/os_unittest.cpp | 110 |
7 files changed, 148 insertions, 2 deletions
@@ -14,10 +14,10 @@ * limitations under the License. */ -#include <errno.h> - #include <algorithm> +#include <cerrno> #include <cstdint> +#include <cstring> #include "android-base/logging.h" @@ -70,6 +70,26 @@ Os::Timestamp Os::GetTimestamp(clockid_t clock_id) const { return now_timestamp; } +void Os::Nanosleep(uint32_t sleep_time_nsec) { + struct timespec sleep_timespec = { + 0, // tv_sec + SAFELY_CLAMP(sleep_time_nsec, decltype(timespec::tv_nsec), 0, kMaxNanos)}; + + int failed = 0; + do { + struct timespec remaining_timespec; + failed = raw_os_->Nanosleep(&sleep_timespec, &remaining_timespec); + sleep_timespec = remaining_timespec; + } while (failed && errno == EINTR && sleep_timespec.tv_nsec > 0); + + if (failed && errno != EINTR) { + // The only other documented errors for the underlying nanosleep() call are + // EFAULT and EINVAL. But we always pass valid pointers, and the values in + // |sleep_timespec| are always valid. + LOG(FATAL) << "Unexpected error: " << std::strerror(errno); + } +} + std::tuple<size_t, Os::Errno> Os::ReceiveDatagram(int fd, void* buf, size_t buflen) { // recv() takes a size_t, but returns an ssize_t. That means that the largest @@ -49,6 +49,7 @@ class Os { }; static constexpr int kInvalidFd = -1; + static constexpr auto kMaxNanos = 999'999'999; // Constructs an Os instance. Os(); @@ -68,6 +69,10 @@ class Os { // Returns the current time, as reported by the clock with |clock_id|. virtual Timestamp GetTimestamp(clockid_t clock_id) const; + // Suspends execution of this process, for |sleep_time_nsec|. The passed + // value must not exceed kMaxNanos. + virtual void Nanosleep(uint32_t sleep_time_nsec); + // Receives a datagram of up to |buflen| from |fd|, writing the data to |buf|. // Returns the size of the datagram, and the result of the operation (0 for // success, |errno| otherwise). @@ -39,6 +39,10 @@ int RawOs::GetControlSocket(const char* socket_name) { return android_get_control_socket(socket_name); } +int RawOs::Nanosleep(const struct timespec* req, struct timespec* rem) { + return nanosleep(req, rem); +} + ssize_t RawOs::Recv(int sockfd, void* buf, size_t buflen, int flags) { return recv(sockfd, buf, buflen, flags); } @@ -41,6 +41,10 @@ class RawOs { // See android_get_control_socket(). virtual int GetControlSocket(const char* socket_name); + // See nanosleep(). + virtual int Nanosleep(NONNULL const struct timespec* req, + struct timespec* rem); + // See recv(). virtual ssize_t Recv(int sockfd, void* buf, size_t buflen, int flags); diff --git a/tests/mock_os.h b/tests/mock_os.h index 3e02730..410b099 100644 --- a/tests/mock_os.h +++ b/tests/mock_os.h @@ -36,6 +36,7 @@ class MockOs : public Os { MOCK_CONST_METHOD1(GetTimestamp, Timestamp(clockid_t clock_id)); MOCK_METHOD1(GetControlSocket, std::tuple<int, Errno>(const std::string& socket_name)); + MOCK_METHOD1(Nanosleep, void(uint32_t sleep_time_nsec)); MOCK_METHOD3(ReceiveDatagram, std::tuple<size_t, Errno>(int fd, void* buf, size_t buflen)); MOCK_METHOD3(Write, std::tuple<size_t, Os::Errno>(int fd, const void* buf, diff --git a/tests/mock_raw_os.h b/tests/mock_raw_os.h index 3931cb0..9661635 100644 --- a/tests/mock_raw_os.h +++ b/tests/mock_raw_os.h @@ -33,6 +33,8 @@ class MockRawOs : public RawOs { MOCK_CONST_METHOD2(ClockGettime, int(clockid_t clock_id, struct timespec* tspec)); MOCK_METHOD1(GetControlSocket, int(const char* socket_name)); + MOCK_METHOD2(Nanosleep, + int(const struct timespec* req, struct timespec* rem)); MOCK_METHOD4(Recv, ssize_t(int sockfd, void* buf, size_t buflen, int flags)); MOCK_METHOD3(Write, ssize_t(int fd, const void* buf, size_t buflen)); diff --git a/tests/os_unittest.cpp b/tests/os_unittest.cpp index e90755a..0cfb493 100644 --- a/tests/os_unittest.cpp +++ b/tests/os_unittest.cpp @@ -15,6 +15,7 @@ */ #include <array> +#include <iostream> #include <memory> #include <tuple> @@ -25,11 +26,25 @@ #include "wifilogd/os.h" #include "wifilogd/tests/mock_raw_os.h" +// This function must be defined in the same namespace as |timespec|. Hence the +// placement of this function at the top level. +inline void PrintTo(const timespec& ts, ::std::ostream* os) { + *os << "[secs:" << ts.tv_sec << " " + << "nsecs:" << ts.tv_nsec << "]"; +} + namespace android { namespace wifilogd { namespace { using ::testing::_; +using ::testing::Invoke; +using ::testing::InSequence; +using ::testing::Matcher; +using ::testing::MatcherInterface; +using ::testing::MatchResultListener; +using ::testing::NotNull; +using ::testing::Pointee; using ::testing::Return; using ::testing::SetArgumentPointee; using ::testing::SetErrnoAndReturn; @@ -52,6 +67,29 @@ class OsTest : public ::testing::Test { MockRawOs* raw_os_; }; +class TimespecMatcher : public MatcherInterface<const timespec&> { + public: + explicit TimespecMatcher(const timespec& expected) : expected_(expected) {} + + virtual void DescribeTo(::std::ostream* os) const { + *os << "equals "; + PrintTo(expected_, os); + } + + virtual bool MatchAndExplain(const timespec& actual, + MatchResultListener* /* listener */) const { + return actual.tv_sec == expected_.tv_sec && + actual.tv_nsec == expected_.tv_nsec; + } + + private: + const timespec& expected_; +}; + +Matcher<const timespec&> EqualsTimespec(const timespec& expected) { + return MakeMatcher(new TimespecMatcher(expected)); +} + } // namespace TEST_F(OsTest, GetControlSocketReturnsFdAndZeroOnSuccess) { @@ -86,6 +124,72 @@ TEST_F(OsTest, GetTimestampSucceeds) { EXPECT_EQ(kFakeNsecs, received.nsecs); } +TEST_F(OsTest, NanosleepPassesNormalValueToSyscall) { + constexpr auto kSleepTimeNsec = 100; + EXPECT_CALL(*raw_os_, + Nanosleep(Pointee(EqualsTimespec({0, kSleepTimeNsec})), _)); + os_->Nanosleep(kSleepTimeNsec); +} + +TEST_F(OsTest, NanosleepPassesMaxmimalValueToSyscall) { + EXPECT_CALL(*raw_os_, + Nanosleep(Pointee(EqualsTimespec({0, Os::kMaxNanos})), _)); + os_->Nanosleep(Os::kMaxNanos); +} + +TEST_F(OsTest, NanosleepPassesZeroValueToSyscall) { + EXPECT_CALL(*raw_os_, Nanosleep(Pointee(EqualsTimespec({0, 0})), _)); + os_->Nanosleep(0); +} + +TEST_F(OsTest, NanosleepClampsOverlyLargeValue) { + EXPECT_CALL(*raw_os_, + Nanosleep(Pointee(EqualsTimespec({0, Os::kMaxNanos})), _)); + os_->Nanosleep(Os::kMaxNanos + 1); +} + +TEST_F(OsTest, NanosleepRetriesOnInterruptedCall) { + InSequence seq; + EXPECT_CALL(*raw_os_, Nanosleep(_, NotNull())) + .WillOnce(Invoke([](const timespec* /* desired */, timespec* remaining) { + *remaining = {0, 100}; + errno = EINTR; + return -1; + })); + EXPECT_CALL(*raw_os_, Nanosleep(Pointee(EqualsTimespec({0, 100})), _)); + os_->Nanosleep(Os::kMaxNanos); +} + +TEST_F(OsTest, NanosleepRetriesMultipleTimesIfNecessary) { + InSequence seq; + EXPECT_CALL(*raw_os_, Nanosleep(_, NotNull())) + .WillOnce(Invoke([](const timespec* /* desired */, timespec* remaining) { + *remaining = {0, 100}; + errno = EINTR; + return -1; + })); + EXPECT_CALL(*raw_os_, Nanosleep(_, NotNull())) + .WillOnce(Invoke([](const timespec* /* desired */, timespec* remaining) { + *remaining = {0, 50}; + errno = EINTR; + return -1; + })); + EXPECT_CALL(*raw_os_, Nanosleep(Pointee(EqualsTimespec({0, 50})), _)); + os_->Nanosleep(Os::kMaxNanos); +} + +TEST_F(OsTest, NanosleepIgnoresEintrWithZeroTimeRemaining) { + InSequence seq; + EXPECT_CALL(*raw_os_, Nanosleep(_, NotNull())) + .WillOnce(Invoke([](const timespec* /* desired */, timespec* remaining) { + *remaining = {0, 0}; + errno = EINTR; + return -1; + })); + EXPECT_CALL(*raw_os_, Nanosleep(_, _)).Times(0); + os_->Nanosleep(Os::kMaxNanos); +} + TEST_F(OsTest, ReceiveDatagramReturnsCorrectValueForMaxSizedDatagram) { constexpr int kFakeFd = 100; std::array<uint8_t, 8192> buffer{}; @@ -217,6 +321,12 @@ TEST_F(OsDeathTest, GetTimestampRawOsErrorCausesDeath) { EXPECT_DEATH(os_->GetTimestamp(CLOCK_REALTIME), "Unexpected error"); } +TEST_F(OsDeathTest, NanosleepUnexpectedErrorCausesDeath) { + ON_CALL(*raw_os_, Nanosleep(Pointee(EqualsTimespec({0, Os::kMaxNanos})), _)) + .WillByDefault(SetErrnoAndReturn(EFAULT, -1)); + EXPECT_DEATH(os_->Nanosleep(Os::kMaxNanos), "Unexpected error"); +} + TEST_F(OsDeathTest, ReceiveDatagramWithOverlyLargeBufferCausesDeath) { constexpr int kFakeFd = 100; std::array<uint8_t, 8192> buffer{}; |