import changes from `dev' branch of rmottola/Arctic-Fox:

- Bug 1180189 - Fix crash in mozilla::a11y::HTMLTableRowAccessible::GroupPosition, r=MarcoZ (a2ee5f2df8)
- Bug 1194859 - crash in mozilla::a11y::ARIAGridCellAccessible::GroupPosition, r=marcoz (879bad005f)
- Bug 1194859 - crash in mozilla::a11y::ARIAGridCellAccessible::GroupPosition(), part2, r=marcoz (38193050c0)
- comment and style (a3ae06e5e6)
- var/let (7b3552e2ba)
- var-let and some misspatch fixes (a6d3b0c9cb)
- Bug 1141978 - Support rowgroup and colgroup HTML scope, r=marcoz (11eedb88f0)
- Bug 1146257 - spanned headers don't work well in our table code, r=marcoz (d9b11584c2)
- remove unused MultiCompartmentMatcher (480d895553)
- Bug 1168170 - Mark reference counted members of nsTimerImpl::mCallback as MOZ_OWNING_REF. r=froydnj (b84c55460a)
- Bug 1178363 - make nsTimerImpl::SetDelayInternal a private method; r=poiru (5f70ed869e)
- Bug 1178363 - make nsTimerImpl::Fire a private method; r=poiru (4b6007c376)
- Bug 1178363 - make MOZ_TASK_TRACER-dependent bits of nsTimerImpl private; r=poiru (e1f54cde97)
- Bug 1178363 - make nsTimerImpl::PostTimerEvent a private method; r=poiru (0a0b71eae3)
- Bug 1178363 - make nsTimerImpl::GetGeneration a private method; r=poiru (593d3a3f2d)
- Bug 1059572 - Part 0: Fuzz test for timers. r=nfroyd (7b03728aa4)
- Bug 1059572 - Part 0.5: Fixes for pre-existing problems in TestTimers. r=nfroyd (e463afc995)
- Bug 1059572 - Part 1: Move PostTimerEvent to TimerThread to allow TimerThread's monitor to protect it. r=nfroyd (5e56ce272a)
- Bug 1059572 - Part 2: Make absolutely sure a timer is removed before reinitting it. r=nfroyd (7d916a9aa9)
- Bug 1190735 - Remove nsITimer.TYPE_REPEATING_PRECISE. r=froydnj. (34f7e4c02e)
- Bug 1203427 (part 5) - Add logging of timer firings. r=froydnj. (664954eef7)
This commit is contained in:
2022-09-15 12:15:50 +08:00
parent 8b64ccb093
commit d7ee65a30f
49 changed files with 1510 additions and 538 deletions
+265 -9
View File
@@ -8,6 +8,7 @@
#include "TimerThread.h"
#include "nsThreadUtils.h"
#include "plarena.h"
#include "pratom.h"
#include "nsIObserverService.h"
@@ -79,6 +80,200 @@ TimerObserverRunnable::Run()
} // namespace
namespace {
// TimerEventAllocator is a thread-safe allocator used only for nsTimerEvents.
// It's needed to avoid contention over the default allocator lock when
// firing timer events (see bug 733277). The thread-safety is required because
// nsTimerEvent objects are allocated on the timer thread, and freed on another
// thread. Because TimerEventAllocator has its own lock, contention over that
// lock is limited to the allocation and deallocation of nsTimerEvent objects.
//
// Because this allocator is layered over PLArenaPool, it never shrinks -- even
// "freed" nsTimerEvents aren't truly freed, they're just put onto a free-list
// for later recycling. So the amount of memory consumed will always be equal
// to the high-water mark consumption. But nsTimerEvents are small and it's
// unusual to have more than a few hundred of them, so this shouldn't be a
// problem in practice.
class TimerEventAllocator
{
private:
struct FreeEntry
{
FreeEntry* mNext;
};
PLArenaPool mPool;
FreeEntry* mFirstFree;
mozilla::Monitor mMonitor;
public:
TimerEventAllocator()
: mFirstFree(nullptr)
, mMonitor("TimerEventAllocator")
{
PL_InitArenaPool(&mPool, "TimerEventPool", 4096, /* align = */ 0);
}
~TimerEventAllocator()
{
PL_FinishArenaPool(&mPool);
}
void* Alloc(size_t aSize);
void Free(void* aPtr);
};
} // namespace
class nsTimerEvent : public nsRunnable
{
public:
NS_IMETHOD Run();
nsTimerEvent()
: mTimer()
, mGeneration(0)
{
MOZ_COUNT_CTOR(nsTimerEvent);
// Note: We override operator new for this class, and the override is
// fallible!
sAllocatorUsers++;
}
TimeStamp mInitTime;
static void Init();
static void Shutdown();
static void DeleteAllocatorIfNeeded();
static void* operator new(size_t aSize) CPP_THROW_NEW
{
return sAllocator->Alloc(aSize);
}
void operator delete(void* aPtr)
{
sAllocator->Free(aPtr);
DeleteAllocatorIfNeeded();
}
already_AddRefed<nsTimerImpl> ForgetTimer()
{
return mTimer.forget();
}
void SetTimer(already_AddRefed<nsTimerImpl> aTimer)
{
mTimer = aTimer;
mGeneration = mTimer->GetGeneration();
}
private:
~nsTimerEvent()
{
MOZ_COUNT_DTOR(nsTimerEvent);
MOZ_ASSERT(!sCanDeleteAllocator || sAllocatorUsers > 0,
"This will result in us attempting to deallocate the nsTimerEvent allocator twice");
sAllocatorUsers--;
}
nsRefPtr<nsTimerImpl> mTimer;
int32_t mGeneration;
static TimerEventAllocator* sAllocator;
static Atomic<int32_t> sAllocatorUsers;
static bool sCanDeleteAllocator;
};
TimerEventAllocator* nsTimerEvent::sAllocator = nullptr;
Atomic<int32_t> nsTimerEvent::sAllocatorUsers;
bool nsTimerEvent::sCanDeleteAllocator = false;
namespace {
void*
TimerEventAllocator::Alloc(size_t aSize)
{
MOZ_ASSERT(aSize == sizeof(nsTimerEvent));
mozilla::MonitorAutoLock lock(mMonitor);
void* p;
if (mFirstFree) {
p = mFirstFree;
mFirstFree = mFirstFree->mNext;
} else {
PL_ARENA_ALLOCATE(p, &mPool, aSize);
if (!p) {
return nullptr;
}
}
return p;
}
void
TimerEventAllocator::Free(void* aPtr)
{
mozilla::MonitorAutoLock lock(mMonitor);
FreeEntry* entry = reinterpret_cast<FreeEntry*>(aPtr);
entry->mNext = mFirstFree;
mFirstFree = entry;
}
} // namespace
void
nsTimerEvent::Init()
{
sAllocator = new TimerEventAllocator();
}
void
nsTimerEvent::Shutdown()
{
sCanDeleteAllocator = true;
DeleteAllocatorIfNeeded();
}
void
nsTimerEvent::DeleteAllocatorIfNeeded()
{
if (sCanDeleteAllocator && sAllocatorUsers == 0) {
delete sAllocator;
sAllocator = nullptr;
}
}
NS_IMETHODIMP
nsTimerEvent::Run()
{
if (mGeneration != mTimer->GetGeneration()) {
return NS_OK;
}
if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
TimeStamp now = TimeStamp::Now();
MOZ_LOG(GetTimerLog(), LogLevel::Debug,
("[this=%p] time between PostTimerEvent() and Fire(): %fms\n",
this, (now - mInitTime).ToMilliseconds()));
}
mTimer->Fire();
// Since nsTimerImpl is not thread-safe, we should release |mTimer|
// here in the target thread to avoid race condition. Otherwise,
// ~nsTimerEvent() which calls nsTimerImpl::Release() could run in the
// timer thread and result in race condition.
mTimer = nullptr;
return NS_OK;
}
nsresult
TimerThread::Init()
{
@@ -93,6 +288,8 @@ TimerThread::Init()
return NS_OK;
}
nsTimerEvent::Init();
if (mInitInProgress.exchange(true) == false) {
// We hold on to mThread to keep the thread alive.
nsresult rv = NS_NewThread(getter_AddRefs(mThread), this);
@@ -167,6 +364,8 @@ TimerThread::Shutdown()
mThread->Shutdown(); // wait for the thread to die
nsTimerEvent::Shutdown();
MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("TimerThread::Shutdown end\n"));
return NS_OK;
}
@@ -262,15 +461,10 @@ TimerThread::Run()
("Timer thread woke up %fms from when it was supposed to\n",
fabs((now - timerRef->mTimeout).ToMilliseconds())));
{
// We release mMonitor around the Fire call to avoid deadlock.
MonitorAutoUnlock unlock(mMonitor);
// We are going to let the call to PostTimerEvent here handle the
// release of the timer so that we don't end up releasing the timer
// on the TimerThread instead of on the thread it targets.
timerRef = nsTimerImpl::PostTimerEvent(timerRef.forget());
}
// We are going to let the call to PostTimerEvent here handle the
// release of the timer so that we don't end up releasing the timer
// on the TimerThread instead of on the thread it targets.
timerRef = PostTimerEvent(timerRef.forget());
if (timerRef) {
// We got our reference back due to an error.
@@ -484,6 +678,68 @@ TimerThread::ReleaseTimerInternal(nsTimerImpl* aTimer)
NS_RELEASE(aTimer);
}
already_AddRefed<nsTimerImpl>
TimerThread::PostTimerEvent(already_AddRefed<nsTimerImpl> aTimerRef)
{
mMonitor.AssertCurrentThreadOwns();
nsRefPtr<nsTimerImpl> timer(aTimerRef);
if (!timer->mEventTarget) {
NS_ERROR("Attempt to post timer event to NULL event target");
return timer.forget();
}
// XXX we may want to reuse this nsTimerEvent in the case of repeating timers.
// Since we already addref'd 'timer', we don't need to addref here.
// We will release either in ~nsTimerEvent(), or pass the reference back to
// the caller. We need to copy the generation number from this timer into the
// event, so we can avoid firing a timer that was re-initialized after being
// canceled.
nsRefPtr<nsTimerEvent> event = new nsTimerEvent;
if (!event) {
return timer.forget();
}
if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) {
event->mInitTime = TimeStamp::Now();
}
// If this is a repeating precise timer, we need to calculate the time for
// the next timer to fire before we make the callback. But don't re-arm.
if (timer->IsRepeatingPrecisely()) {
timer->SetDelayInternal(timer->mDelay);
}
#ifdef MOZ_TASK_TRACER
// During the dispatch of TimerEvent, we overwrite the current TraceInfo
// partially with the info saved in timer earlier, and restore it back by
// AutoSaveCurTraceInfo.
AutoSaveCurTraceInfo saveCurTraceInfo;
(timer->GetTracedTask()).SetTLSTraceInfo();
#endif
nsIEventTarget* target = timer->mEventTarget;
event->SetTimer(timer.forget());
nsresult rv;
{
// We release mMonitor around the Dispatch because if this timer is targeted
// at the TimerThread we'll deadlock.
MonitorAutoUnlock unlock(mMonitor);
rv = target->Dispatch(event, NS_DISPATCH_NORMAL);
}
if (NS_FAILED(rv)) {
timer = event->ForgetTimer();
RemoveTimerInternal(timer);
return timer.forget();
}
return nullptr;
}
void
TimerThread::DoBeforeSleep()
{