Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 79a3981e authored by Jeff Brown's avatar Jeff Brown
Browse files

Fix Looper leaks in MediaRecorderStressTest.

The test was failing periodically due to too many files being open.
This change attempts to resolve the problem on the theory that
signaling pipe file descriptors are being leaked due to the large
number of Looper instances created during the test run.

However, it's still possible there are other leaks elsewhere.

Change-Id: I71f9f12d21605c47c9217c72c51e6c768142ce10
parent f4619308
Loading
Loading
Loading
Loading
+89 −111
Original line number Diff line number Diff line
@@ -23,10 +23,13 @@ import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import android.hardware.Camera;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.os.Handler;
import android.os.Looper;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;
@@ -51,105 +54,90 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
    private static final int NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER = 200;
    private static final long WAIT_TIME_CAMERA_TEST = 3000;  // 3 second
    private static final long WAIT_TIME_RECORDER_TEST = 6000;  // 6 second
    private static final long WAIT_TIME_RECORD = 10000;  // 10 seconds
    private static final long WAIT_TIME_PLAYBACK = 6000;  // 6 second
    private static final String OUTPUT_FILE = "/sdcard/temp";
    private static final String OUTPUT_FILE_EXT = ".3gp";
    private static final String MEDIA_STRESS_OUTPUT =
        "/sdcard/mediaStressOutput.txt";
    private Looper mCameraLooper = null;
    private Looper mRecorderLooper = null;
    private final Object lock = new Object();
    private final Object recorderlock = new Object();
    private static int WAIT_FOR_COMMAND_TO_COMPLETE = 10000;  // Milliseconds.
    private final CameraErrorCallback mCameraErrorCallback = new CameraErrorCallback();
    private final RecorderErrorCallback mRecorderErrorCallback = new RecorderErrorCallback();

    private final static int WAIT_TIMEOUT = 10000;
    private Thread mLooperThread;
    private Handler mHandler;

    public MediaRecorderStressTest() {
        super("com.android.mediaframeworktest", MediaFrameworkTest.class);
    }

    protected void setUp() throws Exception {
        final Semaphore sem = new Semaphore(0);
        mLooperThread = new Thread() {
            @Override
            public void run() {
                Log.v(TAG, "starting looper");
                Looper.prepare();
                mHandler = new Handler();
                sem.release();
                Looper.loop();
                Log.v(TAG, "quit looper");
            }
        };
        mLooperThread.start();
        if (! sem.tryAcquire(WAIT_TIMEOUT, TimeUnit.MILLISECONDS)) {
            fail("Failed to start the looper.");
        }

        getActivity();
        super.setUp();
    }

    private final class CameraErrorCallback implements android.hardware.Camera.ErrorCallback {
        public void onError(int error, android.hardware.Camera camera) {
            if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) {
                assertTrue("Camera test mediaserver died", false);
    @Override
    protected void tearDown() throws Exception {
        if (mHandler != null) {
            mHandler.getLooper().quit();
            mHandler = null;
        }
        if (mLooperThread != null) {
            mLooperThread.join(WAIT_TIMEOUT);
            if (mLooperThread.isAlive()) {
                fail("Failed to stop the looper.");
            }
            mLooperThread = null;
        }

    private final class RecorderErrorCallback implements MediaRecorder.OnErrorListener {
        public void onError(MediaRecorder mr, int what, int extra) {
            // fail the test case no matter what error come up
            assertTrue("mediaRecorder error", false);
        }
        super.tearDown();
    }

    private void initializeCameraMessageLooper() {
        Log.v(TAG, "start looper");
        new Thread() {
    private void runOnLooper(final Runnable command) throws InterruptedException {
        final Semaphore sem = new Semaphore(0);
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                // Set up a looper to be used by camera.
                Looper.prepare();
                Log.v(TAG, "start loopRun");
                mCameraLooper = Looper.myLooper();
                mCamera = Camera.open();
                synchronized (lock) {
                    lock.notify();
                }
                Looper.loop();
                Log.v(TAG, "initializeMessageLooper: quit.");
                try {
                    command.run();
                } finally {
                    sem.release();
                }
        }.start();
            }

    private void initializeRecorderMessageLooper() {
        Log.v(TAG, "start looper");
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                Log.v(TAG, "start loopRun");
                mRecorderLooper = Looper.myLooper();
                mRecorder = new MediaRecorder();
                synchronized (recorderlock) {
                    recorderlock.notify();
        });
        if (! sem.tryAcquire(WAIT_TIMEOUT, TimeUnit.MILLISECONDS)) {
            fail("Failed to run the command on the looper.");
        }
                Looper.loop();  // Blocks forever until Looper.quit() is called.
                Log.v(TAG, "initializeMessageLooper: quit.");
            }
        }.start();
    }

    /*
     * Terminates the message looper thread.
     */
    private void terminateCameraMessageLooper() {
        mCameraLooper.quit();
        try {
            Thread.sleep(1000);
        } catch (Exception e){
            Log.v(TAG, e.toString());
    private final class CameraErrorCallback implements android.hardware.Camera.ErrorCallback {
        public void onError(int error, android.hardware.Camera camera) {
            if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) {
                assertTrue("Camera test mediaserver died", false);
            }
        }
        mCamera.release();
    }

    /*
     * Terminates the message looper thread.
     */
    private void terminateRecorderMessageLooper() {
        mRecorderLooper.quit();
        try {
            Thread.sleep(1000);
        } catch (Exception e){
            Log.v(TAG, e.toString());
    private final class RecorderErrorCallback implements MediaRecorder.OnErrorListener {
        public void onError(MediaRecorder mr, int what, int extra) {
            // fail the test case no matter what error come up
            assertTrue("mediaRecorder error", false);
        }
        mRecorder.release();
    }

    //Test case for stressing the camera preview.
@@ -167,20 +155,18 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
            output.write("No of loop: ");

            for (int i = 0; i< NUMBER_OF_CAMERA_STRESS_LOOPS; i++) {
                synchronized (lock) {
                    initializeCameraMessageLooper();
                    try {
                        lock.wait(WAIT_FOR_COMMAND_TO_COMPLETE);
                    } catch(Exception e) {
                        Log.v(TAG, "wait was interrupted.");
                    }
                runOnLooper(new Runnable() {
                    @Override
                    public void run() {
                        mCamera = Camera.open();
                    }
                });
                mCamera.setErrorCallback(mCameraErrorCallback);
                mCamera.setPreviewDisplay(mSurfaceHolder);
                mCamera.startPreview();
                Thread.sleep(WAIT_TIME_CAMERA_TEST);
                mCamera.stopPreview();
                terminateCameraMessageLooper();
                mCamera.release();
                output.write(" ," + i);
            }
        } catch (Exception e) {
@@ -206,14 +192,12 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
            output.write("No of loop: ");
            Log.v(TAG, "Start preview");
            for (int i = 0; i < NUMBER_OF_RECORDER_STRESS_LOOPS; i++) {
                synchronized (recorderlock) {
                    initializeRecorderMessageLooper();
                    try {
                        recorderlock.wait(WAIT_FOR_COMMAND_TO_COMPLETE);
                    } catch(Exception e) {
                        Log.v(TAG, "wait was interrupted.");
                    }
                runOnLooper(new Runnable() {
                    @Override
                    public void run() {
                        mRecorder = new MediaRecorder();
                    }
                });
                Log.v(TAG, "counter = " + i);
                filename = OUTPUT_FILE + i + OUTPUT_FILE_EXT;
                Log.v(TAG, filename);
@@ -233,7 +217,7 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
                Log.v(TAG, "before release");
                Thread.sleep(WAIT_TIME_RECORDER_TEST);
                mRecorder.reset();
                terminateRecorderMessageLooper();
                mRecorder.release();
                output.write(", " + i);
            }
        } catch (Exception e) {
@@ -259,32 +243,28 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
            Log.v(TAG, "Start preview");
            output.write("No of loop: ");
            for (int i = 0; i < NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER; i++) {
                synchronized (lock) {
                    initializeCameraMessageLooper();
                    try {
                        lock.wait(WAIT_FOR_COMMAND_TO_COMPLETE);
                    } catch(Exception e) {
                        Log.v(TAG, "wait was interrupted.");
                    }
                runOnLooper(new Runnable() {
                    @Override
                    public void run() {
                        mCamera = Camera.open();
                    }
                });
                mCamera.setErrorCallback(mCameraErrorCallback);
                mCamera.setPreviewDisplay(mSurfaceHolder);
                mCamera.startPreview();
                Thread.sleep(WAIT_TIME_CAMERA_TEST);
                mCamera.stopPreview();
                terminateCameraMessageLooper();
                mCamera.release();
                mCamera = null;
                Log.v(TAG, "release camera");
                filename = OUTPUT_FILE + i + OUTPUT_FILE_EXT;
                Log.v(TAG, filename);
                synchronized (recorderlock) {
                    initializeRecorderMessageLooper();
                    try {
                        recorderlock.wait(WAIT_FOR_COMMAND_TO_COMPLETE);
                    } catch(Exception e) {
                        Log.v(TAG, "wait was interrupted.");
                    }
                runOnLooper(new Runnable() {
                    @Override
                    public void run() {
                        mRecorder = new MediaRecorder();
                    }
                });
                mRecorder.setOnErrorListener(mRecorderErrorCallback);
                mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
                mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
@@ -299,7 +279,7 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
                mRecorder.prepare();
                Log.v(TAG, "before release");
                Thread.sleep(WAIT_TIME_CAMERA_TEST);
                terminateRecorderMessageLooper();
                mRecorder.release();
                Log.v(TAG, "release video recorder");
                output.write(", " + i);
            }
@@ -358,14 +338,12 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
            for (int i = 0; i < iterations; i++){
                filename = OUTPUT_FILE + i + OUTPUT_FILE_EXT;
                Log.v(TAG, filename);
                synchronized (recorderlock) {
                    initializeRecorderMessageLooper();
                    try {
                        recorderlock.wait(WAIT_FOR_COMMAND_TO_COMPLETE);
                    } catch(Exception e) {
                        Log.v(TAG, "wait was interrupted.");
                    }
                runOnLooper(new Runnable() {
                    @Override
                    public void run() {
                        mRecorder = new MediaRecorder();
                    }
                });
                Log.v(TAG, "iterations : " + iterations);
                Log.v(TAG, "video_encoder : " + video_encoder);
                Log.v(TAG, "audio_encoder : " + audio_encoder);
@@ -391,7 +369,7 @@ public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<Me
                Thread.sleep(record_duration);
                Log.v(TAG, "Before stop");
                mRecorder.stop();
                terminateRecorderMessageLooper();
                mRecorder.release();
                //start the playback
                MediaPlayer mp = new MediaPlayer();
                mp.setDataSource(filename);