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:
@@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 &&
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.");
|
||||||
|
|||||||
Reference in New Issue
Block a user