Bug 1486596 - Add tests to ensure GeckoSession can be collected by the GC r=jchen
authorJames Willcox <snorp@snorp.net>
Thu, 30 Aug 2018 13:18:57 -0500
changeset 439488 c2b5dfcbd692d0c713ace2076d00b8d973c9a400
parent 439487 7e5bfc676ff76850c341dc0e17787cc761c1b49d
child 439489 fa568bd2149fb9f324fab80f8511063bb1819a81
push id34776
push usernerli@mozilla.com
push dateThu, 04 Oct 2018 04:03:46 +0000
treeherdermozilla-central@8b1f1ebed0f0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjchen
bugs1486596
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1486596 - Add tests to ensure GeckoSession can be collected by the GC r=jchen
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt
@@ -6,31 +6,42 @@ package org.mozilla.geckoview.test
 
 import org.mozilla.geckoview.GeckoSession
 import org.mozilla.geckoview.GeckoSessionSettings
 import org.mozilla.geckoview.GeckoView
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ClosedSessionAtStart
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.NullDelegate
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
 import org.mozilla.geckoview.test.util.Callbacks
+import org.mozilla.geckoview.test.util.UiThreadUtils
 
+import android.os.Debug
 import android.os.Parcelable
+import android.os.SystemClock
 import android.support.test.InstrumentationRegistry
 import android.support.test.filters.MediumTest
 import android.support.test.runner.AndroidJUnit4
+import android.util.Log
 import android.util.SparseArray
 
 import org.hamcrest.Matchers.*
 import org.junit.Test
 import org.junit.runner.RunWith
+import java.io.File
+import java.io.IOException
+import java.lang.ref.ReferenceQueue
+import java.lang.ref.WeakReference
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
 @ReuseSession(false)
 class SessionLifecycleTest : BaseSessionTest() {
+    companion object {
+        val LOGTAG = "SessionLifecycleTest"
+    }
 
     @Test fun open_interleaved() {
         val session1 = sessionRule.createOpenSession()
         val session2 = sessionRule.createOpenSession()
         session1.close()
         val session3 = sessionRule.createOpenSession()
         session2.close()
         session3.close()
@@ -365,9 +376,72 @@ class SessionLifecycleTest : BaseSession
 
             newSession.close()
             assertThat("New session can be closed", newSession.isOpen, equalTo(false))
         }
 
         sessionRule.session.reload()
         sessionRule.session.waitForPageStop()
     }
+
+    @Test fun collectClosed() {
+        // We can't use a normal scoped function like `run` because
+        // those are inlined, which leaves a local reference.
+        fun createSession(): QueuedWeakReference<GeckoSession> {
+            return QueuedWeakReference<GeckoSession>(GeckoSession())
+        }
+
+        waitUntilCollected(createSession())
+    }
+
+    @Test fun collectAfterClose() {
+        fun createSession(): QueuedWeakReference<GeckoSession> {
+            val s = GeckoSession()
+            s.open(sessionRule.runtime)
+            s.close()
+            return QueuedWeakReference<GeckoSession>(s)
+        }
+
+        waitUntilCollected(createSession())
+    }
+
+    @Test fun collectOpen() {
+        fun createSession(): QueuedWeakReference<GeckoSession> {
+            val s = GeckoSession()
+            s.open(sessionRule.runtime)
+            return QueuedWeakReference<GeckoSession>(s)
+        }
+
+        waitUntilCollected(createSession())
+    }
+
+    private fun dumpHprof() {
+        try {
+            val dest = File(InstrumentationRegistry.getTargetContext()
+                    .filesDir.parent, "dump.hprof").absolutePath
+            Debug.dumpHprofData(dest)
+            Log.d(LOGTAG, "Dumped hprof to $dest")
+        } catch (e: IOException) {
+            Log.e(LOGTAG, "Failed to dump hprof", e)
+        }
+
+    }
+
+    private fun waitUntilCollected(ref: QueuedWeakReference<*>) {
+        val start = SystemClock.uptimeMillis()
+        while (ref.queue.poll() == null) {
+            val elapsed = SystemClock.uptimeMillis() - start
+            if (elapsed > sessionRule.timeoutMillis) {
+                dumpHprof()
+                throw UiThreadUtils.TimeoutException("Timed out after " + elapsed + "ms")
+            }
+
+            try {
+                UiThreadUtils.loopUntilIdle(100)
+            } catch (e: UiThreadUtils.TimeoutException) {
+            }
+            Runtime.getRuntime().gc()
+        }
+    }
+
+    class QueuedWeakReference<T> @JvmOverloads constructor(obj: T, var queue: ReferenceQueue<T> =
+            ReferenceQueue()) : WeakReference<T>(obj, queue)
 }