Camera: Avoid holding locks during extension initialization

Holding the interface lock while initializing the extension
is not really necessary and can potentially result in a deadlock
in case of failure where callbacks try to close the camera capture
session in the handler thread, and another thread tries to close
the camera device acquiring the interface lock there:
Thread1:
 CameraDeviceImpl.close()
    acquire the device interface lock
    release advanced extension
        try to acquire the extension characteristics lock

Thread2:
 InitializationSessionHandler.onFailure()
    while holding the extension characteristics lock
    mCaptureSession.close()
        try to acquire the device interface lock

Additionally re-use the device interface lock for the extension session
synchronization. There is no real need to use a separate lock which
complicates the lock acquire order.

Bug: 270276341
Test: atest -c -d
cts/tests/camera/src/android/hardware/camera2/cts/CameraExtensionSessionTest.java

Change-Id: Ice808c8d5538e3adb3220d7ea51dea4b14cc2f44
This commit is contained in:
Emilian Peev
2023-02-23 16:48:53 -08:00
parent ae41acbe90
commit 6231472fb1
2 changed files with 105 additions and 81 deletions

View File

@@ -98,15 +98,15 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
// Lock to synchronize cross-thread access to device public interface
final Object mInterfaceLock = new Object(); // access from this class and Session only!
final Object mInterfaceLock;
/**
* @hide
*/
@RequiresPermission(android.Manifest.permission.CAMERA)
public static CameraAdvancedExtensionSessionImpl createCameraAdvancedExtensionSession(
@NonNull CameraDevice cameraDevice, @NonNull Context ctx,
@NonNull ExtensionSessionConfiguration config, int sessionId)
@NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
@NonNull Context ctx, @NonNull ExtensionSessionConfiguration config, int sessionId)
throws CameraAccessException, RemoteException {
long clientId = CameraExtensionCharacteristics.registerClient(ctx);
if (clientId < 0) {
@@ -183,7 +183,8 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
}
private CameraAdvancedExtensionSessionImpl(long extensionClientId,
@NonNull IAdvancedExtenderImpl extender, @NonNull CameraDevice cameraDevice,
@NonNull IAdvancedExtenderImpl extender,
@NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
@Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface,
@NonNull CameraExtensionSession.StateCallback callback, @NonNull Executor executor,
int sessionId) {
@@ -200,6 +201,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
mInitialized = false;
mInitializeHandler = new InitializeSessionHandler();
mSessionId = sessionId;
mInterfaceLock = cameraDevice.mInterfaceLock;
}
/**
@@ -511,13 +513,14 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
public void onConfigured(@NonNull CameraCaptureSession session) {
synchronized (mInterfaceLock) {
mCaptureSession = session;
try {
CameraExtensionCharacteristics.initializeSession(mInitializeHandler);
} catch (RemoteException e) {
Log.e(TAG, "Failed to initialize session! Extension service does"
+ " not respond!");
notifyConfigurationFailure();
}
}
try {
CameraExtensionCharacteristics.initializeSession(mInitializeHandler);
} catch (RemoteException e) {
Log.e(TAG, "Failed to initialize session! Extension service does"
+ " not respond!");
notifyConfigurationFailure();
}
}
}
@@ -525,46 +528,56 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
private class InitializeSessionHandler extends IInitializeSessionCallback.Stub {
@Override
public void onSuccess() {
boolean status = true;
synchronized (mInterfaceLock) {
try {
if (mSessionProcessor != null) {
mSessionProcessor.onCaptureSessionStart(mRequestProcessor);
mInitialized = true;
} else {
Log.v(TAG, "Failed to start capture session, session released before " +
"extension start!");
status = false;
mCaptureSession.close();
mHandler.post(new Runnable() {
@Override
public void run() {
boolean status = true;
synchronized (mInterfaceLock) {
try {
if (mSessionProcessor != null) {
mSessionProcessor.onCaptureSessionStart(mRequestProcessor);
mInitialized = true;
} else {
Log.v(TAG, "Failed to start capture session, session " +
" released before extension start!");
status = false;
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to start capture session,"
+ " extension service does not respond!");
status = false;
mInitialized = false;
}
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to start capture session,"
+ " extension service does not respond!");
status = false;
mCaptureSession.close();
}
}
if (status) {
final long ident = Binder.clearCallingIdentity();
try {
mExecutor.execute(
() -> mCallbacks.onConfigured(CameraAdvancedExtensionSessionImpl.this));
} finally {
Binder.restoreCallingIdentity(ident);
if (status) {
final long ident = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mCallbacks.onConfigured(
CameraAdvancedExtensionSessionImpl.this));
} finally {
Binder.restoreCallingIdentity(ident);
}
} else {
onFailure();
}
}
} else {
notifyConfigurationFailure();
}
});
}
@Override
public void onFailure() {
mCaptureSession.close();
Log.e(TAG, "Failed to initialize proxy service session!"
+ " This can happen when trying to configure multiple "
+ "concurrent extension sessions!");
notifyConfigurationFailure();
mHandler.post(new Runnable() {
@Override
public void run() {
mCaptureSession.close();
Log.e(TAG, "Failed to initialize proxy service session!"
+ " This can happen when trying to configure multiple "
+ "concurrent extension sessions!");
notifyConfigurationFailure();
}
});
}
}

View File

@@ -114,7 +114,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
private boolean mInternalRepeatingRequestEnabled = true;
// Lock to synchronize cross-thread access to device public interface
final Object mInterfaceLock = new Object(); // access from this class and Session only!
final Object mInterfaceLock;
private static int nativeGetSurfaceFormat(Surface surface) {
return SurfaceUtils.getSurfaceFormat(surface);
@@ -125,7 +125,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
*/
@RequiresPermission(android.Manifest.permission.CAMERA)
public static CameraExtensionSessionImpl createCameraExtensionSession(
@NonNull CameraDevice cameraDevice,
@NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
@NonNull Context ctx,
@NonNull ExtensionSessionConfiguration config,
int sessionId)
@@ -223,7 +223,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
@NonNull IPreviewExtenderImpl previewExtender,
@NonNull List<Size> previewSizes,
long extensionClientId,
@NonNull CameraDevice cameraDevice,
@NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice,
@Nullable Surface repeatingRequestSurface,
@Nullable Surface burstCaptureSurface,
@NonNull StateCallback callback,
@@ -249,6 +249,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
mSupportedRequestKeys = requestKeys;
mSupportedResultKeys = resultKeys;
mCaptureResultsSupported = !resultKeys.isEmpty();
mInterfaceLock = cameraDevice.mInterfaceLock;
}
private void initializeRepeatingRequestPipeline() throws RemoteException {
@@ -856,46 +857,56 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
private class InitializeSessionHandler extends IInitializeSessionCallback.Stub {
@Override
public void onSuccess() {
boolean status = true;
ArrayList<CaptureStageImpl> initialRequestList =
compileInitialRequestList();
if (!initialRequestList.isEmpty()) {
try {
setInitialCaptureRequest(initialRequestList,
new InitialRequestHandler(
mRepeatingRequestImageCallback));
} catch (CameraAccessException e) {
Log.e(TAG,
"Failed to initialize the initial capture "
+ "request!");
status = false;
}
} else {
try {
setRepeatingRequest(mPreviewExtender.getCaptureStage(),
new PreviewRequestHandler(null, null, null,
mRepeatingRequestImageCallback));
} catch (CameraAccessException | RemoteException e) {
Log.e(TAG,
"Failed to initialize internal repeating "
+ "request!");
status = false;
}
mHandler.post(new Runnable() {
@Override
public void run() {
boolean status = true;
ArrayList<CaptureStageImpl> initialRequestList =
compileInitialRequestList();
if (!initialRequestList.isEmpty()) {
try {
setInitialCaptureRequest(initialRequestList,
new InitialRequestHandler(
mRepeatingRequestImageCallback));
} catch (CameraAccessException e) {
Log.e(TAG,
"Failed to initialize the initial capture "
+ "request!");
status = false;
}
} else {
try {
setRepeatingRequest(mPreviewExtender.getCaptureStage(),
new PreviewRequestHandler(null, null, null,
mRepeatingRequestImageCallback));
} catch (CameraAccessException | RemoteException e) {
Log.e(TAG,
"Failed to initialize internal repeating "
+ "request!");
status = false;
}
}
}
if (!status) {
notifyConfigurationFailure();
}
if (!status) {
notifyConfigurationFailure();
}
}
});
}
@Override
public void onFailure() {
mCaptureSession.close();
Log.e(TAG, "Failed to initialize proxy service session!"
+ " This can happen when trying to configure multiple "
+ "concurrent extension sessions!");
notifyConfigurationFailure();
mHandler.post(new Runnable() {
@Override
public void run() {
mCaptureSession.close();
Log.e(TAG, "Failed to initialize proxy service session!"
+ " This can happen when trying to configure multiple "
+ "concurrent extension sessions!");
notifyConfigurationFailure();
}
});
}
}