// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/synchronization/atomic_flag.h" #include "base/bind.h" #include "base/logging.h" #include "base/single_thread_task_runner.h" #include "base/synchronization/waitable_event.h" #include "base/test/gtest_util.h" #include "base/threading/platform_thread.h" #include "base/threading/thread.h" #include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { namespace { void ExpectSetFlagDeath(AtomicFlag* flag) { ASSERT_TRUE(flag); EXPECT_DCHECK_DEATH(flag->Set()); } // Busy waits (to explicitly avoid using synchronization constructs that would // defeat the purpose of testing atomics) until |tested_flag| is set and then // verifies that non-atomic |*expected_after_flag| is true and sets |*done_flag| // before returning if it's non-null. void BusyWaitUntilFlagIsSet(AtomicFlag* tested_flag, bool* expected_after_flag, AtomicFlag* done_flag) { while (!tested_flag->IsSet()) PlatformThread::YieldCurrentThread(); EXPECT_TRUE(*expected_after_flag); if (done_flag) done_flag->Set(); } } // namespace TEST(AtomicFlagTest, SimpleSingleThreadedTest) { AtomicFlag flag; ASSERT_FALSE(flag.IsSet()); flag.Set(); ASSERT_TRUE(flag.IsSet()); } TEST(AtomicFlagTest, DoubleSetTest) { AtomicFlag flag; ASSERT_FALSE(flag.IsSet()); flag.Set(); ASSERT_TRUE(flag.IsSet()); flag.Set(); ASSERT_TRUE(flag.IsSet()); } TEST(AtomicFlagTest, ReadFromDifferentThread) { // |tested_flag| is the one being tested below. AtomicFlag tested_flag; // |expected_after_flag| is used to confirm that sequential consistency is // obtained around |tested_flag|. bool expected_after_flag = false; // |reset_flag| is used to confirm the test flows as intended without using // synchronization constructs which would defeat the purpose of exercising // atomics. AtomicFlag reset_flag; Thread thread("AtomicFlagTest.ReadFromDifferentThread"); ASSERT_TRUE(thread.Start()); thread.task_runner()->PostTask(FROM_HERE, BindOnce(&BusyWaitUntilFlagIsSet, &tested_flag, &expected_after_flag, &reset_flag)); // To verify that IsSet() fetches the flag's value from memory every time it // is called (not just the first time that it is called on a thread), sleep // before setting the flag. PlatformThread::Sleep(TimeDelta::FromMilliseconds(20)); // |expected_after_flag| is used to verify that all memory operations // performed before |tested_flag| is Set() are visible to threads that can see // IsSet(). expected_after_flag = true; tested_flag.Set(); // Sleep again to give the busy loop time to observe the flag and verify // expectations. PlatformThread::Sleep(TimeDelta::FromMilliseconds(20)); // Use |reset_flag| to confirm that the above completed (which the rest of // this test assumes). while (!reset_flag.IsSet()) PlatformThread::YieldCurrentThread(); tested_flag.UnsafeResetForTesting(); EXPECT_FALSE(tested_flag.IsSet()); expected_after_flag = false; // Perform the same test again after the controlled UnsafeResetForTesting(), // |thread| is guaranteed to be synchronized past the // |UnsafeResetForTesting()| call when the task runs per the implicit // synchronization in the post task mechanism. thread.task_runner()->PostTask(FROM_HERE, BindOnce(&BusyWaitUntilFlagIsSet, &tested_flag, &expected_after_flag, nullptr)); PlatformThread::Sleep(TimeDelta::FromMilliseconds(20)); expected_after_flag = true; tested_flag.Set(); // The |thread|'s destructor will block until the posted task completes, so // the test will time out if it fails to see the flag be set. } TEST(AtomicFlagTest, SetOnDifferentSequenceDeathTest) { // Checks that Set() can't be called from another sequence after being called // on this one. AtomicFlag should die on a DCHECK if Set() is called again // from another sequence. // Note: flag must be declared before the Thread so that its destructor runs // later. Otherwise there's a race between destructing flag and running // ExpectSetFlagDeath. AtomicFlag flag; ::testing::FLAGS_gtest_death_test_style = "threadsafe"; Thread t("AtomicFlagTest.SetOnDifferentThreadDeathTest"); ASSERT_TRUE(t.Start()); EXPECT_TRUE(t.WaitUntilThreadStarted()); flag.Set(); t.task_runner()->PostTask(FROM_HERE, BindOnce(&ExpectSetFlagDeath, &flag)); } } // namespace base