SurfaceTextureHelper: Fix startListening()/stopListening() race

SurfaceTextureHelper.startListening() is asynchronous and posts a Runnable to the handler thread. If stopListening() is called before that Runnable is executed, the Runnable will set the listener after stopListening() has been called. Then the next call to startListening() will fail with "SurfaceTextureHelper listener has already been set."

This CL adds a test to reproduce this bug, and a fix.

BUG=5519,b/27677772

Review URL: https://codereview.webrtc.org/1806013003

Cr-Commit-Position: refs/heads/master@{#12030}
This commit is contained in:
magjed
2016-03-17 03:13:43 -07:00
committed by Commit bot
parent 0de1c1374c
commit d8ddb796e4
2 changed files with 60 additions and 13 deletions

View File

@ -293,6 +293,19 @@ class SurfaceTextureHelper {
private boolean hasPendingTexture = false;
private volatile boolean isTextureInUse = false;
private boolean isQuitting = false;
// |pendingListener| is set in setListener() and the runnable is posted to the handler thread.
// setListener() is not allowed to be called again before stopListening(), so this is thread safe.
private OnTextureFrameAvailableListener pendingListener;
final Runnable setListenerRunnable = new Runnable() {
@Override
public void run() {
Logging.d(TAG, "Setting listener to " + pendingListener);
listener = pendingListener;
pendingListener = null;
// May alredy have a pending frame - try delivering it.
tryDeliverTextureFrame();
}
};
private SurfaceTextureHelper(EglBase.Context sharedContext, Handler handler) {
if (handler.getLooper().getThread() != Thread.currentThread()) {
@ -332,17 +345,11 @@ class SurfaceTextureHelper {
* call stopListening() first.
*/
public void startListening(final OnTextureFrameAvailableListener listener) {
if (this.listener != null) {
if (this.listener != null || this.pendingListener != null) {
throw new IllegalStateException("SurfaceTextureHelper listener has already been set.");
}
handler.post(new Runnable() {
@Override
public void run() {
SurfaceTextureHelper.this.listener = listener;
// May alredy have a pending frame - try delivering it.
tryDeliverTextureFrame();
}
});
this.pendingListener = listener;
handler.post(setListenerRunnable);
}
/**
@ -354,7 +361,10 @@ class SurfaceTextureHelper {
if (handler.getLooper().getThread() != Thread.currentThread()) {
throw new IllegalStateException("Wrong thread.");
}
Logging.d(TAG, "stopListening()");
handler.removeCallbacks(setListenerRunnable);
this.listener = null;
this.pendingListener = null;
}
/**
@ -401,6 +411,7 @@ class SurfaceTextureHelper {
* guaranteed to not receive any more onTextureFrameAvailable() after this function returns.
*/
public void dispose() {
Logging.d(TAG, "dispose()");
if (handler.getLooper().getThread() == Thread.currentThread()) {
isQuitting = true;
if (!isTextureInUse) {