Bug 1424834 - Replace nsHostRecord.callbacks with LinkedList<RefPtr<nsResolveHostCallback>> r=mayhemer
☠☠ backed out by 5572465c08a9 ☠ ☠
authorValentin Gosu <valentin.gosu@gmail.com>
Fri, 15 Dec 2017 15:46:24 -0600
changeset 396640 49cc08386f17
parent 396639 0e1b938f8377
child 396641 2503df83bbd9
push id33104
push usernerli@mozilla.com
push date2017-12-17 21:43 +0000
treeherdermozilla-central@e3048146437d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmayhemer
bugs1424834
milestone59.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 1424834 - Replace nsHostRecord.callbacks with LinkedList<RefPtr<nsResolveHostCallback>> r=mayhemer * nsResolveHostCallback extends nsISupports (for addref-ing and because nsDNSAsyncRequest implements nsICancelable) * nsResolveHostCallback extends LinkedListElement<RefPtr<nsResolveHostCallback>> so the list can properly manage references * nsDNSAsyncRequest and nsDNSSyncRequest properly implement nsISupports and use RefPtr to manage lifetimes MozReview-Commit-ID: 5NvBcWZzfyN
netwerk/dns/nsDNSService2.cpp
netwerk/dns/nsHostResolver.cpp
netwerk/dns/nsHostResolver.h
--- a/netwerk/dns/nsDNSService2.cpp
+++ b/netwerk/dns/nsDNSService2.cpp
@@ -290,18 +290,16 @@ nsDNSRecord::ReportUnusable(uint16_t aPo
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 
 class nsDNSAsyncRequest final : public nsResolveHostCallback
                               , public nsICancelable
 {
-    ~nsDNSAsyncRequest() = default;
-
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSICANCELABLE
 
     nsDNSAsyncRequest(nsHostResolver   *res,
                       const nsACString &host,
                       const OriginAttributes &attrs,
                       nsIDNSListener   *listener,
@@ -326,38 +324,38 @@ public:
 
     RefPtr<nsHostResolver> mResolver;
     nsCString                mHost; // hostname we're resolving
     const OriginAttributes   mOriginAttributes; // The originAttributes for this resolving
     nsCOMPtr<nsIDNSListener> mListener;
     uint16_t                 mFlags;
     uint16_t                 mAF;
     nsCString                mNetworkInterface;
+private:
+    virtual ~nsDNSAsyncRequest() = default;
 };
 
+NS_IMPL_ISUPPORTS(nsDNSAsyncRequest, nsICancelable)
+
 void
 nsDNSAsyncRequest::OnResolveHostComplete(nsHostResolver *resolver,
                                          nsHostRecord   *hostRecord,
                                          nsresult        status)
 {
     // need to have an owning ref when we issue the callback to enable
     // the caller to be able to addref/release multiple times without
     // destroying the record prematurely.
     nsCOMPtr<nsIDNSRecord> rec;
     if (NS_SUCCEEDED(status)) {
         NS_ASSERTION(hostRecord, "no host record");
         rec = new nsDNSRecord(hostRecord);
     }
 
     mListener->OnLookupComplete(this, rec, status);
     mListener = nullptr;
-
-    // release the reference to ourselves that was added before we were
-    // handed off to the host resolver.
-    NS_RELEASE_THIS();
 }
 
 bool
 nsDNSAsyncRequest::EqualsAsyncListener(nsIDNSListener *aListener)
 {
     nsCOMPtr<nsIDNSListenerProxy> wrapper = do_QueryInterface(mListener);
     if (wrapper) {
         nsCOMPtr<nsIDNSListener> originalListener;
@@ -375,50 +373,53 @@ nsDNSAsyncRequest::SizeOfIncludingThis(M
     // The following fields aren't measured.
     // - mHost, because it's a non-owning pointer
     // - mResolver, because it's a non-owning pointer
     // - mListener, because it's a non-owning pointer
 
     return n;
 }
 
-NS_IMPL_ISUPPORTS(nsDNSAsyncRequest, nsICancelable)
-
 NS_IMETHODIMP
 nsDNSAsyncRequest::Cancel(nsresult reason)
 {
     NS_ENSURE_ARG(NS_FAILED(reason));
     mResolver->DetachCallback(mHost.get(), mOriginAttributes, mFlags, mAF,
                               mNetworkInterface.get(), this, reason);
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 
-class nsDNSSyncRequest : public nsResolveHostCallback
+class nsDNSSyncRequest
+    : public nsResolveHostCallback
 {
+    NS_DECL_THREADSAFE_ISUPPORTS
 public:
     explicit nsDNSSyncRequest(PRMonitor *mon)
         : mDone(false)
         , mStatus(NS_OK)
         , mMonitor(mon) {}
-    virtual ~nsDNSSyncRequest() = default;
 
     void OnResolveHostComplete(nsHostResolver *, nsHostRecord *, nsresult) override;
     bool EqualsAsyncListener(nsIDNSListener *aListener) override;
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const override;
 
     bool                   mDone;
     nsresult               mStatus;
     RefPtr<nsHostRecord> mHostRecord;
 
 private:
+    virtual ~nsDNSSyncRequest() = default;
+
     PRMonitor             *mMonitor;
 };
 
+NS_IMPL_ISUPPORTS0(nsDNSSyncRequest)
+
 void
 nsDNSSyncRequest::OnResolveHostComplete(nsHostResolver *resolver,
                                         nsHostRecord   *hostRecord,
                                         nsresult        status)
 {
     // store results, and wake up nsDNSService::Resolve to process results.
     PR_EnterMonitor(mMonitor);
     mDone = true;
@@ -793,26 +794,27 @@ nsDNSService::AsyncResolveExtended(const
                                       listener, target_, attrs,
                                       result);
 }
 
 NS_IMETHODIMP
 nsDNSService::AsyncResolveExtendedNative(const nsACString        &aHostname,
                                          uint32_t                 flags,
                                          const nsACString        &aNetworkInterface,
-                                         nsIDNSListener          *listener,
+                                         nsIDNSListener          *aListener,
                                          nsIEventTarget          *target_,
                                          const OriginAttributes  &aOriginAttributes,
                                          nsICancelable          **result)
 {
     // grab reference to global host resolver and IDN service.  beware
     // simultaneous shutdown!!
     RefPtr<nsHostResolver> res;
     nsCOMPtr<nsIIDNService> idn;
     nsCOMPtr<nsIEventTarget> target = target_;
+    nsCOMPtr<nsIDNSListener> listener = aListener;
     bool localDomain = false;
     {
         MutexAutoLock lock(mLock);
 
         if (mDisablePrefetch && (flags & RESOLVE_SPECULATE))
             return NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
 
         res = mResolver;
@@ -845,31 +847,26 @@ nsDNSService::AsyncResolveExtendedNative
     }
 
     if (target) {
         listener = new DNSListenerProxy(listener, target);
     }
 
     uint16_t af = GetAFForLookup(hostname, flags);
 
-    auto *req =
+    MOZ_ASSERT(listener);
+    RefPtr<nsDNSAsyncRequest> req =
         new nsDNSAsyncRequest(res, hostname, aOriginAttributes, listener, flags, af,
                               aNetworkInterface);
     if (!req)
         return NS_ERROR_OUT_OF_MEMORY;
-    NS_ADDREF(*result = req);
 
-    // addref for resolver; will be released when OnResolveHostComplete is called.
-    NS_ADDREF(req);
     rv = res->ResolveHost(req->mHost.get(), req->mOriginAttributes, flags, af,
                           req->mNetworkInterface.get(), req);
-    if (NS_FAILED(rv)) {
-        NS_RELEASE(req);
-        NS_RELEASE(*result);
-    }
+    req.forget(result);
     return rv;
 }
 
 NS_IMETHODIMP
 nsDNSService::CancelAsyncResolve(const nsACString &aHostname,
                                  uint32_t          aFlags,
                                  nsIDNSListener   *aListener,
                                  nsresult          aReason,
@@ -1050,35 +1047,33 @@ nsDNSService::ResolveInternal(const nsAC
     // we need to use a monitor! ;-)
     //
 
     PRMonitor *mon = PR_NewMonitor();
     if (!mon)
         return NS_ERROR_OUT_OF_MEMORY;
 
     PR_EnterMonitor(mon);
-    nsDNSSyncRequest syncReq(mon);
+    RefPtr<nsDNSSyncRequest> syncReq = new nsDNSSyncRequest(mon);
 
     uint16_t af = GetAFForLookup(hostname, flags);
 
-    rv = res->ResolveHost(hostname.get(), aOriginAttributes, flags, af, "", &syncReq);
+    rv = res->ResolveHost(hostname.get(), aOriginAttributes, flags, af, "", syncReq);
     if (NS_SUCCEEDED(rv)) {
         // wait for result
-        while (!syncReq.mDone)
+        while (!syncReq->mDone) {
             PR_Wait(mon, PR_INTERVAL_NO_TIMEOUT);
+        }
 
-        if (NS_FAILED(syncReq.mStatus))
-            rv = syncReq.mStatus;
-        else {
-            NS_ASSERTION(syncReq.mHostRecord, "no host record");
-            auto *rec = new nsDNSRecord(syncReq.mHostRecord);
-            if (!rec)
-                rv = NS_ERROR_OUT_OF_MEMORY;
-            else
-                NS_ADDREF(*result = rec);
+        if (NS_FAILED(syncReq->mStatus)) {
+            rv = syncReq->mStatus;
+        } else {
+            NS_ASSERTION(syncReq->mHostRecord, "no host record");
+            RefPtr<nsDNSRecord> rec = new nsDNSRecord(syncReq->mHostRecord);
+            rec.forget(result);
         }
     }
 
     PR_ExitMonitor(mon);
     PR_DestroyMonitor(mon);
     return rv;
 }
 
--- a/netwerk/dns/nsHostResolver.cpp
+++ b/netwerk/dns/nsHostResolver.cpp
@@ -184,17 +184,16 @@ nsHostRecord::nsHostRecord(const nsHostK
     af = key->af;
     netInterface = host + strlen(key->host) + 1;
     memcpy((char *) netInterface, key->netInterface,
            strlen(key->netInterface) + 1);
     originSuffix = netInterface + strlen(key->netInterface) + 1;
     memcpy((char *) originSuffix, key->originSuffix,
            strlen(key->originSuffix) + 1);
     PR_INIT_CLIST(this);
-    PR_INIT_CLIST(&callbacks);
 }
 
 nsresult
 nsHostRecord::Create(const nsHostKey *key, nsHostRecord **result)
 {
     size_t hostLen = strlen(key->host) + 1;
     size_t netInterfaceLen = strlen(key->netInterface) + 1;
     size_t originSuffixLen = strlen(key->originSuffix) + 1;
@@ -225,16 +224,18 @@ nsHostRecord::CopyExpirationTimesAndFlag
     mValidStart = aFromHostRecord->mValidStart;
     mValidEnd = aFromHostRecord->mValidEnd;
     mGraceStart = aFromHostRecord->mGraceStart;
     mDoomed = aFromHostRecord->mDoomed;
 }
 
 nsHostRecord::~nsHostRecord()
 {
+    mCallbacks.clear();
+
     Telemetry::Accumulate(Telemetry::DNS_BLACKLIST_COUNT, mBlacklistedCount);
     delete addr_info;
 }
 
 bool
 nsHostRecord::Blacklisted(NetAddr *aQuery)
 {
     // must call locked
@@ -321,41 +322,39 @@ nsHostRecord::HasUsableResult(const mozi
     if (CheckExpiration(now) == EXP_EXPIRED) {
         return false;
     }
 
     return addr_info || addr || negative;
 }
 
 static size_t
-SizeOfResolveHostCallbackListExcludingHead(const PRCList *head,
+SizeOfResolveHostCallbackListExcludingHead(const mozilla::LinkedList<RefPtr<nsResolveHostCallback>>& aCallbacks,
                                            MallocSizeOf mallocSizeOf)
 {
-    size_t n = 0;
-    PRCList *curr = head->next;
-    while (curr != head) {
-        nsResolveHostCallback *callback =
-            static_cast<nsResolveHostCallback*>(curr);
-        n += callback->SizeOfIncludingThis(mallocSizeOf);
-        curr = curr->next;
+    size_t n = 0; // TODO: should be aCallbacks.sizeOfIncludingThis(mallocSizeOf);
+
+    for (const nsResolveHostCallback* t = aCallbacks.getFirst(); t; t = t->getNext()) {
+      n += t->SizeOfIncludingThis(mallocSizeOf);
     }
+
     return n;
 }
 
 size_t
 nsHostRecord::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) const
 {
     size_t n = mallocSizeOf(this);
 
     // The |host| field (inherited from nsHostKey) actually points to extra
     // memory that is allocated beyond the end of the nsHostRecord (see
     // nsHostRecord::Create()).  So it will be included in the
     // |mallocSizeOf(this)| call above.
 
-    n += SizeOfResolveHostCallbackListExcludingHead(&callbacks, mallocSizeOf);
+    n += SizeOfResolveHostCallbackListExcludingHead(mCallbacks, mallocSizeOf);
     n += addr_info ? addr_info->SizeOfIncludingThis(mallocSizeOf) : 0;
     n += mallocSizeOf(addr.get());
 
     n += mBlacklistedItems.ShallowSizeOfExcludingThis(mallocSizeOf);
     for (size_t i = 0; i < mBlacklistedItems.Length(); i++) {
         n += mBlacklistedItems[i].SizeOfExcludingThisIfUnshared(mallocSizeOf);
     }
     return n;
@@ -728,29 +727,30 @@ nsHostResolver::MoveQueue(nsHostRecord *
 }
 
 nsresult
 nsHostResolver::ResolveHost(const char             *host,
                             const OriginAttributes &aOriginAttributes,
                             uint16_t                flags,
                             uint16_t                af,
                             const char             *netInterface,
-                            nsResolveHostCallback  *callback)
+                            nsResolveHostCallback  *aCallback)
 {
     NS_ENSURE_TRUE(host && *host, NS_ERROR_UNEXPECTED);
     NS_ENSURE_TRUE(netInterface, NS_ERROR_UNEXPECTED);
 
     LOG(("Resolving host [%s%s%s]%s.\n", LOG_HOST(host, netInterface),
          flags & RES_BYPASS_CACHE ? " - bypassing cache" : ""));
 
     // ensure that we are working with a valid hostname before proceeding.  see
     // bug 304904 for details.
     if (!net_IsValidHostName(nsDependentCString(host)))
         return NS_ERROR_UNKNOWN_HOST;
 
+    RefPtr<nsResolveHostCallback> callback(aCallback);
     // if result is set inside the lock, then we need to issue the
     // callback before returning.
     RefPtr<nsHostRecord> result;
     nsresult status = NS_OK, rv = NS_OK;
     {
         MutexAutoLock lock(mLock);
 
         if (mShutdown)
@@ -926,36 +926,36 @@ nsHostResolver::ResolveHost(const char  
                 }
                 // If no valid address was found in the cache or this is an
                 // AF_UNSPEC request, then start a new lookup.
                 if (!result) {
                     LOG(("  No usable address in cache for host [%s%s%s].",
                          LOG_HOST(host, netInterface)));
 
                     // Add callback to the list of pending callbacks.
-                    PR_APPEND_LINK(callback, &he->rec->callbacks);
+                    he->rec->mCallbacks.insertBack(callback);
                     he->rec->flags = flags;
                     rv = IssueLookup(he->rec);
                     Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
                                           METHOD_NETWORK_FIRST);
-                    if (NS_FAILED(rv)) {
-                        PR_REMOVE_AND_INIT_LINK(callback);
+                    if (NS_FAILED(rv) && callback->isInList()) {
+                        callback->remove();
                     }
                     else {
                         LOG(("  DNS lookup for host [%s%s%s] blocking "
                              "pending 'getaddrinfo' query: callback [%p]",
-                             LOG_HOST(host, netInterface), callback));
+                             LOG_HOST(host, netInterface), callback.get()));
                     }
                 }
             }
             else {
                 LOG(("  Host [%s%s%s] is being resolved. Appending callback "
-                     "[%p].", LOG_HOST(host, netInterface), callback));
+                     "[%p].", LOG_HOST(host, netInterface), callback.get()));
 
-                PR_APPEND_LINK(callback, &he->rec->callbacks);
+                he->rec->mCallbacks.insertBack(callback);
                 if (he->rec->onQueue) {
                     Telemetry::Accumulate(Telemetry::DNS_LOOKUP_METHOD2,
                                           METHOD_NETWORK_SHARED);
 
                     // Consider the case where we are on a pending queue of
                     // lower priority than the request is being made at.
                     // In that case we should upgrade to the higher queue.
 
@@ -971,60 +971,66 @@ nsHostResolver::ResolveHost(const char  
                         MoveQueue(he->rec, mMediumQ);
                         he->rec->flags = flags;
                         mIdleThreadCV.Notify();
                     }
                 }
             }
         }
     }
+
     if (result) {
+        if (callback->isInList()) {
+            callback->remove();
+        }
         callback->OnResolveHostComplete(this, result, status);
     }
 
     return rv;
 }
 
 void
 nsHostResolver::DetachCallback(const char             *host,
                                const OriginAttributes &aOriginAttributes,
                                uint16_t                flags,
                                uint16_t                af,
                                const char             *netInterface,
-                               nsResolveHostCallback  *callback,
+                               nsResolveHostCallback  *aCallback,
                                nsresult                status)
 {
     RefPtr<nsHostRecord> rec;
+    RefPtr<nsResolveHostCallback> callback(aCallback);
+
     {
         MutexAutoLock lock(mLock);
 
         nsAutoCString originSuffix;
         aOriginAttributes.CreateSuffix(originSuffix);
 
         nsHostKey key = { host, flags, af, netInterface, originSuffix.get() };
         auto he = static_cast<nsHostDBEnt*>(mDB.Search(&key));
         if (he) {
             // walk list looking for |callback|... we cannot assume
             // that it will be there!
-            PRCList *node = he->rec->callbacks.next;
-            while (node != &he->rec->callbacks) {
-                if (static_cast<nsResolveHostCallback *>(node) == callback) {
-                    PR_REMOVE_LINK(callback);
+
+            for (nsResolveHostCallback* c: he->rec->mCallbacks) {
+                if (c == callback) {
                     rec = he->rec;
+                    c->remove();
                     break;
                 }
-                node = node->next;
             }
         }
     }
 
     // complete callback with the given status code; this would only be done if
     // the record was in the process of being resolved.
-    if (rec)
+    if (rec) {
         callback->OnResolveHostComplete(this, rec, status);
+    }
 }
 
 nsresult
 nsHostResolver::ConditionallyCreateThread(nsHostRecord *rec)
 {
     if (mNumIdleThreads) {
         // wake up idle thread to process this lookup
         mIdleThreadCV.Notify();
@@ -1288,30 +1294,30 @@ different_rrset(AddrInfo *rrset1, AddrIn
 // CompleteLookup() checks if the resolving should be redone and if so it
 // returns LOOKUP_RESOLVEAGAIN, but only if 'status' is not NS_ERROR_ABORT.
 // takes ownership of AddrInfo parameter
 nsHostResolver::LookupStatus
 nsHostResolver::CompleteLookup(nsHostRecord* rec, nsresult status, AddrInfo* newRRSet)
 {
     // get the list of pending callbacks for this lookup, and notify
     // them that the lookup is complete.
-    PRCList cbs;
-    PR_INIT_CLIST(&cbs);
+    mozilla::LinkedList<RefPtr<nsResolveHostCallback>> cbs;
+
     {
         MutexAutoLock lock(mLock);
 
         if (rec->mResolveAgain && (status != NS_ERROR_ABORT)) {
             LOG(("nsHostResolver record %p resolve again due to flushcache\n", rec));
             rec->mResolveAgain = false;
             delete newRRSet;
             return LOOKUP_RESOLVEAGAIN;
         }
 
         // grab list of callbacks to notify
-        MoveCList(rec->callbacks, cbs);
+        cbs = mozilla::Move(rec->mCallbacks);
 
         // update record fields.  We might have a rec->addr_info already if a
         // previous lookup result expired and we're reresolving it..
         AddrInfo  *old_addr_info;
         {
             MutexAutoLock lock(rec->addr_info_lock);
             if (different_rrset(rec->addr_info, newRRSet)) {
                 LOG(("nsHostResolver record %p new gencnt\n", rec));
@@ -1369,25 +1375,18 @@ nsHostResolver::CompleteLookup(nsHostRec
                 NS_WARNING_ASSERTION(
                     NS_SUCCEEDED(rv),
                     "Could not issue second async lookup for TTL.");
             }
 #endif
         }
     }
 
-    if (!PR_CLIST_IS_EMPTY(&cbs)) {
-        PRCList *node = cbs.next;
-        while (node != &cbs) {
-            nsResolveHostCallback *callback =
-                    static_cast<nsResolveHostCallback *>(node);
-            node = node->next;
-            callback->OnResolveHostComplete(this, rec, status);
-            // NOTE: callback must not be dereferenced after this point!!
-        }
+    for (nsResolveHostCallback* c = cbs.getFirst(); c; c = c->removeAndGetNext()) {
+        c->OnResolveHostComplete(this, rec, status);
     }
 
     NS_RELEASE(rec);
 
     return LOOKUP_OK;
 }
 
 void
@@ -1405,34 +1404,28 @@ nsHostResolver::CancelAsyncRequest(const
     nsAutoCString originSuffix;
     aOriginAttributes.CreateSuffix(originSuffix);
 
     // Lookup the host record associated with host, flags & address family
     nsHostKey key = { host, flags, af, netInterface, originSuffix.get() };
     auto he = static_cast<nsHostDBEnt*>(mDB.Search(&key));
     if (he) {
         nsHostRecord* recPtr = nullptr;
-        PRCList *node = he->rec->callbacks.next;
-        // Remove the first nsDNSAsyncRequest callback which matches the
-        // supplied listener object
-        while (node != &he->rec->callbacks) {
-            nsResolveHostCallback *callback
-                = static_cast<nsResolveHostCallback *>(node);
-            if (callback && (callback->EqualsAsyncListener(aListener))) {
-                // Remove from the list of callbacks
-                PR_REMOVE_LINK(callback);
+
+        for (RefPtr<nsResolveHostCallback> c : he->rec->mCallbacks) {
+            if (c->EqualsAsyncListener(aListener)) {
+                c->remove();
                 recPtr = he->rec;
-                callback->OnResolveHostComplete(this, recPtr, status);
+                c->OnResolveHostComplete(this, recPtr, status);
                 break;
             }
-            node = node->next;
         }
 
         // If there are no more callbacks, remove the hash table entry
-        if (recPtr && PR_CLIST_IS_EMPTY(&recPtr->callbacks)) {
+        if (recPtr && recPtr->mCallbacks.isEmpty()) {
             mDB.Remove((nsHostKey *)recPtr);
             // If record is on a Queue, remove it and then deref it
             if (recPtr->next != recPtr) {
                 PR_REMOVE_LINK(recPtr);
                 NS_RELEASE(recPtr);
             }
         }
     }
--- a/netwerk/dns/nsHostResolver.h
+++ b/netwerk/dns/nsHostResolver.h
@@ -15,16 +15,17 @@
 #include "nsISupportsImpl.h"
 #include "nsIDNSListener.h"
 #include "nsIDNSService.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "GetAddrInfo.h"
 #include "mozilla/net/DNS.h"
 #include "mozilla/net/DashboardTypes.h"
+#include "mozilla/LinkedList.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
 
 class nsHostResolver;
 class nsHostRecord;
 class nsResolveHostCallback;
 
 #define MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY  3
@@ -125,18 +126,17 @@ public:
     static DnsPriority GetPriority(uint16_t aFlags);
 
     bool RemoveOrRefresh(); // Mark records currently being resolved as needed
                             // to resolve again.
 
 private:
     friend class nsHostResolver;
 
-
-    PRCList callbacks; /* list of callbacks */
+    mozilla::LinkedList<RefPtr<nsResolveHostCallback>> mCallbacks;
 
     bool    resolving; /* true if this record is being resolved, which means
                         * that it is either on the pending queue or owned by
                         * one of the worker threads. */
 
     bool    onQueue;  /* true if pending and on the queue (not yet given to getaddrinfo())*/
     bool    usingAnyThread; /* true if off queue and contributing to mActiveAnyThreadCount */
     bool    mDoomed; /* explicitly expired */
@@ -158,20 +158,23 @@ private:
     // of gencnt.
     nsTArray<nsCString> mBlacklistedItems;
 
     explicit nsHostRecord(const nsHostKey *key);           /* use Create() instead */
    ~nsHostRecord();
 };
 
 /**
- * ResolveHost callback object.  It's PRCList members are used by
- * the nsHostResolver and should not be used by anything else.
+ * This class is used to notify listeners when a ResolveHost operation is
+ * complete. Classes that derive it must implement threadsafe nsISupports
+ * to be able to use RefPtr with this class.
  */
-class NS_NO_VTABLE nsResolveHostCallback : public PRCList
+class nsResolveHostCallback
+    : public mozilla::LinkedListElement<RefPtr<nsResolveHostCallback>>
+    , public nsISupports
 {
 public:
     /**
      * OnResolveHostComplete
      *
      * this function is called to complete a host lookup initiated by
      * nsHostResolver::ResolveHost.  it may be invoked recursively from
      * ResolveHost or on an unspecified background thread.
@@ -200,16 +203,18 @@ public:
      * the original listener is known.
      *
      * @param aListener
      *        nsIDNSListener object associated with the original request
      */
     virtual bool EqualsAsyncListener(nsIDNSListener *aListener) = 0;
 
     virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf) const = 0;
+protected:
+    virtual ~nsResolveHostCallback() = default;
 };
 
 /**
  * nsHostResolver - an asynchronous host name resolver.
  */
 class nsHostResolver
 {
     typedef mozilla::CondVar CondVar;