Bug 1755481: correct documentation of `nsIClipboard::getData`. r=mccr8
[gecko.git] / xpcom / threads / TimerThread.cpp
bloba7b66370e9b5301c87c049746c7c31c49548afcc
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsTimerImpl.h"
8 #include "TimerThread.h"
10 #include "GeckoProfiler.h"
11 #include "nsThreadUtils.h"
12 #include "pratom.h"
14 #include "nsIObserverService.h"
15 #include "mozilla/Services.h"
16 #include "mozilla/ChaosMode.h"
17 #include "mozilla/ArenaAllocator.h"
18 #include "mozilla/ArrayUtils.h"
19 #include "mozilla/BinarySearch.h"
20 #include "mozilla/OperatorNewExtensions.h"
21 #include "mozilla/StaticPrefs_timer.h"
23 #include <math.h>
25 using namespace mozilla;
27 NS_IMPL_ISUPPORTS_INHERITED(TimerThread, Runnable, nsIObserver)
29 TimerThread::TimerThread()
30 : Runnable("TimerThread"),
31 mInitialized(false),
32 mMonitor("TimerThread.mMonitor"),
33 mShutdown(false),
34 mWaiting(false),
35 mNotified(false),
36 mSleeping(false),
37 mAllowedEarlyFiringMicroseconds(0) {}
39 TimerThread::~TimerThread() {
40 mThread = nullptr;
42 NS_ASSERTION(mTimers.IsEmpty(), "Timers remain in TimerThread::~TimerThread");
45 nsresult TimerThread::InitLocks() { return NS_OK; }
47 namespace {
49 class TimerObserverRunnable : public Runnable {
50 public:
51 explicit TimerObserverRunnable(nsIObserver* aObserver)
52 : mozilla::Runnable("TimerObserverRunnable"), mObserver(aObserver) {}
54 NS_DECL_NSIRUNNABLE
56 private:
57 nsCOMPtr<nsIObserver> mObserver;
60 NS_IMETHODIMP
61 TimerObserverRunnable::Run() {
62 nsCOMPtr<nsIObserverService> observerService =
63 mozilla::services::GetObserverService();
64 if (observerService) {
65 observerService->AddObserver(mObserver, "sleep_notification", false);
66 observerService->AddObserver(mObserver, "wake_notification", false);
67 observerService->AddObserver(mObserver, "suspend_process_notification",
68 false);
69 observerService->AddObserver(mObserver, "resume_process_notification",
70 false);
72 return NS_OK;
75 } // namespace
77 namespace {
79 // TimerEventAllocator is a thread-safe allocator used only for nsTimerEvents.
80 // It's needed to avoid contention over the default allocator lock when
81 // firing timer events (see bug 733277). The thread-safety is required because
82 // nsTimerEvent objects are allocated on the timer thread, and freed on another
83 // thread. Because TimerEventAllocator has its own lock, contention over that
84 // lock is limited to the allocation and deallocation of nsTimerEvent objects.
86 // Because this is layered over ArenaAllocator, it never shrinks -- even
87 // "freed" nsTimerEvents aren't truly freed, they're just put onto a free-list
88 // for later recycling. So the amount of memory consumed will always be equal
89 // to the high-water mark consumption. But nsTimerEvents are small and it's
90 // unusual to have more than a few hundred of them, so this shouldn't be a
91 // problem in practice.
93 class TimerEventAllocator {
94 private:
95 struct FreeEntry {
96 FreeEntry* mNext;
99 ArenaAllocator<4096> mPool;
100 FreeEntry* mFirstFree;
101 mozilla::Monitor mMonitor;
103 public:
104 TimerEventAllocator()
105 : mPool(), mFirstFree(nullptr), mMonitor("TimerEventAllocator") {}
107 ~TimerEventAllocator() = default;
109 void* Alloc(size_t aSize);
110 void Free(void* aPtr);
113 } // namespace
115 // This is a nsICancelableRunnable because we can dispatch it to Workers and
116 // those can be shut down at any time, and in these cases, Cancel() is called
117 // instead of Run().
118 class nsTimerEvent final : public CancelableRunnable {
119 public:
120 NS_IMETHOD Run() override;
122 nsresult Cancel() override {
123 mTimer->Cancel();
124 return NS_OK;
127 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
128 NS_IMETHOD GetName(nsACString& aName) override;
129 #endif
131 explicit nsTimerEvent(already_AddRefed<nsTimerImpl> aTimer,
132 ProfilerThreadId aTimerThreadId)
133 : mozilla::CancelableRunnable("nsTimerEvent"),
134 mTimer(aTimer),
135 mGeneration(mTimer->GetGeneration()),
136 mTimerThreadId(aTimerThreadId) {
137 // Note: We override operator new for this class, and the override is
138 // fallible!
139 sAllocatorUsers++;
141 if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug) ||
142 profiler_thread_is_being_profiled_for_markers(mTimerThreadId)) {
143 mInitTime = TimeStamp::Now();
147 static void Init();
148 static void Shutdown();
149 static void DeleteAllocatorIfNeeded();
151 static void* operator new(size_t aSize) noexcept(true) {
152 return sAllocator->Alloc(aSize);
154 void operator delete(void* aPtr) {
155 sAllocator->Free(aPtr);
156 sAllocatorUsers--;
157 DeleteAllocatorIfNeeded();
160 already_AddRefed<nsTimerImpl> ForgetTimer() { return mTimer.forget(); }
162 private:
163 nsTimerEvent(const nsTimerEvent&) = delete;
164 nsTimerEvent& operator=(const nsTimerEvent&) = delete;
165 nsTimerEvent& operator=(const nsTimerEvent&&) = delete;
167 ~nsTimerEvent() {
168 MOZ_ASSERT(!sCanDeleteAllocator || sAllocatorUsers > 0,
169 "This will result in us attempting to deallocate the "
170 "nsTimerEvent allocator twice");
173 TimeStamp mInitTime;
174 RefPtr<nsTimerImpl> mTimer;
175 const int32_t mGeneration;
176 ProfilerThreadId mTimerThreadId;
178 static TimerEventAllocator* sAllocator;
180 static Atomic<int32_t, SequentiallyConsistent> sAllocatorUsers;
181 static Atomic<bool, SequentiallyConsistent> sCanDeleteAllocator;
184 TimerEventAllocator* nsTimerEvent::sAllocator = nullptr;
185 Atomic<int32_t, SequentiallyConsistent> nsTimerEvent::sAllocatorUsers;
186 Atomic<bool, SequentiallyConsistent> nsTimerEvent::sCanDeleteAllocator;
188 namespace {
190 void* TimerEventAllocator::Alloc(size_t aSize) {
191 MOZ_ASSERT(aSize == sizeof(nsTimerEvent));
193 mozilla::MonitorAutoLock lock(mMonitor);
195 void* p;
196 if (mFirstFree) {
197 p = mFirstFree;
198 mFirstFree = mFirstFree->mNext;
199 } else {
200 p = mPool.Allocate(aSize, fallible);
203 return p;
206 void TimerEventAllocator::Free(void* aPtr) {
207 mozilla::MonitorAutoLock lock(mMonitor);
209 FreeEntry* entry = reinterpret_cast<FreeEntry*>(aPtr);
211 entry->mNext = mFirstFree;
212 mFirstFree = entry;
215 } // namespace
217 void nsTimerEvent::Init() { sAllocator = new TimerEventAllocator(); }
219 void nsTimerEvent::Shutdown() {
220 sCanDeleteAllocator = true;
221 DeleteAllocatorIfNeeded();
224 void nsTimerEvent::DeleteAllocatorIfNeeded() {
225 if (sCanDeleteAllocator && sAllocatorUsers == 0) {
226 delete sAllocator;
227 sAllocator = nullptr;
231 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
232 NS_IMETHODIMP
233 nsTimerEvent::GetName(nsACString& aName) {
234 bool current;
235 MOZ_RELEASE_ASSERT(
236 NS_SUCCEEDED(mTimer->mEventTarget->IsOnCurrentThread(&current)) &&
237 current);
239 mTimer->GetName(aName);
240 return NS_OK;
242 #endif
244 NS_IMETHODIMP
245 nsTimerEvent::Run() {
246 if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
247 TimeStamp now = TimeStamp::Now();
248 MOZ_LOG(GetTimerLog(), LogLevel::Debug,
249 ("[this=%p] time between PostTimerEvent() and Fire(): %fms\n", this,
250 (now - mInitTime).ToMilliseconds()));
253 if (profiler_thread_is_being_profiled_for_markers(mTimerThreadId)) {
254 nsAutoCString name;
255 mTimer->GetName(name);
256 PROFILER_MARKER_TEXT(
257 "PostTimerEvent", OTHER,
258 MarkerOptions(MOZ_LIKELY(mInitTime)
259 ? MarkerTiming::IntervalUntilNowFrom(mInitTime)
260 : MarkerTiming::InstantNow(),
261 MarkerThreadId(mTimerThreadId)),
262 name);
265 mTimer->Fire(mGeneration);
267 return NS_OK;
270 nsresult TimerThread::Init() {
271 mMonitor.AssertCurrentThreadOwns();
272 MOZ_LOG(GetTimerLog(), LogLevel::Debug,
273 ("TimerThread::Init [%d]\n", mInitialized));
275 if (!mInitialized) {
276 nsTimerEvent::Init();
278 // We hold on to mThread to keep the thread alive.
279 nsresult rv = NS_NewNamedThread("Timer", getter_AddRefs(mThread), this);
280 if (NS_FAILED(rv)) {
281 mThread = nullptr;
282 } else {
283 RefPtr<TimerObserverRunnable> r = new TimerObserverRunnable(this);
284 if (NS_IsMainThread()) {
285 r->Run();
286 } else {
287 NS_DispatchToMainThread(r);
291 mInitialized = true;
294 if (!mThread) {
295 return NS_ERROR_FAILURE;
298 return NS_OK;
301 nsresult TimerThread::Shutdown() {
302 MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("TimerThread::Shutdown begin\n"));
304 if (!mThread) {
305 return NS_ERROR_NOT_INITIALIZED;
308 nsTArray<RefPtr<nsTimerImpl>> timers;
310 // lock scope
311 MonitorAutoLock lock(mMonitor);
313 mShutdown = true;
315 // notify the cond var so that Run() can return
316 if (mWaiting) {
317 mNotified = true;
318 mMonitor.Notify();
321 // Need to copy content of mTimers array to a local array
322 // because call to timers' Cancel() (and release its self)
323 // must not be done under the lock. Destructor of a callback
324 // might potentially call some code reentering the same lock
325 // that leads to unexpected behavior or deadlock.
326 // See bug 422472.
327 for (const UniquePtr<Entry>& entry : mTimers) {
328 timers.AppendElement(entry->Take());
331 mTimers.Clear();
334 for (const RefPtr<nsTimerImpl>& timer : timers) {
335 if (timer) {
336 timer->Cancel();
340 mThread->Shutdown(); // wait for the thread to die
342 nsTimerEvent::Shutdown();
344 MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("TimerThread::Shutdown end\n"));
345 return NS_OK;
348 namespace {
350 struct MicrosecondsToInterval {
351 PRIntervalTime operator[](size_t aMs) const {
352 return PR_MicrosecondsToInterval(aMs);
356 struct IntervalComparator {
357 int operator()(PRIntervalTime aInterval) const {
358 return (0 < aInterval) ? -1 : 1;
362 } // namespace
364 NS_IMETHODIMP
365 TimerThread::Run() {
366 MonitorAutoLock lock(mMonitor);
368 mProfilerThreadId = profiler_current_thread_id();
370 // We need to know how many microseconds give a positive PRIntervalTime. This
371 // is platform-dependent and we calculate it at runtime, finding a value |v|
372 // such that |PR_MicrosecondsToInterval(v) > 0| and then binary-searching in
373 // the range [0, v) to find the ms-to-interval scale.
374 uint32_t usForPosInterval = 1;
375 while (PR_MicrosecondsToInterval(usForPosInterval) == 0) {
376 usForPosInterval <<= 1;
379 size_t usIntervalResolution;
380 BinarySearchIf(MicrosecondsToInterval(), 0, usForPosInterval,
381 IntervalComparator(), &usIntervalResolution);
382 MOZ_ASSERT(PR_MicrosecondsToInterval(usIntervalResolution - 1) == 0);
383 MOZ_ASSERT(PR_MicrosecondsToInterval(usIntervalResolution) == 1);
385 // Half of the amount of microseconds needed to get positive PRIntervalTime.
386 // We use this to decide how to round our wait times later
387 mAllowedEarlyFiringMicroseconds = usIntervalResolution / 2;
388 bool forceRunNextTimer = false;
390 while (!mShutdown) {
391 // Have to use PRIntervalTime here, since PR_WaitCondVar takes it
392 TimeDuration waitFor;
393 bool forceRunThisTimer = forceRunNextTimer;
394 forceRunNextTimer = false;
396 if (mSleeping) {
397 // Sleep for 0.1 seconds while not firing timers.
398 uint32_t milliseconds = 100;
399 if (ChaosMode::isActive(ChaosFeature::TimerScheduling)) {
400 milliseconds = ChaosMode::randomUint32LessThan(200);
402 waitFor = TimeDuration::FromMilliseconds(milliseconds);
403 } else {
404 waitFor = TimeDuration::Forever();
405 TimeStamp now = TimeStamp::Now();
407 RemoveLeadingCanceledTimersInternal();
409 if (!mTimers.IsEmpty()) {
410 if (now >= mTimers[0]->Value()->mTimeout || forceRunThisTimer) {
411 next:
412 // NB: AddRef before the Release under RemoveTimerInternal to avoid
413 // mRefCnt passing through zero, in case all other refs than the one
414 // from mTimers have gone away (the last non-mTimers[i]-ref's Release
415 // must be racing with us, blocked in gThread->RemoveTimer waiting
416 // for TimerThread::mMonitor, under nsTimerImpl::Release.
418 RefPtr<nsTimerImpl> timerRef(mTimers[0]->Take());
419 RemoveFirstTimerInternal();
421 MOZ_LOG(GetTimerLog(), LogLevel::Debug,
422 ("Timer thread woke up %fms from when it was supposed to\n",
423 fabs((now - timerRef->mTimeout).ToMilliseconds())));
425 // We are going to let the call to PostTimerEvent here handle the
426 // release of the timer so that we don't end up releasing the timer
427 // on the TimerThread instead of on the thread it targets.
429 LogTimerEvent::Run run(timerRef.get());
430 PostTimerEvent(timerRef.forget());
433 if (mShutdown) {
434 break;
437 // Update now, as PostTimerEvent plus the locking may have taken a
438 // tick or two, and we may goto next below.
439 now = TimeStamp::Now();
443 RemoveLeadingCanceledTimersInternal();
445 if (!mTimers.IsEmpty()) {
446 TimeStamp timeout = mTimers[0]->Value()->mTimeout;
448 // Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer
449 // is due now or overdue.
451 // Note that we can only sleep for integer values of a certain
452 // resolution. We use mAllowedEarlyFiringMicroseconds, calculated
453 // before, to do the optimal rounding (i.e., of how to decide what
454 // interval is so small we should not wait at all).
455 double microseconds = (timeout - now).ToMilliseconds() * 1000;
457 if (ChaosMode::isActive(ChaosFeature::TimerScheduling)) {
458 // The mean value of sFractions must be 1 to ensure that
459 // the average of a long sequence of timeouts converges to the
460 // actual sum of their times.
461 static const float sFractions[] = {0.0f, 0.25f, 0.5f, 0.75f,
462 1.0f, 1.75f, 2.75f};
463 microseconds *= sFractions[ChaosMode::randomUint32LessThan(
464 ArrayLength(sFractions))];
465 forceRunNextTimer = true;
468 if (microseconds < mAllowedEarlyFiringMicroseconds) {
469 forceRunNextTimer = false;
470 goto next; // round down; execute event now
472 waitFor = TimeDuration::FromMicroseconds(microseconds);
473 if (waitFor.IsZero()) {
474 // round up, wait the minimum time we can wait
475 waitFor = TimeDuration::FromMicroseconds(1);
479 if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
480 if (waitFor == TimeDuration::Forever())
481 MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("waiting forever\n"));
482 else
483 MOZ_LOG(GetTimerLog(), LogLevel::Debug,
484 ("waiting for %f\n", waitFor.ToMilliseconds()));
488 mWaiting = true;
489 mNotified = false;
491 AUTO_PROFILER_TRACING_MARKER("TimerThread", "Wait", OTHER);
492 mMonitor.Wait(waitFor);
494 if (mNotified) {
495 forceRunNextTimer = false;
497 mWaiting = false;
500 return NS_OK;
503 nsresult TimerThread::AddTimer(nsTimerImpl* aTimer,
504 const MutexAutoLock& aProofOfLock) {
505 MonitorAutoLock lock(mMonitor);
507 if (!aTimer->mEventTarget) {
508 return NS_ERROR_NOT_INITIALIZED;
511 nsresult rv = Init();
512 if (NS_FAILED(rv)) {
513 return rv;
516 // Add the timer to our list.
517 if (!AddTimerInternal(aTimer)) {
518 return NS_ERROR_OUT_OF_MEMORY;
521 // Awaken the timer thread.
522 if (mWaiting && mTimers[0]->Value() == aTimer) {
523 mNotified = true;
524 mMonitor.Notify();
527 if (profiler_thread_is_being_profiled_for_markers(mProfilerThreadId)) {
528 struct TimerMarker {
529 static constexpr Span<const char> MarkerTypeName() {
530 return MakeStringSpan("Timer");
532 static void StreamJSONMarkerData(
533 baseprofiler::SpliceableJSONWriter& aWriter,
534 const ProfilerString8View& aTimerName, uint32_t aDelay,
535 MarkerThreadId aThreadId) {
536 aWriter.StringProperty("name", aTimerName);
537 aWriter.IntProperty("delay", aDelay);
538 if (!aThreadId.IsUnspecified()) {
539 // Tech note: If `ToNumber()` returns a uint64_t, the conversion to
540 // int64_t is "implementation-defined" before C++20. This is
541 // acceptable here, because this is a one-way conversion to a unique
542 // identifier that's used to visually separate data by thread on the
543 // front-end.
544 aWriter.IntProperty("threadId", static_cast<int64_t>(
545 aThreadId.ThreadId().ToNumber()));
548 static MarkerSchema MarkerTypeDisplay() {
549 using MS = MarkerSchema;
550 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
551 schema.AddKeyLabelFormatSearchable("name", "Name", MS::Format::String,
552 MS::Searchable::Searchable);
553 schema.AddKeyLabelFormat("delay", "Delay", MS::Format::Milliseconds);
554 schema.SetTableLabel(
555 "{marker.name} - {marker.data.name} - {marker.data.delay}");
556 return schema;
560 nsAutoCString name;
561 aTimer->GetName(name, aProofOfLock);
563 nsLiteralCString prefix("Anonymous_");
564 profiler_add_marker(
565 "AddTimer", geckoprofiler::category::OTHER,
566 MarkerOptions(MarkerThreadId(mProfilerThreadId),
567 MarkerStack::MaybeCapture(
568 StringHead(name, prefix.Length()) == prefix)),
569 TimerMarker{}, name, aTimer->mDelay.ToMilliseconds(),
570 MarkerThreadId::CurrentThread());
573 return NS_OK;
576 nsresult TimerThread::RemoveTimer(nsTimerImpl* aTimer,
577 const MutexAutoLock& aProofOfLock) {
578 MonitorAutoLock lock(mMonitor);
580 // Remove the timer from our array. Tell callers that aTimer was not found
581 // by returning NS_ERROR_NOT_AVAILABLE.
583 if (!RemoveTimerInternal(aTimer)) {
584 return NS_ERROR_NOT_AVAILABLE;
587 // Awaken the timer thread.
588 if (mWaiting) {
589 mNotified = true;
590 mMonitor.Notify();
593 if (profiler_thread_is_being_profiled_for_markers(mProfilerThreadId)) {
594 nsAutoCString name;
595 aTimer->GetName(name, aProofOfLock);
597 nsLiteralCString prefix("Anonymous_");
598 PROFILER_MARKER_TEXT(
599 "RemoveTimer", OTHER,
600 MarkerOptions(MarkerThreadId(mProfilerThreadId),
601 MarkerStack::MaybeCapture(
602 StringHead(name, prefix.Length()) == prefix)),
603 name);
606 return NS_OK;
609 TimeStamp TimerThread::FindNextFireTimeForCurrentThread(TimeStamp aDefault,
610 uint32_t aSearchBound) {
611 MonitorAutoLock lock(mMonitor);
612 TimeStamp timeStamp = aDefault;
613 uint32_t index = 0;
615 #ifdef DEBUG
616 TimeStamp firstTimeStamp;
617 Entry* initialFirstEntry = nullptr;
618 if (!mTimers.IsEmpty()) {
619 initialFirstEntry = mTimers[0].get();
620 firstTimeStamp = mTimers[0]->Timeout();
622 #endif
624 auto end = mTimers.end();
625 while (end != mTimers.begin()) {
626 nsTimerImpl* timer = mTimers[0]->Value();
627 if (timer) {
628 if (timer->mTimeout > aDefault) {
629 timeStamp = aDefault;
630 break;
633 // Don't yield to timers created with the *_LOW_PRIORITY type.
634 if (!timer->IsLowPriority()) {
635 bool isOnCurrentThread = false;
636 nsresult rv =
637 timer->mEventTarget->IsOnCurrentThread(&isOnCurrentThread);
638 if (NS_SUCCEEDED(rv) && isOnCurrentThread) {
639 timeStamp = timer->mTimeout;
640 break;
644 if (++index > aSearchBound) {
645 // Track the currently highest timeout so that we can bail out when we
646 // reach the bound or when we find a timer for the current thread.
647 // This won't give accurate information if we stop before finding
648 // any timer for the current thread, but at least won't report too
649 // long idle period.
650 timeStamp = timer->mTimeout;
651 break;
655 std::pop_heap(mTimers.begin(), end, Entry::UniquePtrLessThan);
656 --end;
659 while (end != mTimers.end()) {
660 ++end;
661 std::push_heap(mTimers.begin(), end, Entry::UniquePtrLessThan);
664 #ifdef DEBUG
665 if (!mTimers.IsEmpty()) {
666 if (firstTimeStamp != mTimers[0]->Timeout()) {
667 TimeStamp now = TimeStamp::Now();
668 printf_stderr(
669 "firstTimeStamp %f, mTimers[0]->Timeout() %f, "
670 "initialFirstTimer %p, current first %p\n",
671 (firstTimeStamp - now).ToMilliseconds(),
672 (mTimers[0]->Timeout() - now).ToMilliseconds(), initialFirstEntry,
673 mTimers[0].get());
676 MOZ_ASSERT_IF(!mTimers.IsEmpty(), firstTimeStamp == mTimers[0]->Timeout());
677 #endif
679 return timeStamp;
682 // This function must be called from within a lock
683 // Also: we hold the mutex for the nsTimerImpl.
684 bool TimerThread::AddTimerInternal(nsTimerImpl* aTimer) {
685 mMonitor.AssertCurrentThreadOwns();
686 aTimer->mMutex.AssertCurrentThreadOwns();
687 if (mShutdown) {
688 return false;
691 TimeStamp now = TimeStamp::Now();
693 LogTimerEvent::LogDispatch(aTimer);
695 UniquePtr<Entry>* entry = mTimers.AppendElement(
696 MakeUnique<Entry>(now, aTimer->mTimeout, aTimer), mozilla::fallible);
697 if (!entry) {
698 return false;
701 std::push_heap(mTimers.begin(), mTimers.end(), Entry::UniquePtrLessThan);
703 return true;
706 // This function must be called from within a lock
707 // Also: we hold the mutex for the nsTimerImpl.
708 bool TimerThread::RemoveTimerInternal(nsTimerImpl* aTimer) {
709 mMonitor.AssertCurrentThreadOwns();
710 aTimer->mMutex.AssertCurrentThreadOwns();
711 if (!aTimer || !aTimer->mHolder) {
712 return false;
714 aTimer->mHolder->Forget(aTimer);
715 return true;
718 void TimerThread::RemoveLeadingCanceledTimersInternal() {
719 mMonitor.AssertCurrentThreadOwns();
721 // Move all canceled timers from the front of the list to
722 // the back of the list using std::pop_heap(). We do this
723 // without actually removing them from the list so we can
724 // modify the nsTArray in a single bulk operation.
725 auto sortedEnd = mTimers.end();
726 while (sortedEnd != mTimers.begin() && !mTimers[0]->Value()) {
727 std::pop_heap(mTimers.begin(), sortedEnd, Entry::UniquePtrLessThan);
728 --sortedEnd;
731 // If there were no canceled timers then we are done.
732 if (sortedEnd == mTimers.end()) {
733 return;
736 // Finally, remove the canceled timers from the back of the
737 // nsTArray.
738 mTimers.RemoveLastElements(mTimers.end() - sortedEnd);
741 void TimerThread::RemoveFirstTimerInternal() {
742 mMonitor.AssertCurrentThreadOwns();
743 MOZ_ASSERT(!mTimers.IsEmpty());
744 std::pop_heap(mTimers.begin(), mTimers.end(), Entry::UniquePtrLessThan);
745 mTimers.RemoveLastElement();
748 void TimerThread::PostTimerEvent(already_AddRefed<nsTimerImpl> aTimerRef) {
749 mMonitor.AssertCurrentThreadOwns();
751 RefPtr<nsTimerImpl> timer(aTimerRef);
752 if (!timer->mEventTarget) {
753 NS_ERROR("Attempt to post timer event to NULL event target");
754 return;
757 // XXX we may want to reuse this nsTimerEvent in the case of repeating timers.
759 // Since we already addref'd 'timer', we don't need to addref here.
760 // We will release either in ~nsTimerEvent(), or pass the reference back to
761 // the caller. We need to copy the generation number from this timer into the
762 // event, so we can avoid firing a timer that was re-initialized after being
763 // canceled.
765 nsCOMPtr<nsIEventTarget> target = timer->mEventTarget;
767 void* p = nsTimerEvent::operator new(sizeof(nsTimerEvent));
768 if (!p) {
769 return;
771 RefPtr<nsTimerEvent> event =
772 ::new (KnownNotNull, p) nsTimerEvent(timer.forget(), mProfilerThreadId);
774 nsresult rv;
776 // We release mMonitor around the Dispatch because if the Dispatch interacts
777 // with the timer API we'll deadlock.
778 MonitorAutoUnlock unlock(mMonitor);
779 rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
780 if (NS_FAILED(rv)) {
781 timer = event->ForgetTimer();
782 // We do this to avoid possible deadlock by taking the two locks in a
783 // different order than is used in RemoveTimer(). RemoveTimer() has
784 // aTimer->mMutex first. We use timer.get() to keep static analysis
785 // happy
786 MutexAutoLock lock1(timer.get()->mMutex);
787 MonitorAutoLock lock2(mMonitor);
788 RemoveTimerInternal(timer.get());
793 void TimerThread::DoBeforeSleep() {
794 // Mainthread
795 MonitorAutoLock lock(mMonitor);
796 mSleeping = true;
799 // Note: wake may be notified without preceding sleep notification
800 void TimerThread::DoAfterSleep() {
801 // Mainthread
802 MonitorAutoLock lock(mMonitor);
803 mSleeping = false;
805 // Wake up the timer thread to re-process the array to ensure the sleep delay
806 // is correct, and fire any expired timers (perhaps quite a few)
807 mNotified = true;
808 PROFILER_MARKER_UNTYPED("AfterSleep", OTHER,
809 MarkerThreadId(mProfilerThreadId));
810 mMonitor.Notify();
813 NS_IMETHODIMP
814 TimerThread::Observe(nsISupports* /* aSubject */, const char* aTopic,
815 const char16_t* /* aData */) {
816 if (StaticPrefs::timer_ignore_sleep_wake_notifications()) {
817 return NS_OK;
820 if (strcmp(aTopic, "sleep_notification") == 0 ||
821 strcmp(aTopic, "suspend_process_notification") == 0) {
822 DoBeforeSleep();
823 } else if (strcmp(aTopic, "wake_notification") == 0 ||
824 strcmp(aTopic, "resume_process_notification") == 0) {
825 DoAfterSleep();
828 return NS_OK;
831 uint32_t TimerThread::AllowedEarlyFiringMicroseconds() {
832 MonitorAutoLock lock(mMonitor);
833 return mAllowedEarlyFiringMicroseconds;