blob: ec1f28e540da2674cecf4a83e81108816a9339d2 [file] [log] [blame]
// Copyright 2019 Google LLC
//
// 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
//
// https://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 "base/wait_handle.h"
#include <unistd.h>
#include <string>
#include <thread> // NOLINT
#include <type_traits>
#include "absl/time/time.h"
#include "base/status.h"
#include "base/status_matchers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
// StatusOr<bool> will be true if the status is ok, which is bad.
#define ASSERT_STATUSOR_TRUE(x) ASSERT_TRUE(x.ValueOrDie())
#define ASSERT_STATUSOR_FALSE(x) ASSERT_FALSE(x.ValueOrDie())
namespace iree {
namespace {
using ::testing::_;
using ::testing::Return;
// Tests the AlwaysSignaling helper.
TEST(WaitHandleTest, AlwaysSignaling) {
ASSERT_OK(WaitHandle::AlwaysSignaling().Wait());
EXPECT_FALSE(WaitHandle::AlwaysSignaling().DebugString().empty());
}
// Tests the AlwaysFailing helper.
TEST(WaitHandleTest, AlwaysFailing) {
ASSERT_FALSE(WaitHandle::AlwaysFailing().Wait().ok());
EXPECT_FALSE(WaitHandle::AlwaysFailing().DebugString().empty());
}
// Tests the basic lifecycle of a permanently signaled wait handle.
TEST(WaitHandleTest, LifecyclePermanentSignaled) {
// Just to be sure it's ok to safely no-op a WaitHandle value.
WaitHandle wh_never_used;
(void)wh_never_used;
// Try waiting; should return immediately.
WaitHandle wh0;
ASSERT_OK(wh0.Wait());
// Waits on multiple permanent handles should be ok.
WaitHandle wh1;
ASSERT_OK(WaitHandle::WaitAll({&wh0, &wh1}));
}
// Tests moving permanent WaitHandles around.
TEST(WaitHandleTest, MovePermanent) {
WaitHandle wh0;
WaitHandle wh1{std::move(wh0)};
WaitHandle wh2 = std::move(wh1);
wh1 = std::move(wh2);
}
// Tests moving around real handles (that may require closing).
TEST(WaitHandleTest, MoveRealHandle) {
ManualResetEvent fence0;
WaitHandle wh0 = fence0.OnSet();
WaitHandle wh1{std::move(wh0)};
WaitHandle wh2 = std::move(wh1);
wh1 = std::move(wh2);
// Now overwrite the handle value to force a close.
ManualResetEvent fence1;
WaitHandle wh3 = fence1.OnSet();
wh1 = std::move(wh3);
wh1 = WaitHandle(); // Ensure handle dies first.
}
// Tests the various forms of waiting on a single WaitHandle.
// Since these just call WaitAll we leave the involved testing to those.
TEST(WaitHandleTest, SingleWait) {
WaitHandle wh;
ASSERT_OK(wh.Wait());
ASSERT_OK(wh.Wait(absl::Now() + absl::Seconds(1)));
ASSERT_OK(wh.Wait(absl::Seconds(1)));
ASSERT_STATUSOR_TRUE(wh.TryWait());
}
// Tests using WaitAll with no valid handles. This should no-op.
TEST(WaitHandleTest, WaitAllNop) {
ASSERT_OK(WaitHandle::WaitAll({}));
ASSERT_OK(WaitHandle::WaitAll({nullptr}));
ASSERT_OK(WaitHandle::WaitAll({nullptr, nullptr}));
}
// Tests polling with WaitAll with multiple wait handles.
TEST(WaitHandleTest, WaitAllPoll) {
ManualResetEvent fence0;
WaitHandle wh0 = fence0.OnSet();
ManualResetEvent fence1;
WaitHandle wh1 = fence1.OnSet();
// Poll; should return immediately with timeout.
ASSERT_TRUE(IsDeadlineExceeded(
WaitHandle::WaitAll({&wh0, &wh1}, absl::InfinitePast())));
// Notify fence1.
ASSERT_OK(fence1.Set());
// Poll; should return immediately with timeout as fence1 is not signaled.
ASSERT_TRUE(IsDeadlineExceeded(
WaitHandle::WaitAll({&wh0, &wh1}, absl::InfinitePast())));
// Notify fence0.
ASSERT_OK(fence0.Set());
// Poll again; should return immediately with success.
ASSERT_OK(WaitHandle::WaitAll({&wh0, &wh1}, absl::InfinitePast()));
}
// Tests waiting when the first file handle is invalid. This is to verify a
// workaround for bad poll() behavior with fds[0] == -1.
TEST(WaitHandleTest, WaitAllWithInvalid0) {
ManualResetEvent fence;
WaitHandle wh = fence.OnSet();
// Poll; should return immediately with timeout as fence is not signaled.
ASSERT_TRUE(IsDeadlineExceeded(
WaitHandle::WaitAll({nullptr, &wh}, absl::InfinitePast())));
// Notify fence.
ASSERT_OK(fence.Set());
// Poll again; should return immediately with success.
ASSERT_OK(WaitHandle::WaitAll({nullptr, &wh}, absl::InfinitePast()));
}
// Tests exceeding the timeout deadline with WaitAll.
TEST(WaitHandleTest, WaitAllTimeout) {
ManualResetEvent fence;
WaitHandle wh = fence.OnSet();
// Wait with timeout on the unsignaled fence:
// Via polling (should never block):
ASSERT_TRUE(
IsDeadlineExceeded(WaitHandle::WaitAll({&wh}, absl::InfinitePast())));
ASSERT_STATUSOR_FALSE(WaitHandle::TryWaitAll({&wh}));
// Via time in the near future (should block):
ASSERT_TRUE(
IsDeadlineExceeded(WaitHandle::WaitAll({&wh}, absl::Milliseconds(250))));
// Via time in the past, should exceed deadline.
ASSERT_TRUE(
IsDeadlineExceeded(WaitHandle::WaitAll({&wh}, absl::Milliseconds(-250))));
// Notify and ensure no more timeouts.
ASSERT_OK(fence.Set());
ASSERT_OK(WaitHandle::WaitAll({&wh}, absl::InfinitePast()));
ASSERT_STATUSOR_TRUE(WaitHandle::TryWaitAll({&wh}));
ASSERT_OK(WaitHandle::WaitAll({&wh}, absl::Milliseconds(250)));
// Via time in the past, should exceed deadline even if signaled.
ASSERT_TRUE(
IsDeadlineExceeded(WaitHandle::WaitAll({&wh}, absl::Milliseconds(-250))));
}
// Tests using WaitAll to wait on other threads.
TEST(WaitHandleTest, WaitAllThreaded) {
// Spin up two threads.
ManualResetEvent fence0;
std::thread t0{[&]() {
::usleep(absl::ToInt64Microseconds(absl::Milliseconds(250)));
ASSERT_OK(fence0.Set());
}};
ManualResetEvent fence1;
std::thread t1{[&]() {
::usleep(absl::ToInt64Microseconds(absl::Milliseconds(250)));
ASSERT_OK(fence1.Set());
}};
// Wait on both threads to complete.
WaitHandle wh0 = fence0.OnSet();
WaitHandle wh1 = fence1.OnSet();
ASSERT_OK(WaitHandle::WaitAll({&wh0, &wh1}));
t0.join();
t1.join();
}
// Tests using WaitAll with multiple wait handles from the same fence.
TEST(WaitHandleTest, WaitAllSameSource) {
ManualResetEvent fence;
WaitHandle wh0 = fence.OnSet();
WaitHandle wh1 = fence.OnSet();
ASSERT_TRUE(IsDeadlineExceeded(
WaitHandle::WaitAll({&wh0, &wh1}, absl::InfinitePast())));
ASSERT_OK(fence.Set());
ASSERT_OK(WaitHandle::WaitAll({&wh0, &wh1}));
}
// Tests using WaitAll with literally the same wait handles.
TEST(WaitHandleTest, WaitAllSameHandle) {
ManualResetEvent fence;
WaitHandle wh = fence.OnSet();
ASSERT_TRUE(IsDeadlineExceeded(
WaitHandle::WaitAll({&wh, &wh}, absl::InfinitePast())));
ASSERT_OK(fence.Set());
ASSERT_OK(WaitHandle::WaitAll({&wh, &wh}));
}
// Tests WaitAll when a wait handle fails.
TEST(WaitHandleTest, WaitAllFailure) {
WaitHandle good_wh;
// Create a purposefully bad handle to induce an error.
WaitHandle bad_wh = WaitHandle::AlwaysFailing();
// Should fail with some posixy error.
ASSERT_FALSE(WaitHandle::WaitAll({&good_wh, &bad_wh}).ok());
}
// Tests using WaitAny with no valid handles. This should no-op.
TEST(WaitHandleTest, WaitAnyNop) {
ASSERT_TRUE(IsInvalidArgument(WaitHandle::WaitAny({}).status()));
ASSERT_OK_AND_ASSIGN(int index, WaitHandle::WaitAny({nullptr}));
ASSERT_EQ(0, index);
ASSERT_OK_AND_ASSIGN(index, WaitHandle::WaitAny({nullptr, nullptr}));
ASSERT_EQ(0, index);
}
// Tests polling with WaitAny with multiple wait handles.
TEST(WaitHandleTest, WaitAnyPoll) {
ManualResetEvent fence0;
WaitHandle wh0 = fence0.OnSet();
ManualResetEvent fence1;
WaitHandle wh1 = fence1.OnSet();
// Poll; should return immediately with timeout.
ASSERT_TRUE(IsDeadlineExceeded(
WaitHandle::WaitAny({&wh0, &wh1}, absl::InfinitePast()).status()));
// Notify fence1.
ASSERT_OK(fence1.Set());
// Poll; should return immediately with fence1 signaled.
ASSERT_OK_AND_ASSIGN(int index,
WaitHandle::WaitAny({&wh0, &wh1}, absl::InfinitePast()));
EXPECT_EQ(1, index);
// Notify fence0.
ASSERT_OK(fence0.Set());
// Poll again; should return immediately; which one is signaled is undefined.
ASSERT_OK_AND_ASSIGN(index,
WaitHandle::WaitAny({&wh0, &wh1}, absl::InfinitePast()));
ASSERT_TRUE(index == 0 || index == 1);
}
// Tests exceeding the timeout deadline with WaitAny.
TEST(WaitHandleTest, WaitAnyTimeout) {
ManualResetEvent fence0;
WaitHandle wh0 = fence0.OnSet();
ManualResetEvent fence1;
WaitHandle wh1 = fence1.OnSet();
// Wait with timeout on the unsignaled fences:
// Via polling (should never block):
ASSERT_TRUE(IsDeadlineExceeded(
WaitHandle::WaitAny({&wh0, &wh1}, absl::InfinitePast()).status()));
ASSERT_OK_AND_ASSIGN(int index, WaitHandle::TryWaitAny({&wh0, &wh1}));
ASSERT_EQ(-1, index);
// Via time in the near future (should block):
ASSERT_TRUE(IsDeadlineExceeded(
WaitHandle::WaitAny({&wh0, &wh1}, absl::Milliseconds(250)).status()));
// Notify one of the fences. Should return immediately.
ASSERT_OK(fence1.Set());
ASSERT_OK_AND_ASSIGN(index,
WaitHandle::WaitAny({&wh0, &wh1}, absl::InfinitePast()));
ASSERT_EQ(1, index);
ASSERT_OK_AND_ASSIGN(index, WaitHandle::TryWaitAny({&wh0, &wh1}));
ASSERT_EQ(1, index);
ASSERT_OK_AND_ASSIGN(
index, WaitHandle::WaitAny({&wh0, &wh1}, absl::Milliseconds(250)));
ASSERT_EQ(1, index);
// The unnotified fence should still timeout.
ASSERT_TRUE(IsDeadlineExceeded(
WaitHandle::WaitAny({&wh0}, absl::InfinitePast()).status()));
ASSERT_OK_AND_ASSIGN(index, WaitHandle::TryWaitAny({&wh0}));
ASSERT_EQ(-1, index);
ASSERT_TRUE(IsDeadlineExceeded(
WaitHandle::WaitAny({&wh0}, absl::Milliseconds(250)).status()));
// Notify last fence and ensure complete.
ASSERT_OK(fence0.Set());
ASSERT_OK_AND_ASSIGN(index,
WaitHandle::WaitAny({&wh0}, absl::InfinitePast()));
ASSERT_EQ(0, index);
ASSERT_OK_AND_ASSIGN(index, WaitHandle::TryWaitAny({&wh0}));
ASSERT_EQ(0, index);
ASSERT_OK_AND_ASSIGN(index,
WaitHandle::WaitAny({&wh0}, absl::Milliseconds(250)));
ASSERT_EQ(0, index);
}
// Tests using WaitAny to wait on other threads.
TEST(WaitHandleTest, WaitAnyThreaded) {
// Spin up two threads.
// t1 will wait on t0 such that they will act in sequence.
ManualResetEvent fence0;
std::thread t0{[&]() {
::usleep(absl::ToInt64Microseconds(absl::Milliseconds(250)));
ASSERT_OK(fence0.Set());
}};
ManualResetEvent fence1;
std::thread t1{[&]() {
ASSERT_OK(fence0.OnSet().Wait());
::usleep(absl::ToInt64Microseconds(absl::Milliseconds(250)));
ASSERT_OK(fence1.Set());
}};
// Wait on both threads. We expect 0 to complete first.
WaitHandle wh0 = fence0.OnSet();
WaitHandle wh1 = fence1.OnSet();
ASSERT_OK_AND_ASSIGN(int index, WaitHandle::WaitAny({&wh0, &wh1}));
ASSERT_EQ(0, index);
// Now wait for thread 1.
ASSERT_OK_AND_ASSIGN(index, WaitHandle::WaitAny({&wh1}));
ASSERT_EQ(0, index);
t0.join();
t1.join();
}
// Tests using WaitAny with multiple wait handles from the same fence.
TEST(WaitHandleTest, WaitAnySameSource) {
ManualResetEvent fence;
WaitHandle wh0 = fence.OnSet();
WaitHandle wh1 = fence.OnSet();
ASSERT_TRUE(IsDeadlineExceeded(
WaitHandle::WaitAny({&wh0, &wh1}, absl::InfinitePast()).status()));
ASSERT_OK(fence.Set());
ASSERT_OK_AND_ASSIGN(int index, WaitHandle::WaitAny({&wh0, &wh1}));
ASSERT_TRUE(index == 0 || index == 1);
}
// Tests using WaitAny with literally the same wait handles.
TEST(WaitHandleTest, WaitAnySameHandle) {
ManualResetEvent fence;
WaitHandle wh = fence.OnSet();
ASSERT_TRUE(IsDeadlineExceeded(
WaitHandle::WaitAny({&wh, &wh}, absl::InfinitePast()).status()));
ASSERT_OK(fence.Set());
ASSERT_OK_AND_ASSIGN(int index, WaitHandle::WaitAny({&wh, &wh}));
ASSERT_TRUE(index == 0 || index == 1);
}
// Tests WaitAny when a wait handle fails.
TEST(WaitHandleTest, WaitAnyFailure) {
WaitHandle good_wh;
// Create a purposefully bad handle to induce an error.
WaitHandle bad_wh = WaitHandle::AlwaysFailing();
// Should fail with some posixy error.
ASSERT_FALSE(WaitHandle::WaitAny({&good_wh, &bad_wh}).ok());
}
// ManualResetEvent with innards exposed. Meh.
class ExposedManualResetEvent : public ManualResetEvent {
public:
using ManualResetEvent::AcquireFdForWait;
using ManualResetEvent::TryResolveWakeOnFd;
};
// Mock type for the WaitableObject methods.
class MockWaitableObject : public ::testing::StrictMock<WaitableObject> {
public:
MockWaitableObject() : ::testing::StrictMock<WaitableObject>() {}
MOCK_CONST_METHOD0(DebugString, std::string());
MOCK_METHOD1(AcquireFdForWait,
StatusOr<std::pair<FdType, int>>(absl::Time deadline));
MOCK_METHOD1(TryResolveWakeOnFd, StatusOr<bool>(int fd));
WaitHandle OnSomething() { return WaitHandle(add_ref(this)); }
};
// Tests normal AcquireFdForWait + TryResolveWakeOnFd use.
TEST(WaitableObjectTest, AcquireAndResolve) {
MockWaitableObject mwo;
WaitHandle wh = mwo.OnSomething();
// Use a MRE for testing, as we can just use its fd.
ExposedManualResetEvent mre;
// Try waiting; we should see the AcquireFdForWait and then return because
// the fd has not been resolved.
EXPECT_CALL(mwo, AcquireFdForWait(_)).WillOnce([&](absl::Time deadline) {
// Return the valid FD from the MRE.
return mre.AcquireFdForWait(deadline);
});
ASSERT_STATUSOR_FALSE(wh.TryWait());
// Signal the MRE.
ASSERT_OK(mre.Set());
// Try waiting again; we should get the AcquireFdForWait and then also get
// the TryResolveWakeOnFd.
EXPECT_CALL(mwo, AcquireFdForWait(_)).WillOnce([&](absl::Time deadline) {
// Return the valid (and now signaled) FD from the MRE.
return mre.AcquireFdForWait(deadline);
});
EXPECT_CALL(mwo, TryResolveWakeOnFd(_)).WillOnce(Return(true));
ASSERT_STATUSOR_TRUE(wh.TryWait());
}
// Tests timing out in AcquireFdForWait.
TEST(WaitableObjectTest, AcquireFdForWaitTimeout) {
ManualResetEvent mre;
WaitHandle always_wait = mre.OnSet();
WaitHandle always_signal = WaitHandle::AlwaysSignaling();
MockWaitableObject mwo;
WaitHandle wh = mwo.OnSomething();
// Make the AcquireFdForWait take longer than the timeout. We should hit
// deadline exceeded even though always_wait hasn't be signaled.
EXPECT_CALL(mwo, AcquireFdForWait(_)).WillOnce([](absl::Time deadline) {
::usleep(absl::ToInt64Microseconds(absl::Milliseconds(10)));
return std::make_pair(WaitableObject::FdType::kPermanent,
WaitableObject::kInvalidFd);
});
ASSERT_TRUE(IsDeadlineExceeded(WaitHandle::WaitAll(
{&wh, &always_signal}, absl::Now() - absl::Milliseconds(250))));
}
// Tests TryResolveWakeOnFd when a handle is a permanent kSignaledFd.
TEST(WaitableObjectTest, SignaledFd) {
MockWaitableObject mwo;
WaitHandle wh = mwo.OnSomething();
// Return the kSignaledFd handle and expect that we still get our notify call.
// We can do this multiple times.
for (int i = 0; i < 4; ++i) {
EXPECT_CALL(mwo, AcquireFdForWait(_))
.WillOnce(Return(std::make_pair(WaitableObject::FdType::kPermanent,
WaitableObject::kSignaledFd)));
EXPECT_CALL(mwo, TryResolveWakeOnFd(WaitableObject::kSignaledFd))
.WillOnce(Return(true));
ASSERT_STATUSOR_TRUE(wh.TryWait());
}
}
// Tests that waiting will not resolve if TryResolveWakeOnFd returns false.
TEST(WaitableObjectTest, UnresolvedWake) {
MockWaitableObject mwo;
WaitHandle wh = mwo.OnSomething();
// Fail to resolve the first time.
// Since we are only trying to wait it should bail.
EXPECT_CALL(mwo, AcquireFdForWait(_))
.WillOnce(Return(std::make_pair(WaitableObject::FdType::kPermanent,
WaitableObject::kSignaledFd)));
EXPECT_CALL(mwo, TryResolveWakeOnFd(WaitableObject::kSignaledFd))
.WillOnce(Return(false));
ASSERT_STATUSOR_FALSE(wh.TryWait());
// Resolve on the next try.
EXPECT_CALL(mwo, AcquireFdForWait(_))
.WillOnce(Return(std::make_pair(WaitableObject::FdType::kPermanent,
WaitableObject::kSignaledFd)));
EXPECT_CALL(mwo, TryResolveWakeOnFd(WaitableObject::kSignaledFd))
.WillOnce(Return(true));
ASSERT_STATUSOR_TRUE(wh.TryWait());
}
// Tests the normal lifecycle of a ManualResetEvent.
TEST(ManualResetEventTest, Lifecycle) {
ManualResetEvent ev;
EXPECT_FALSE(ev.DebugString().empty());
WaitHandle wh0 = ev.OnSet();
EXPECT_EQ(ev.DebugString(), wh0.DebugString());
WaitHandle wh1 = ev.OnSet();
EXPECT_EQ(ev.DebugString(), wh1.DebugString());
// Should not be set.
ASSERT_STATUSOR_FALSE(wh0.TryWait());
ASSERT_STATUSOR_FALSE(wh1.TryWait());
// Set should be sticky.
ASSERT_OK(ev.Set());
ASSERT_STATUSOR_TRUE(wh0.TryWait());
ASSERT_STATUSOR_TRUE(wh1.TryWait());
// Reset should clear.
ASSERT_OK(ev.Reset());
ASSERT_STATUSOR_FALSE(wh0.TryWait());
ASSERT_STATUSOR_FALSE(wh1.TryWait());
// Setting again should enable the previous WaitHandles to be signaled.
ASSERT_OK(ev.Set());
ASSERT_STATUSOR_TRUE(wh0.TryWait());
ASSERT_STATUSOR_TRUE(wh1.TryWait());
}
// Tests moving ManualResetEvents around.
TEST(ManualResetEventTest, Move) {
ManualResetEvent ev0;
WaitHandle wh = ev0.OnSet();
ManualResetEvent ev1{std::move(ev0)};
ManualResetEvent ev2 = std::move(ev1);
ev1 = std::move(ev2);
ASSERT_OK(ev1.Set());
ASSERT_STATUSOR_TRUE(wh.TryWait());
}
// Tests redundantly setting and resetting ManualResetEvents.
TEST(ManualResetEventTest, RedundantUse) {
ManualResetEvent ev;
ASSERT_OK(ev.Reset());
ASSERT_OK(ev.Reset());
ASSERT_FALSE(ev.OnSet().TryWait().ValueOrDie());
ASSERT_OK(ev.Set());
ASSERT_OK(ev.Set());
ASSERT_TRUE(ev.OnSet().TryWait().ValueOrDie());
ASSERT_OK(ev.Reset());
ASSERT_FALSE(ev.OnSet().TryWait().ValueOrDie());
}
// Tests waiting on an initially-set ManualResetEvent;
TEST(ManualResetEventTest, SetThenWait) {
ManualResetEvent ev;
ASSERT_OK(ev.Set());
ASSERT_TRUE(ev.OnSet().TryWait().ValueOrDie());
}
// Tests that dangling an event will not wake waiters.
// This is intentional (for now); we could with a bit of wrangling make it so
// that WaitableObjects tracked their waiters and ensured they were all cleaned
// up, but that seems hard. Don't drop your objects.
TEST(ManualResetEventTest, NeverSet) {
ManualResetEvent ev;
WaitHandle wh = ev.OnSet();
ASSERT_STATUSOR_FALSE(wh.TryWait());
// Kill event to unblock waiters.
ev = ManualResetEvent();
// Waiter should not have woken.
ASSERT_STATUSOR_FALSE(wh.TryWait());
}
} // namespace
} // namespace iree