Skip to content

Commit 58aedbc

Browse files
author
Jeff Brown
committed
Fix possible races in vsync infrastructure.
Applications sometimes crashed on exit due to the display event receiver pipe apparently being closed while still a member of the Looper's epoll fd set. This patch fixes a few different possible races related to the display event receiver lifecycle. 1. The receiver used to play a little dance with the Looper, registering and unregistering its callback after each vsync request. This code was a holdover from a time before the surface flinger supported one-shot vsync requests, so we can get rid of it and make things a lot simpler. 2. When the Choreographer is being accessed from outside the UI thread, it needs to take great care that it does not touch the display event receiver. Bad things could happen if the receiver is handling a vsync event on the Looper and the receiver is disposed concurrently. 3. It was possible for the Choreographer to attempt to dispose the receiver while handling a vsync message. Now we defer disposing the receiver for a little while, which is also nice because we may be able to avoid disposing the receiver altogether if we find that we need it again a little while later. Bug: 5974105 Change-Id: I77a158f51b0b689af34d07aee4245b969e6260d6
1 parent 0952c30 commit 58aedbc

File tree

3 files changed

+89
-32
lines changed

3 files changed

+89
-32
lines changed

core/java/android/view/Choreographer.java

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,20 @@
3131
* This object is thread-safe. Other threads can add and remove listeners
3232
* or schedule work to occur at a later time on the UI thread.
3333
*
34+
* Ensuring thread-safety is a little tricky because the {@link DisplayEventReceiver}
35+
* can only be accessed from the UI thread so operations that touch the event receiver
36+
* are posted to the UI thread if needed.
37+
*
3438
* @hide
3539
*/
3640
public final class Choreographer extends Handler {
3741
private static final String TAG = "Choreographer";
3842
private static final boolean DEBUG = false;
3943

44+
// Amount of time in ms to wait before actually disposing of the display event
45+
// receiver after all listeners have been removed.
46+
private static final long DISPOSE_RECEIVER_DELAY = 200;
47+
4048
// The default amount of time in ms between animation frames.
4149
// When vsync is not enabled, we want to have some idea of how long we should
4250
// wait before posting the next animation message. It is important that the
@@ -78,6 +86,8 @@ protected Choreographer initialValue() {
7886

7987
private static final int MSG_DO_ANIMATION = 0;
8088
private static final int MSG_DO_DRAW = 1;
89+
private static final int MSG_DO_SCHEDULE_VSYNC = 2;
90+
private static final int MSG_DO_DISPOSE_RECEIVER = 3;
8191

8292
private final Object mLock = new Object();
8393

@@ -88,6 +98,7 @@ protected Choreographer initialValue() {
8898

8999
private boolean mAnimationScheduled;
90100
private boolean mDrawScheduled;
101+
private boolean mFrameDisplayEventReceiverNeeded;
91102
private FrameDisplayEventReceiver mFrameDisplayEventReceiver;
92103
private long mLastAnimationTime;
93104
private long mLastDrawTime;
@@ -158,10 +169,21 @@ private void scheduleAnimationLocked() {
158169
if (DEBUG) {
159170
Log.d(TAG, "Scheduling vsync for animation.");
160171
}
161-
if (mFrameDisplayEventReceiver == null) {
162-
mFrameDisplayEventReceiver = new FrameDisplayEventReceiver(mLooper);
172+
173+
// If running on the Looper thread, then schedule the vsync immediately,
174+
// otherwise post a message to schedule the vsync from the UI thread
175+
// as soon as possible.
176+
if (!mFrameDisplayEventReceiverNeeded) {
177+
mFrameDisplayEventReceiverNeeded = true;
178+
if (mFrameDisplayEventReceiver != null) {
179+
removeMessages(MSG_DO_DISPOSE_RECEIVER);
180+
}
181+
}
182+
if (isRunningOnLooperThreadLocked()) {
183+
doScheduleVsyncLocked();
184+
} else {
185+
sendMessageAtFrontOfQueue(obtainMessage(MSG_DO_SCHEDULE_VSYNC));
163186
}
164-
mFrameDisplayEventReceiver.scheduleVsync();
165187
} else {
166188
final long now = SystemClock.uptimeMillis();
167189
final long nextAnimationTime = Math.max(mLastAnimationTime + sFrameDelay, now);
@@ -224,6 +246,12 @@ public void handleMessage(Message msg) {
224246
case MSG_DO_DRAW:
225247
doDraw();
226248
break;
249+
case MSG_DO_SCHEDULE_VSYNC:
250+
doScheduleVsync();
251+
break;
252+
case MSG_DO_DISPOSE_RECEIVER:
253+
doDisposeReceiver();
254+
break;
227255
}
228256
}
229257

@@ -295,6 +323,30 @@ private void doDraw() {
295323
}
296324
}
297325

326+
private void doScheduleVsync() {
327+
synchronized (mLock) {
328+
doScheduleVsyncLocked();
329+
}
330+
}
331+
332+
private void doScheduleVsyncLocked() {
333+
if (mFrameDisplayEventReceiverNeeded && mAnimationScheduled) {
334+
if (mFrameDisplayEventReceiver == null) {
335+
mFrameDisplayEventReceiver = new FrameDisplayEventReceiver(mLooper);
336+
}
337+
mFrameDisplayEventReceiver.scheduleVsync();
338+
}
339+
}
340+
341+
private void doDisposeReceiver() {
342+
synchronized (mLock) {
343+
if (!mFrameDisplayEventReceiverNeeded && mFrameDisplayEventReceiver != null) {
344+
mFrameDisplayEventReceiver.dispose();
345+
mFrameDisplayEventReceiver = null;
346+
}
347+
}
348+
}
349+
298350
/**
299351
* Adds an animation listener.
300352
*
@@ -386,7 +438,9 @@ private void stopTimingLoopIfNoListenersLocked() {
386438

387439
if (mAnimationScheduled) {
388440
mAnimationScheduled = false;
389-
if (!USE_VSYNC) {
441+
if (USE_VSYNC) {
442+
removeMessages(MSG_DO_SCHEDULE_VSYNC);
443+
} else {
390444
removeMessages(MSG_DO_ANIMATION);
391445
}
392446
}
@@ -398,13 +452,24 @@ private void stopTimingLoopIfNoListenersLocked() {
398452
}
399453
}
400454

401-
if (mFrameDisplayEventReceiver != null) {
402-
mFrameDisplayEventReceiver.dispose();
403-
mFrameDisplayEventReceiver = null;
455+
// Post a message to dispose the display event receiver if we haven't needed
456+
// it again after a certain amount of time has elapsed. Another reason to
457+
// defer disposal is that it is possible for use to attempt to dispose the
458+
// receiver while handling a vsync event that it dispatched, which might
459+
// cause a few problems...
460+
if (mFrameDisplayEventReceiverNeeded) {
461+
mFrameDisplayEventReceiverNeeded = false;
462+
if (mFrameDisplayEventReceiver != null) {
463+
sendEmptyMessageDelayed(MSG_DO_DISPOSE_RECEIVER, DISPOSE_RECEIVER_DELAY);
464+
}
404465
}
405466
}
406467
}
407468

469+
private boolean isRunningOnLooperThreadLocked() {
470+
return Looper.myLooper() == mLooper;
471+
}
472+
408473
/**
409474
* Listens for animation frame timing events.
410475
*/

core/java/android/view/DisplayEventReceiver.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
/**
2626
* Provides a low-level mechanism for an application to receive display events
2727
* such as vertical sync.
28+
*
29+
* The display event receive is NOT thread safe. Moreover, its methods must only
30+
* be called on the Looper thread to which it is attached.
31+
*
2832
* @hide
2933
*/
3034
public abstract class DisplayEventReceiver {

core/jni/android_view_DisplayEventReceiver.cpp

Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -59,21 +59,20 @@ class NativeDisplayEventReceiver : public RefBase {
5959
sp<Looper> mLooper;
6060
DisplayEventReceiver mReceiver;
6161
bool mWaitingForVsync;
62-
bool mFdCallbackRegistered;
6362
};
6463

6564

6665
NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env,
6766
jobject receiverObj, const sp<Looper>& looper) :
6867
mReceiverObjGlobal(env->NewGlobalRef(receiverObj)),
69-
mLooper(looper), mWaitingForVsync(false), mFdCallbackRegistered(false) {
68+
mLooper(looper), mWaitingForVsync(false) {
7069
ALOGV("receiver %p ~ Initializing input event receiver.", this);
7170
}
7271

7372
NativeDisplayEventReceiver::~NativeDisplayEventReceiver() {
7473
ALOGV("receiver %p ~ Disposing display event receiver.", this);
7574

76-
if (mFdCallbackRegistered) {
75+
if (!mReceiver.initCheck()) {
7776
mLooper->removeFd(mReceiver.getFd());
7877
}
7978

@@ -88,6 +87,11 @@ status_t NativeDisplayEventReceiver::initialize() {
8887
return result;
8988
}
9089

90+
int rc = mLooper->addFd(mReceiver.getFd(), 0, ALOOPER_EVENT_INPUT,
91+
handleReceiveCallback, this);
92+
if (rc < 0) {
93+
return UNKNOWN_ERROR;
94+
}
9195
return OK;
9296
}
9397

@@ -113,15 +117,6 @@ status_t NativeDisplayEventReceiver::scheduleVsync() {
113117
return status;
114118
}
115119

116-
if (!mFdCallbackRegistered) {
117-
int rc = mLooper->addFd(mReceiver.getFd(), 0, ALOOPER_EVENT_INPUT,
118-
handleReceiveCallback, this);
119-
if (rc < 0) {
120-
return UNKNOWN_ERROR;
121-
}
122-
mFdCallbackRegistered = true;
123-
}
124-
125120
mWaitingForVsync = true;
126121
}
127122
return OK;
@@ -133,7 +128,6 @@ int NativeDisplayEventReceiver::handleReceiveCallback(int receiveFd, int events,
133128
if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
134129
ALOGE("Display event receiver pipe was closed or an error occurred. "
135130
"events=0x%x", events);
136-
r->mFdCallbackRegistered = false;
137131
return 0; // remove the callback
138132
}
139133

@@ -150,7 +144,7 @@ int NativeDisplayEventReceiver::handleReceiveCallback(int receiveFd, int events,
150144
DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
151145
ssize_t n;
152146
while ((n = r->mReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
153-
ALOGV("receiver %p ~ Read %d events.", this, int(n));
147+
ALOGV("receiver %p ~ Read %d events.", data, int(n));
154148
while (n-- > 0) {
155149
if (buf[n].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
156150
vsyncTimestamp = buf[n].header.timestamp;
@@ -161,34 +155,28 @@ int NativeDisplayEventReceiver::handleReceiveCallback(int receiveFd, int events,
161155
}
162156

163157
if (vsyncTimestamp < 0) {
164-
ALOGV("receiver %p ~ Woke up but there was no vsync pulse!", this);
158+
ALOGV("receiver %p ~ Woke up but there was no vsync pulse!", data);
165159
return 1; // keep the callback, did not obtain a vsync pulse
166160
}
167161

168162
ALOGV("receiver %p ~ Vsync pulse: timestamp=%lld, count=%d",
169-
this, vsyncTimestamp, vsyncCount);
163+
data, vsyncTimestamp, vsyncCount);
170164
r->mWaitingForVsync = false;
171165

172166
JNIEnv* env = AndroidRuntime::getJNIEnv();
173167

174-
ALOGV("receiver %p ~ Invoking vsync handler.", this);
168+
ALOGV("receiver %p ~ Invoking vsync handler.", data);
175169
env->CallVoidMethod(r->mReceiverObjGlobal,
176170
gDisplayEventReceiverClassInfo.dispatchVsync, vsyncTimestamp, vsyncCount);
177-
ALOGV("receiver %p ~ Returned from vsync handler.", this);
171+
ALOGV("receiver %p ~ Returned from vsync handler.", data);
178172

179173
if (env->ExceptionCheck()) {
180174
ALOGE("An exception occurred while dispatching a vsync event.");
181175
LOGE_EX(env);
182176
env->ExceptionClear();
183177
}
184178

185-
// Check whether dispatchVsync called scheduleVsync reentrantly and set mWaitingForVsync.
186-
// If so, keep the callback, otherwise remove it.
187-
if (r->mWaitingForVsync) {
188-
return 1; // keep the callback
189-
}
190-
r->mFdCallbackRegistered = false;
191-
return 0; // remove the callback
179+
return 1; // keep the callback
192180
}
193181

194182

0 commit comments

Comments
 (0)