diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java index 964bca65..05ced09a 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.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) { diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java index c68145c4..dae3de0a 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java @@ -695,13 +695,18 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv protected Task 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) { diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java index 8a129744..baedf583 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java @@ -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) {} }