Bug 1424834 - Replace nsHostRecord.callbacks with LinkedList<RefPtr<nsResolveHostCallback>> r=mayhemer draft
authorValentin Gosu <valentin.gosu@gmail.com>
Wed, 20 Dec 2017 01:13:46 +0100
changeset 713267 67adcbd20a29803e5abbb3d16324f9cac6771d28
parent 713235 1624b88874765bf57e9feba176d30149c748d9d2
child 713268 b6e99e20db84993c0141573d6957e1723b81ed92
push id93601
push uservalentin.gosu@gmail.com
push dateWed, 20 Dec 2017 00:17:24 +0000
reviewersmayhemer
bugs1424834
milestone59.0a1
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;