[WTF] Implement WTF::ThreadGroup
authorutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 19 Jul 2017 08:43:57 +0000 (08:43 +0000)
committerutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 19 Jul 2017 08:43:57 +0000 (08:43 +0000)
https://bugs.webkit.org/show_bug.cgi?id=174081

Reviewed by Mark Lam.

Source/JavaScriptCore:

Large part of MachineThreads are now removed and replaced with WTF::ThreadGroup.
And SamplingProfiler and others interact with WTF::Thread directly.

* API/tests/ExecutionTimeLimitTest.cpp:
* heap/MachineStackMarker.cpp:
(JSC::MachineThreads::MachineThreads):
(JSC::captureStack):
(JSC::MachineThreads::tryCopyOtherThreadStack):
(JSC::MachineThreads::tryCopyOtherThreadStacks):
(JSC::MachineThreads::gatherConservativeRoots):
(JSC::ActiveMachineThreadsManager::Locker::Locker): Deleted.
(JSC::ActiveMachineThreadsManager::add): Deleted.
(JSC::ActiveMachineThreadsManager::remove): Deleted.
(JSC::ActiveMachineThreadsManager::contains): Deleted.
(JSC::ActiveMachineThreadsManager::ActiveMachineThreadsManager): Deleted.
(JSC::activeMachineThreadsManager): Deleted.
(JSC::MachineThreads::~MachineThreads): Deleted.
(JSC::MachineThreads::addCurrentThread): Deleted.
(): Deleted.
(JSC::MachineThreads::removeThread): Deleted.
(JSC::MachineThreads::removeThreadIfFound): Deleted.
(JSC::MachineThreads::MachineThread::MachineThread): Deleted.
(JSC::MachineThreads::MachineThread::getRegisters): Deleted.
(JSC::MachineThreads::MachineThread::Registers::stackPointer): Deleted.
(JSC::MachineThreads::MachineThread::Registers::framePointer): Deleted.
(JSC::MachineThreads::MachineThread::Registers::instructionPointer): Deleted.
(JSC::MachineThreads::MachineThread::Registers::llintPC): Deleted.
(JSC::MachineThreads::MachineThread::captureStack): Deleted.
* heap/MachineStackMarker.h:
(JSC::MachineThreads::addCurrentThread):
(JSC::MachineThreads::getLock):
(JSC::MachineThreads::threads):
(JSC::MachineThreads::MachineThread::suspend): Deleted.
(JSC::MachineThreads::MachineThread::resume): Deleted.
(JSC::MachineThreads::MachineThread::threadID): Deleted.
(JSC::MachineThreads::MachineThread::stackBase): Deleted.
(JSC::MachineThreads::MachineThread::stackEnd): Deleted.
(JSC::MachineThreads::threadsListHead): Deleted.
* runtime/SamplingProfiler.cpp:
(JSC::FrameWalker::isValidFramePointer):
(JSC::SamplingProfiler::SamplingProfiler):
(JSC::SamplingProfiler::takeSample):
(JSC::SamplingProfiler::noticeCurrentThreadAsJSCExecutionThread):
* runtime/SamplingProfiler.h:
* wasm/WasmMachineThreads.cpp:
(JSC::Wasm::resetInstructionCacheOnAllThreads):

Source/WebCore:

* page/ResourceUsageThread.h:

Source/WebKit:

* Shared/AsyncRequest.h:

Source/WTF:

This patch implements WTF::ThreadGroup. It implements core of JSC::MachineThreads with more reliable way.
JSC::MachineThreads was complicated because of managing dead threads. Each JSC::MachineThreads has its
own TLS with a registered destructor. And everytime a thread dies, the registered TLS destructor is called.
And this destructor will remove the current dying thread from JSC::MachineThreads.

However the above implementation is tricky. And each JSC::MachineThreads requires own TLS space, which is
not considered in WTF's Windows ThreadSpecific implementation. Current design works well since we only
have small number of MachineThreads right now.

Instead, we use more reliable way. After introducing WTF::Thread, WTF::Thread has WTF::Thread::didExit,
which is called when associated TLS (with WTF::Thread) is destroyed. We leverage this mechanism to remove
WTF::Thread from MachineThreads.

This patch introduces WTF::ThreadGroup. It is tightly integrated with WTF::Thread: WTF::Thread knows
ThreadGroups which includes this thread. And WTF::ThreadGroup of course knows WTF::Threads added to it.
WTF::Thread::didExit carefully remove itself from WTF::ThreadGroups.

The most important part of this patch is locking. WTF::Thread can die. And WTF::ThreadGroup can die.
If we take a design using two fine grain locks in WTF::Thread and WTF::ThreadGroup, we easily encounter
dead lock. Consider the following case.

1. When adding WTF::Thread (TH) to WTF::ThreadGroup (THG), we first hold a lock of THG, and hold a lock of TH (locking order is THG -> TH).
2. When TH dies, TH need to hold a lock of TH to iterate THGs. And we hold a lock of THG to unregister TH from it (locking order is TH -> THG).
3. When suspending and resuming THs in THG, we first hold a lock of THG. And then, we hold a lock of TH to suspend and resume it (locking order is THG -> TH).
4. When destroying THG, we need to hold a lock of TH to unregister THG from TH. We can hold a lock of THG before that (locking order is THG -> TH).

Then, it easily causes dead lock. We cannot swap the locking order of (2) since iterating THG requires a lock of TH.
To solve this problem, we use std::shared_ptr and std::weak_ptr.

1. When adding WTF::Thread (TH) to WTF::ThreadGroup (THG), we first hold THG, and hold a lock of TH. (THG -> TH)
2. When TH dies, TH first hold lock of TH. And we use std::weak_ptr<>::lock() to retain non-destructed ThreadGroups.
If some of ThreadGroups are dying, we just ignore them. It is ok because such a ThreadGroup will be destructed. So we do not need to unregister this thread from
such a ThreadGroup. Then, we have Vector<std::shared_ptr<ThreadGroup>>. So we unlock a lock of TH. To unregister a thread from thread group, we first hold a
lock of THG and then hold a lock of TH. Both lifetime is ensured: THG is retained by std::shared_ptr. And TH is itself. (TH), (THG -> TH).
3. When suspending and resuming THs in THG, we first hold a lock of THG. And then, we hold a lock of TH to suspend and resume it (THG -> TH).
4. When destroying THG, we hold a lock of THG. And hold a lock of TH. During holding THG's lock, registered thread never dies because (2) holds THG lock. (THG -> TH).

We also fix suspend and resume locking mechanism to avoid dead lock. We should hold the global lock when suspending and resuming.
If we use per-thread lock, the suspended thread can hold the lock of the other threads. It causes dead lock.

* WTF.xcodeproj/project.pbxproj:
* wtf/AutomaticThread.cpp:
* wtf/CMakeLists.txt:
* wtf/CrossThreadCopier.h:
* wtf/ParkingLot.h:
* wtf/ThreadGroup.cpp: Copied from Source/JavaScriptCore/wasm/WasmMachineThreads.cpp.
(WTF::ThreadGroup::~ThreadGroup):
(WTF::ThreadGroup::add):
(WTF::ThreadGroup::addCurrentThread):
* wtf/ThreadGroup.h: Copied from Source/JavaScriptCore/wasm/WasmMachineThreads.cpp.
(WTF::ThreadGroup::create):
(WTF::ThreadGroup::threads):
(WTF::ThreadGroup::getLock):
(WTF::ThreadGroup::weakFromThis):
* wtf/Threading.cpp:
(WTF::shouldRemoveThreadFromThreadGroup):
(WTF::Thread::didExit):
(WTF::Thread::addToThreadGroup):
(WTF::Thread::removeFromThreadGroup):
* wtf/Threading.h:
* wtf/ThreadingPthreads.cpp:
(WTF::Thread::resume):
(WTF::Thread::getRegisters):
* wtf/ThreadingWin.cpp:
(WTF::Thread::resume):
(WTF::Thread::getRegisters):

Tools:

Add WTF::ThreadGroup tests.

* TestWebKitAPI/CMakeLists.txt:
* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WTF/ThreadGroup.cpp: Added.
(TestWebKitAPI::testThreadGroup):
(TestWebKitAPI::TEST):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@219653 268f45cc-cd09-0410-ab3c-d52691b4dbfc

27 files changed:
Source/JavaScriptCore/API/tests/ExecutionTimeLimitTest.cpp
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/heap/MachineStackMarker.cpp
Source/JavaScriptCore/heap/MachineStackMarker.h
Source/JavaScriptCore/runtime/SamplingProfiler.cpp
Source/JavaScriptCore/runtime/SamplingProfiler.h
Source/JavaScriptCore/wasm/WasmMachineThreads.cpp
Source/WTF/ChangeLog
Source/WTF/WTF.xcodeproj/project.pbxproj
Source/WTF/wtf/AutomaticThread.cpp
Source/WTF/wtf/CMakeLists.txt
Source/WTF/wtf/CrossThreadCopier.h
Source/WTF/wtf/ParkingLot.h
Source/WTF/wtf/ThreadGroup.cpp [new file with mode: 0644]
Source/WTF/wtf/ThreadGroup.h [new file with mode: 0644]
Source/WTF/wtf/Threading.cpp
Source/WTF/wtf/Threading.h
Source/WTF/wtf/ThreadingPthreads.cpp
Source/WTF/wtf/ThreadingWin.cpp
Source/WebCore/ChangeLog
Source/WebCore/page/ResourceUsageThread.h
Source/WebKit/ChangeLog
Source/WebKit/Shared/AsyncRequest.h
Tools/ChangeLog
Tools/TestWebKitAPI/CMakeLists.txt
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WTF/ThreadGroup.cpp [new file with mode: 0644]

index 73f324d..05c0114 100644 (file)
@@ -36,6 +36,7 @@
 #include <wtf/Condition.h>
 #include <wtf/CurrentTime.h>
 #include <wtf/Lock.h>
+#include <wtf/Threading.h>
 #include <wtf/text/StringBuilder.h>
 
 #if HAVE(MACH_EXCEPTIONS)
index f73bf83..fe8cca2 100644 (file)
@@ -1,3 +1,57 @@
+2017-07-19  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        [WTF] Implement WTF::ThreadGroup
+        https://bugs.webkit.org/show_bug.cgi?id=174081
+
+        Reviewed by Mark Lam.
+
+        Large part of MachineThreads are now removed and replaced with WTF::ThreadGroup.
+        And SamplingProfiler and others interact with WTF::Thread directly.
+
+        * API/tests/ExecutionTimeLimitTest.cpp:
+        * heap/MachineStackMarker.cpp:
+        (JSC::MachineThreads::MachineThreads):
+        (JSC::captureStack):
+        (JSC::MachineThreads::tryCopyOtherThreadStack):
+        (JSC::MachineThreads::tryCopyOtherThreadStacks):
+        (JSC::MachineThreads::gatherConservativeRoots):
+        (JSC::ActiveMachineThreadsManager::Locker::Locker): Deleted.
+        (JSC::ActiveMachineThreadsManager::add): Deleted.
+        (JSC::ActiveMachineThreadsManager::remove): Deleted.
+        (JSC::ActiveMachineThreadsManager::contains): Deleted.
+        (JSC::ActiveMachineThreadsManager::ActiveMachineThreadsManager): Deleted.
+        (JSC::activeMachineThreadsManager): Deleted.
+        (JSC::MachineThreads::~MachineThreads): Deleted.
+        (JSC::MachineThreads::addCurrentThread): Deleted.
+        (): Deleted.
+        (JSC::MachineThreads::removeThread): Deleted.
+        (JSC::MachineThreads::removeThreadIfFound): Deleted.
+        (JSC::MachineThreads::MachineThread::MachineThread): Deleted.
+        (JSC::MachineThreads::MachineThread::getRegisters): Deleted.
+        (JSC::MachineThreads::MachineThread::Registers::stackPointer): Deleted.
+        (JSC::MachineThreads::MachineThread::Registers::framePointer): Deleted.
+        (JSC::MachineThreads::MachineThread::Registers::instructionPointer): Deleted.
+        (JSC::MachineThreads::MachineThread::Registers::llintPC): Deleted.
+        (JSC::MachineThreads::MachineThread::captureStack): Deleted.
+        * heap/MachineStackMarker.h:
+        (JSC::MachineThreads::addCurrentThread):
+        (JSC::MachineThreads::getLock):
+        (JSC::MachineThreads::threads):
+        (JSC::MachineThreads::MachineThread::suspend): Deleted.
+        (JSC::MachineThreads::MachineThread::resume): Deleted.
+        (JSC::MachineThreads::MachineThread::threadID): Deleted.
+        (JSC::MachineThreads::MachineThread::stackBase): Deleted.
+        (JSC::MachineThreads::MachineThread::stackEnd): Deleted.
+        (JSC::MachineThreads::threadsListHead): Deleted.
+        * runtime/SamplingProfiler.cpp:
+        (JSC::FrameWalker::isValidFramePointer):
+        (JSC::SamplingProfiler::SamplingProfiler):
+        (JSC::SamplingProfiler::takeSample):
+        (JSC::SamplingProfiler::noticeCurrentThreadAsJSCExecutionThread):
+        * runtime/SamplingProfiler.h:
+        * wasm/WasmMachineThreads.cpp:
+        (JSC::Wasm::resetInstructionCacheOnAllThreads):
+
 2017-07-18  Andy Estes  <aestes@apple.com>
 
         [Xcode] Enable CLANG_WARN_RANGE_LOOP_ANALYSIS
index 989bbc5..1de8caa 100644 (file)
 #include "MachineStackMarker.h"
 
 #include "ConservativeRoots.h"
-#include "GPRInfo.h"
-#include "Heap.h"
-#include "JSArray.h"
-#include "JSCInlines.h"
-#include "LLIntPCRanges.h"
-#include "MacroAssembler.h"
-#include "VM.h"
+#include "MachineContext.h"
 #include <setjmp.h>
 #include <stdlib.h>
-#include <wtf/MainThread.h>
+#include <wtf/BitVector.h>
+#include <wtf/PageBlock.h>
 #include <wtf/StdLibExtras.h>
 
 using namespace WTF;
 
 namespace JSC {
 
-class ActiveMachineThreadsManager;
-static ActiveMachineThreadsManager& activeMachineThreadsManager();
-
-class ActiveMachineThreadsManager {
-    WTF_MAKE_NONCOPYABLE(ActiveMachineThreadsManager);
-public:
-
-    class Locker {
-    public:
-        Locker(ActiveMachineThreadsManager& manager)
-            : m_locker(manager.m_lock)
-        {
-        }
-
-    private:
-        LockHolder m_locker;
-    };
-
-    void add(MachineThreads* machineThreads)
-    {
-        LockHolder managerLock(m_lock);
-        m_set.add(machineThreads);
-    }
-
-    void THREAD_SPECIFIC_CALL remove(MachineThreads* machineThreads)
-    {
-        LockHolder managerLock(m_lock);
-        auto recordedMachineThreads = m_set.take(machineThreads);
-        RELEASE_ASSERT(recordedMachineThreads == machineThreads);
-    }
-
-    bool contains(MachineThreads* machineThreads)
-    {
-        return m_set.contains(machineThreads);
-    }
-
-private:
-    typedef HashSet<MachineThreads*> MachineThreadsSet;
-
-    ActiveMachineThreadsManager() { }
-    
-    Lock m_lock;
-    MachineThreadsSet m_set;
-
-    friend ActiveMachineThreadsManager& activeMachineThreadsManager();
-};
-
-static ActiveMachineThreadsManager& activeMachineThreadsManager()
-{
-    static std::once_flag initializeManagerOnceFlag;
-    static ActiveMachineThreadsManager* manager = nullptr;
-
-    std::call_once(initializeManagerOnceFlag, [] {
-        manager = new ActiveMachineThreadsManager();
-    });
-    return *manager;
-}
-
-#if CPU(X86_64) && OS(DARWIN)
-#define FILL_CALLEE_SAVES_FOR_CRASH_INFO(number)     \
-    asm volatile(                                    \
-        "movq $0xc0defefe000000" number ", %%rbx;" \
-        "movq $0xc0defefe000000" number ", %%r12;" \
-        "movq $0xc0defefe000000" number ", %%r13;" \
-        "movq $0xc0defefe000000" number ", %%r14;" \
-        "movq $0xc0defefe000000" number ", %%r15;" \
-        :                                            \
-        :                                            \
-        : "%rbx", "%r12", "%r13", "%r14", "%r15"     \
-    );
-
-#define FILL_CALLER_SAVES_FOR_CRASH_INFO(number)     \
-    asm volatile(                                    \
-        "movq $0xc0defefe000000" number ", %%rax;" \
-        "movq $0xc0defefe000000" number ", %%rdi;" \
-        "movq $0xc0defefe000000" number ", %%rsi;" \
-        "movq $0xc0defefe000000" number ", %%rdx;" \
-        "movq $0xc0defefe000000" number ", %%rcx;" \
-        "movq $0xc0defefe000000" number ", %%r8;"  \
-        "movq $0xc0defefe000000" number ", %%r9;"  \
-        "movq $0xc0defefe000000" number ", %%r10;" \
-        "movq $0xc0defefe000000" number ", %%r11;" \
-        :                                            \
-        :                                            \
-        : "%rax", "%rdi", "%rsi", "%rdx", "%rcx", "%r8", "%r9", "%r10", "%r11" \
-    );
-#else
-#define FILL_CALLEE_SAVES_FOR_CRASH_INFO(number)
-#define FILL_CALLER_SAVES_FOR_CRASH_INFO(number)
-#endif
-
 MachineThreads::MachineThreads()
-    : m_registeredThreads()
-    , m_threadSpecificForMachineThreads(0)
-{
-    FILL_CALLEE_SAVES_FOR_CRASH_INFO("01");
-    threadSpecificKeyCreate(&m_threadSpecificForMachineThreads, removeThread);
-    FILL_CALLEE_SAVES_FOR_CRASH_INFO("02");
-    activeMachineThreadsManager().add(this);
-    FILL_CALLER_SAVES_FOR_CRASH_INFO("03");
-}
-
-MachineThreads::~MachineThreads()
+    : m_threadGroup(ThreadGroup::create())
 {
-    activeMachineThreadsManager().remove(this);
-    threadSpecificKeyDelete(m_threadSpecificForMachineThreads);
-
-    LockHolder registeredThreadsLock(m_registeredThreadsMutex);
-    for (MachineThread* current = m_registeredThreads.head(); current;) {
-        MachineThread* next = current->next();
-        delete current;
-        current = next;
-    }
-}
-
-void MachineThreads::addCurrentThread()
-{
-    if (threadSpecificGet(m_threadSpecificForMachineThreads)) {
-#ifndef NDEBUG
-        LockHolder lock(m_registeredThreadsMutex);
-        ASSERT(threadSpecificGet(m_threadSpecificForMachineThreads) == this);
-#endif
-        return;
-    }
-
-    MachineThread* thread = new MachineThread();
-    threadSpecificSet(m_threadSpecificForMachineThreads, this);
-
-    LockHolder lock(m_registeredThreadsMutex);
-
-    m_registeredThreads.append(thread);
-}
-
-auto MachineThreads::machineThreadForCurrentThread() -> MachineThread*
-{
-    LockHolder lock(m_registeredThreadsMutex);
-    ThreadIdentifier id = currentThread();
-    for (MachineThread* thread = m_registeredThreads.head(); thread; thread = thread->next()) {
-        if (thread->threadID() == id)
-            return thread;
-    }
-
-    RELEASE_ASSERT_NOT_REACHED();
-    return nullptr;
-}
-
-void THREAD_SPECIFIC_CALL MachineThreads::removeThread(void* p)
-{
-    auto& manager = activeMachineThreadsManager();
-    ActiveMachineThreadsManager::Locker lock(manager);
-    auto machineThreads = static_cast<MachineThreads*>(p);
-    if (manager.contains(machineThreads)) {
-        // There's a chance that the MachineThreads registry that this thread
-        // was registered with was already destructed, and another one happened
-        // to be instantiated at the same address. Hence, this thread may or
-        // may not be found in this MachineThreads registry. We only need to
-        // do a removal if this thread is found in it.
-
-#if OS(WINDOWS)
-        // On Windows the thread specific destructor is also called when the
-        // main thread is exiting. This may lead to the main thread waiting
-        // forever for the machine thread lock when exiting, if the sampling
-        // profiler thread was terminated by the system while holding the
-        // machine thread lock.
-        if (WTF::isMainThread())
-            return;
-#endif
-
-        machineThreads->removeThreadIfFound(currentThread());
-    }
-}
-
-void MachineThreads::removeThreadIfFound(ThreadIdentifier id)
-{
-    LockHolder lock(m_registeredThreadsMutex);
-    for (MachineThread* current = m_registeredThreads.head(); current; current = current->next()) {
-        if (current->threadID() == id) {
-            m_registeredThreads.remove(current);
-            delete current;
-            break;
-        }
-    }
 }
 
 SUPPRESS_ASAN
@@ -235,52 +51,6 @@ void MachineThreads::gatherFromCurrentThread(ConservativeRoots& conservativeRoot
     conservativeRoots.add(currentThreadState.stackTop, currentThreadState.stackOrigin, jitStubRoutines, codeBlocks);
 }
 
-MachineThreads::MachineThread::MachineThread()
-    : m_thread(WTF::Thread::current())
-{
-}
-
-size_t MachineThreads::MachineThread::getRegisters(MachineThread::Registers& registers)
-{
-    WTF::PlatformRegisters& regs = registers.regs;
-    return m_thread->getRegisters(regs);
-}
-
-void* MachineThreads::MachineThread::Registers::stackPointer() const
-{
-    return MachineContext::stackPointer(regs);
-}
-
-#if ENABLE(SAMPLING_PROFILER)
-void* MachineThreads::MachineThread::Registers::framePointer() const
-{
-#if OS(WINDOWS) || HAVE(MACHINE_CONTEXT)
-    return MachineContext::framePointer(regs);
-#else
-#error Need a way to get the frame pointer for another thread on this platform
-#endif
-}
-
-void* MachineThreads::MachineThread::Registers::instructionPointer() const
-{
-#if OS(WINDOWS) || HAVE(MACHINE_CONTEXT)
-    return MachineContext::instructionPointer(regs);
-#else
-#error Need a way to get the instruction pointer for another thread on this platform
-#endif
-}
-
-void* MachineThreads::MachineThread::Registers::llintPC() const
-{
-    // LLInt uses regT4 as PC.
-#if OS(WINDOWS) || HAVE(MACHINE_CONTEXT)
-    return MachineContext::llintInstructionPointer(regs);
-#else
-#error Need a way to get the LLIntPC for another thread on this platform
-#endif
-}
-#endif // ENABLE(SAMPLING_PROFILER)
-
 static inline int osRedZoneAdjustment()
 {
     int redZoneAdjustment = 0;
@@ -296,17 +66,17 @@ static inline int osRedZoneAdjustment()
     return redZoneAdjustment;
 }
 
-std::pair<void*, size_t> MachineThreads::MachineThread::captureStack(void* stackTop)
+static std::pair<void*, size_t> captureStack(Thread& thread, void* stackTop)
 {
-    char* begin = reinterpret_cast_ptr<char*>(stackBase());
+    char* begin = reinterpret_cast_ptr<char*>(thread.stack().origin());
     char* end = bitwise_cast<char*>(WTF::roundUpToMultipleOf<sizeof(void*)>(reinterpret_cast<uintptr_t>(stackTop)));
     ASSERT(begin >= end);
 
     char* endWithRedZone = end + osRedZoneAdjustment();
     ASSERT(WTF::roundUpToMultipleOf<sizeof(void*)>(reinterpret_cast<uintptr_t>(endWithRedZone)) == reinterpret_cast<uintptr_t>(endWithRedZone));
 
-    if (endWithRedZone < stackEnd())
-        endWithRedZone = reinterpret_cast_ptr<char*>(stackEnd());
+    if (endWithRedZone < thread.stack().end())
+        endWithRedZone = reinterpret_cast_ptr<char*>(thread.stack().end());
 
     std::swap(begin, endWithRedZone);
     return std::make_pair(begin, endWithRedZone - begin);
@@ -339,20 +109,20 @@ static void copyMemory(void* dst, const void* src, size_t size)
 // significant performance loss as tryCopyOtherThreadStack is only called as part of an O(heapsize)
 // operation. As the heap is generally much larger than the stack the performance hit is minimal.
 // See: https://bugs.webkit.org/show_bug.cgi?id=146297
-void MachineThreads::tryCopyOtherThreadStack(MachineThread* thread, void* buffer, size_t capacity, size_t* size)
+void MachineThreads::tryCopyOtherThreadStack(Thread& thread, void* buffer, size_t capacity, size_t* size)
 {
-    MachineThread::Registers registers;
-    size_t registersSize = thread->getRegisters(registers);
+    PlatformRegisters registers;
+    size_t registersSize = thread.getRegisters(registers);
 
     // This is a workaround for <rdar://problem/27607384>. libdispatch recycles work
     // queue threads without running pthread exit destructors. This can cause us to scan a
     // thread during work queue initialization, when the stack pointer is null.
-    if (UNLIKELY(!registers.stackPointer())) {
+    if (UNLIKELY(!MachineContext::stackPointer(registers))) {
         *size = 0;
         return;
     }
 
-    std::pair<void*, size_t> stack = thread->captureStack(registers.stackPointer());
+    std::pair<void*, size_t> stack = captureStack(thread, MachineContext::stackPointer(registers));
 
     bool canCopy = *size + registersSize + stack.second <= capacity;
 
@@ -365,7 +135,7 @@ void MachineThreads::tryCopyOtherThreadStack(MachineThread* thread, void* buffer
     *size += stack.second;
 }
 
-bool MachineThreads::tryCopyOtherThreadStacks(const AbstractLocker&, void* buffer, size_t capacity, size_t* size)
+bool MachineThreads::tryCopyOtherThreadStacks(const AbstractLocker& locker, void* buffer, size_t capacity, size_t* size)
 {
     // Prevent two VMs from suspending each other's threads at the same time,
     // which can cause deadlock: <rdar://problem/20300842>.
@@ -374,60 +144,49 @@ bool MachineThreads::tryCopyOtherThreadStacks(const AbstractLocker&, void* buffe
 
     *size = 0;
 
-    ThreadIdentifier id = currentThread();
-    int numberOfThreads = 0; // Using 0 to denote that we haven't counted the number of threads yet.
-    int index = 1;
-    DoublyLinkedList<MachineThread> threadsToBeDeleted;
+    Thread& currentThread = Thread::current();
+    const auto& threads = m_threadGroup->threads(locker);
+    BitVector isSuspended(threads.size());
 
-    for (MachineThread* thread = m_registeredThreads.head(); thread; index++) {
-        if (thread->threadID() != id) {
-            auto result = thread->suspend();
+    {
+        unsigned index = 0;
+        for (auto& thread : threads) {
+            if (thread.get() != currentThread) {
+                auto result = thread->suspend();
+                if (result)
+                    isSuspended.set(index);
+                else {
 #if OS(DARWIN)
-            if (!result) {
-                if (!numberOfThreads)
-                    numberOfThreads = m_registeredThreads.size();
-
-                ASSERT(result.error() != KERN_SUCCESS);
-
-                WTFReportError(__FILE__, __LINE__, WTF_PRETTY_FUNCTION,
-                    "JavaScript garbage collection encountered an invalid thread (err 0x%x): Thread [%d/%d: %p] id %u.",
-                    result.error(), index, numberOfThreads, thread, thread->threadID());
-
-                // Put the invalid thread on the threadsToBeDeleted list.
-                // We can't just delete it here because we have suspended other
-                // threads, and they may still be holding the C heap lock which
-                // we need for deleting the invalid thread. Hence, we need to
-                // defer the deletion till after we have resumed all threads.
-                MachineThread* nextThread = thread->next();
-                m_registeredThreads.remove(thread);
-                threadsToBeDeleted.append(thread);
-                thread = nextThread;
-                continue;
-            }
-#else
-            UNUSED_PARAM(numberOfThreads);
-            ASSERT_UNUSED(result, result);
+                    // These threads will be removed from the ThreadGroup. Thus, we do not do anything here except for reporting.
+                    ASSERT(result.error() != KERN_SUCCESS);
+                    WTFReportError(__FILE__, __LINE__, WTF_PRETTY_FUNCTION,
+                        "JavaScript garbage collection encountered an invalid thread (err 0x%x): Thread [%d/%d: %p] id %u.",
+                        result.error(), index, threads.size(), thread.ptr(), thread->id());
 #endif
+                }
+            }
+            ++index;
         }
-        thread = thread->next();
     }
 
-    for (MachineThread* thread = m_registeredThreads.head(); thread; thread = thread->next()) {
-        if (thread->threadID() != id)
-            tryCopyOtherThreadStack(thread, buffer, capacity, size);
+    {
+        unsigned index = 0;
+        for (auto& thread : threads) {
+            if (isSuspended.get(index))
+                tryCopyOtherThreadStack(thread.get(), buffer, capacity, size);
+            ++index;
+        }
     }
 
-    for (MachineThread* thread = m_registeredThreads.head(); thread; thread = thread->next()) {
-        if (thread->threadID() != id)
-            thread->resume();
+    {
+        unsigned index = 0;
+        for (auto& thread : threads) {
+            if (isSuspended.get(index))
+                thread->resume();
+            ++index;
+        }
     }
 
-    for (MachineThread* thread = threadsToBeDeleted.head(); thread; ) {
-        MachineThread* nextThread = thread->next();
-        delete thread;
-        thread = nextThread;
-    }
-    
     return *size <= capacity;
 }
 
@@ -448,8 +207,8 @@ void MachineThreads::gatherConservativeRoots(ConservativeRoots& conservativeRoot
     size_t size;
     size_t capacity = 0;
     void* buffer = nullptr;
-    LockHolder lock(m_registeredThreadsMutex);
-    while (!tryCopyOtherThreadStacks(lock, buffer, capacity, &size))
+    auto locker = holdLock(m_threadGroup->getLock());
+    while (!tryCopyOtherThreadStacks(locker, buffer, capacity, &size))
         growBuffer(size, &buffer, &capacity);
 
     if (!buffer)
index c1510d6..900b179 100644 (file)
 
 #pragma once
 
-#include "MachineContext.h"
 #include "RegisterState.h"
-#include <wtf/DoublyLinkedList.h>
 #include <wtf/Lock.h>
 #include <wtf/ScopedLambda.h>
-#include <wtf/ThreadSpecific.h>
+#include <wtf/ThreadGroup.h>
 
 namespace JSC {
 
@@ -45,58 +43,22 @@ class MachineThreads {
     WTF_MAKE_NONCOPYABLE(MachineThreads);
 public:
     MachineThreads();
-    ~MachineThreads();
 
     void gatherConservativeRoots(ConservativeRoots&, JITStubRoutineSet&, CodeBlockSet&, CurrentThreadState*);
 
-    JS_EXPORT_PRIVATE void addCurrentThread(); // Only needs to be called by clients that can use the same heap from multiple threads.
+    // Only needs to be called by clients that can use the same heap from multiple threads.
+    void addCurrentThread() { m_threadGroup->addCurrentThread(); }
 
-    class MachineThread : public DoublyLinkedListNode<MachineThread> {
-        WTF_MAKE_FAST_ALLOCATED;
-    public:
-        MachineThread();
-
-        struct Registers {
-            void* stackPointer() const;
-#if ENABLE(SAMPLING_PROFILER)
-            void* framePointer() const;
-            void* instructionPointer() const;
-            void* llintPC() const;
-#endif // ENABLE(SAMPLING_PROFILER)
-            PlatformRegisters regs;
-        };
-
-        Expected<void, Thread::PlatformSuspendError> suspend() { return m_thread->suspend(); }
-        void resume() { m_thread->resume(); }
-        size_t getRegisters(Registers& regs);
-        std::pair<void*, size_t> captureStack(void* stackTop);
-
-        WTF::ThreadIdentifier threadID() const { return m_thread->id(); }
-        void* stackBase() const { return m_thread->stack().origin(); }
-        void* stackEnd() const { return m_thread->stack().end(); }
-
-        Ref<WTF::Thread> m_thread;
-        MachineThread* m_next { nullptr };
-        MachineThread* m_prev { nullptr };
-    };
-
-    Lock& getLock() { return m_registeredThreadsMutex; }
-    const DoublyLinkedList<MachineThread>& threadsListHead(const AbstractLocker&) const { ASSERT(m_registeredThreadsMutex.isLocked()); return m_registeredThreads; }
-    MachineThread* machineThreadForCurrentThread();
+    std::mutex& getLock() { return m_threadGroup->getLock(); }
+    const ListHashSet<Ref<Thread>>& threads(const AbstractLocker& locker) const { return m_threadGroup->threads(locker); }
 
 private:
     void gatherFromCurrentThread(ConservativeRoots&, JITStubRoutineSet&, CodeBlockSet&, CurrentThreadState&);
 
-    void tryCopyOtherThreadStack(MachineThread*, void*, size_t capacity, size_t*);
+    void tryCopyOtherThreadStack(Thread&, void*, size_t capacity, size_t*);
     bool tryCopyOtherThreadStacks(const AbstractLocker&, void*, size_t capacity, size_t*);
 
-    static void THREAD_SPECIFIC_CALL removeThread(void*);
-
-    void removeThreadIfFound(ThreadIdentifier);
-
-    Lock m_registeredThreadsMutex;
-    DoublyLinkedList<MachineThread> m_registeredThreads;
-    WTF::ThreadSpecificKey m_threadSpecificForMachineThreads;
+    std::shared_ptr<ThreadGroup> m_threadGroup;
 };
 
 #define DECLARE_AND_COMPUTE_CURRENT_THREAD_STATE(stateName) \
index 44e84fe..0159de1 100644 (file)
@@ -39,6 +39,7 @@
 #include "JSCInlines.h"
 #include "JSFunction.h"
 #include "LLIntPCRanges.h"
+#include "MachineContext.h"
 #include "MarkedBlock.h"
 #include "MarkedBlockSet.h"
 #include "MarkedSpaceInlines.h"
@@ -165,10 +166,9 @@ protected:
     bool isValidFramePointer(void* exec)
     {
         uint8_t* fpCast = bitwise_cast<uint8_t*>(exec);
-        const auto& threadList = m_vm.heap.machineThreads().threadsListHead(m_machineThreadsLocker);
-        for (MachineThreads::MachineThread* thread = threadList.head(); thread; thread = thread->next()) {
-            uint8_t* stackBase = static_cast<uint8_t*>(thread->stackBase());
-            uint8_t* stackLimit = static_cast<uint8_t*>(thread->stackEnd());
+        for (auto& thread : m_vm.heap.machineThreads().threads(m_machineThreadsLocker)) {
+            uint8_t* stackBase = static_cast<uint8_t*>(thread->stack().origin());
+            uint8_t* stackLimit = static_cast<uint8_t*>(thread->stack().end());
             RELEASE_ASSERT(stackBase);
             RELEASE_ASSERT(stackLimit);
             if (fpCast <= stackBase && fpCast >= stackLimit)
@@ -278,7 +278,6 @@ SamplingProfiler::SamplingProfiler(VM& vm, RefPtr<Stopwatch>&& stopwatch)
     , m_weakRandom()
     , m_stopwatch(WTFMove(stopwatch))
     , m_timingInterval(std::chrono::microseconds(Options::sampleInterval()))
-    , m_jscExecutionThread(nullptr)
     , m_isPaused(false)
     , m_isShutDown(false)
 {
@@ -338,7 +337,7 @@ void SamplingProfiler::takeSample(const AbstractLocker&, std::chrono::microsecon
     if (m_vm.entryScope) {
         double nowTime = m_stopwatch->elapsedTime();
 
-        LockHolder machineThreadsLocker(m_vm.heap.machineThreads().getLock());
+        auto machineThreadsLocker = holdLock(m_vm.heap.machineThreads().getLock());
         LockHolder codeBlockSetLocker(m_vm.heap.codeBlockSet().getLock());
         LockHolder executableAllocatorLocker(ExecutableAllocator::singleton().getLock());
 
@@ -352,12 +351,12 @@ void SamplingProfiler::takeSample(const AbstractLocker&, std::chrono::microsecon
             bool topFrameIsLLInt = false;
             void* llintPC;
             {
-                MachineThreads::MachineThread::Registers registers;
+                PlatformRegisters registers;
                 m_jscExecutionThread->getRegisters(registers);
-                machineFrame = registers.framePointer();
+                machineFrame = MachineContext::framePointer(registers);
                 callFrame = static_cast<ExecState*>(machineFrame);
-                machinePC = registers.instructionPointer();
-                llintPC = registers.llintPC();
+                machinePC = MachineContext::instructionPointer(registers);
+                llintPC = MachineContext::llintInstructionPointer(registers);
             }
             // FIXME: Lets have a way of detecting when we're parsing code.
             // https://bugs.webkit.org/show_bug.cgi?id=152761
@@ -678,7 +677,7 @@ void SamplingProfiler::pause(const AbstractLocker&)
 void SamplingProfiler::noticeCurrentThreadAsJSCExecutionThread(const AbstractLocker&)
 {
     ASSERT(m_lock.isLocked());
-    m_jscExecutionThread = m_vm.heap.machineThreads().machineThreadForCurrentThread();
+    m_jscExecutionThread = &Thread::current();
 }
 
 void SamplingProfiler::noticeCurrentThreadAsJSCExecutionThread()
index 78b9beb..38a179c 100644 (file)
@@ -196,7 +196,7 @@ private:
     double m_lastTime;
     Lock m_lock;
     RefPtr<Thread> m_thread;
-    MachineThreads::MachineThread* m_jscExecutionThread;
+    RefPtr<Thread> m_jscExecutionThread;
     bool m_isPaused;
     bool m_isShutDown;
     bool m_needsReportAtExit { false };
index 0a367fd..fe658e4 100644 (file)
@@ -55,10 +55,8 @@ void startTrackingCurrentThread()
 void resetInstructionCacheOnAllThreads()
 {
     auto locker = holdLock(wasmThreads().getLock());
-
-    const DoublyLinkedList<MachineThreads::MachineThread>& threads = wasmThreads().threadsListHead(locker);
-    for (const auto* thread = threads.head(); thread; thread = thread->next()) {
-        sendMessage(thread->m_thread.get(), [] (const PlatformRegisters&) {
+    for (auto& thread : wasmThreads().threads(locker)) {
+        sendMessage(thread.get(), [] (const PlatformRegisters&) {
             // It's likely that the signal handler will already reset the instruction cache but we might as well be sure.
             WTF::crossModifyingCodeFence();
         });
index 6f28a9d..b0bca92 100644 (file)
@@ -1,3 +1,77 @@
+2017-07-19  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        [WTF] Implement WTF::ThreadGroup
+        https://bugs.webkit.org/show_bug.cgi?id=174081
+
+        Reviewed by Mark Lam.
+
+        This patch implements WTF::ThreadGroup. It implements core of JSC::MachineThreads with more reliable way.
+        JSC::MachineThreads was complicated because of managing dead threads. Each JSC::MachineThreads has its
+        own TLS with a registered destructor. And everytime a thread dies, the registered TLS destructor is called.
+        And this destructor will remove the current dying thread from JSC::MachineThreads.
+
+        However the above implementation is tricky. And each JSC::MachineThreads requires own TLS space, which is
+        not considered in WTF's Windows ThreadSpecific implementation. Current design works well since we only
+        have small number of MachineThreads right now.
+
+        Instead, we use more reliable way. After introducing WTF::Thread, WTF::Thread has WTF::Thread::didExit,
+        which is called when associated TLS (with WTF::Thread) is destroyed. We leverage this mechanism to remove
+        WTF::Thread from MachineThreads.
+
+        This patch introduces WTF::ThreadGroup. It is tightly integrated with WTF::Thread: WTF::Thread knows
+        ThreadGroups which includes this thread. And WTF::ThreadGroup of course knows WTF::Threads added to it.
+        WTF::Thread::didExit carefully remove itself from WTF::ThreadGroups.
+
+        The most important part of this patch is locking. WTF::Thread can die. And WTF::ThreadGroup can die.
+        If we take a design using two fine grain locks in WTF::Thread and WTF::ThreadGroup, we easily encounter
+        dead lock. Consider the following case.
+
+        1. When adding WTF::Thread (TH) to WTF::ThreadGroup (THG), we first hold a lock of THG, and hold a lock of TH (locking order is THG -> TH).
+        2. When TH dies, TH need to hold a lock of TH to iterate THGs. And we hold a lock of THG to unregister TH from it (locking order is TH -> THG).
+        3. When suspending and resuming THs in THG, we first hold a lock of THG. And then, we hold a lock of TH to suspend and resume it (locking order is THG -> TH).
+        4. When destroying THG, we need to hold a lock of TH to unregister THG from TH. We can hold a lock of THG before that (locking order is THG -> TH).
+
+        Then, it easily causes dead lock. We cannot swap the locking order of (2) since iterating THG requires a lock of TH.
+        To solve this problem, we use std::shared_ptr and std::weak_ptr.
+
+        1. When adding WTF::Thread (TH) to WTF::ThreadGroup (THG), we first hold THG, and hold a lock of TH. (THG -> TH)
+        2. When TH dies, TH first hold lock of TH. And we use std::weak_ptr<>::lock() to retain non-destructed ThreadGroups.
+        If some of ThreadGroups are dying, we just ignore them. It is ok because such a ThreadGroup will be destructed. So we do not need to unregister this thread from
+        such a ThreadGroup. Then, we have Vector<std::shared_ptr<ThreadGroup>>. So we unlock a lock of TH. To unregister a thread from thread group, we first hold a
+        lock of THG and then hold a lock of TH. Both lifetime is ensured: THG is retained by std::shared_ptr. And TH is itself. (TH), (THG -> TH).
+        3. When suspending and resuming THs in THG, we first hold a lock of THG. And then, we hold a lock of TH to suspend and resume it (THG -> TH).
+        4. When destroying THG, we hold a lock of THG. And hold a lock of TH. During holding THG's lock, registered thread never dies because (2) holds THG lock. (THG -> TH).
+
+        We also fix suspend and resume locking mechanism to avoid dead lock. We should hold the global lock when suspending and resuming.
+        If we use per-thread lock, the suspended thread can hold the lock of the other threads. It causes dead lock.
+
+        * WTF.xcodeproj/project.pbxproj:
+        * wtf/AutomaticThread.cpp:
+        * wtf/CMakeLists.txt:
+        * wtf/CrossThreadCopier.h:
+        * wtf/ParkingLot.h:
+        * wtf/ThreadGroup.cpp: Copied from Source/JavaScriptCore/wasm/WasmMachineThreads.cpp.
+        (WTF::ThreadGroup::~ThreadGroup):
+        (WTF::ThreadGroup::add):
+        (WTF::ThreadGroup::addCurrentThread):
+        * wtf/ThreadGroup.h: Copied from Source/JavaScriptCore/wasm/WasmMachineThreads.cpp.
+        (WTF::ThreadGroup::create):
+        (WTF::ThreadGroup::threads):
+        (WTF::ThreadGroup::getLock):
+        (WTF::ThreadGroup::weakFromThis):
+        * wtf/Threading.cpp:
+        (WTF::shouldRemoveThreadFromThreadGroup):
+        (WTF::Thread::didExit):
+        (WTF::Thread::addToThreadGroup):
+        (WTF::Thread::removeFromThreadGroup):
+        * wtf/Threading.h:
+        * wtf/ThreadingPthreads.cpp:
+        (WTF::Thread::resume):
+        (WTF::Thread::getRegisters):
+        * wtf/ThreadingWin.cpp:
+        (WTF::Thread::resume):
+        (WTF::Thread::getRegisters):
+
 2017-07-18  Andy Estes  <aestes@apple.com>
 
         [Xcode] Enable CLANG_WARN_RANGE_LOOP_ANALYSIS
index 2e96c83..7886f16 100644 (file)
                DCEE22011CEA7551000C2396 /* BlockObjCExceptions.mm in Sources */ = {isa = PBXBuildFile; fileRef = DCEE21FD1CEA7551000C2396 /* BlockObjCExceptions.mm */; };
                DCEE22031CEA7551000C2396 /* PlatformUserPreferredLanguagesMac.mm in Sources */ = {isa = PBXBuildFile; fileRef = DCEE21FF1CEA7551000C2396 /* PlatformUserPreferredLanguagesMac.mm */; };
                E15556F518A0CC18006F48FB /* CryptographicUtilities.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E15556F318A0CC18006F48FB /* CryptographicUtilities.cpp */; };
+               E311FB171F0A568B003C08DE /* ThreadGroup.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E311FB151F0A568B003C08DE /* ThreadGroup.cpp */; };
                E3200AB81E9A536D003B59D2 /* ThreadHolder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E3200AB51E9A536D003B59D2 /* ThreadHolder.cpp */; };
                E38C41251EB4E04C0042957D /* CPUTimeCocoa.mm in Sources */ = {isa = PBXBuildFile; fileRef = E38C41241EB4E04C0042957D /* CPUTimeCocoa.mm */; };
                E38C41281EB4E0680042957D /* CPUTime.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E38C41261EB4E0680042957D /* CPUTime.cpp */; };
                DE5A09FB1BA36992003D4424 /* CommonCryptoSPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommonCryptoSPI.h; sourceTree = "<group>"; };
                E15556F318A0CC18006F48FB /* CryptographicUtilities.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CryptographicUtilities.cpp; sourceTree = "<group>"; };
                E15556F418A0CC18006F48FB /* CryptographicUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptographicUtilities.h; sourceTree = "<group>"; };
+               E311FB151F0A568B003C08DE /* ThreadGroup.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ThreadGroup.cpp; sourceTree = "<group>"; };
+               E311FB161F0A568B003C08DE /* ThreadGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThreadGroup.h; sourceTree = "<group>"; };
                E3200AB41E9A536D003B59D2 /* PlatformRegisters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlatformRegisters.h; sourceTree = "<group>"; };
                E3200AB51E9A536D003B59D2 /* ThreadHolder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ThreadHolder.cpp; sourceTree = "<group>"; };
                E3200AB61E9A536D003B59D2 /* ThreadHolder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThreadHolder.h; sourceTree = "<group>"; };
                                E3E158251EADA53C004A079D /* SystemFree.h */,
                                0FB317C31C488001007E395A /* SystemTracing.h */,
                                A8A4732F151A825B004123FF /* ThreadFunctionInvocation.h */,
+                               E311FB151F0A568B003C08DE /* ThreadGroup.cpp */,
+                               E311FB161F0A568B003C08DE /* ThreadGroup.h */,
                                E3200AB51E9A536D003B59D2 /* ThreadHolder.cpp */,
                                E3200AB61E9A536D003B59D2 /* ThreadHolder.h */,
                                A8A47330151A825B004123FF /* ThreadHolderPthreads.cpp */,
                                A8A47448151A825B004123FF /* ThreadHolderPthreads.cpp in Sources */,
                                A8A4744A151A825B004123FF /* Threading.cpp in Sources */,
                                A8A4744E151A825B004123FF /* ThreadingPthreads.cpp in Sources */,
+                               E311FB171F0A568B003C08DE /* ThreadGroup.cpp in Sources */,
                                5311BD5C1EA822F900525281 /* ThreadMessage.cpp in Sources */,
                                0F66B2901DC97BAB004A1D3F /* TimeWithDynamicClockType.cpp in Sources */,
                                1C181C8F1D307AB800F5FA16 /* UTextProvider.cpp in Sources */,
index 8111876..690c80f 100644 (file)
@@ -27,6 +27,7 @@
 #include "AutomaticThread.h"
 
 #include "DataLog.h"
+#include "Threading.h"
 
 namespace WTF {
 
index da6752d..f73bd5b 100644 (file)
@@ -132,6 +132,7 @@ set(WTF_HEADERS
     StringPrintStream.h
     SystemFree.h
     SystemTracing.h
+    ThreadGroup.h
     ThreadHolder.cpp
     ThreadMessage.h
     ThreadSafeRefCounted.h
@@ -246,6 +247,7 @@ set(WTF_SOURCES
     StackStats.cpp
     StackTrace.cpp
     StringPrintStream.cpp
+    ThreadGroup.cpp
     ThreadMessage.cpp
     Threading.cpp
     TimeWithDynamicClockType.cpp
index 3f649db..83f7b4b 100644 (file)
@@ -35,6 +35,7 @@
 #include <wtf/Forward.h>
 #include <wtf/HashSet.h>
 #include <wtf/RefPtr.h>
+#include <wtf/ThreadSafeRefCounted.h>
 #include <wtf/text/WTFString.h>
 
 namespace WTF {
index b38ff30..8487aaa 100644 (file)
 
 #include <wtf/Atomics.h>
 #include <wtf/ScopedLambda.h>
-#include <wtf/Threading.h>
 #include <wtf/TimeWithDynamicClockType.h>
 
 namespace WTF {
 
+class Thread;
+
 class ParkingLot {
     ParkingLot() = delete;
     ParkingLot(const ParkingLot&) = delete;
diff --git a/Source/WTF/wtf/ThreadGroup.cpp b/Source/WTF/wtf/ThreadGroup.cpp
new file mode 100644 (file)
index 0000000..829c990
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 Yusuke Suzuki <utatane.tea@gmail.com>.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "ThreadGroup.h"
+
+#include <wtf/NeverDestroyed.h>
+
+namespace WTF {
+
+ThreadGroup::~ThreadGroup()
+{
+    std::lock_guard<std::mutex> locker(m_lock);
+    for (auto& thread : m_threads)
+        thread->removeFromThreadGroup(locker, *this);
+}
+
+bool ThreadGroup::add(Thread& thread)
+{
+    std::lock_guard<std::mutex> locker(m_lock);
+    return thread.addToThreadGroup(locker, *this);
+}
+
+void ThreadGroup::addCurrentThread()
+{
+    bool isAdded = add(Thread::current());
+    ASSERT_UNUSED(isAdded, isAdded);
+}
+
+} // namespace WTF
diff --git a/Source/WTF/wtf/ThreadGroup.h b/Source/WTF/wtf/ThreadGroup.h
new file mode 100644 (file)
index 0000000..db94900
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 Yusuke Suzuki <utatane.tea@gmail.com>.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <memory>
+#include <wtf/ListHashSet.h>
+#include <wtf/Lock.h>
+#include <wtf/Threading.h>
+
+namespace WTF {
+
+class ThreadGroup : public std::enable_shared_from_this<ThreadGroup> {
+public:
+    friend class Thread;
+
+    static std::shared_ptr<ThreadGroup> create()
+    {
+        return std::make_shared<ThreadGroup>();
+    }
+
+    WTF_EXPORT_PRIVATE bool add(Thread&);
+    WTF_EXPORT_PRIVATE void addCurrentThread();
+
+    const ListHashSet<Ref<Thread>>& threads(const AbstractLocker&) const { return m_threads; }
+
+    std::mutex& getLock() { return m_lock; }
+
+    WTF_EXPORT_PRIVATE ~ThreadGroup();
+
+    ThreadGroup() = default;
+
+private:
+    std::weak_ptr<ThreadGroup> weakFromThis()
+    {
+        return shared_from_this();
+    }
+
+    // We use std::mutex since it can be used when deallocating TLS.
+    std::mutex m_lock;
+    ListHashSet<Ref<Thread>> m_threads;
+};
+
+}
+
+using WTF::ThreadGroup;
index 8362ee3..ce1dd7e 100644 (file)
 #include "config.h"
 #include "Threading.h"
 
-#include "dtoa.h"
-#include "dtoa/cached-powers.h"
 #include <algorithm>
 #include <cmath>
 #include <cstring>
 #include <wtf/DateMath.h>
 #include <wtf/PrintStream.h>
 #include <wtf/RandomNumberSeed.h>
+#include <wtf/ThreadGroup.h>
 #include <wtf/ThreadHolder.h>
 #include <wtf/ThreadMessage.h>
 #include <wtf/ThreadingPrimitives.h>
@@ -130,12 +129,70 @@ void Thread::initialize()
     m_stack = StackBounds::currentThreadStackBounds();
 }
 
+static bool shouldRemoveThreadFromThreadGroup()
+{
+#if OS(WINDOWS)
+    // On Windows the thread specific destructor is also called when the
+    // main thread is exiting. This may lead to the main thread waiting
+    // forever for the thread group lock when exiting, if the sampling
+    // profiler thread was terminated by the system while holding the
+    // thread group lock.
+    if (WTF::isMainThread())
+        return false;
+#endif
+    return true;
+}
+
 void Thread::didExit()
 {
+    if (shouldRemoveThreadFromThreadGroup()) {
+        Vector<std::shared_ptr<ThreadGroup>> threadGroups;
+        {
+            std::lock_guard<std::mutex> locker(m_mutex);
+            for (auto& threadGroup : m_threadGroups) {
+                // If ThreadGroup is just being destroyed,
+                // we do not need to perform unregistering.
+                if (auto retained = threadGroup.lock())
+                    threadGroups.append(WTFMove(retained));
+            }
+            m_isShuttingDown = true;
+        }
+        for (auto& threadGroup : threadGroups) {
+            std::lock_guard<std::mutex> threadGroupLocker(threadGroup->getLock());
+            std::lock_guard<std::mutex> locker(m_mutex);
+            threadGroup->m_threads.remove(*this);
+        }
+    }
+    // We would like to say "thread is exited" after unregistering threads from thread groups.
+    // So we need to separate m_isShuttingDown from m_didExit.
     std::lock_guard<std::mutex> locker(m_mutex);
     m_didExit = true;
 }
 
+bool Thread::addToThreadGroup(const std::lock_guard<std::mutex>& threadGroupLocker, ThreadGroup& threadGroup)
+{
+    UNUSED_PARAM(threadGroupLocker);
+    std::lock_guard<std::mutex> locker(m_mutex);
+    if (m_isShuttingDown)
+        return false;
+    if (threadGroup.m_threads.add(*this).isNewEntry)
+        m_threadGroups.append(threadGroup.weakFromThis());
+    return true;
+}
+
+void Thread::removeFromThreadGroup(const std::lock_guard<std::mutex>& threadGroupLocker, ThreadGroup& threadGroup)
+{
+    UNUSED_PARAM(threadGroupLocker);
+    std::lock_guard<std::mutex> locker(m_mutex);
+    if (m_isShuttingDown)
+        return;
+    m_threadGroups.removeFirstMatching([&] (auto weakPtr) {
+        if (auto sharedPtr = weakPtr.lock())
+            return sharedPtr.get() == &threadGroup;
+        return false;
+    });
+}
+
 void Thread::setCurrentThreadIsUserInteractive(int relativePriority)
 {
 #if HAVE(QOS_CLASSES)
index 8881cce..883d78d 100644 (file)
@@ -40,6 +40,7 @@
 #include <wtf/RefPtr.h>
 #include <wtf/StackBounds.h>
 #include <wtf/ThreadSafeRefCounted.h>
+#include <wtf/Vector.h>
 
 #if USE(PTHREADS) && !OS(DARWIN)
 #include <semaphore.h>
 
 namespace WTF {
 
+class AbstractLocker;
 class ThreadMessageData;
 
 using ThreadIdentifier = uint32_t;
 typedef void (*ThreadFunction)(void* argument);
 
+class ThreadGroup;
 class ThreadHolder;
 class PrintStream;
 
 class Thread : public ThreadSafeRefCounted<Thread> {
 public:
+    friend class ThreadGroup;
     friend class ThreadHolder;
 
     WTF_EXPORT_PRIVATE ~Thread();
@@ -175,11 +179,17 @@ protected:
     void didJoin() { m_joinableState = Joined; }
     bool hasExited() { return m_didExit; }
 
+    // These functions are only called from ThreadGroup.
+    bool addToThreadGroup(const std::lock_guard<std::mutex>& threadGroupLocker, ThreadGroup&);
+    void removeFromThreadGroup(const std::lock_guard<std::mutex>& threadGroupLocker, ThreadGroup&);
+
     // WordLock & Lock rely on ThreadSpecific. But Thread object can be destroyed even after ThreadSpecific things are destroyed.
     std::mutex m_mutex;
     ThreadIdentifier m_id { 0 };
     JoinableState m_joinableState { Joinable };
     StackBounds m_stack { StackBounds::emptyBounds() };
+    Vector<std::weak_ptr<ThreadGroup>> m_threadGroups;
+    bool m_isShuttingDown { false };
     bool m_didExit { false };
 #if USE(PTHREADS)
     pthread_t m_handle;
index c21590b..99aea08 100644 (file)
@@ -40,6 +40,7 @@
 #include <wtf/RawPointer.h>
 #include <wtf/StdLibExtras.h>
 #include <wtf/ThreadFunctionInvocation.h>
+#include <wtf/ThreadGroup.h>
 #include <wtf/ThreadHolder.h>
 #include <wtf/ThreadingPrimitives.h>
 #include <wtf/WordLock.h>
@@ -73,6 +74,8 @@
 
 namespace WTF {
 
+static StaticLock globalSuspendLock;
+
 Thread::Thread()
 {
 #if !OS(DARWIN)
@@ -92,7 +95,6 @@ Thread::~Thread()
 // We use SIGUSR1 to suspend and resume machine threads in JavaScriptCore.
 static constexpr const int SigThreadSuspendResume = SIGUSR1;
 static std::atomic<Thread*> targetThread { nullptr };
-static StaticWordLock globalSuspendLock;
 
 #if COMPILER(GCC)
 #pragma GCC diagnostic push
@@ -329,65 +331,64 @@ bool Thread::signal(int signalNumber)
 auto Thread::suspend() -> Expected<void, PlatformSuspendError>
 {
     RELEASE_ASSERT_WITH_MESSAGE(id() != currentThread(), "We do not support suspending the current thread itself.");
-    std::lock_guard<std::mutex> locker(m_mutex);
+    // During suspend, suspend or resume should not be executed from the other threads.
+    // We use global lock instead of per thread lock.
+    // Consider the following case, there are threads A and B.
+    // And A attempt to suspend B and B attempt to suspend A.
+    // A and B send signals. And later, signals are delivered to A and B.
+    // In that case, both will be suspended.
+    //
+    // And it is important to use a global lock to suspend and resume. Let's consider using per-thread lock.
+    // Your issuing thread (A) attempts to suspend the target thread (B). Then, you will suspend the thread (C) additionally.
+    // This case frequently happens if you stop threads to perform stack scanning. But thread (B) may hold the lock of thread (C).
+    // In that case, dead lock happens. Using global lock here avoids this dead lock.
+    LockHolder locker(globalSuspendLock);
 #if OS(DARWIN)
     kern_return_t result = thread_suspend(m_platformThread);
     if (result != KERN_SUCCESS)
         return makeUnexpected(result);
     return { };
 #else
-    {
-        // During suspend, suspend or resume should not be executed from the other threads.
-        // We use global lock instead of per thread lock.
-        // Consider the following case, there are threads A and B.
-        // And A attempt to suspend B and B attempt to suspend A.
-        // A and B send signals. And later, signals are delivered to A and B.
-        // In that case, both will be suspended.
-        WordLockHolder locker(globalSuspendLock);
-        if (!m_suspendCount) {
-            // Ideally, we would like to use pthread_sigqueue. It allows us to pass the argument to the signal handler.
-            // But it can be used in a few platforms, like Linux.
-            // Instead, we use Thread* stored in the thread local storage to pass it to the signal handler.
-            targetThread.store(this);
-            int result = pthread_kill(m_handle, SigThreadSuspendResume);
-            if (result)
-                return makeUnexpected(result);
-            sem_wait(&m_semaphoreForSuspendResume);
-            // Release barrier ensures that this operation is always executed after all the above processing is done.
-            m_suspended.store(true, std::memory_order_release);
-        }
-        ++m_suspendCount;
-        return { };
+    if (!m_suspendCount) {
+        // Ideally, we would like to use pthread_sigqueue. It allows us to pass the argument to the signal handler.
+        // But it can be used in a few platforms, like Linux.
+        // Instead, we use Thread* stored in the thread local storage to pass it to the signal handler.
+        targetThread.store(this);
+        int result = pthread_kill(m_handle, SigThreadSuspendResume);
+        if (result)
+            return makeUnexpected(result);
+        sem_wait(&m_semaphoreForSuspendResume);
+        // Release barrier ensures that this operation is always executed after all the above processing is done.
+        m_suspended.store(true, std::memory_order_release);
     }
+    ++m_suspendCount;
+    return { };
 #endif
 }
 
 void Thread::resume()
 {
-    std::lock_guard<std::mutex> locker(m_mutex);
+    // During resume, suspend or resume should not be executed from the other threads.
+    LockHolder locker(globalSuspendLock);
 #if OS(DARWIN)
     thread_resume(m_platformThread);
 #else
-    {
-        // During resume, suspend or resume should not be executed from the other threads.
-        WordLockHolder locker(globalSuspendLock);
-        if (m_suspendCount == 1) {
-            // When allowing SigThreadSuspendResume interrupt in the signal handler by sigsuspend and SigThreadSuspendResume is actually issued,
-            // the signal handler itself will be called once again.
-            // There are several ways to distinguish the handler invocation for suspend and resume.
-            // 1. Use different signal numbers. And check the signal number in the handler.
-            // 2. Use some arguments to distinguish suspend and resume in the handler. If pthread_sigqueue can be used, we can take this.
-            // 3. Use thread local storage with atomic variables in the signal handler.
-            // In this implementaiton, we take (3). suspended flag is used to distinguish it.
-            targetThread.store(this);
-            if (pthread_kill(m_handle, SigThreadSuspendResume) == ESRCH)
-                return;
-            sem_wait(&m_semaphoreForSuspendResume);
-            // Release barrier ensures that this operation is always executed after all the above processing is done.
-            m_suspended.store(false, std::memory_order_release);
-        }
-        --m_suspendCount;
+    if (m_suspendCount == 1) {
+        // When allowing SigThreadSuspendResume interrupt in the signal handler by sigsuspend and SigThreadSuspendResume is actually issued,
+        // the signal handler itself will be called once again.
+        // There are several ways to distinguish the handler invocation for suspend and resume.
+        // 1. Use different signal numbers. And check the signal number in the handler.
+        // 2. Use some arguments to distinguish suspend and resume in the handler. If pthread_sigqueue can be used, we can take this.
+        // 3. Use thread local storage with atomic variables in the signal handler.
+        // In this implementaiton, we take (3). suspended flag is used to distinguish it.
+        targetThread.store(this);
+        if (pthread_kill(m_handle, SigThreadSuspendResume) == ESRCH)
+            return;
+        sem_wait(&m_semaphoreForSuspendResume);
+        // Release barrier ensures that this operation is always executed after all the above processing is done.
+        m_suspended.store(false, std::memory_order_release);
     }
+    --m_suspendCount;
 #endif
 }
 
@@ -426,7 +427,7 @@ static ThreadStateMetadata threadStateMetadata()
 
 size_t Thread::getRegisters(PlatformRegisters& registers)
 {
-    std::lock_guard<std::mutex> locker(m_mutex);
+    LockHolder locker(globalSuspendLock);
 #if OS(DARWIN)
     auto metadata = threadStateMetadata();
     kern_return_t result = thread_get_state(m_platformThread, metadata.flavor, (thread_state_t)&registers, &metadata.userCount);
index ee1c4d4..9ac18bd 100644 (file)
@@ -91,6 +91,7 @@
 #include <process.h>
 #include <windows.h>
 #include <wtf/CurrentTime.h>
+#include <wtf/Lock.h>
 #include <wtf/MainThread.h>
 #include <wtf/MathExtras.h>
 #include <wtf/NeverDestroyed.h>
 
 namespace WTF {
 
+static StaticLock globalSuspendLock;
+
 Thread::Thread()
 {
 }
@@ -243,7 +246,7 @@ void Thread::detach()
 auto Thread::suspend() -> Expected<void, PlatformSuspendError>
 {
     RELEASE_ASSERT_WITH_MESSAGE(id() != currentThread(), "We do not support suspending the current thread itself.");
-    std::lock_guard<std::mutex> locker(m_mutex);
+    LockHolder locker(globalSuspendLock);
     DWORD result = SuspendThread(m_handle);
     if (result != (DWORD)-1)
         return { };
@@ -253,13 +256,13 @@ auto Thread::suspend() -> Expected<void, PlatformSuspendError>
 // During resume, suspend or resume should not be executed from the other threads.
 void Thread::resume()
 {
-    std::lock_guard<std::mutex> locker(m_mutex);
+    LockHolder locker(globalSuspendLock);
     ResumeThread(m_handle);
 }
 
 size_t Thread::getRegisters(PlatformRegisters& registers)
 {
-    std::lock_guard<std::mutex> locker(m_mutex);
+    LockHolder locker(globalSuspendLock);
     registers.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
     GetThreadContext(m_handle, &registers);
     return sizeof(CONTEXT);
index deea4ae..0af91d9 100644 (file)
@@ -1,3 +1,12 @@
+2017-07-19  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        [WTF] Implement WTF::ThreadGroup
+        https://bugs.webkit.org/show_bug.cgi?id=174081
+
+        Reviewed by Mark Lam.
+
+        * page/ResourceUsageThread.h:
+
 2017-07-18  Andy Estes  <aestes@apple.com>
 
         [Xcode] Enable CLANG_WARN_RANGE_LOOP_ANALYSIS
index 824b648..9416c76 100644 (file)
@@ -35,6 +35,7 @@
 #include <wtf/Lock.h>
 #include <wtf/NeverDestroyed.h>
 #include <wtf/Noncopyable.h>
+#include <wtf/Threading.h>
 
 namespace JSC {
 class VM;
index ae3aa2c..70c7894 100644 (file)
@@ -1,3 +1,12 @@
+2017-07-19  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        [WTF] Implement WTF::ThreadGroup
+        https://bugs.webkit.org/show_bug.cgi?id=174081
+
+        Reviewed by Mark Lam.
+
+        * Shared/AsyncRequest.h:
+
 2017-07-18  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         WebDriver: handle invalid selector errors
index 4277616..78b240d 100644 (file)
@@ -27,6 +27,7 @@
 #ifndef AsyncRequest_h
 #define AsyncRequest_h
 
+#include <wtf/Function.h>
 #include <wtf/HashMap.h>
 #include <wtf/RefCounted.h>
 #include <wtf/RefPtr.h>
index 1700133..5872274 100644 (file)
@@ -1,3 +1,18 @@
+2017-07-19  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        [WTF] Implement WTF::ThreadGroup
+        https://bugs.webkit.org/show_bug.cgi?id=174081
+
+        Reviewed by Mark Lam.
+
+        Add WTF::ThreadGroup tests.
+
+        * TestWebKitAPI/CMakeLists.txt:
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WTF/ThreadGroup.cpp: Added.
+        (TestWebKitAPI::testThreadGroup):
+        (TestWebKitAPI::TEST):
+
 2017-07-18  Andy Estes  <aestes@apple.com>
 
         [Xcode] Enable CLANG_WARN_RANGE_LOOP_ANALYSIS
index 0e3e0f6..7e1676f 100644 (file)
@@ -81,6 +81,7 @@ set(TestWTF_SOURCES
     ${TESTWEBKITAPI_DIR}/Tests/WTF/StringOperators.cpp
     ${TESTWEBKITAPI_DIR}/Tests/WTF/StringView.cpp
     ${TESTWEBKITAPI_DIR}/Tests/WTF/TextBreakIterator.cpp
+    ${TESTWEBKITAPI_DIR}/Tests/WTF/ThreadGroup.cpp
     ${TESTWEBKITAPI_DIR}/Tests/WTF/Time.cpp
     ${TESTWEBKITAPI_DIR}/Tests/WTF/UniqueRef.cpp
     ${TESTWEBKITAPI_DIR}/Tests/WTF/Variant.cpp
index 3aa5699..f049007 100644 (file)
                D34E08761E4E42E1005FF14A /* WKWebViewGetContents.mm in Sources */ = {isa = PBXBuildFile; fileRef = D3BE5E341E4CE85E00FD563A /* WKWebViewGetContents.mm */; };
                E1220DCA155B28AA0013E2FC /* MemoryCacheDisableWithinResourceLoadDelegate.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = E1220DC9155B287D0013E2FC /* MemoryCacheDisableWithinResourceLoadDelegate.html */; };
                E194E1BD177E53C7009C4D4E /* StopLoadingFromDidReceiveResponse.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = E194E1BC177E534A009C4D4E /* StopLoadingFromDidReceiveResponse.html */; };
+               E3DEA8111F0A589000CBC2E8 /* ThreadGroup.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E3DEA8101F0A588000CBC2E8 /* ThreadGroup.cpp */; };
                ECA680CE1E68CC0900731D20 /* StringUtilities.mm in Sources */ = {isa = PBXBuildFile; fileRef = ECA680CD1E68CC0900731D20 /* StringUtilities.mm */; };
                F407FE391F1D0DFC0017CF25 /* enormous.svg in Copy Resources */ = {isa = PBXBuildFile; fileRef = F407FE381F1D0DE60017CF25 /* enormous.svg */; };
                F415086D1DA040C50044BE9B /* play-audio-on-click.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F415086C1DA040C10044BE9B /* play-audio-on-click.html */; };
                E194E1BA177E5145009C4D4E /* StopLoadingFromDidReceiveResponse.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = StopLoadingFromDidReceiveResponse.mm; sourceTree = "<group>"; };
                E194E1BC177E534A009C4D4E /* StopLoadingFromDidReceiveResponse.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = StopLoadingFromDidReceiveResponse.html; sourceTree = "<group>"; };
                E19DB9781B32137C00DB38D4 /* NavigatorLanguage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = NavigatorLanguage.mm; sourceTree = "<group>"; };
+               E3DEA8101F0A588000CBC2E8 /* ThreadGroup.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ThreadGroup.cpp; sourceTree = "<group>"; };
                E40019301ACE9B5C001B0A2A /* BloomFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BloomFilter.cpp; sourceTree = "<group>"; };
                E490296714E2E3A4002BEDD1 /* TypingStyleCrash.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TypingStyleCrash.mm; sourceTree = "<group>"; };
                E4A757D3178AEA5B00B5D7A4 /* Deque.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Deque.cpp; sourceTree = "<group>"; };
                                7C74D42D188228F300E5ED57 /* StringView.cpp */,
                                5597F8341D9596C80066BC21 /* SynchronizedFixedQueue.cpp */,
                                9329AA281DE3F81E003ABD07 /* TextBreakIterator.cpp */,
+                               E3DEA8101F0A588000CBC2E8 /* ThreadGroup.cpp */,
                                5311BD5D1EA9490D00525281 /* ThreadMessages.cpp */,
                                0F2C20B71DCD544800542D9E /* Time.cpp */,
                                5C5E633D1D0B67940085A025 /* UniqueRef.cpp */,
                        isa = PBXSourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               E3DEA8111F0A589000CBC2E8 /* ThreadGroup.cpp in Sources */,
                                7C83DE991D0A590C00FEBCF3 /* AtomicString.cpp in Sources */,
                                1ADAD1501D77A9F600212586 /* BlockPtr.mm in Sources */,
                                7C83DE9C1D0A590C00FEBCF3 /* BloomFilter.cpp in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WTF/ThreadGroup.cpp b/Tools/TestWebKitAPI/Tests/WTF/ThreadGroup.cpp
new file mode 100644 (file)
index 0000000..e935c1a
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2017 Yusuke Suzuki <utatane.tea@gmail.com>.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#include <wtf/Condition.h>
+#include <wtf/ThreadGroup.h>
+#include <wtf/Vector.h>
+
+namespace TestWebKitAPI {
+
+enum class Mode { Add, AddCurrentThread };
+static void testThreadGroup(Mode mode)
+{
+    std::shared_ptr<ThreadGroup> threadGroup = ThreadGroup::create();
+    unsigned numberOfThreads = 16;
+    unsigned waitingThreads = 0;
+    bool restarting = false;
+    Lock lock;
+    Condition condition;
+    Condition restartCondition;
+    Vector<RefPtr<Thread>> threads;
+
+    {
+        auto locker = holdLock(lock);
+        for (unsigned i = 0; i < numberOfThreads; ++i) {
+            RefPtr<Thread> thread = Thread::create("ThreadGroupWorker", [&] {
+                auto locker = holdLock(lock);
+                if (mode == Mode::AddCurrentThread)
+                    threadGroup->addCurrentThread();
+                ++waitingThreads;
+                condition.notifyOne();
+                restartCondition.wait(lock, [&] {
+                    return restarting;
+                });
+            });
+            if (mode == Mode::Add)
+                EXPECT_EQ(threadGroup->add(*thread), true);
+            threads.append(thread);
+        }
+
+        condition.wait(lock, [&] {
+            return waitingThreads == numberOfThreads;
+        });
+    }
+
+    {
+        auto threadGroupLocker = holdLock(threadGroup->getLock());
+        EXPECT_EQ(threads.size(), numberOfThreads);
+        EXPECT_EQ(threadGroup->threads(threadGroupLocker).size(), numberOfThreads);
+        {
+            auto locker = holdLock(lock);
+            restarting = true;
+            restartCondition.notifyAll();
+        }
+
+        // While holding ThreadGroup lock, threads do not exit.
+        WTF::sleep(0.1);
+        EXPECT_EQ(threadGroup->threads(threadGroupLocker).size(), numberOfThreads);
+    }
+    {
+        for (auto& thread : threads)
+            thread->waitForCompletion();
+
+        auto threadGroupLocker = holdLock(threadGroup->getLock());
+        EXPECT_EQ(threadGroup->threads(threadGroupLocker).size(), 0u);
+    }
+}
+
+TEST(WTF, ThreadGroupAdd)
+{
+    testThreadGroup(Mode::Add);
+}
+
+TEST(WTF, ThreadGroupAddCurrentThread)
+{
+    testThreadGroup(Mode::AddCurrentThread);
+}
+
+TEST(WTF, ThreadGroupDoNotAddDeadThread)
+{
+    std::shared_ptr<ThreadGroup> threadGroup = ThreadGroup::create();
+    RefPtr<Thread> thread = Thread::create("ThreadGroupWorker", [&] { });
+    thread->waitForCompletion();
+    EXPECT_EQ(threadGroup->add(*thread), false);
+
+    auto threadGroupLocker = holdLock(threadGroup->getLock());
+    EXPECT_EQ(threadGroup->threads(threadGroupLocker).size(), 0u);
+}
+
+TEST(WTF, ThreadGroupAddDuplicateThreads)
+{
+    bool restarting = false;
+    Lock lock;
+    Condition restartCondition;
+    std::shared_ptr<ThreadGroup> threadGroup = ThreadGroup::create();
+    RefPtr<Thread> thread = Thread::create("ThreadGroupWorker", [&] {
+        auto locker = holdLock(lock);
+        restartCondition.wait(lock, [&] {
+            return restarting;
+        });
+    });
+    EXPECT_EQ(threadGroup->add(*thread), true);
+    EXPECT_EQ(threadGroup->add(*thread), true);
+
+    {
+        auto threadGroupLocker = holdLock(threadGroup->getLock());
+        EXPECT_EQ(threadGroup->threads(threadGroupLocker).size(), 1u);
+    }
+
+    {
+        auto locker = holdLock(lock);
+        restarting = true;
+        restartCondition.notifyAll();
+    }
+    thread->waitForCompletion();
+    {
+        auto threadGroupLocker = holdLock(threadGroup->getLock());
+        EXPECT_EQ(threadGroup->threads(threadGroupLocker).size(), 0u);
+    }
+}
+
+} // namespace TestWebKitAPI