| // 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 |