Bug 1251675 - Add a `Mutex<T>` type, based on Rust's `std::sync::Mutex<T>`. r=terrence, r=jimb
authorNick Fitzgerald <fitzgen@gmail.com>
Wed, 02 Mar 2016 11:50:00 -0500
changeset 323020 f5d666bda9a75c46cc8ed753015c3f4a8b0e41ba
parent 323019 2f2d8d5d1b5a5823a07d492bbcbce0c1a53c9af8
child 323021 1b0a91b8897fae77ec44b4bc7f98b11808db19f5
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence, jimb
bugs1251675
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1251675 - Add a `Mutex<T>` type, based on Rust's `std::sync::Mutex<T>`. r=terrence, r=jimb
js/src/jsapi-tests/moz.build
js/src/jsapi-tests/testMutex.cpp
js/src/moz.build
js/src/vm/Mutex.cpp
js/src/vm/Mutex.h
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -54,16 +54,17 @@ UNIFIED_SOURCES += [
     'testIntString.cpp',
     'testIntTypesABI.cpp',
     'testIsInsideNursery.cpp',
     'testJSEvaluateScript.cpp',
     'testLookup.cpp',
     'testLooselyEqual.cpp',
     'testMappedArrayBuffer.cpp',
     'testMutedErrors.cpp',
+    'testMutex.cpp',
     'testNewObject.cpp',
     'testNewTargetInvokeConstructor.cpp',
     'testNullRoot.cpp',
     'testObjectEmulatingUndefined.cpp',
     'testOOM.cpp',
     'testParseJSON.cpp',
     'testPersistentRooted.cpp',
     'testPreserveJitCode.cpp',
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testMutex.cpp
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/IntegerRange.h"
+#include "js/Vector.h"
+#include "jsapi-tests/tests.h"
+#include "vm/Mutex.h"
+
+// One thread for each bit in our counter.
+const static uint8_t numThreads = 64;
+
+struct CounterAndBit
+{
+    uint8_t bit;
+    const js::Mutex<uint64_t>& counter;
+
+    CounterAndBit(uint8_t bit, const js::Mutex<uint64_t>& counter)
+      : bit(bit)
+      , counter(counter)
+    {
+        MOZ_ASSERT(bit < numThreads);
+    }
+};
+
+void
+printDiagnosticMessage(uint64_t seen)
+{
+    fprintf(stderr, "Thread %p saw ", PR_GetCurrentThread());
+    for (auto i : mozilla::MakeRange(numThreads)) {
+        if (seen & (uint64_t(1) << i))
+            fprintf(stderr, "1");
+        else
+            fprintf(stderr, "0");
+    }
+    fprintf(stderr, "\n");
+}
+
+void
+setBitAndCheck(void* arg)
+{
+    auto& counterAndBit = *static_cast<CounterAndBit*>(arg);
+
+    while (true) {
+        {
+            // Set our bit. Repeatedly setting it is idempotent.
+            auto guard = counterAndBit.counter.lock();
+            printDiagnosticMessage(guard);
+            guard |= (uint64_t(1) << counterAndBit.bit);
+        }
+
+        {
+            // Check to see if we have observed all the other threads setting
+            // their bit as well.
+            auto guard = counterAndBit.counter.lock();
+            printDiagnosticMessage(guard);
+            if (guard == UINT64_MAX) {
+                js_delete(&counterAndBit);
+                return;
+            }
+        }
+    }
+}
+
+BEGIN_TEST(testMutex)
+{
+    auto maybeCounter = js::Mutex<uint64_t>::Create(0);
+    CHECK(maybeCounter.isSome());
+
+    js::Mutex<uint64_t> counter(mozilla::Move(*maybeCounter));
+
+    js::Vector<PRThread*> threads(cx);
+    CHECK(threads.reserve(numThreads));
+
+    for (auto i : mozilla::MakeRange(numThreads)) {
+        auto counterAndBit = js_new<CounterAndBit>(i, counter);
+        CHECK(counterAndBit);
+        auto thread = PR_CreateThread(PR_USER_THREAD,
+                                      setBitAndCheck,
+                                      (void *) counterAndBit,
+                                      PR_PRIORITY_NORMAL,
+                                      PR_LOCAL_THREAD,
+                                      PR_JOINABLE_THREAD,
+                                      0);
+        CHECK(thread);
+        threads.infallibleAppend(thread);
+    }
+
+    for (auto thread : threads) {
+        CHECK(PR_JoinThread(thread) == PR_SUCCESS);
+    }
+
+    return true;
+}
+END_TEST(testMutex)
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -307,16 +307,17 @@ UNIFIED_SOURCES += [
     'vm/GeneratorObject.cpp',
     'vm/GlobalObject.cpp',
     'vm/HelperThreads.cpp',
     'vm/Id.cpp',
     'vm/Interpreter.cpp',
     'vm/JSONParser.cpp',
     'vm/MemoryMetrics.cpp',
     'vm/Monitor.cpp',
+    'vm/Mutex.cpp',
     'vm/NativeObject.cpp',
     'vm/ObjectGroup.cpp',
     'vm/PIC.cpp',
     'vm/Printer.cpp',
     'vm/Probes.cpp',
     'vm/ProxyObject.cpp',
     'vm/ReceiverGuard.cpp',
     'vm/RegExpObject.cpp',
new file mode 100644
--- /dev/null
+++ b/js/src/vm/Mutex.cpp
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "vm/Mutex.h"
+
+namespace js {
+
+/* static */ mozilla::Maybe<detail::MutexBase>
+detail::MutexBase::Create()
+{
+    auto lock = PR_NewLock();
+    if (!lock)
+        return mozilla::Nothing();
+
+    return mozilla::Some(detail::MutexBase(lock));
+}
+
+detail::MutexBase::~MutexBase()
+{
+    if (lock_)
+        PR_DestroyLock(lock_);
+}
+
+void
+detail::MutexBase::acquire() const
+{
+    PR_Lock(lock_);
+}
+
+void
+detail::MutexBase::release() const
+{
+    MOZ_RELEASE_ASSERT(PR_Unlock(lock_) == PR_SUCCESS);
+}
+
+} // namespace js
new file mode 100644
--- /dev/null
+++ b/js/src/vm/Mutex.h
@@ -0,0 +1,215 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef js_Mutex_h
+#define js_Mutex_h
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Move.h"
+
+#include "jslock.h"
+
+namespace js {
+
+namespace detail {
+
+class MutexBase
+{
+  private:
+    mutable PRLock* lock_;
+
+    MutexBase(const MutexBase&) = delete;
+    MutexBase& operator=(const MutexBase&) = delete;
+
+  public:
+    // This move constructor is only public for `mozilla::Forward`.
+    MutexBase(MutexBase&& rhs)
+      : lock_(rhs.lock_)
+    {
+        MOZ_ASSERT(&rhs != this, "self-move disallowed!");
+        rhs.lock_ = nullptr;
+    }
+
+    ~MutexBase();
+
+  protected:
+    explicit MutexBase(PRLock* lock)
+      : lock_(lock)
+    {
+        MOZ_ASSERT(lock_);
+    }
+
+    static mozilla::Maybe<MutexBase> Create();
+
+    void acquire() const;
+    void release() const;
+};
+
+} // namespace detail
+
+/**
+ * A mutual exclusion lock class.
+ *
+ * `Mutex` provides an RAII guard to automatically lock and unlock when
+ * accessing the protected inner value.
+ *
+ * Unlike the STL's `std::mutex`, the protected value is internal to this
+ * class. This is a huge win: one no longer has to rely on documentation to
+ * explain the relationship between a lock and its protected data, and the type
+ * system can enforce[0] it.
+ *
+ * For example, suppose we have a counter class:
+ *
+ *     class Counter
+ *     {
+ *         int32_t i;
+ *
+ *       public:
+ *         void inc(int32_t n) { i += n; }
+ *     };
+ *
+ * If we share a counter across threads with `std::mutex`, we rely solely on
+ * comments to document the relationship between the lock and its data, like
+ * this:
+ *
+ *     class SharedCounter
+ *     {
+ *         // Remember to acquire `counter_lock` when accessing `counter`, pretty please!
+ *         Counter counter;
+ *         std::mutex counter_lock;
+ *
+ *       public:
+ *         void inc(size_t n) {
+ *             // Whoops, forgot to acquire the lock! Off to the races!
+ *             counter.inc(n);
+ *         }
+ *     };
+ *
+ * In contrast, `Mutex` wraps the protected value, enabling the type system to
+ * enforce that we acquire the lock before accessing the value:
+ *
+ *     class SharedCounter
+ *     {
+ *         Mutex<Counter> counter;
+ *
+ *       public:
+ *         void inc(size_t n) {
+ *             auto guard = counter.lock();
+ *             guard->inc(n);
+ *         }
+ *     };
+ *
+ * The API design is based on Rust's `std::sync::Mutex<T>` type.
+ *
+ * [0]: Of course, we don't have a borrow checker in C++, so the type system
+ *      cannot guarantee that you don't stash references received from
+ *      `Mutex<T>::Guard` somewhere such that the reference outlives the guard's
+ *      lifetime and therefore becomes invalid. To help avoid this last
+ *      foot-gun, prefer using the guard directly! Do not store raw references
+ *      to the protected value in other structures!
+ */
+template <typename T>
+class Mutex : private detail::MutexBase
+{
+    mutable T value_;
+
+    Mutex(const Mutex&) = delete;
+    Mutex& operator=(const Mutex&) = delete;
+
+    template <typename U>
+    explicit Mutex(U&& u, MutexBase&& base)
+      : MutexBase(mozilla::Move(base))
+      , value_(mozilla::Forward<U>(u))
+    { }
+
+  public:
+    /**
+     * Create a new `Mutex`, with perfect forwarding of the protected value.
+     *
+     * On success, `mozilla::Some` is returned. On failure, `mozilla::Nothing`
+     * is returned.
+     */
+    template <typename U>
+    static mozilla::Maybe<Mutex<T>> Create(U&& u) {
+        auto base = detail::MutexBase::Create();
+        if (base.isNothing())
+            return mozilla::Nothing();
+        return mozilla::Some(Mutex(mozilla::Forward<U>(u), mozilla::Move(*base)));
+    }
+
+    Mutex(Mutex&& rhs)
+      : MutexBase(mozilla::Move(static_cast<MutexBase&&>(rhs)))
+      , value_(mozilla::Move(rhs.value_))
+    {
+        MOZ_ASSERT(&rhs != this, "self-move disallowed!");
+    }
+
+    Mutex& operator=(Mutex&& rhs) {
+        this->~Mutex();
+        new (this) Mutex(mozilla::Move(rhs));
+        return *this;
+    }
+
+    /**
+     * An RAII class that provides exclusive access to a `Mutex<T>`'s protected
+     * inner `T` value.
+     *
+     * Note that this is intentionally marked MOZ_STACK_CLASS instead of
+     * MOZ_RAII_CLASS, as the latter disallows moves and returning by value, but
+     * Guard utilizes both.
+     */
+    class MOZ_STACK_CLASS Guard
+    {
+        const Mutex* parent_;
+
+        Guard(const Guard&) = delete;
+        Guard& operator=(const Guard&) = delete;
+
+      public:
+        explicit Guard(const Mutex& parent)
+          : parent_(&parent)
+        {
+            parent_->acquire();
+        }
+
+        Guard(Guard&& rhs)
+          : parent_(rhs.parent_)
+        {
+            MOZ_ASSERT(&rhs != this, "self-move disallowed!");
+            rhs.parent_ = nullptr;
+        }
+
+        Guard& operator=(Guard&& rhs) {
+            this->~Guard();
+            new (this) Guard(mozilla::Move(rhs));
+            return *this;
+        }
+
+        T& get() const {
+            MOZ_ASSERT(parent_);
+            return parent_->value_;
+        }
+
+        operator T& () const { return get(); }
+        T* operator->() const { return &get(); }
+
+        ~Guard() {
+            if (parent_)
+                parent_->release();
+        }
+    };
+
+    /**
+     * Access the protected inner `T` value for exclusive reading and writing.
+     */
+    Guard lock() const {
+        return Guard(*this);
+    }
+};
+
+} // namespace js
+
+#endif // js_Mutex_h