/* * Copyright (C) 2016-2020 Apple Inc. All rights reserved. * * 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 #include #include #include #include #include namespace WTF { // Often, we create threads that have this as their body: // // for (;;) { // { // Locker locker { m_lock }; // for (;;) { // [1] stuff that could break, return, or fall through; // m_condition.wait(m_lock); // } // } // // [2] do work; // } // // When we do this, we don't always do a good job of managing this thread's lifetime, which may lead // to this thread sitting around even when it is not needed. // // AutomaticThread is here to help you in these situations. It encapsulates a lock, a condition // variable, and a thread. It will automatically shut the thread down after a timeout of inactivity. // You use AutomaticThread by subclassing it, and put any state that is needed between [1] and [2] // in the subclass. // // The terminology we use is: // // [1] PollResult AutomaticThread::poll() // [2] WorkResult AutomaticThread::work() // // Note that poll() and work() may not be called on the same thread every time, since this will shut // down the thread as necessary. This is legal since m_condition.wait(m_lock) can drop the lock, and // so there is no reason to keep the thread around. class AutomaticThread; class AutomaticThreadCondition : public ThreadSafeRefCounted { public: static WTF_EXPORT_PRIVATE Ref create(); WTF_EXPORT_PRIVATE ~AutomaticThreadCondition(); WTF_EXPORT_PRIVATE void notifyOne(const AbstractLocker&); WTF_EXPORT_PRIVATE void notifyAll(const AbstractLocker&); // You can reuse this condition for other things, just as you would any other condition. // However, since conflating conditions could lead to thundering herd, it's best to avoid it. // One known-good case for one-true-condition is when the communication involves just two // threads. In such cases, the thread doing the notifyAll() can wake up at most one thread - // its partner. WTF_EXPORT_PRIVATE void wait(Lock& lock) WTF_REQUIRES_LOCK(lock); WTF_EXPORT_PRIVATE bool waitFor(Lock& lock, Seconds) WTF_REQUIRES_LOCK(lock); private: friend class AutomaticThread; WTF_EXPORT_PRIVATE AutomaticThreadCondition(); void add(const AbstractLocker&, AutomaticThread*); void remove(const AbstractLocker&, AutomaticThread*); bool contains(const AbstractLocker&, AutomaticThread*); Condition m_condition; Vector m_threads; }; class WTF_EXPORT_PRIVATE AutomaticThread : public ThreadSafeRefCounted { public: // Note that if you drop all of your references to an AutomaticThread then as soon as there is a // timeout during which it doesn't get woken up, it will simply die on its own. This is a // permanent kind of death where the AutomaticThread object goes away, rather than the temporary // kind of death where AutomaticThread lives but its underlying thread dies. All you have to do // to prevent permanent death is keep a ref to AutomaticThread. At time of writing, every user of // AutomaticThread keeps a ref to it and does join() as part of the shutdown process, so only the // temporary kind of automatic death happens in practice. We keep the permanent death feature // because it leads to an easy-to-understand reference counting discipline (AutomaticThread holds // strong ref to AutomaticThreadCondition and the underlying thread holds a strong ref to // AutomaticThread). virtual ~AutomaticThread(); // Sometimes it's possible to optimize for the case that there is no underlying thread. bool hasUnderlyingThread(const AbstractLocker&) const { return m_hasUnderlyingThread; } // This attempts to quickly stop the thread. This will succeed if the thread happens to not be // running. Returns true if the thread has been stopped. A good idiom for stopping your automatic // thread is to first try this, and if that doesn't work, to tell the thread using your own // mechanism (set some flag and then notify the condition). bool tryStop(const AbstractLocker&); bool isWaiting(const AbstractLocker&); bool notify(const AbstractLocker&); void join(); virtual const char* name() const { return "WTF::AutomaticThread"; } protected: // This logically creates the thread, but in reality the thread won't be created until someone // calls AutomaticThreadCondition::notifyOne() or notifyAll(). AutomaticThread(const AbstractLocker&, Box, Ref&&, Seconds timeout = 10_s); AutomaticThread(const AbstractLocker&, Box, Ref&&, ThreadType, Seconds timeout = 10_s); // To understand PollResult and WorkResult, imagine that poll() and work() are being called like // so: // // void AutomaticThread::runThread() // { // for (;;) { // { // Locker locker { m_lock }; // for (;;) { // PollResult result = poll(); // if (result == PollResult::Work) // break; // if (result == PollResult::Stop) // return; // RELEASE_ASSERT(result == PollResult::Wait); // m_condition.wait(m_lock); // } // } // // WorkResult result = work(); // if (result == WorkResult::Stop) // return; // RELEASE_ASSERT(result == WorkResult::Continue); // } // } enum class PollResult { Work, Stop, Wait }; virtual PollResult poll(const AbstractLocker&) = 0; enum class WorkResult { Continue, Stop }; virtual WorkResult work() = 0; // It's sometimes useful to allocate resources while the thread is running, and to destroy them // when the thread dies. These methods let you do this. You can override these methods, and you // can be sure that the default ones don't do anything (so you don't need a super call). virtual void threadDidStart(); virtual void threadIsStopping(const AbstractLocker&); // Control whether this automatic thread should sleep when timeout happens. // By overriding this function, we can customize how automatic threads will sleep. // For example, when you have thread pool, you can decrease active threads moderately. virtual bool shouldSleep(const AbstractLocker&) { return true; } private: friend class AutomaticThreadCondition; void start(const AbstractLocker&); Box m_lock; Ref m_condition; Seconds m_timeout; ThreadType m_threadType { ThreadType::Unknown }; bool m_isRunning { true }; bool m_isWaiting { false }; bool m_hasUnderlyingThread { false }; Condition m_waitCondition; Condition m_isRunningCondition; }; } // namespace WTF using WTF::AutomaticThread; using WTF::AutomaticThreadCondition;