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) { if (mCamera != null) {
try { try {
LOG.i("onStopEngine:", "Clean up.", "Releasing camera."); LOG.i("onStopEngine:", "Clean up.", "Releasing camera.");
// TODO just like Camera1Engine, this call can hang (at least on emulators) and make // Just like Camera2Engine, this call can hang (at least on emulators) and if
// the destroy() cleanup fail the timeout, thus leaving camera in a bad state. // 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). // 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 E Camera3-Device: Camera 0: clearStreamingRequest: Device has encountered a serious error
// 12:29:32.163 1512 1512 E Camera2Client: stopPreviewL: Camera 0: Can't stop streaming: Function not implemented (-38) // 12:29:32.163 E Camera2-StreamingProcessor: stopStream: Camera 0: Can't clear stream request: 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.163 E Camera2Client: stopPreviewL: Camera 0: Can't stop streaming: Function not implemented (-38)
// 12:29:32.274 1512 1512 E Camera2-CallbackProcessor: deleteStream: Unable to delete callback stream: Device or resource busy (-16) // 12:29:32.273 E Camera2-StreamingProcessor: deletePreviewStream: Unable to delete old preview 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.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(); mCamera.release();
LOG.i("onStopEngine:", "Clean up.", "Released camera."); LOG.i("onStopEngine:", "Clean up.", "Released camera.");
} catch (Exception e) { } catch (Exception e) {

@ -695,13 +695,18 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
protected Task<Void> onStopEngine() { protected Task<Void> onStopEngine() {
try { try {
LOG.i("onStopEngine:", "Clean up.", "Releasing camera."); LOG.i("onStopEngine:", "Clean up.", "Releasing camera.");
// TODO just like Camera1Engine, this call can hang (at least on emulators) and make // Just like Camera1Engine, this call can hang (at least on emulators) and if
// the destroy() cleanup fail the timeout, thus leaving camera in a bad state. // 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: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: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.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.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 // 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(); mCamera.close();
LOG.i("onStopEngine:", "Clean up.", "Released camera."); LOG.i("onStopEngine:", "Clean up.", "Released camera.");
} catch (Exception e) { } catch (Exception e) {

@ -333,22 +333,25 @@ public abstract class CameraEngine implements
* Calls {@link #stop(boolean)} and waits for it. * Calls {@link #stop(boolean)} and waits for it.
* Not final due to mockito requirements. * 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. * 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 * NOTE: Should not be called on the orchestrator thread! This would cause deadlocks due to us
* awaiting for {@link #stop(boolean)} to return. * awaiting for {@link #stop(boolean)} to return.
*/ */
public void destroy(boolean forever) { public void destroy(boolean unrecoverably) {
LOG.i("DESTROY:", "state:", getState(), LOG.i("DESTROY:", "state:", getState(),
"thread:", Thread.currentThread(), "thread:", Thread.currentThread(),
"forever:", forever); "unrecoverably:", unrecoverably);
if (forever) { if (unrecoverably) {
// Prevent CameraEngine leaks. Don't set to null, or exceptions // Prevent CameraEngine leaks. Don't set to null, or exceptions
// inside the standard stop() method might crash the main thread. // inside the standard stop() method might crash the main thread.
mHandler.getThread().setUncaughtExceptionHandler(new NoOpExceptionHandler()); 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. // Cannot use Tasks.await() because we might be on the UI thread.
final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch latch = new CountDownLatch(1);
stop(true).addOnCompleteListener( stop(true).addOnCompleteListener(
@ -360,11 +363,13 @@ public abstract class CameraEngine implements
} }
}); });
try { try {
boolean success = latch.await(10, TimeUnit.SECONDS); boolean success = latch.await(2000, TimeUnit.MILLISECONDS);
if (!success) { 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(), "Current thread:", Thread.currentThread(),
"Handler thread: ", mHandler.getThread()); "Handler thread: ", mHandler.getThread(),
"Trying again...");
destroy(unrecoverably);
} }
} catch (InterruptedException ignore) {} } catch (InterruptedException ignore) {}
} }

Loading…
Cancel
Save