forked from mirrors/gecko-dev
		
	Because DelayedRunnables are fire-and-forget, there is no way for a targeted EventTarget to clean them up on shutdown. Thus if a timer fires after EventTarget shutdown it will fail to dispatch the timer event, and avoid releasing the timer callback because it's not on the targeted thread. This causes a leak as there is a ref-cycle between nsTimerImpl::mCallback and DelayedRunnable::mTimer. This patch adds nsIDelayedRunnableObserver for a target to observe which DelayedRunnables are relying on their timer to run them. This allows the target to schedule a shutdown task to cancel those timers and release the runnables on the target thread. Supported DelayedRunnable targets with this patch are TaskQueues, eventqueue-based nsThreads and XPCOMThreadWrappers that wrap a supported nsThread. An assertion makes sure at runtime that future new uses of DelayedRunnable target nsIDelayedRunnableObserver-supported event targets. Differential Revision: https://phabricator.services.mozilla.com/D109781
		
			
				
	
	
		
			444 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			444 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | 
						|
/* 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 nsThread_h__
 | 
						|
#define nsThread_h__
 | 
						|
 | 
						|
#include "MainThreadUtils.h"
 | 
						|
#include "mozilla/AlreadyAddRefed.h"
 | 
						|
#include "mozilla/Atomics.h"
 | 
						|
#include "mozilla/Attributes.h"
 | 
						|
#include "mozilla/EventQueue.h"
 | 
						|
#include "mozilla/LinkedList.h"
 | 
						|
#include "mozilla/MemoryReporting.h"
 | 
						|
#include "mozilla/Mutex.h"
 | 
						|
#include "mozilla/NotNull.h"
 | 
						|
#include "mozilla/PerformanceCounter.h"
 | 
						|
#include "mozilla/RefPtr.h"
 | 
						|
#include "mozilla/TaskDispatcher.h"
 | 
						|
#include "mozilla/TimeStamp.h"
 | 
						|
#include "mozilla/UniquePtr.h"
 | 
						|
#include "nsIDelayedRunnableObserver.h"
 | 
						|
#include "nsIDirectTaskDispatcher.h"
 | 
						|
#include "nsIEventTarget.h"
 | 
						|
#include "nsISerialEventTarget.h"
 | 
						|
#include "nsISupportsPriority.h"
 | 
						|
#include "nsIThread.h"
 | 
						|
#include "nsIThreadInternal.h"
 | 
						|
#include "nsTArray.h"
 | 
						|
 | 
						|
namespace mozilla {
 | 
						|
class CycleCollectedJSContext;
 | 
						|
class DelayedRunnable;
 | 
						|
class SynchronizedEventQueue;
 | 
						|
class ThreadEventQueue;
 | 
						|
class ThreadEventTarget;
 | 
						|
 | 
						|
template <typename T, size_t Length>
 | 
						|
class Array;
 | 
						|
}  // namespace mozilla
 | 
						|
 | 
						|
using mozilla::NotNull;
 | 
						|
 | 
						|
class nsIRunnable;
 | 
						|
class nsLocalExecutionRecord;
 | 
						|
class nsThreadEnumerator;
 | 
						|
 | 
						|
// See https://www.w3.org/TR/longtasks
 | 
						|
#define LONGTASK_BUSY_WINDOW_MS 50
 | 
						|
 | 
						|
// A class for managing performance counter state.
 | 
						|
namespace mozilla {
 | 
						|
class PerformanceCounterState {
 | 
						|
 public:
 | 
						|
  explicit PerformanceCounterState(const uint32_t& aNestedEventLoopDepthRef,
 | 
						|
                                   bool aIsMainThread)
 | 
						|
      : mNestedEventLoopDepth(aNestedEventLoopDepthRef),
 | 
						|
        mIsMainThread(aIsMainThread),
 | 
						|
        // Does it really make sense to initialize these to "now" when we
 | 
						|
        // haven't run any tasks?
 | 
						|
        mLastLongTaskEnd(TimeStamp::Now()),
 | 
						|
        mLastLongNonIdleTaskEnd(mLastLongTaskEnd) {}
 | 
						|
 | 
						|
  class Snapshot {
 | 
						|
   public:
 | 
						|
    Snapshot(uint32_t aOldEventLoopDepth, PerformanceCounter* aCounter,
 | 
						|
             bool aOldIsIdleRunnable)
 | 
						|
        : mOldEventLoopDepth(aOldEventLoopDepth),
 | 
						|
          mOldPerformanceCounter(aCounter),
 | 
						|
          mOldIsIdleRunnable(aOldIsIdleRunnable) {}
 | 
						|
 | 
						|
    Snapshot(const Snapshot&) = default;
 | 
						|
    Snapshot(Snapshot&&) = default;
 | 
						|
 | 
						|
   private:
 | 
						|
    friend class PerformanceCounterState;
 | 
						|
 | 
						|
    const uint32_t mOldEventLoopDepth;
 | 
						|
    // Non-const so we can move out of it and avoid the extra refcounting.
 | 
						|
    RefPtr<PerformanceCounter> mOldPerformanceCounter;
 | 
						|
    const bool mOldIsIdleRunnable;
 | 
						|
  };
 | 
						|
 | 
						|
  // Notification that a runnable is about to run.  This captures a snapshot of
 | 
						|
  // our current state before we reset to prepare for the new runnable.  This
 | 
						|
  // muast be called after mNestedEventLoopDepth has been incremented for the
 | 
						|
  // runnable execution.  The performance counter passed in should be the one
 | 
						|
  // for the relevant runnable and may be null.  aIsIdleRunnable should be true
 | 
						|
  // if and only if the runnable has idle priority.
 | 
						|
  Snapshot RunnableWillRun(PerformanceCounter* Counter, TimeStamp aNow,
 | 
						|
                           bool aIsIdleRunnable);
 | 
						|
 | 
						|
  // Notification that a runnable finished executing.  This must be passed the
 | 
						|
  // snapshot that RunnableWillRun returned for the same runnable.  This must be
 | 
						|
  // called before mNestedEventLoopDepth is decremented after the runnable's
 | 
						|
  // execution.
 | 
						|
  void RunnableDidRun(Snapshot&& aSnapshot);
 | 
						|
 | 
						|
  const TimeStamp& LastLongTaskEnd() const { return mLastLongTaskEnd; }
 | 
						|
  const TimeStamp& LastLongNonIdleTaskEnd() const {
 | 
						|
    return mLastLongNonIdleTaskEnd;
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  // Called to report accumulated time, as needed, when we're about to run a
 | 
						|
  // runnable or just finished running one.
 | 
						|
  void MaybeReportAccumulatedTime(TimeStamp aNow);
 | 
						|
 | 
						|
  // Whether the runnable we are about to run, or just ran, is a nested
 | 
						|
  // runnable, in the sense that there is some other runnable up the stack
 | 
						|
  // spinning the event loop.  This must be called before we change our
 | 
						|
  // mCurrentEventLoopDepth (when about to run a new event) or after we restore
 | 
						|
  // it (after we ran one).
 | 
						|
  bool IsNestedRunnable() const {
 | 
						|
    return mNestedEventLoopDepth > mCurrentEventLoopDepth;
 | 
						|
  }
 | 
						|
 | 
						|
  // The event loop depth of the currently running runnable.  Set to the max
 | 
						|
  // value of a uint32_t when there is no runnable running, so when starting to
 | 
						|
  // run a toplevel (not nested) runnable IsNestedRunnable() will test false.
 | 
						|
  uint32_t mCurrentEventLoopDepth = std::numeric_limits<uint32_t>::max();
 | 
						|
 | 
						|
  // A reference to the nsThread's mNestedEventLoopDepth, so we can
 | 
						|
  // see what it is right now.
 | 
						|
  const uint32_t& mNestedEventLoopDepth;
 | 
						|
 | 
						|
  // A boolean that indicates whether the currently running runnable is an idle
 | 
						|
  // runnable.  Only has a useful value between RunnableWillRun() being called
 | 
						|
  // and RunnableDidRun() returning.
 | 
						|
  bool mCurrentRunnableIsIdleRunnable = false;
 | 
						|
 | 
						|
  // Whether we're attached to the mainthread nsThread.
 | 
						|
  const bool mIsMainThread;
 | 
						|
 | 
						|
  // The timestamp from which time to be accounted for should be measured.  This
 | 
						|
  // can be the start of a runnable running or the end of a nested runnable
 | 
						|
  // running.
 | 
						|
  TimeStamp mCurrentTimeSliceStart;
 | 
						|
 | 
						|
  // Information about when long tasks last ended.
 | 
						|
  TimeStamp mLastLongTaskEnd;
 | 
						|
  TimeStamp mLastLongNonIdleTaskEnd;
 | 
						|
 | 
						|
  // The performance counter to use for accumulating the runtime of
 | 
						|
  // the currently running event.  May be null, in which case the
 | 
						|
  // event's running time should not be accounted to any performance
 | 
						|
  // counters.
 | 
						|
  RefPtr<PerformanceCounter> mCurrentPerformanceCounter;
 | 
						|
};
 | 
						|
}  // namespace mozilla
 | 
						|
 | 
						|
// A native thread
 | 
						|
class nsThread : public nsIThreadInternal,
 | 
						|
                 public nsISupportsPriority,
 | 
						|
                 public nsIDelayedRunnableObserver,
 | 
						|
                 public nsIDirectTaskDispatcher,
 | 
						|
                 private mozilla::LinkedListElement<nsThread> {
 | 
						|
  friend mozilla::LinkedList<nsThread>;
 | 
						|
  friend mozilla::LinkedListElement<nsThread>;
 | 
						|
 | 
						|
 public:
 | 
						|
  NS_DECL_THREADSAFE_ISUPPORTS
 | 
						|
  NS_DECL_NSIEVENTTARGET_FULL
 | 
						|
  NS_DECL_NSITHREAD
 | 
						|
  NS_DECL_NSITHREADINTERNAL
 | 
						|
  NS_DECL_NSISUPPORTSPRIORITY
 | 
						|
  NS_DECL_NSIDIRECTTASKDISPATCHER
 | 
						|
 | 
						|
  enum MainThreadFlag { MAIN_THREAD, NOT_MAIN_THREAD };
 | 
						|
 | 
						|
  nsThread(NotNull<mozilla::SynchronizedEventQueue*> aQueue,
 | 
						|
           MainThreadFlag aMainThread, uint32_t aStackSize);
 | 
						|
 | 
						|
 private:
 | 
						|
  nsThread();
 | 
						|
 | 
						|
 public:
 | 
						|
  // Initialize this as a named wrapper for a new PRThread.
 | 
						|
  nsresult Init(const nsACString& aName);
 | 
						|
 | 
						|
  // Initialize this as a wrapper for the current PRThread.
 | 
						|
  nsresult InitCurrentThread();
 | 
						|
 | 
						|
 private:
 | 
						|
  // Initializes the mThreadId and stack base/size members, and adds the thread
 | 
						|
  // to the ThreadList().
 | 
						|
  void InitCommon();
 | 
						|
 | 
						|
 public:
 | 
						|
  // The PRThread corresponding to this thread.
 | 
						|
  PRThread* GetPRThread() const { return mThread; }
 | 
						|
 | 
						|
  const void* StackBase() const { return mStackBase; }
 | 
						|
  size_t StackSize() const { return mStackSize; }
 | 
						|
 | 
						|
  uint32_t ThreadId() const { return mThreadId; }
 | 
						|
 | 
						|
  // If this flag is true, then the nsThread was created using
 | 
						|
  // nsIThreadManager::NewThread.
 | 
						|
  bool ShutdownRequired() { return mShutdownRequired; }
 | 
						|
 | 
						|
  // Lets GetRunningEventDelay() determine if the pool this is part
 | 
						|
  // of has an unstarted thread
 | 
						|
  void SetPoolThreadFreePtr(mozilla::Atomic<bool, mozilla::Relaxed>* aPtr) {
 | 
						|
    mIsAPoolThreadFree = aPtr;
 | 
						|
  }
 | 
						|
 | 
						|
  void SetScriptObserver(mozilla::CycleCollectedJSContext* aScriptObserver);
 | 
						|
 | 
						|
  uint32_t RecursionDepth() const;
 | 
						|
 | 
						|
  void ShutdownComplete(NotNull<struct nsThreadShutdownContext*> aContext);
 | 
						|
 | 
						|
  void WaitForAllAsynchronousShutdowns();
 | 
						|
 | 
						|
  static const uint32_t kRunnableNameBufSize = 1000;
 | 
						|
  static mozilla::Array<char, kRunnableNameBufSize> sMainThreadRunnableName;
 | 
						|
 | 
						|
  mozilla::SynchronizedEventQueue* EventQueue() { return mEvents.get(); }
 | 
						|
 | 
						|
  bool ShuttingDown() const { return mShutdownContext != nullptr; }
 | 
						|
 | 
						|
  static bool GetLabeledRunnableName(nsIRunnable* aEvent, nsACString& aName,
 | 
						|
                                     mozilla::EventQueuePriority aPriority);
 | 
						|
 | 
						|
  virtual mozilla::PerformanceCounter* GetPerformanceCounter(
 | 
						|
      nsIRunnable* aEvent) const;
 | 
						|
 | 
						|
  static mozilla::PerformanceCounter* GetPerformanceCounterBase(
 | 
						|
      nsIRunnable* aEvent);
 | 
						|
 | 
						|
  size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 | 
						|
 | 
						|
  // Returns the size of this object, its PRThread, and its shutdown contexts,
 | 
						|
  // but excluding its event queues.
 | 
						|
  size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 | 
						|
 | 
						|
  size_t SizeOfEventQueues(mozilla::MallocSizeOf aMallocSizeOf) const;
 | 
						|
 | 
						|
  static nsThreadEnumerator Enumerate();
 | 
						|
 | 
						|
  // When entering local execution mode a new event queue is created and used as
 | 
						|
  // an event source. This queue is only accessible through an
 | 
						|
  // nsLocalExecutionGuard constructed from the nsLocalExecutionRecord returned
 | 
						|
  // by this function, effectively restricting the events that get run while in
 | 
						|
  // local execution mode to those dispatched by the owner of the guard object.
 | 
						|
  //
 | 
						|
  // Local execution is not nestable. When the nsLocalExecutionGuard is
 | 
						|
  // destructed, the thread exits the local execution mode.
 | 
						|
  //
 | 
						|
  // Note that code run in local execution mode is not considered a task in the
 | 
						|
  // spec sense. Events from the local queue are considered part of the
 | 
						|
  // enclosing task and as such do not trigger profiling hooks, observer
 | 
						|
  // notifications, etc.
 | 
						|
  nsLocalExecutionRecord EnterLocalExecution();
 | 
						|
 | 
						|
  void SetUseHangMonitor(bool aValue) {
 | 
						|
    MOZ_ASSERT(IsOnCurrentThread());
 | 
						|
    mUseHangMonitor = aValue;
 | 
						|
  }
 | 
						|
 | 
						|
  void OnDelayedRunnableCreated(mozilla::DelayedRunnable* aRunnable) override;
 | 
						|
  void OnDelayedRunnableScheduled(mozilla::DelayedRunnable* aRunnable) override;
 | 
						|
  void OnDelayedRunnableRan(mozilla::DelayedRunnable* aRunnable) override;
 | 
						|
 | 
						|
 private:
 | 
						|
  void DoMainThreadSpecificProcessing() const;
 | 
						|
 | 
						|
 protected:
 | 
						|
  friend class nsThreadShutdownEvent;
 | 
						|
 | 
						|
  friend class nsThreadEnumerator;
 | 
						|
 | 
						|
  virtual ~nsThread();
 | 
						|
 | 
						|
  static void ThreadFunc(void* aArg);
 | 
						|
 | 
						|
  // Helper
 | 
						|
  already_AddRefed<nsIThreadObserver> GetObserver() {
 | 
						|
    nsIThreadObserver* obs;
 | 
						|
    nsThread::GetObserver(&obs);
 | 
						|
    return already_AddRefed<nsIThreadObserver>(obs);
 | 
						|
  }
 | 
						|
 | 
						|
  struct nsThreadShutdownContext* ShutdownInternal(bool aSync);
 | 
						|
 | 
						|
  friend class nsThreadManager;
 | 
						|
  friend class nsThreadPool;
 | 
						|
 | 
						|
  static mozilla::OffTheBooksMutex& ThreadListMutex();
 | 
						|
  static mozilla::LinkedList<nsThread>& ThreadList();
 | 
						|
  static void ClearThreadList();
 | 
						|
 | 
						|
  void AddToThreadList();
 | 
						|
  void MaybeRemoveFromThreadList();
 | 
						|
 | 
						|
  // Whether or not these members have a value determines whether the nsThread
 | 
						|
  // is treated as a full XPCOM thread or as a thin wrapper.
 | 
						|
  //
 | 
						|
  // For full nsThreads, they will always contain valid pointers. For thin
 | 
						|
  // wrappers around non-XPCOM threads, they will be null, and event dispatch
 | 
						|
  // methods which rely on them will fail (and assert) if called.
 | 
						|
  RefPtr<mozilla::SynchronizedEventQueue> mEvents;
 | 
						|
  RefPtr<mozilla::ThreadEventTarget> mEventTarget;
 | 
						|
 | 
						|
  // The shutdown contexts for any other threads we've asked to shut down.
 | 
						|
  using ShutdownContexts =
 | 
						|
      nsTArray<mozilla::UniquePtr<struct nsThreadShutdownContext>>;
 | 
						|
 | 
						|
  // Helper for finding a ShutdownContext in the contexts array.
 | 
						|
  struct ShutdownContextsComp {
 | 
						|
    bool Equals(const ShutdownContexts::elem_type& a,
 | 
						|
                const ShutdownContexts::elem_type::Pointer b) const;
 | 
						|
  };
 | 
						|
 | 
						|
  ShutdownContexts mRequestedShutdownContexts;
 | 
						|
  // The shutdown context for ourselves.
 | 
						|
  struct nsThreadShutdownContext* mShutdownContext;
 | 
						|
 | 
						|
  mozilla::CycleCollectedJSContext* mScriptObserver;
 | 
						|
 | 
						|
  void* mStackBase = nullptr;
 | 
						|
  uint32_t mStackSize;
 | 
						|
  uint32_t mThreadId;
 | 
						|
 | 
						|
  uint32_t mNestedEventLoopDepth;
 | 
						|
 | 
						|
  mozilla::Atomic<bool> mShutdownRequired;
 | 
						|
 | 
						|
  int8_t mPriority;
 | 
						|
 | 
						|
  const bool mIsMainThread;
 | 
						|
  bool mUseHangMonitor;
 | 
						|
  mozilla::Atomic<bool, mozilla::Relaxed>* mIsAPoolThreadFree;
 | 
						|
 | 
						|
  // Set to true if this thread creates a JSRuntime.
 | 
						|
  bool mCanInvokeJS;
 | 
						|
 | 
						|
  bool mHasTLSEntry = false;
 | 
						|
 | 
						|
  // The time the currently running event spent in event queues, and
 | 
						|
  // when it started running.  If no event is running, they are
 | 
						|
  // TimeDuration() & TimeStamp().
 | 
						|
  mozilla::TimeDuration mLastEventDelay;
 | 
						|
  mozilla::TimeStamp mLastEventStart;
 | 
						|
 | 
						|
#ifdef EARLY_BETA_OR_EARLIER
 | 
						|
  nsCString mNameForWakeupTelemetry;
 | 
						|
  mozilla::TimeStamp mLastWakeupCheckTime;
 | 
						|
  uint32_t mWakeupCount = 0;
 | 
						|
#endif
 | 
						|
 | 
						|
  mozilla::PerformanceCounterState mPerformanceCounterState;
 | 
						|
 | 
						|
  bool mIsInLocalExecutionMode = false;
 | 
						|
 | 
						|
  mozilla::SimpleTaskQueue mDirectTasks;
 | 
						|
};
 | 
						|
 | 
						|
struct nsThreadShutdownContext {
 | 
						|
  nsThreadShutdownContext(NotNull<nsThread*> aTerminatingThread,
 | 
						|
                          NotNull<nsThread*> aJoiningThread,
 | 
						|
                          bool aAwaitingShutdownAck)
 | 
						|
      : mTerminatingThread(aTerminatingThread),
 | 
						|
        mTerminatingPRThread(aTerminatingThread->GetPRThread()),
 | 
						|
        mJoiningThread(aJoiningThread),
 | 
						|
        mAwaitingShutdownAck(aAwaitingShutdownAck),
 | 
						|
        mIsMainThreadJoining(NS_IsMainThread()) {
 | 
						|
    MOZ_COUNT_CTOR(nsThreadShutdownContext);
 | 
						|
  }
 | 
						|
  MOZ_COUNTED_DTOR(nsThreadShutdownContext)
 | 
						|
 | 
						|
  // NB: This will be the last reference.
 | 
						|
  NotNull<RefPtr<nsThread>> mTerminatingThread;
 | 
						|
  PRThread* const mTerminatingPRThread;
 | 
						|
  NotNull<nsThread*> MOZ_UNSAFE_REF(
 | 
						|
      "Thread manager is holding reference to joining thread") mJoiningThread;
 | 
						|
  bool mAwaitingShutdownAck;
 | 
						|
  bool mIsMainThreadJoining;
 | 
						|
};
 | 
						|
 | 
						|
// This RAII class controls the duration of the associated nsThread's local
 | 
						|
// execution mode and provides access to the local event target. (See
 | 
						|
// nsThread::EnterLocalExecution() for details.) It is constructed from an
 | 
						|
// nsLocalExecutionRecord, which can only be constructed by nsThread.
 | 
						|
class MOZ_RAII nsLocalExecutionGuard final {
 | 
						|
 public:
 | 
						|
  MOZ_IMPLICIT nsLocalExecutionGuard(
 | 
						|
      nsLocalExecutionRecord&& aLocalExecutionRecord);
 | 
						|
  nsLocalExecutionGuard(const nsLocalExecutionGuard&) = delete;
 | 
						|
  nsLocalExecutionGuard(nsLocalExecutionGuard&&) = delete;
 | 
						|
  ~nsLocalExecutionGuard();
 | 
						|
 | 
						|
  nsCOMPtr<nsISerialEventTarget> GetEventTarget() const {
 | 
						|
    return mLocalEventTarget;
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  mozilla::SynchronizedEventQueue& mEventQueueStack;
 | 
						|
  nsCOMPtr<nsISerialEventTarget> mLocalEventTarget;
 | 
						|
  bool& mLocalExecutionFlag;
 | 
						|
};
 | 
						|
 | 
						|
class MOZ_TEMPORARY_CLASS nsLocalExecutionRecord final {
 | 
						|
 private:
 | 
						|
  friend class nsThread;
 | 
						|
  friend class nsLocalExecutionGuard;
 | 
						|
 | 
						|
  nsLocalExecutionRecord(mozilla::SynchronizedEventQueue& aEventQueueStack,
 | 
						|
                         bool& aLocalExecutionFlag)
 | 
						|
      : mEventQueueStack(aEventQueueStack),
 | 
						|
        mLocalExecutionFlag(aLocalExecutionFlag) {}
 | 
						|
 | 
						|
  nsLocalExecutionRecord(nsLocalExecutionRecord&&) = default;
 | 
						|
 | 
						|
 public:
 | 
						|
  nsLocalExecutionRecord(const nsLocalExecutionRecord&) = delete;
 | 
						|
 | 
						|
 private:
 | 
						|
  mozilla::SynchronizedEventQueue& mEventQueueStack;
 | 
						|
  bool& mLocalExecutionFlag;
 | 
						|
};
 | 
						|
 | 
						|
class MOZ_STACK_CLASS nsThreadEnumerator final {
 | 
						|
 public:
 | 
						|
  nsThreadEnumerator() = default;
 | 
						|
 | 
						|
  auto begin() { return nsThread::ThreadList().begin(); }
 | 
						|
  auto end() { return nsThread::ThreadList().end(); }
 | 
						|
 | 
						|
 private:
 | 
						|
  mozilla::OffTheBooksMutexAutoLock mMal{nsThread::ThreadListMutex()};
 | 
						|
};
 | 
						|
 | 
						|
#if defined(XP_UNIX) && !defined(ANDROID) && !defined(DEBUG) && HAVE_UALARM && \
 | 
						|
    defined(_GNU_SOURCE)
 | 
						|
#  define MOZ_CANARY
 | 
						|
 | 
						|
extern int sCanaryOutputFD;
 | 
						|
#endif
 | 
						|
 | 
						|
#endif  // nsThread_h__
 |