Skip to content

Commit

Permalink
Import synchronization utilities into FML. (flutter#5312)
Browse files Browse the repository at this point in the history
  • Loading branch information
chinmaygarde authored May 18, 2018
1 parent ef7a459 commit 2e6aad3
Show file tree
Hide file tree
Showing 10 changed files with 838 additions and 0 deletions.
8 changes: 8 additions & 0 deletions fml/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
source_set("fml") {
sources = [
"build_config.h",
"closure.h",
"compiler_specific.h",
"eintr_wrapper.h",
"export.h",
Expand Down Expand Up @@ -34,6 +35,10 @@ source_set("fml") {
"native_library.h",
"paths.cc",
"paths.h",
"synchronization/thread_annotations.h",
"synchronization/thread_checker.h",
"synchronization/waitable_event.cc",
"synchronization/waitable_event.h",
"task_runner.cc",
"task_runner.h",
"thread.cc",
Expand Down Expand Up @@ -149,6 +154,9 @@ executable("fml_unittests") {
"memory/ref_counted_unittest.cc",
"memory/weak_ptr_unittest.cc",
"message_loop_unittests.cc",
"synchronization/thread_annotations_unittest.cc",
"synchronization/thread_checker_unittest.cc",
"synchronization/waitable_event_unittest.cc",
"thread_local_unittests.cc",
"thread_unittests.cc",
"time/time_delta_unittest.cc",
Expand Down
16 changes: 16 additions & 0 deletions fml/closure.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_FML_CLOSURE_H_
#define FLUTTER_FML_CLOSURE_H_

#include <functional>

namespace fml {

using closure = std::function<void()>;

} // namespace fml

#endif // FLUTTER_FML_CLOSURE_H_
89 changes: 89 additions & 0 deletions fml/synchronization/thread_annotations.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Macros for static thread-safety analysis.
//
// These are from http://clang.llvm.org/docs/ThreadSafetyAnalysis.html (and thus
// really derive from google3's thread_annotations.h).
//
// TODO(vtl): We're still using the old-fashioned, deprecated annotations
// ("locks" instead of "capabilities"), since the new ones don't work yet (in
// particular, |TRY_ACQUIRE()| doesn't work: b/19264527).
// https://github.com/domokit/mojo/issues/314

#ifndef FLUTTER_FML_SYNCHRONIZATION_THREAD_ANNOTATIONS_H_
#define FLUTTER_FML_SYNCHRONIZATION_THREAD_ANNOTATIONS_H_

// Enable thread-safety attributes only with clang.
// The attributes can be safely erased when compiling with other compilers.
#if defined(__clang__)
#define FML_THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
#else
#define FML_THREAD_ANNOTATION_ATTRIBUTE__(x)
#endif

#define FML_GUARDED_BY(x) FML_THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))

#define FML_PT_GUARDED_BY(x) FML_THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))

#define FML_ACQUIRE(...) \
FML_THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__))

#define FML_RELEASE(...) \
FML_THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))

#define FML_ACQUIRED_AFTER(...) \
FML_THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))

#define FML_ACQUIRED_BEFORE(...) \
FML_THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))

#define FML_EXCLUSIVE_LOCKS_REQUIRED(...) \
FML_THREAD_ANNOTATION_ATTRIBUTE__(exclusive_locks_required(__VA_ARGS__))

#define FML_SHARED_LOCKS_REQUIRED(...) \
FML_THREAD_ANNOTATION_ATTRIBUTE__(shared_locks_required(__VA_ARGS__))

#define FML_LOCKS_EXCLUDED(...) \
FML_THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))

#define FML_LOCK_RETURNED(x) FML_THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))

#define FML_LOCKABLE FML_THREAD_ANNOTATION_ATTRIBUTE__(lockable)

#define FML_SCOPED_LOCKABLE FML_THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)

#define FML_EXCLUSIVE_LOCK_FUNCTION(...) \
FML_THREAD_ANNOTATION_ATTRIBUTE__(exclusive_lock_function(__VA_ARGS__))

#define FML_SHARED_LOCK_FUNCTION(...) \
FML_THREAD_ANNOTATION_ATTRIBUTE__(shared_lock_function(__VA_ARGS__))

#define FML_ASSERT_EXCLUSIVE_LOCK(...) \
FML_THREAD_ANNOTATION_ATTRIBUTE__(assert_exclusive_lock(__VA_ARGS__))

#define FML_ASSERT_SHARED_LOCK(...) \
FML_THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_lock(__VA_ARGS__))

#define FML_EXCLUSIVE_TRYLOCK_FUNCTION(...) \
FML_THREAD_ANNOTATION_ATTRIBUTE__(exclusive_trylock_function(__VA_ARGS__))

#define FML_SHARED_TRYLOCK_FUNCTION(...) \
FML_THREAD_ANNOTATION_ATTRIBUTE__(shared_trylock_function(__VA_ARGS__))

#define FML_UNLOCK_FUNCTION(...) \
FML_THREAD_ANNOTATION_ATTRIBUTE__(unlock_function(__VA_ARGS__))

#define FML_NO_THREAD_SAFETY_ANALYSIS \
FML_THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)

// Use this in the header to annotate a function/method as not being
// thread-safe. This is equivalent to |FML_NO_THREAD_SAFETY_ANALYSIS|, but
// semantically different: it declares that the caller must abide by additional
// restrictions. Limitation: Unfortunately, you can't apply this to a method in
// an interface (i.e., pure virtual method) and have it applied automatically to
// implementations.
#define FML_NOT_THREAD_SAFE FML_NO_THREAD_SAFETY_ANALYSIS

#endif // FLUTTER_FML_SYNCHRONIZATION_THREAD_ANNOTATIONS_H_
127 changes: 127 additions & 0 deletions fml/synchronization/thread_annotations_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Tests of the static thread annotation macros. These fall into two categories,
// positive tests (testing that correct code compiles and works) and negative
// tests (testing that incorrect code does not compile).
//
// Unfortunately, we don't have systematic/automated negative compilation tests.
// So instead we have some cheesy macros that you can define to enable
// individual compilation failures.

#include "flutter/fml/synchronization/thread_annotations.h"

#include <mutex>

#include "flutter/fml/macros.h"
#include "gtest/gtest.h"

// Uncomment these to enable particular compilation failure tests.
// #define NC_GUARDED_BY
// TODO(vtl): |ACQUIRED_{BEFORE,AFTER}()| are currently unimplemented in clang
// as of 2015-07-06 ("To be fixed in a future update."). So this actually
// compiles!
// #define NC_ACQUIRED_BEFORE

namespace fml {
namespace {

// Test FML_GUARDED_BY ---------------------------------------------------------

class GuardedByClass {
public:
GuardedByClass() : x_() {}
~GuardedByClass() {}

void GoodSet(int x) {
mu_.lock();
x_ = x;
mu_.unlock();
}

#ifdef NC_GUARDED_BY
void BadSet(int x) { x_ = x; }
#endif

private:
std::mutex mu_;
int x_ FML_GUARDED_BY(mu_);

FML_DISALLOW_COPY_AND_ASSIGN(GuardedByClass);
};

TEST(ThreadAnnotationsTest, GuardedBy) {
GuardedByClass c;
c.GoodSet(123);
}

// Test FML_ACQUIRED_BEFORE ----------------------------------------------------

class AcquiredBeforeClass2;

class AcquiredBeforeClass1 {
public:
AcquiredBeforeClass1() {}
~AcquiredBeforeClass1() {}

void NoOp() {
mu_.lock();
mu_.unlock();
}

#ifdef NC_ACQUIRED_BEFORE
void BadMethod(AcquiredBeforeClass2* c2);
#endif

private:
friend class AcquiredBeforeClass2;

std::mutex mu_;

FML_DISALLOW_COPY_AND_ASSIGN(AcquiredBeforeClass1);
};

class AcquiredBeforeClass2 {
public:
AcquiredBeforeClass2() {}
~AcquiredBeforeClass2() {}

void NoOp() {
mu_.lock();
mu_.unlock();
}

void GoodMethod(AcquiredBeforeClass1* c1) {
mu_.lock();
c1->NoOp();
mu_.unlock();
}

private:
std::mutex mu_ FML_ACQUIRED_BEFORE(AcquiredBeforeClass1::mu_);

FML_DISALLOW_COPY_AND_ASSIGN(AcquiredBeforeClass2);
};

#ifdef NC_ACQUIRED_BEFORE
void AcquiredBeforeClass1::BadMethod(AcquiredBeforeClass2* c2) {
mu_.lock();
c2->NoOp();
mu_.unlock();
}
#endif

TEST(ThreadAnnotationsTest, AcquiredBefore) {
AcquiredBeforeClass1 c1;
AcquiredBeforeClass2 c2;
c2.GoodMethod(&c1);
#ifdef NC_ACQUIRED_BEFORE
c1.BadMethod(&c2);
#endif
}

// TODO(vtl): Test more things.

} // namespace
} // namespace fml
71 changes: 71 additions & 0 deletions fml/synchronization/thread_checker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// A class for checking that the current thread is/isn't the same as an initial
// thread.

#ifndef FLUTTER_FML_SYNCHRONIZATION_THREAD_CHECKER_H_
#define FLUTTER_FML_SYNCHRONIZATION_THREAD_CHECKER_H_

#include "flutter/fml/build_config.h"

#if defined(OS_WIN)
#include <windows.h>
#else
#include <pthread.h>
#endif

#include "flutter/fml/logging.h"
#include "flutter/fml/macros.h"

namespace fml {

// A simple class that records the identity of the thread that it was created
// on, and at later points can tell if the current thread is the same as its
// creation thread. This class is thread-safe.
//
// Note: Unlike Chromium's |base::ThreadChecker|, this is *not* Debug-only (so
// #ifdef it out if you want something Debug-only). (Rationale: Having a
// |CalledOnValidThread()| that lies in Release builds seems bad. Moreover,
// there's a small space cost to having even an empty class. )
class ThreadChecker final {
public:
#if defined(OS_WIN)
ThreadChecker() : self_(GetCurrentThreadId()) {}
~ThreadChecker() {}

bool IsCreationThreadCurrent() const { return GetCurrentThreadId() == self_; }

private:
const DWORD self_;

#else
ThreadChecker() : self_(pthread_self()) {}
~ThreadChecker() {}

// Returns true if the current thread is the thread this object was created
// on and false otherwise.
bool IsCreationThreadCurrent() const {
return !!pthread_equal(pthread_self(), self_);
}

private:
const pthread_t self_;
#endif

FML_DISALLOW_COPY_AND_ASSIGN(ThreadChecker);
};

#ifndef NDEBUG
#define FML_DECLARE_THREAD_CHECKER(c) fml::ThreadChecker c
#define FML_DCHECK_CREATION_THREAD_IS_CURRENT(c) \
FML_DCHECK((c).IsCreationThreadCurrent())
#else
#define FML_DECLARE_THREAD_CHECKER(c)
#define FML_DCHECK_CREATION_THREAD_IS_CURRENT(c) ((void)0)
#endif

} // namespace fml

#endif // FLUTTER_FML_SYNCHRONIZATION_THREAD_CHECKER_H_
37 changes: 37 additions & 0 deletions fml/synchronization/thread_checker_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2016 The Fuchsia 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 "flutter/fml/synchronization/thread_checker.h"

#include <thread>

#include "gtest/gtest.h"

namespace fml {
namespace {

TEST(ThreadCheckerTest, SameThread) {
ThreadChecker checker;
EXPECT_TRUE(checker.IsCreationThreadCurrent());
}

// Note: This test depends on |std::thread| being compatible with
// |pthread_self()|.
TEST(ThreadCheckerTest, DifferentThreads) {
ThreadChecker checker1;
EXPECT_TRUE(checker1.IsCreationThreadCurrent());

std::thread thread([&checker1]() {
ThreadChecker checker2;
EXPECT_TRUE(checker2.IsCreationThreadCurrent());
EXPECT_FALSE(checker1.IsCreationThreadCurrent());
});
thread.join();

// Note: Without synchronization, we can't look at |checker2| from the main
// thread.
}

} // namespace
} // namespace fml
Loading

0 comments on commit 2e6aad3

Please sign in to comment.