From b1f93026e65adf04cbf5254880ac6b18bdac72a2 Mon Sep 17 00:00:00 2001 From: Agi Sferro Date: Wed, 26 Jan 2022 16:40:56 +0000 Subject: [PATCH] Bug 1745996 - Send a memory pressure event when deactivating sessions. r=jnicol,calu WebRender retains about 50MBs of memory for every window. When switching between lots of tabs on Android (where 1 tab = 1 window), this can cause problems as the app will consume a significant amount of memory. To avoid this problem, we send a memory pressure event whenever a session is deactivated, which signals to WebRender that it should deallocate the memory. This message is sent on a delay of 10s to avoid interfering with tab switching, and we cancel the message if the tab becomes active again before we fire the memory pressure event. Co-Authored-By: Cathy Lu Co-Authored-By: Jonathan Almeida [:jonalmeida] Differential Revision: https://phabricator.services.mozilla.com/D136965 --- gfx/layers/ipc/CompositorBridgeParent.cpp | 5 +++++ gfx/layers/ipc/CompositorBridgeParent.h | 2 ++ .../ipc/ContentCompositorBridgeParent.cpp | 6 ++++++ .../ipc/ContentCompositorBridgeParent.h | 2 ++ gfx/layers/ipc/PCompositorBridge.ipdl | 2 ++ .../org/mozilla/gecko/util/ThreadUtils.java | 8 ++++++++ .../org/mozilla/geckoview/GeckoSession.java | 20 +++++++++++++++++++ widget/android/nsWindow.cpp | 15 ++++++++++++++ 8 files changed, 60 insertions(+) diff --git a/gfx/layers/ipc/CompositorBridgeParent.cpp b/gfx/layers/ipc/CompositorBridgeParent.cpp index e9b2fa7575c8..a6871ad8f696 100644 --- a/gfx/layers/ipc/CompositorBridgeParent.cpp +++ b/gfx/layers/ipc/CompositorBridgeParent.cpp @@ -520,6 +520,11 @@ mozilla::ipc::IPCResult CompositorBridgeParent::RecvFlushRendering( return IPC_OK(); } +mozilla::ipc::IPCResult CompositorBridgeParent::RecvNotifyMemoryPressure() { + NotifyMemoryPressure(); + return IPC_OK(); +} + mozilla::ipc::IPCResult CompositorBridgeParent::RecvFlushRenderingAsync( const wr::RenderReasons& aReasons) { if (mWrBridge) { diff --git a/gfx/layers/ipc/CompositorBridgeParent.h b/gfx/layers/ipc/CompositorBridgeParent.h index 62021bf91144..220e5e9f26c1 100644 --- a/gfx/layers/ipc/CompositorBridgeParent.h +++ b/gfx/layers/ipc/CompositorBridgeParent.h @@ -242,6 +242,7 @@ class CompositorBridgeParentBase : public PCompositorBridgeParent, const LayersId& id, CompositorOptions* compositorOptions) = 0; virtual mozilla::ipc::IPCResult RecvFlushRendering( const wr::RenderReasons& aReasons) = 0; + virtual mozilla::ipc::IPCResult RecvNotifyMemoryPressure() = 0; virtual mozilla::ipc::IPCResult RecvWaitOnTransactionProcessed() = 0; virtual mozilla::ipc::IPCResult RecvStartFrameTimeRecording( const int32_t& bufferSize, uint32_t* startIndex) = 0; @@ -324,6 +325,7 @@ class CompositorBridgeParent final : public CompositorBridgeParentBase, return IPC_OK(); } + mozilla::ipc::IPCResult RecvNotifyMemoryPressure() override; mozilla::ipc::IPCResult RecvBeginRecording( const TimeStamp& aRecordingStart, BeginRecordingResolver&& aResolve) override; diff --git a/gfx/layers/ipc/ContentCompositorBridgeParent.cpp b/gfx/layers/ipc/ContentCompositorBridgeParent.cpp index 9b3a641ad0e6..c81171519240 100644 --- a/gfx/layers/ipc/ContentCompositorBridgeParent.cpp +++ b/gfx/layers/ipc/ContentCompositorBridgeParent.cpp @@ -242,6 +242,12 @@ ContentCompositorBridgeParent::RecvMapAndNotifyChildCreated( return IPC_FAIL_NO_REASON(this); } +mozilla::ipc::IPCResult +ContentCompositorBridgeParent::RecvNotifyMemoryPressure() { + // This can only be called from the browser process. + return IPC_FAIL_NO_REASON(this); +} + mozilla::ipc::IPCResult ContentCompositorBridgeParent::RecvCheckContentOnlyTDR( const uint32_t& sequenceNum, bool* isContentOnlyTDR) { *isContentOnlyTDR = false; diff --git a/gfx/layers/ipc/ContentCompositorBridgeParent.h b/gfx/layers/ipc/ContentCompositorBridgeParent.h index cf5f51969939..00fbece5136e 100644 --- a/gfx/layers/ipc/ContentCompositorBridgeParent.h +++ b/gfx/layers/ipc/ContentCompositorBridgeParent.h @@ -84,6 +84,8 @@ class ContentCompositorBridgeParent final : public CompositorBridgeParentBase { return IPC_OK(); } + mozilla::ipc::IPCResult RecvNotifyMemoryPressure() override; + mozilla::ipc::IPCResult RecvCheckContentOnlyTDR( const uint32_t& sequenceNum, bool* isContentOnlyTDR) override; diff --git a/gfx/layers/ipc/PCompositorBridge.ipdl b/gfx/layers/ipc/PCompositorBridge.ipdl index 73952cb85e54..6e0f723f8c16 100644 --- a/gfx/layers/ipc/PCompositorBridge.ipdl +++ b/gfx/layers/ipc/PCompositorBridge.ipdl @@ -173,6 +173,8 @@ parent: sync NotifyChildRecreated(LayersId id) returns (CompositorOptions compositorOptions); + async NotifyMemoryPressure(); + // Make sure any pending composites are started immediately and // block until they are completed. sync FlushRendering(RenderReasons aReasons); diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ThreadUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ThreadUtils.java index d69c9e9bf327..00625800c91e 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ThreadUtils.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ThreadUtils.java @@ -60,6 +60,14 @@ public final class ThreadUtils { sUiHandler.post(runnable); } + public static void postToUiThreadDelayed(final Runnable runnable, final long delayMillis) { + sUiHandler.postDelayed(runnable, delayMillis); + } + + public static void removeUiThreadCallbacks(final Runnable runnable) { + sUiHandler.removeCallbacks(runnable); + } + public static Handler getBackgroundHandler() { return GeckoBackgroundThread.getHandler(); } diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java index 0a8bcc3cba11..bc7563f5992e 100644 --- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java @@ -90,6 +90,19 @@ public class GeckoSession { private static final int DATA_URI_MAX_LENGTH = 2 * 1024 * 1024; + // Delay running compositor memory pressure by 10s to avoid interfering with tab switching. + private static final int NOTIFY_MEMORY_PRESSURE_DELAY_MS = 10 * 1000; + + private final Runnable mNotifyMemoryPressure = + new Runnable() { + @Override + public void run() { + if (mCompositorReady) { + mCompositor.notifyMemoryPressure(); + } + } + }; + private enum State implements NativeQueue.State { INITIAL(0), READY(1); @@ -195,6 +208,9 @@ public class GeckoSession { @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko") public native void setDynamicToolbarMaxHeight(int height); + @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko") + public native void notifyMemoryPressure(); + // Gecko thread pauses compositor; blocks UI thread. @WrapForJNI(calledFrom = "ui", dispatchTo = "current") public native void syncPauseCompositor(); @@ -2090,6 +2106,10 @@ public class GeckoSession { if (!active) { mEventDispatcher.dispatch("GeckoView:FlushSessionState", null); + ThreadUtils.postToUiThreadDelayed(mNotifyMemoryPressure, NOTIFY_MEMORY_PRESSURE_DELAY_MS); + } else { + // Delete any pending memory pressure events since we're active again. + ThreadUtils.removeUiThreadCallbacks(mNotifyMemoryPressure); } ThreadUtils.runOnUiThread(() -> getAutofillSupport().onActiveChanged(active)); diff --git a/widget/android/nsWindow.cpp b/widget/android/nsWindow.cpp index d5795b055a4f..8c5b24807f4b 100644 --- a/widget/android/nsWindow.cpp +++ b/widget/android/nsWindow.cpp @@ -1117,6 +1117,21 @@ class LayerViewSupport final gkWindow->Resize(aLeft, aTop, aWidth, aHeight, /* repaint */ false); } + void NotifyMemoryPressure() { + MOZ_ASSERT(NS_IsMainThread()); + auto acc = mWindow.Access(); + if (!acc) { + return; // Already shut down. + } + + nsWindow* gkWindow = acc->GetNsWindow(); + if (!gkWindow) { + return; + } + + gkWindow->mCompositorBridgeChild->SendNotifyMemoryPressure(); + } + void SetDynamicToolbarMaxHeight(int32_t aHeight) { MOZ_ASSERT(NS_IsMainThread()); auto acc = mWindow.Access();