Bug 1658571 - Don't create a reference cycle when catching redirects of a preload. r=smaug
authorEmilio Cobos Álvarez <emilio@crisal.io>
Thu, 24 Sep 2020 16:01:50 +0000
changeset 550177 22bfabd8ad785bb674b52ddb45dbc83e5c020b3c
parent 550176 24fbb9837b45d74e6a4ed0b44a9cd8134cdedbfb
child 550178 8572515cd18795c8262ae7258d90975554859024
push id37809
push userapavel@mozilla.com
push dateFri, 25 Sep 2020 03:37:48 +0000
treeherdermozilla-central@4846ccf88574 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1658571
milestone83.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 1658571 - Don't create a reference cycle when catching redirects of a preload. r=smaug PreloaderBase -> RedirectSink -> PreloaderBase is a strong, non-cycle-collected reference cycle, which in cases where we don't drop the channel because we never get an NotifyStop notification, it can cause leaks. I'm investigating the root cause of the lack of NotifyStop, but this should fix the leak and is correct anyhow. Move the class to the cpp file to ease debugging and changes. Differential Revision: https://phabricator.services.mozilla.com/D91259
uriloader/preload/PreloaderBase.cpp
uriloader/preload/PreloaderBase.h
--- a/uriloader/preload/PreloaderBase.cpp
+++ b/uriloader/preload/PreloaderBase.cpp
@@ -17,35 +17,63 @@
 constexpr static bool kCancelAndRemovePreloadOnZeroReferences = false;
 
 namespace mozilla {
 
 PreloaderBase::UsageTimer::UsageTimer(PreloaderBase* aPreload,
                                       dom::Document* aDocument)
     : mDocument(aDocument), mPreload(aPreload) {}
 
+class PreloaderBase::RedirectSink final : public nsIInterfaceRequestor,
+                                          public nsIChannelEventSink,
+                                          public nsIRedirectResultListener {
+  RedirectSink() = delete;
+  virtual ~RedirectSink();
+
+ public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIINTERFACEREQUESTOR
+  NS_DECL_NSICHANNELEVENTSINK
+  NS_DECL_NSIREDIRECTRESULTLISTENER
+
+  RedirectSink(PreloaderBase* aPreloader, nsIInterfaceRequestor* aCallbacks);
+
+ private:
+  WeakPtr<PreloaderBase> mPreloader;
+  nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+  nsCOMPtr<nsIChannel> mRedirectChannel;
+};
+
 PreloaderBase::RedirectSink::RedirectSink(PreloaderBase* aPreloader,
                                           nsIInterfaceRequestor* aCallbacks)
-    : mPreloader(new nsMainThreadPtrHolder<PreloaderBase>(
-          "RedirectSink.mPreloader", aPreloader)),
-      mCallbacks(aCallbacks) {}
+    : mPreloader(aPreloader), mCallbacks(aCallbacks) {}
+
+PreloaderBase::RedirectSink::~RedirectSink() {
+  MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
+                        "Should figure out how to safely drop mPreloader "
+                        "otherwise");
+}
 
 NS_IMPL_ISUPPORTS(PreloaderBase::RedirectSink, nsIInterfaceRequestor,
                   nsIChannelEventSink, nsIRedirectResultListener)
 
 NS_IMETHODIMP PreloaderBase::RedirectSink::AsyncOnChannelRedirect(
     nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
     nsIAsyncVerifyRedirectCallback* aCallback) {
+  MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+
   mRedirectChannel = aNewChannel;
 
   // Deliberately adding this before confirmation.
   nsCOMPtr<nsIURI> uri;
   aNewChannel->GetOriginalURI(getter_AddRefs(uri));
-  mPreloader->mRedirectRecords.AppendElement(
-      RedirectRecord(aFlags, uri.forget()));
+  if (mPreloader) {
+    mPreloader->mRedirectRecords.AppendElement(
+        RedirectRecord(aFlags, uri.forget()));
+  }
 
   if (mCallbacks) {
     nsCOMPtr<nsIChannelEventSink> sink(do_GetInterface(mCallbacks));
     if (sink) {
       return sink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags,
                                           aCallback);
     }
   }
--- a/uriloader/preload/PreloaderBase.h
+++ b/uriloader/preload/PreloaderBase.h
@@ -145,35 +145,17 @@ class PreloaderBase : public SupportsWea
   void ReportUsageTelemetry();
 
   // A helper class that will update the PreloaderBase.mChannel member when a
   // redirect happens, so that we can reprioritize or cancel when needed.
   // Having a separate class instead of implementing this on PreloaderBase
   // directly is to keep PreloaderBase as simple as possible so that derived
   // classes don't have to deal with calling super when implementing these
   // interfaces from some reason as well.
-  class RedirectSink final : public nsIInterfaceRequestor,
-                             public nsIChannelEventSink,
-                             public nsIRedirectResultListener {
-    RedirectSink() = delete;
-    virtual ~RedirectSink() = default;
-
-   public:
-    NS_DECL_THREADSAFE_ISUPPORTS
-    NS_DECL_NSIINTERFACEREQUESTOR
-    NS_DECL_NSICHANNELEVENTSINK
-    NS_DECL_NSIREDIRECTRESULTLISTENER
-
-    RedirectSink(PreloaderBase* aPreloader, nsIInterfaceRequestor* aCallbacks);
-
-   private:
-    nsMainThreadPtrHandle<PreloaderBase> mPreloader;
-    nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
-    nsCOMPtr<nsIChannel> mRedirectChannel;
-  };
+  class RedirectSink;
 
   // A timer callback to trigger the unuse warning for this preload
   class UsageTimer final : public nsITimerCallback {
     NS_DECL_ISUPPORTS
     NS_DECL_NSITIMERCALLBACK
 
     UsageTimer(PreloaderBase* aPreload, dom::Document* aDocument);