gecko-dev/gfx/thebes/VsyncSource.h
Markus Stange 213f2aaa74 Bug 1781167 - Allow stacking calls to Add/RemoveVsyncDispatcher so that we survive the sequence Add,Add,Remove. r=jrmuizel
This fixes a bug which caused Firefox windows to become frozen after some time.

Full credit goes to Susan and RandyS for bisecting the regressor of this bug, and
to Jeff DeFouw for debugging the issue and finding the cause.

The bug here is a "state race" between the VsyncDispatcher state and
the VsyncSource state. Both are protected by locks, and the code that
runs in those locks respectively can see a different orders of invocations.

VsyncDispatcher::UpdateVsyncStatus does this thing where it updates its state inside
a lock, gathers some information, and then calls methods on VsyncSource *outside* the lock.
Since it calls those methods outside the lock, these calls can end up being executed
in a different order than the state changes were observed inside the lock.

Here's the bad scenario in detail, with the same VsyncDispatcher being used from
two different threads, turning a Remove,Add into an Add,Remove:

```
Thread A                                       Thread B

VsyncDispatcher::UpdateVsync
 |
 |----> Enter VsyncDispatcher lock
 |    |                                         VsyncDispatcher::UpdateVsync
 |    |   state->mIsObservingVsync = false       |
 |    |   (We want to stop listening)            |
 |    |                                          |
 |<---- Exit VsyncDispatcher lock                |
 |                                               |----> Enter VsyncDispatcher lock
 |                                               |    |
 |                                               |    |   state->mIsObservingVsync = true
 |                                               |    |   (We want to start listening)
 |                                               |    |
 |                                               |<----  Exit VsyncDispatcher lock
 |                                               |
 |                                               |----> Enter VsyncSource::AddVsyncDispatcher
 |                                               |    |
 |                                               |    |----> Enter VsyncSource lock
 |                                               |    |    |
 |                                               |    |    |  state->mDispatchers.Contains(aVsyncDispatcher)
 |----> VsyncSource::RemoveVsyncDispatcher       |    |    |  VsyncDispatcher already present in list, not doing anything
 |    |                                          |    |    |
 |    |                                          |    |<---- Exit VsyncSource lock
 |    |                                          |    |
 |    |                                          |<---- Exit VsyncSource::AddVsyncDispatcher
 |    |----> Enter VsyncSource lock
 |    |    |
 |    |    |  Removing aVsyncDispatcher from state->mDispatchers
 |    |    |
 |    |<---- Exit VsyncSource lock
 |    |
 |<---- Exit VsyncSource::AddVsyncDispatcher
```

Now the VsyncDispatcher thinks it is still observing vsync, but it is
no longer registered with the VsyncSource.

This patch makes it so that two calls to AddVsyncDispatcher followed by one call
to RemoveVsyncDispatcher result in the VsyncDispatcher still being registered.
AddVsyncDispatcher is no longer idempotent.

Differential Revision: https://phabricator.services.mozilla.com/D162760
2022-11-22 23:46:44 +00:00

120 lines
3.8 KiB
C++

/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* 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 GFX_VSYNCSOURCE_H
#define GFX_VSYNCSOURCE_H
#include "nsTArray.h"
#include "mozilla/DataMutex.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Maybe.h"
#include "mozilla/Mutex.h"
#include "mozilla/TimeStamp.h"
#include "nsISupportsImpl.h"
#include "mozilla/layers/LayersTypes.h"
namespace mozilla {
class VsyncDispatcher;
class VsyncObserver;
struct VsyncEvent;
class VsyncIdType {};
typedef layers::BaseTransactionId<VsyncIdType> VsyncId;
namespace gfx {
// Controls how and when to enable/disable vsync. Lives as long as the
// gfxPlatform does on the parent process
class VsyncSource {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VsyncSource)
typedef mozilla::VsyncDispatcher VsyncDispatcher;
public:
VsyncSource();
// Notified when this display's vsync callback occurs, on the vsync thread
// Different platforms give different aVsyncTimestamp values.
// macOS: TimeStamp::Now() or the output time of the previous vsync
// callback, whichever is older.
// Windows: It's messy, see gfxWindowsPlatform.
// Android: TODO
//
// @param aVsyncTimestamp The time of the Vsync that just occured. Needs to
// be at or before the time of the NotifyVsync call.
// @param aOutputTimestamp The estimated timestamp at which drawing will
// appear on the screen, if the drawing happens within a certain
// (unknown) budget. Useful for Audio/Video sync. On platforms where
// this timestamp is provided by the system (macOS), it is a much more
// stable and consistent timing source than the time at which the vsync
// callback is called.
virtual void NotifyVsync(const TimeStamp& aVsyncTimestamp,
const TimeStamp& aOutputTimestamp);
// Can be called on any thread.
// Adding the same dispatcher multiple times will increment a count.
// This means that the sequence "Add, Add, Remove" has the same behavior as
// "Add, Remove, Add".
void AddVsyncDispatcher(VsyncDispatcher* aDispatcher);
void RemoveVsyncDispatcher(VsyncDispatcher* aDispatcher);
virtual TimeDuration GetVsyncRate();
// These should all only be called on the main thread
virtual void EnableVsync() = 0;
virtual void DisableVsync() = 0;
virtual bool IsVsyncEnabled() = 0;
virtual void Shutdown() = 0;
// Returns the rate of the fastest enabled VsyncSource or Nothing().
static Maybe<TimeDuration> GetFastestVsyncRate();
protected:
virtual ~VsyncSource();
private:
// Can be called on any thread
void UpdateVsyncStatus();
struct DispatcherRefWithCount {
// The dispatcher.
RefPtr<VsyncDispatcher> mDispatcher;
// The number of add calls minus the number of remove calls for this
// dispatcher. Should always be > 0 as long as this dispatcher is in
// mDispatchers.
size_t mCount = 0;
};
struct State {
// The set of VsyncDispatchers which are registered with this source.
// At the moment, the length of this array is always zero or one.
// The ability to register multiple dispatchers is not used yet; it is
// intended for when we have one dispatcher per widget.
nsTArray<DispatcherRefWithCount> mDispatchers;
// The vsync ID which we used for the last vsync event.
VsyncId mVsyncId;
};
DataMutex<State> mState;
};
} // namespace gfx
struct VsyncEvent {
VsyncId mId;
TimeStamp mTime;
TimeStamp mOutputTime; // estimate
VsyncEvent(const VsyncId& aId, const TimeStamp& aVsyncTime,
const TimeStamp& aOutputTime)
: mId(aId), mTime(aVsyncTime), mOutputTime(aOutputTime) {}
VsyncEvent() = default;
};
} // namespace mozilla
#endif /* GFX_VSYNCSOURCE_H */