am 04f07d28: am 2a555d0a: am d3edd1c2: am ba164dfb: Merge "camera2: Fix race conditions and deadlocks around configuration" into lmp-dev

* commit '04f07d2808c260db92324fe21338085a4dd9452a':
  camera2: Fix race conditions and deadlocks around configuration
This commit is contained in:
Igor Murashkin
2014-09-26 22:41:15 +00:00
committed by Android Git Automerger
7 changed files with 96 additions and 24 deletions

View File

@@ -633,7 +633,11 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
@Override @Override
public void onDrained() { public void onDrained() {
if (VERBOSE) Log.v(TAG, mIdString + "onIdleDrained"); if (VERBOSE) Log.v(TAG, mIdString + "onIdleDrained");
synchronized (CameraCaptureSessionImpl.this) {
// Take device lock before session lock so that we can call back into device
// without causing a deadlock
synchronized (mDeviceImpl.mInterfaceLock) {
synchronized (CameraCaptureSessionImpl.this) {
/* /*
* The device is now IDLE, and has settled. It will not transition to * The device is now IDLE, and has settled. It will not transition to
* ACTIVE or BUSY again by itself. * ACTIVE or BUSY again by itself.
@@ -642,28 +646,31 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession {
* *
* This operation is idempotent; a session will not be closed twice. * This operation is idempotent; a session will not be closed twice.
*/ */
if (VERBOSE) Log.v(TAG, mIdString + "Session drain complete, skip unconfigure: " + if (VERBOSE)
mSkipUnconfigure); Log.v(TAG, mIdString + "Session drain complete, skip unconfigure: " +
mSkipUnconfigure);
// Fast path: A new capture session has replaced this one; don't unconfigure. // Fast path: A new capture session has replaced this one; don't unconfigure.
if (mSkipUnconfigure) { if (mSkipUnconfigure) {
mStateCallback.onClosed(CameraCaptureSessionImpl.this); mStateCallback.onClosed(CameraCaptureSessionImpl.this);
return; return;
}
// Slow path: #close was called explicitly on this session; unconfigure first
try {
mUnconfigureDrainer.taskStarted();
mDeviceImpl
.configureOutputsChecked(null); // begin transition to unconfigured
} catch (CameraAccessException e) {
// OK: do not throw checked exceptions.
Log.e(TAG, mIdString + "Exception while configuring outputs: ", e);
// TODO: call onError instead of onClosed if this happens
}
mUnconfigureDrainer.beginDrain();
} }
// Slow path: #close was called explicitly on this session; unconfigure first
try {
mUnconfigureDrainer.taskStarted();
mDeviceImpl.configureOutputsChecked(null); // begin transition to unconfigured
} catch (CameraAccessException e) {
// OK: do not throw checked exceptions.
Log.e(TAG, mIdString + "Exception while configuring outputs: ", e);
// TODO: call onError instead of onClosed if this happens
}
mUnconfigureDrainer.beginDrain();
} }
} }
} }

View File

@@ -60,7 +60,7 @@ public class CameraDeviceImpl extends CameraDevice {
private ICameraDeviceUser mRemoteDevice; private ICameraDeviceUser mRemoteDevice;
// Lock to synchronize cross-thread access to device public interface // Lock to synchronize cross-thread access to device public interface
private final Object mInterfaceLock = new Object(); final Object mInterfaceLock = new Object(); // access from this class and Session only!
private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks(); private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks();
private final StateCallback mDeviceCallback; private final StateCallback mDeviceCallback;

View File

@@ -73,6 +73,7 @@ public class CameraDeviceState {
void onError(int errorCode, RequestHolder holder); void onError(int errorCode, RequestHolder holder);
void onConfiguring(); void onConfiguring();
void onIdle(); void onIdle();
void onBusy();
void onCaptureStarted(RequestHolder holder, long timestamp); void onCaptureStarted(RequestHolder holder, long timestamp);
void onCaptureResult(CameraMetadataNative result, RequestHolder holder); void onCaptureResult(CameraMetadataNative result, RequestHolder holder);
} }
@@ -217,6 +218,20 @@ public class CameraDeviceState {
} }
Log.i(TAG, "Legacy camera service transitioning to state " + stateName); Log.i(TAG, "Legacy camera service transitioning to state " + stateName);
} }
// If we transitioned into a non-IDLE/non-ERROR state then mark the device as busy
if(newState != STATE_ERROR && newState != STATE_IDLE) {
if (mCurrentState != newState && mCurrentHandler != null &&
mCurrentListener != null) {
mCurrentHandler.post(new Runnable() {
@Override
public void run() {
mCurrentListener.onBusy();
}
});
}
}
switch(newState) { switch(newState) {
case STATE_ERROR: case STATE_ERROR:
if (mCurrentState != STATE_ERROR && mCurrentHandler != null && if (mCurrentState != STATE_ERROR && mCurrentHandler != null &&

View File

@@ -113,6 +113,9 @@ public class GLThreadManager {
case MSG_ALLOW_FRAMES: case MSG_ALLOW_FRAMES:
mDroppingFrames = false; mDroppingFrames = false;
break; break;
case RequestHandlerThread.MSG_POKE_IDLE_HANDLER:
// OK: Ignore message.
break;
default: default:
Log.e(TAG, "Unhandled message " + msg.what + " on GLThread."); Log.e(TAG, "Unhandled message " + msg.what + " on GLThread.");
break; break;

View File

@@ -21,6 +21,7 @@ import android.graphics.SurfaceTexture;
import android.hardware.Camera; import android.hardware.Camera;
import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.impl.CameraDeviceImpl;
import android.hardware.camera2.impl.CaptureResultExtras; import android.hardware.camera2.impl.CaptureResultExtras;
import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.params.StreamConfiguration; import android.hardware.camera2.params.StreamConfiguration;
@@ -95,7 +96,25 @@ public class LegacyCameraDevice implements AutoCloseable {
new CameraDeviceState.CameraDeviceStateListener() { new CameraDeviceState.CameraDeviceStateListener() {
@Override @Override
public void onError(final int errorCode, final RequestHolder holder) { public void onError(final int errorCode, final RequestHolder holder) {
mIdle.open(); if (DEBUG) {
Log.d(TAG, "onError called, errorCode = " + errorCode);
}
switch (errorCode) {
/*
* Only be considered idle if we hit a fatal error
* and no further requests can be processed.
*/
case CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DISCONNECTED:
case CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_SERVICE:
case CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE: {
mIdle.open();
if (DEBUG) {
Log.d(TAG, "onError - opening idle");
}
}
}
final CaptureResultExtras extras = getExtrasFromRequest(holder); final CaptureResultExtras extras = getExtrasFromRequest(holder);
mResultHandler.post(new Runnable() { mResultHandler.post(new Runnable() {
@Override @Override
@@ -124,6 +143,10 @@ public class LegacyCameraDevice implements AutoCloseable {
@Override @Override
public void onIdle() { public void onIdle() {
if (DEBUG) {
Log.d(TAG, "onIdle called");
}
mIdle.open(); mIdle.open();
mResultHandler.post(new Runnable() { mResultHandler.post(new Runnable() {
@@ -142,6 +165,15 @@ public class LegacyCameraDevice implements AutoCloseable {
}); });
} }
@Override
public void onBusy() {
mIdle.close();
if (DEBUG) {
Log.d(TAG, "onBusy called");
}
}
@Override @Override
public void onCaptureStarted(final RequestHolder holder, final long timestamp) { public void onCaptureStarted(final RequestHolder holder, final long timestamp) {
final CaptureResultExtras extras = getExtrasFromRequest(holder); final CaptureResultExtras extras = getExtrasFromRequest(holder);

View File

@@ -23,6 +23,15 @@ import android.os.Looper;
import android.os.MessageQueue; import android.os.MessageQueue;
public class RequestHandlerThread extends HandlerThread { public class RequestHandlerThread extends HandlerThread {
/**
* Ensure that the MessageQueue's idle handler gets run by poking the message queue;
* normally if the message queue is already idle, the idle handler won't get invoked.
*
* <p>Users of this handler thread should ignore this message.</p>
*/
public final static int MSG_POKE_IDLE_HANDLER = -1;
private final ConditionVariable mStarted = new ConditionVariable(false); private final ConditionVariable mStarted = new ConditionVariable(false);
private final ConditionVariable mIdle = new ConditionVariable(true); private final ConditionVariable mIdle = new ConditionVariable(true);
private Handler.Callback mCallback; private Handler.Callback mCallback;
@@ -86,12 +95,15 @@ public class RequestHandlerThread extends HandlerThread {
// Blocks until thread is idling // Blocks until thread is idling
public void waitUntilIdle() { public void waitUntilIdle() {
Looper looper = waitAndGetHandler().getLooper(); Handler handler = waitAndGetHandler();
Looper looper = handler.getLooper();
if (looper.isIdling()) { if (looper.isIdling()) {
return; return;
} }
mIdle.close(); mIdle.close();
looper.getQueue().addIdleHandler(mIdleHandler); looper.getQueue().addIdleHandler(mIdleHandler);
// Ensure that the idle handler gets run even if the looper already went idle
handler.sendEmptyMessage(MSG_POKE_IDLE_HANDLER);
if (looper.isIdling()) { if (looper.isIdling()) {
return; return;
} }

View File

@@ -864,6 +864,9 @@ public class RequestThreadManager {
} }
resetJpegSurfaceFormats(mCallbackOutputs); resetJpegSurfaceFormats(mCallbackOutputs);
break; break;
case RequestHandlerThread.MSG_POKE_IDLE_HANDLER:
// OK: Ignore message.
break;
default: default:
throw new AssertionError("Unhandled message " + msg.what + throw new AssertionError("Unhandled message " + msg.what +
" on RequestThread."); " on RequestThread.");