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