blob: 1c8175d5720bcd0a7ccf0f5b44390c9c1de1b2b9 [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/condition_variable.h"
#include "starboard/mutex.h"
#include "starboard/nplb/thread_helpers.h"
#include "starboard/thread.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace starboard {
namespace nplb {
namespace {
void DoSunnyDay(TakeThenSignalContext* context, bool check_timeout) {
SbThread thread =
SbThreadCreate(0, kSbThreadNoPriority, kSbThreadNoAffinity, true, NULL,
TakeThenSignalEntryPoint, context);
const SbTime kDelay = kSbTimeMillisecond * 10;
// Allow millisecond-level precision.
const SbTime kPrecision = kSbTimeMillisecond;
// 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)));
SbTimeMonotonic start = SbTimeGetMonotonicNow();
SbConditionVariableResult result = SbConditionVariableWaitTimed(
&context->condition, &context->mutex, kDelay);
EXPECT_EQ(kSbConditionVariableTimedOut, result);
SbTimeMonotonic elapsed = SbTimeGetMonotonicNow() - 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();
SbTimeMonotonic start = SbTimeGetMonotonicNow();
// 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, SbTimeGetMonotonicNow() - 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, SunnyDay) {
TakeThenSignalContext context;
context.delay_after_signal = 0;
EXPECT_TRUE(SbMutexCreate(&context.mutex));
EXPECT_TRUE(SbConditionVariableCreate(&context.condition, &context.mutex));
DoSunnyDay(&context, true);
}
TEST(SbConditionVariableWaitTimedTest, 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(SbConditionVariableWaitTimedTest, SunnyDayNearMaxTime) {
const SbTime kOtherDelay = kSbTimeMillisecond * 10;
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 SbTime kDelay = kSbTimeMax;
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();
SbTimeMonotonic start = SbTimeGetMonotonicNow();
// 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, SbTimeGetMonotonicNow() - start +
(context.delay_after_signal / 10));
EXPECT_GT(kDelay, SbTimeGetMonotonicNow() - 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