blob: f27f7b52c8627a4d90f9b1d2099bfd45506f827c [file] [log] [blame]
// Copyright 2015 The Cobalt Authors. All Rights Reserved.
//
// 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 "starboard/common/condition_variable.h"
#include "starboard/common/mutex.h"
#include "starboard/common/time.h"
#include "starboard/nplb/thread_helpers.h"
#include "starboard/thread.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace starboard {
namespace nplb {
namespace {
// The SunnyDay, SunnyDayAutoInit, and SunnyDayNearMaxTime test cases directly
// (performs checks in the test case) or indirectly (invokes DoSunnyDay() which
// performs the checks) rely on timing constraints that are prone to failure,
// such as ensuring an action happens within 10 milliseconds. This requirement
// makes the tests flaky since none of these actions can be guaranteed to always
// run within the specified time.
void DoSunnyDay(TakeThenSignalContext* context, bool check_timeout) {
SbThread thread =
SbThreadCreate(0, kSbThreadNoPriority, kSbThreadNoAffinity, true, NULL,
TakeThenSignalEntryPoint, context);
const int64_t kDelay = 10'000; // 10ms
// Allow two-millisecond-level precision.
const int64_t kPrecision = 2'000; // 2ms
// We know the thread hasn't signaled the condition variable yet, and won't
// unless we tell it, so it should wait at least the whole delay time.
if (check_timeout) {
EXPECT_TRUE(SbMutexIsSuccess(SbMutexAcquire(&context->mutex)));
int64_t start = CurrentMonotonicTime();
SbConditionVariableResult result = SbConditionVariableWaitTimed(
&context->condition, &context->mutex, kDelay);
EXPECT_EQ(kSbConditionVariableTimedOut, result);
int64_t elapsed = CurrentMonotonicTime() - start;
EXPECT_LE(kDelay, elapsed + kPrecision);
EXPECT_GT(kDelay * 2, elapsed - kPrecision);
EXPECT_TRUE(SbMutexRelease(&context->mutex));
}
{
EXPECT_TRUE(SbMutexIsSuccess(SbMutexAcquire(&context->mutex)));
// Tell the thread to signal the condvar, which will cause it to attempt to
// acquire the mutex we are holding.
context->do_signal.Put();
int64_t start = CurrentMonotonicTime();
// We release the mutex when we wait, allowing the thread to actually do the
// signaling, and ensuring we are waiting before it signals.
SbConditionVariableResult result = SbConditionVariableWaitTimed(
&context->condition, &context->mutex, kDelay);
EXPECT_EQ(kSbConditionVariableSignaled, result);
// We should have waited only a very small amount of time.
EXPECT_GT(kDelay, CurrentMonotonicTime() - start);
EXPECT_TRUE(SbMutexRelease(&context->mutex));
}
// Now we wait for the thread to exit.
EXPECT_TRUE(SbThreadJoin(thread, NULL));
EXPECT_TRUE(SbConditionVariableDestroy(&context->condition));
EXPECT_TRUE(SbMutexDestroy(&context->mutex));
}
// Test marked as flaky because it calls DoSunnyDay().
TEST(SbConditionVariableWaitTimedTest, FLAKY_SunnyDay) {
TakeThenSignalContext context;
context.delay_after_signal = 0;
EXPECT_TRUE(SbMutexCreate(&context.mutex));
EXPECT_TRUE(SbConditionVariableCreate(&context.condition, &context.mutex));
DoSunnyDay(&context, true);
}
// Test marked as flaky because it calls DoSunnyDay().
TEST(SbConditionVariableWaitTimedTest, FLAKY_SunnyDayAutoInit) {
{
TakeThenSignalContext context = {TestSemaphore(0), SB_MUTEX_INITIALIZER,
SB_CONDITION_VARIABLE_INITIALIZER, 0};
DoSunnyDay(&context, true);
}
// Without the initial timeout test, the two threads will be racing to
// auto-init the mutex and condition variable. So we run several trials in
// this mode, hoping to have the auto-initting contend in various ways.
const int kTrials = 64;
for (int i = 0; i < kTrials; ++i) {
TakeThenSignalContext context = {TestSemaphore(0), SB_MUTEX_INITIALIZER,
SB_CONDITION_VARIABLE_INITIALIZER, 0};
DoSunnyDay(&context, false);
}
}
// Test marked as flaky because it relies on timing sensitive execution similar
// to DoSunnyDay().
TEST(SbConditionVariableWaitTimedTest, FLAKY_SunnyDayNearMaxTime) {
const int64_t kOtherDelay = 10'000; // 10ms
TakeThenSignalContext context = {TestSemaphore(0), SB_MUTEX_INITIALIZER,
SB_CONDITION_VARIABLE_INITIALIZER,
kOtherDelay};
EXPECT_TRUE(SbMutexCreate(&context.mutex));
EXPECT_TRUE(SbConditionVariableCreate(&context.condition, &context.mutex));
SbThread thread =
SbThreadCreate(0, kSbThreadNoPriority, kSbThreadNoAffinity, true, NULL,
TakeThenSignalEntryPoint, &context);
// Try to wait until the end of time.
const int64_t kDelay = kSbInt64Max;
EXPECT_TRUE(SbMutexIsSuccess(SbMutexAcquire(&context.mutex)));
// Tell the thread to signal the condvar, which will cause it to attempt to
// acquire the mutex we are holding, after it waits for delay_after_signal.
context.do_signal.Put();
int64_t start = CurrentMonotonicTime();
// We release the mutex when we wait, allowing the thread to actually do the
// signaling, and ensuring we are waiting before it signals.
SbConditionVariableResult result =
SbConditionVariableWaitTimed(&context.condition, &context.mutex, kDelay);
EXPECT_EQ(kSbConditionVariableSignaled, result);
// We should have waited at least the delay_after_signal amount, but not the
// full delay.
// Add some padding to tolerate slightly imprecise sleeps.
EXPECT_LT(context.delay_after_signal,
CurrentMonotonicTime() - start + (context.delay_after_signal / 10));
EXPECT_GT(kDelay, CurrentMonotonicTime() - start);
EXPECT_TRUE(SbMutexRelease(&context.mutex));
// Now we wait for the thread to exit.
EXPECT_TRUE(SbThreadJoin(thread, NULL));
EXPECT_TRUE(SbConditionVariableDestroy(&context.condition));
EXPECT_TRUE(SbMutexDestroy(&context.mutex));
}
TEST(SbConditionVariableWaitTimedTest, RainyDayNull) {
SbConditionVariableResult result =
SbConditionVariableWaitTimed(NULL, NULL, 0);
EXPECT_EQ(kSbConditionVariableFailed, result);
SbMutex mutex = SB_MUTEX_INITIALIZER;
result = SbConditionVariableWaitTimed(NULL, &mutex, 0);
EXPECT_EQ(kSbConditionVariableFailed, result);
SbConditionVariable condition = SB_CONDITION_VARIABLE_INITIALIZER;
result = SbConditionVariableWaitTimed(&condition, NULL, 0);
EXPECT_EQ(kSbConditionVariableFailed, result);
}
} // namespace
} // namespace nplb
} // namespace starboard