Avoid deadlocks in CameraEngine

pull/696/head
Mattia Iavarone 6 years ago
parent 7f2183d23e
commit 0e49bc14f4
  1. 21
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java
  2. 9
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  3. 21
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java

@ -292,15 +292,20 @@ public class Camera1Engine extends CameraEngine implements
if (mCamera != null) {
try {
LOG.i("onStopEngine:", "Clean up.", "Releasing camera.");
// TODO just like Camera1Engine, this call can hang (at least on emulators) and make
// the destroy() cleanup fail the timeout, thus leaving camera in a bad state.
// Just like Camera2Engine, this call can hang (at least on emulators) and if
// we don't find a way around the lock, it leaves the camera in a bad state.
// This is anticipated by the exception in onStopBind() (see above).
// 12:29:32.163 1512 1512 E Camera3-Device: Camera 0: clearStreamingRequest: Device has encountered a serious error
// 12:29:32.163 1512 1512 E Camera2-StreamingProcessor: stopStream: Camera 0: Can't clear stream request: Function not implemented (-38)
// 12:29:32.163 1512 1512 E Camera2Client: stopPreviewL: Camera 0: Can't stop streaming: Function not implemented (-38)
// 12:29:32.273 1512 1512 E Camera2-StreamingProcessor: deletePreviewStream: Unable to delete old preview stream: Device or resource busy (-16)
// 12:29:32.274 1512 1512 E Camera2-CallbackProcessor: deleteStream: Unable to delete callback stream: Device or resource busy (-16)
// 12:29:32.274 1512 1512 E Camera3-Device: Camera 0: disconnect: Shutting down in an error state
//
// 12:29:32.163 E Camera3-Device: Camera 0: clearStreamingRequest: Device has encountered a serious error
// 12:29:32.163 E Camera2-StreamingProcessor: stopStream: Camera 0: Can't clear stream request: Function not implemented (-38)
// 12:29:32.163 E Camera2Client: stopPreviewL: Camera 0: Can't stop streaming: Function not implemented (-38)
// 12:29:32.273 E Camera2-StreamingProcessor: deletePreviewStream: Unable to delete old preview stream: Device or resource busy (-16)
// 12:29:32.274 E Camera2-CallbackProcessor: deleteStream: Unable to delete callback stream: Device or resource busy (-16)
// 12:29:32.274 E Camera3-Device: Camera 0: disconnect: Shutting down in an error state
//
// I believe there is a thread deadlock due to this call internally waiting to
// dispatch some callback to us (pending captures, ...), but the callback thread
// is blocked here. We try to workaround this in CameraEngine.destroy().
mCamera.release();
LOG.i("onStopEngine:", "Clean up.", "Released camera.");
} catch (Exception e) {

@ -695,13 +695,18 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
protected Task<Void> onStopEngine() {
try {
LOG.i("onStopEngine:", "Clean up.", "Releasing camera.");
// TODO just like Camera1Engine, this call can hang (at least on emulators) and make
// the destroy() cleanup fail the timeout, thus leaving camera in a bad state.
// Just like Camera1Engine, this call can hang (at least on emulators) and if
// we don't find a way around the lock, it leaves the camera in a bad state.
//
// 12:33:28.152 2888 5470 I CameraEngine: onStopEngine: Clean up. Releasing camera.
// 12:33:29.476 1384 1555 E audio_hw_generic: pcm_write failed cannot write stream data: I/O error
// 12:33:33.206 1512 3616 E Camera3-Device: Camera 0: waitUntilDrainedLocked: Error waiting for HAL to drain: Connection timed out (-110)
// 12:33:33.242 1512 3616 E CameraDeviceClient: detachDevice: waitUntilDrained failed with code 0xffffff92
// 12:33:33.243 1512 3616 E Camera3-Device: Camera 0: disconnect: Shutting down in an error state
//
// I believe there is a thread deadlock due to this call internally waiting to
// dispatch some callback to us (pending captures, ...), but the callback thread
// is blocked here. We try to workaround this in CameraEngine.destroy().
mCamera.close();
LOG.i("onStopEngine:", "Clean up.", "Released camera.");
} catch (Exception e) {

@ -333,22 +333,25 @@ public abstract class CameraEngine implements
* Calls {@link #stop(boolean)} and waits for it.
* Not final due to mockito requirements.
*
* If forever is true, this also releases resources and the engine will not be in a
* If unrecoverably is true, this also releases resources and the engine will not be in a
* functional state after. If forever is false, this really is just a synchronous stop.
*
* NOTE: Should not be called on the orchestrator thread! This would cause deadlocks due to us
* awaiting for {@link #stop(boolean)} to return.
*/
public void destroy(boolean forever) {
public void destroy(boolean unrecoverably) {
LOG.i("DESTROY:", "state:", getState(),
"thread:", Thread.currentThread(),
"forever:", forever);
if (forever) {
"unrecoverably:", unrecoverably);
if (unrecoverably) {
// Prevent CameraEngine leaks. Don't set to null, or exceptions
// inside the standard stop() method might crash the main thread.
mHandler.getThread().setUncaughtExceptionHandler(new NoOpExceptionHandler());
}
// Stop if needed, synchronously and silently.
// Instead of waiting forever, it's better to wait a fixed amount of time and loop.
// This frees the thread in between the loop, and possibly solves deadlock issues
// in the internal camera implementation (see Camera1Engine.onStopEngine() and
// Camera2Engine.onStopEngine()).
// Cannot use Tasks.await() because we might be on the UI thread.
final CountDownLatch latch = new CountDownLatch(1);
stop(true).addOnCompleteListener(
@ -360,11 +363,13 @@ public abstract class CameraEngine implements
}
});
try {
boolean success = latch.await(10, TimeUnit.SECONDS);
boolean success = latch.await(2000, TimeUnit.MILLISECONDS);
if (!success) {
LOG.e("DESTROY: Could not destroy synchronously after 10 seconds.",
LOG.w("DESTROY: Could not destroy synchronously after 2 seconds.",
"Current thread:", Thread.currentThread(),
"Handler thread: ", mHandler.getThread());
"Handler thread: ", mHandler.getThread(),
"Trying again...");
destroy(unrecoverably);
}
} catch (InterruptedException ignore) {}
}

Loading…
Cancel
Save