Bug 628949 - Update visible region / glass regions after we paint. r=roc a=2.0.
[mozilla-central.git] / netwerk / dns / nsHostResolver.cpp
bloba49cc14da6cb8fcba01e7e1524e35d6988549bf4
1 /* vim:set ts=4 sw=4 sts=4 et cin: */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla.
17 * The Initial Developer of the Original Code is IBM Corporation.
18 * Portions created by IBM Corporation are Copyright (C) 2003
19 * IBM Corporation. All Rights Reserved.
21 * Contributor(s):
22 * IBM Corp.
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
38 #if defined(MOZ_LOGGING)
39 #define FORCE_PR_LOG
40 #endif
42 #if defined(HAVE_RES_NINIT)
43 #include <sys/types.h>
44 #include <netinet/in.h>
45 #include <arpa/inet.h>
46 #include <arpa/nameser.h>
47 #include <resolv.h>
48 #define RES_RETRY_ON_FAILURE
49 #endif
51 #include <stdlib.h>
52 #include "nsHostResolver.h"
53 #include "nsNetError.h"
54 #include "nsISupportsBase.h"
55 #include "nsISupportsUtils.h"
56 #include "nsAutoLock.h"
57 #include "nsAutoPtr.h"
58 #include "pratom.h"
59 #include "prthread.h"
60 #include "prerror.h"
61 #include "prcvar.h"
62 #include "prtime.h"
63 #include "prlong.h"
64 #include "prlog.h"
65 #include "pldhash.h"
66 #include "plstr.h"
67 #include "nsURLHelper.h"
69 #include "mozilla/FunctionTimer.h"
71 //----------------------------------------------------------------------------
73 // Use a persistent thread pool in order to avoid spinning up new threads all the time.
74 // In particular, thread creation results in a res_init() call from libc which is
75 // quite expensive.
77 // The pool dynamically grows between 0 and MAX_RESOLVER_THREADS in size. New requests
78 // go first to an idle thread. If that cannot be found and there are fewer than MAX_RESOLVER_THREADS
79 // currently in the pool a new thread is created for high priority requests. If
80 // the new request is at a lower priority a new thread will only be created if
81 // there are fewer than HighThreadThreshold currently outstanding. If a thread cannot be
82 // created or an idle thread located for the request it is queued.
84 // When the pool is greater than HighThreadThreshold in size a thread will be destroyed after
85 // ShortIdleTimeoutSeconds of idle time. Smaller pools use LongIdleTimeoutSeconds for a
86 // timeout period.
88 #define MAX_NON_PRIORITY_REQUESTS 150
90 #define HighThreadThreshold MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY
91 #define LongIdleTimeoutSeconds 300 // for threads 1 -> HighThreadThreshold
92 #define ShortIdleTimeoutSeconds 60 // for threads HighThreadThreshold+1 -> MAX_RESOLVER_THREADS
94 PR_STATIC_ASSERT (HighThreadThreshold <= MAX_RESOLVER_THREADS);
96 //----------------------------------------------------------------------------
98 #if defined(PR_LOGGING)
99 static PRLogModuleInfo *gHostResolverLog = nsnull;
100 #define LOG(args) PR_LOG(gHostResolverLog, PR_LOG_DEBUG, args)
101 #else
102 #define LOG(args)
103 #endif
105 //----------------------------------------------------------------------------
107 static inline void
108 MoveCList(PRCList &from, PRCList &to)
110 if (!PR_CLIST_IS_EMPTY(&from)) {
111 to.next = from.next;
112 to.prev = from.prev;
113 to.next->prev = &to;
114 to.prev->next = &to;
115 PR_INIT_CLIST(&from);
119 static PRUint32
120 NowInMinutes()
122 PRTime now = PR_Now(), minutes, factor;
123 LL_I2L(factor, 60 * PR_USEC_PER_SEC);
124 LL_DIV(minutes, now, factor);
125 PRUint32 result;
126 LL_L2UI(result, minutes);
127 return result;
130 //----------------------------------------------------------------------------
132 #if defined(RES_RETRY_ON_FAILURE)
134 // this class represents the resolver state for a given thread. if we
135 // encounter a lookup failure, then we can invoke the Reset method on an
136 // instance of this class to reset the resolver (in case /etc/resolv.conf
137 // for example changed). this is mainly an issue on GNU systems since glibc
138 // only reads in /etc/resolv.conf once per thread. it may be an issue on
139 // other systems as well.
141 class nsResState
143 public:
144 nsResState()
145 // initialize mLastReset to the time when this object
146 // is created. this means that a reset will not occur
147 // if a thread is too young. the alternative would be
148 // to initialize this to the beginning of time, so that
149 // the first failure would cause a reset, but since the
150 // thread would have just started up, it likely would
151 // already have current /etc/resolv.conf info.
152 : mLastReset(PR_IntervalNow())
156 PRBool Reset()
158 // reset no more than once per second
159 if (PR_IntervalToSeconds(PR_IntervalNow() - mLastReset) < 1)
160 return PR_FALSE;
162 LOG(("calling res_ninit\n"));
164 mLastReset = PR_IntervalNow();
165 return (res_ninit(&_res) == 0);
168 private:
169 PRIntervalTime mLastReset;
172 #endif // RES_RETRY_ON_FAILURE
174 //----------------------------------------------------------------------------
176 // this macro filters out any flags that are not used when constructing the
177 // host key. the significant flags are those that would affect the resulting
178 // host record (i.e., the flags that are passed down to PR_GetAddrInfoByName).
179 #define RES_KEY_FLAGS(_f) ((_f) & nsHostResolver::RES_CANON_NAME)
181 nsresult
182 nsHostRecord::Create(const nsHostKey *key, nsHostRecord **result)
184 PRLock *lock = PR_NewLock();
185 if (!lock)
186 return NS_ERROR_OUT_OF_MEMORY;
188 size_t hostLen = strlen(key->host) + 1;
189 size_t size = hostLen + sizeof(nsHostRecord);
191 nsHostRecord *rec = (nsHostRecord*) ::operator new(size);
192 if (!rec) {
193 PR_DestroyLock(lock);
194 return NS_ERROR_OUT_OF_MEMORY;
197 rec->host = ((char *) rec) + sizeof(nsHostRecord);
198 rec->flags = key->flags;
199 rec->af = key->af;
201 rec->_refc = 1; // addref
202 NS_LOG_ADDREF(rec, 1, "nsHostRecord", sizeof(nsHostRecord));
203 rec->addr_info_lock = lock;
204 rec->addr_info = nsnull;
205 rec->addr_info_gencnt = 0;
206 rec->addr = nsnull;
207 rec->expiration = NowInMinutes();
208 rec->resolving = PR_FALSE;
209 rec->onQueue = PR_FALSE;
210 rec->usingAnyThread = PR_FALSE;
211 PR_INIT_CLIST(rec);
212 PR_INIT_CLIST(&rec->callbacks);
213 rec->negative = PR_FALSE;
214 memcpy((char *) rec->host, key->host, hostLen);
216 *result = rec;
217 return NS_OK;
220 nsHostRecord::~nsHostRecord()
222 if (addr_info_lock)
223 PR_DestroyLock(addr_info_lock);
224 if (addr_info)
225 PR_FreeAddrInfo(addr_info);
226 if (addr)
227 free(addr);
230 //----------------------------------------------------------------------------
232 struct nsHostDBEnt : PLDHashEntryHdr
234 nsHostRecord *rec;
237 static PLDHashNumber
238 HostDB_HashKey(PLDHashTable *table, const void *key)
240 const nsHostKey *hk = static_cast<const nsHostKey *>(key);
241 return PL_DHashStringKey(table, hk->host) ^ RES_KEY_FLAGS(hk->flags) ^ hk->af;
244 static PRBool
245 HostDB_MatchEntry(PLDHashTable *table,
246 const PLDHashEntryHdr *entry,
247 const void *key)
249 const nsHostDBEnt *he = static_cast<const nsHostDBEnt *>(entry);
250 const nsHostKey *hk = static_cast<const nsHostKey *>(key);
252 return !strcmp(he->rec->host, hk->host) &&
253 RES_KEY_FLAGS (he->rec->flags) == RES_KEY_FLAGS(hk->flags) &&
254 he->rec->af == hk->af;
257 static void
258 HostDB_MoveEntry(PLDHashTable *table,
259 const PLDHashEntryHdr *from,
260 PLDHashEntryHdr *to)
262 static_cast<nsHostDBEnt *>(to)->rec =
263 static_cast<const nsHostDBEnt *>(from)->rec;
266 static void
267 HostDB_ClearEntry(PLDHashTable *table,
268 PLDHashEntryHdr *entry)
270 LOG(("evicting record\n"));
271 nsHostDBEnt *he = static_cast<nsHostDBEnt *>(entry);
272 #if defined(DEBUG) && defined(PR_LOGGING)
273 if (!he->rec->addr_info)
274 LOG(("%s: => no addr_info\n", he->rec->host));
275 else {
276 PRInt32 now = (PRInt32) NowInMinutes();
277 PRInt32 diff = (PRInt32) he->rec->expiration - now;
278 LOG(("%s: exp=%d => %s\n",
279 he->rec->host, diff,
280 PR_GetCanonNameFromAddrInfo(he->rec->addr_info)));
281 void *iter = nsnull;
282 PRNetAddr addr;
283 char buf[64];
284 for (;;) {
285 iter = PR_EnumerateAddrInfo(iter, he->rec->addr_info, 0, &addr);
286 if (!iter)
287 break;
288 PR_NetAddrToString(&addr, buf, sizeof(buf));
289 LOG((" %s\n", buf));
292 #endif
293 NS_RELEASE(he->rec);
296 static PRBool
297 HostDB_InitEntry(PLDHashTable *table,
298 PLDHashEntryHdr *entry,
299 const void *key)
301 nsHostDBEnt *he = static_cast<nsHostDBEnt *>(entry);
302 nsHostRecord::Create(static_cast<const nsHostKey *>(key), &he->rec);
303 return PR_TRUE;
306 static PLDHashTableOps gHostDB_ops =
308 PL_DHashAllocTable,
309 PL_DHashFreeTable,
310 HostDB_HashKey,
311 HostDB_MatchEntry,
312 HostDB_MoveEntry,
313 HostDB_ClearEntry,
314 PL_DHashFinalizeStub,
315 HostDB_InitEntry,
318 static PLDHashOperator
319 HostDB_RemoveEntry(PLDHashTable *table,
320 PLDHashEntryHdr *hdr,
321 PRUint32 number,
322 void *arg)
324 return PL_DHASH_REMOVE;
327 //----------------------------------------------------------------------------
329 nsHostResolver::nsHostResolver(PRUint32 maxCacheEntries,
330 PRUint32 maxCacheLifetime)
331 : mMaxCacheEntries(maxCacheEntries)
332 , mMaxCacheLifetime(maxCacheLifetime)
333 , mLock(nsnull)
334 , mIdleThreadCV(nsnull)
335 , mNumIdleThreads(0)
336 , mThreadCount(0)
337 , mActiveAnyThreadCount(0)
338 , mEvictionQSize(0)
339 , mPendingCount(0)
340 , mShutdown(PR_TRUE)
342 mCreationTime = PR_Now();
343 PR_INIT_CLIST(&mHighQ);
344 PR_INIT_CLIST(&mMediumQ);
345 PR_INIT_CLIST(&mLowQ);
346 PR_INIT_CLIST(&mEvictionQ);
348 mLongIdleTimeout = PR_SecondsToInterval(LongIdleTimeoutSeconds);
349 mShortIdleTimeout = PR_SecondsToInterval(ShortIdleTimeoutSeconds);
352 nsHostResolver::~nsHostResolver()
354 if (mIdleThreadCV)
355 PR_DestroyCondVar(mIdleThreadCV);
357 if (mLock)
358 PR_DestroyLock(mLock);
360 PL_DHashTableFinish(&mDB);
363 nsresult
364 nsHostResolver::Init()
366 NS_TIME_FUNCTION;
368 mLock = PR_NewLock();
369 if (!mLock)
370 return NS_ERROR_OUT_OF_MEMORY;
372 mIdleThreadCV = PR_NewCondVar(mLock);
373 if (!mIdleThreadCV)
374 return NS_ERROR_OUT_OF_MEMORY;
376 PL_DHashTableInit(&mDB, &gHostDB_ops, nsnull, sizeof(nsHostDBEnt), 0);
378 mShutdown = PR_FALSE;
380 #if defined(HAVE_RES_NINIT)
381 // We want to make sure the system is using the correct resolver settings,
382 // so we force it to reload those settings whenever we startup a subsequent
383 // nsHostResolver instance. We assume that there is no reason to do this
384 // for the first nsHostResolver instance since that is usually created
385 // during application startup.
386 static int initCount = 0;
387 if (initCount++ > 0) {
388 LOG(("calling res_ninit\n"));
389 res_ninit(&_res);
391 #endif
392 return NS_OK;
395 void
396 nsHostResolver::ClearPendingQueue(PRCList *aPendingQ)
398 // loop through pending queue, erroring out pending lookups.
399 if (!PR_CLIST_IS_EMPTY(aPendingQ)) {
400 PRCList *node = aPendingQ->next;
401 while (node != aPendingQ) {
402 nsHostRecord *rec = static_cast<nsHostRecord *>(node);
403 node = node->next;
404 OnLookupComplete(rec, NS_ERROR_ABORT, nsnull);
409 void
410 nsHostResolver::Shutdown()
412 LOG(("nsHostResolver::Shutdown\n"));
414 PRCList pendingQHigh, pendingQMed, pendingQLow, evictionQ;
415 PR_INIT_CLIST(&pendingQHigh);
416 PR_INIT_CLIST(&pendingQMed);
417 PR_INIT_CLIST(&pendingQLow);
418 PR_INIT_CLIST(&evictionQ);
421 nsAutoLock lock(mLock);
423 mShutdown = PR_TRUE;
425 MoveCList(mHighQ, pendingQHigh);
426 MoveCList(mMediumQ, pendingQMed);
427 MoveCList(mLowQ, pendingQLow);
428 MoveCList(mEvictionQ, evictionQ);
429 mEvictionQSize = 0;
430 mPendingCount = 0;
432 if (mNumIdleThreads)
433 PR_NotifyAllCondVar(mIdleThreadCV);
435 // empty host database
436 PL_DHashTableEnumerate(&mDB, HostDB_RemoveEntry, nsnull);
439 ClearPendingQueue(&pendingQHigh);
440 ClearPendingQueue(&pendingQMed);
441 ClearPendingQueue(&pendingQLow);
443 if (!PR_CLIST_IS_EMPTY(&evictionQ)) {
444 PRCList *node = evictionQ.next;
445 while (node != &evictionQ) {
446 nsHostRecord *rec = static_cast<nsHostRecord *>(node);
447 node = node->next;
448 NS_RELEASE(rec);
452 #ifdef NS_BUILD_REFCNT_LOGGING
454 // Logically join the outstanding worker threads with a timeout.
455 // Use this approach instead of PR_JoinThread() because that does
456 // not allow a timeout which may be necessary for a semi-responsive
457 // shutdown if the thread is blocked on a very slow DNS resolution.
458 // mThreadCount is read outside of mLock, but the worst case
459 // scenario for that race is one extra 25ms sleep.
461 PRIntervalTime delay = PR_MillisecondsToInterval(25);
462 PRIntervalTime stopTime = PR_IntervalNow() + PR_SecondsToInterval(20);
463 while (mThreadCount && PR_IntervalNow() < stopTime)
464 PR_Sleep(delay);
465 #endif
468 static inline PRBool
469 IsHighPriority(PRUint16 flags)
471 return !(flags & (nsHostResolver::RES_PRIORITY_LOW | nsHostResolver::RES_PRIORITY_MEDIUM));
474 static inline PRBool
475 IsMediumPriority(PRUint16 flags)
477 return flags & nsHostResolver::RES_PRIORITY_MEDIUM;
480 static inline PRBool
481 IsLowPriority(PRUint16 flags)
483 return flags & nsHostResolver::RES_PRIORITY_LOW;
486 void
487 nsHostResolver::MoveQueue(nsHostRecord *aRec, PRCList &aDestQ)
489 NS_ASSERTION(aRec->onQueue, "Moving Host Record Not Currently Queued");
491 PR_REMOVE_LINK(aRec);
492 PR_APPEND_LINK(aRec, &aDestQ);
495 nsresult
496 nsHostResolver::ResolveHost(const char *host,
497 PRUint16 flags,
498 PRUint16 af,
499 nsResolveHostCallback *callback)
501 NS_ENSURE_TRUE(host && *host, NS_ERROR_UNEXPECTED);
503 LOG(("nsHostResolver::ResolveHost [host=%s]\n", host));
505 // ensure that we are working with a valid hostname before proceeding. see
506 // bug 304904 for details.
507 if (!net_IsValidHostName(nsDependentCString(host)))
508 return NS_ERROR_UNKNOWN_HOST;
510 // if result is set inside the lock, then we need to issue the
511 // callback before returning.
512 nsRefPtr<nsHostRecord> result;
513 nsresult status = NS_OK, rv = NS_OK;
515 nsAutoLock lock(mLock);
517 if (mShutdown)
518 rv = NS_ERROR_NOT_INITIALIZED;
519 else {
520 PRNetAddr tempAddr;
522 // unfortunately, PR_StringToNetAddr does not properly initialize
523 // the output buffer in the case of IPv6 input. see bug 223145.
524 memset(&tempAddr, 0, sizeof(PRNetAddr));
526 // check to see if there is already an entry for this |host|
527 // in the hash table. if so, then check to see if we can't
528 // just reuse the lookup result. otherwise, if there are
529 // any pending callbacks, then add to pending callbacks queue,
530 // and return. otherwise, add ourselves as first pending
531 // callback, and proceed to do the lookup.
533 nsHostKey key = { host, flags, af };
534 nsHostDBEnt *he = static_cast<nsHostDBEnt *>
535 (PL_DHashTableOperate(&mDB, &key, PL_DHASH_ADD));
537 // if the record is null, then HostDB_InitEntry failed.
538 if (!he || !he->rec)
539 rv = NS_ERROR_OUT_OF_MEMORY;
540 // do we have a cached result that we can reuse?
541 else if (!(flags & RES_BYPASS_CACHE) &&
542 he->rec->HasResult() &&
543 NowInMinutes() <= he->rec->expiration) {
544 LOG(("using cached record\n"));
545 // put reference to host record on stack...
546 result = he->rec;
547 if (he->rec->negative) {
548 status = NS_ERROR_UNKNOWN_HOST;
549 if (!he->rec->resolving)
550 // return the cached failure to the caller, but try and refresh
551 // the record in the background
552 IssueLookup(he->rec);
555 // if the host name is an IP address literal and has been parsed,
556 // go ahead and use it.
557 else if (he->rec->addr) {
558 result = he->rec;
560 // try parsing the host name as an IP address literal to short
561 // circuit full host resolution. (this is necessary on some
562 // platforms like Win9x. see bug 219376 for more details.)
563 else if (PR_StringToNetAddr(host, &tempAddr) == PR_SUCCESS) {
564 // ok, just copy the result into the host record, and be done
565 // with it! ;-)
566 he->rec->addr = (PRNetAddr *) malloc(sizeof(PRNetAddr));
567 if (!he->rec->addr)
568 status = NS_ERROR_OUT_OF_MEMORY;
569 else
570 memcpy(he->rec->addr, &tempAddr, sizeof(PRNetAddr));
571 // put reference to host record on stack...
572 result = he->rec;
574 else if (mPendingCount >= MAX_NON_PRIORITY_REQUESTS &&
575 !IsHighPriority(flags) &&
576 !he->rec->resolving) {
577 // This is a lower priority request and we are swamped, so refuse it.
578 rv = NS_ERROR_DNS_LOOKUP_QUEUE_FULL;
580 // otherwise, hit the resolver...
581 else {
582 // Add callback to the list of pending callbacks.
583 PR_APPEND_LINK(callback, &he->rec->callbacks);
585 if (!he->rec->resolving) {
586 he->rec->flags = flags;
587 rv = IssueLookup(he->rec);
588 if (NS_FAILED(rv))
589 PR_REMOVE_AND_INIT_LINK(callback);
591 else if (he->rec->onQueue) {
592 // Consider the case where we are on a pending queue of
593 // lower priority than the request is being made at.
594 // In that case we should upgrade to the higher queue.
596 if (IsHighPriority(flags) && !IsHighPriority(he->rec->flags)) {
597 // Move from (low|med) to high.
598 MoveQueue(he->rec, mHighQ);
599 he->rec->flags = flags;
600 ConditionallyCreateThread(he->rec);
601 } else if (IsMediumPriority(flags) && IsLowPriority(he->rec->flags)) {
602 // Move from low to med.
603 MoveQueue(he->rec, mMediumQ);
604 he->rec->flags = flags;
605 PR_NotifyCondVar(mIdleThreadCV);
611 if (result)
612 callback->OnLookupComplete(this, result, status);
613 return rv;
616 void
617 nsHostResolver::DetachCallback(const char *host,
618 PRUint16 flags,
619 PRUint16 af,
620 nsResolveHostCallback *callback,
621 nsresult status)
623 nsRefPtr<nsHostRecord> rec;
625 nsAutoLock lock(mLock);
627 nsHostKey key = { host, flags, af };
628 nsHostDBEnt *he = static_cast<nsHostDBEnt *>
629 (PL_DHashTableOperate(&mDB, &key, PL_DHASH_LOOKUP));
630 if (he && he->rec) {
631 // walk list looking for |callback|... we cannot assume
632 // that it will be there!
633 PRCList *node = he->rec->callbacks.next;
634 while (node != &he->rec->callbacks) {
635 if (static_cast<nsResolveHostCallback *>(node) == callback) {
636 PR_REMOVE_LINK(callback);
637 rec = he->rec;
638 break;
640 node = node->next;
645 // complete callback with the given status code; this would only be done if
646 // the record was in the process of being resolved.
647 if (rec)
648 callback->OnLookupComplete(this, rec, status);
651 nsresult
652 nsHostResolver::ConditionallyCreateThread(nsHostRecord *rec)
654 if (mNumIdleThreads) {
655 // wake up idle thread to process this lookup
656 PR_NotifyCondVar(mIdleThreadCV);
658 else if ((mThreadCount < HighThreadThreshold) ||
659 (IsHighPriority(rec->flags) && mThreadCount < MAX_RESOLVER_THREADS)) {
660 // dispatch new worker thread
661 NS_ADDREF_THIS(); // owning reference passed to thread
663 mThreadCount++;
664 PRThread *thr = PR_CreateThread(PR_SYSTEM_THREAD,
665 ThreadFunc,
666 this,
667 PR_PRIORITY_NORMAL,
668 PR_GLOBAL_THREAD,
669 PR_UNJOINABLE_THREAD,
671 if (!thr) {
672 mThreadCount--;
673 NS_RELEASE_THIS();
674 return NS_ERROR_OUT_OF_MEMORY;
677 #if defined(PR_LOGGING)
678 else
679 LOG(("lookup waiting for thread - %s ...\n", rec->host));
680 #endif
681 return NS_OK;
684 nsresult
685 nsHostResolver::IssueLookup(nsHostRecord *rec)
687 nsresult rv = NS_OK;
688 NS_ASSERTION(!rec->resolving, "record is already being resolved");
690 // Add rec to one of the pending queues, possibly removing it from mEvictionQ.
691 // If rec is on mEvictionQ, then we can just move the owning
692 // reference over to the new active queue.
693 if (rec->next == rec)
694 NS_ADDREF(rec);
695 else {
696 PR_REMOVE_LINK(rec);
697 mEvictionQSize--;
700 if (IsHighPriority(rec->flags))
701 PR_APPEND_LINK(rec, &mHighQ);
702 else if (IsMediumPriority(rec->flags))
703 PR_APPEND_LINK(rec, &mMediumQ);
704 else
705 PR_APPEND_LINK(rec, &mLowQ);
706 mPendingCount++;
708 rec->resolving = PR_TRUE;
709 rec->onQueue = PR_TRUE;
711 rv = ConditionallyCreateThread(rec);
713 LOG (("DNS Thread Counters: total=%d any-live=%d idle=%d pending=%d\n",
714 mThreadCount,
715 mActiveAnyThreadCount,
716 mNumIdleThreads,
717 mPendingCount));
719 return rv;
722 void
723 nsHostResolver::DeQueue(PRCList &aQ, nsHostRecord **aResult)
725 *aResult = static_cast<nsHostRecord *>(aQ.next);
726 PR_REMOVE_AND_INIT_LINK(*aResult);
727 mPendingCount--;
728 (*aResult)->onQueue = PR_FALSE;
731 PRBool
732 nsHostResolver::GetHostToLookup(nsHostRecord **result)
734 PRBool timedOut = PR_FALSE;
735 PRIntervalTime epoch, now, timeout;
737 nsAutoLock lock(mLock);
739 timeout = (mNumIdleThreads >= HighThreadThreshold) ? mShortIdleTimeout : mLongIdleTimeout;
740 epoch = PR_IntervalNow();
742 while (!mShutdown) {
743 // remove next record from Q; hand over owning reference. Check high, then med, then low
745 if (!PR_CLIST_IS_EMPTY(&mHighQ)) {
746 DeQueue (mHighQ, result);
747 return PR_TRUE;
750 if (mActiveAnyThreadCount < HighThreadThreshold) {
751 if (!PR_CLIST_IS_EMPTY(&mMediumQ)) {
752 DeQueue (mMediumQ, result);
753 mActiveAnyThreadCount++;
754 (*result)->usingAnyThread = PR_TRUE;
755 return PR_TRUE;
758 if (!PR_CLIST_IS_EMPTY(&mLowQ)) {
759 DeQueue (mLowQ, result);
760 mActiveAnyThreadCount++;
761 (*result)->usingAnyThread = PR_TRUE;
762 return PR_TRUE;
766 // Determining timeout is racy, so allow one cycle through checking the queues
767 // before exiting.
768 if (timedOut)
769 break;
771 // wait for one or more of the following to occur:
772 // (1) the pending queue has a host record to process
773 // (2) the shutdown flag has been set
774 // (3) the thread has been idle for too long
776 mNumIdleThreads++;
777 PR_WaitCondVar(mIdleThreadCV, timeout);
778 mNumIdleThreads--;
780 now = PR_IntervalNow();
782 if ((PRIntervalTime)(now - epoch) >= timeout)
783 timedOut = PR_TRUE;
784 else {
785 // It is possible that PR_WaitCondVar() was interrupted and returned early,
786 // in which case we will loop back and re-enter it. In that case we want to
787 // do so with the new timeout reduced to reflect time already spent waiting.
788 timeout -= (PRIntervalTime)(now - epoch);
789 epoch = now;
793 // tell thread to exit...
794 mThreadCount--;
795 return PR_FALSE;
798 void
799 nsHostResolver::OnLookupComplete(nsHostRecord *rec, nsresult status, PRAddrInfo *result)
801 // get the list of pending callbacks for this lookup, and notify
802 // them that the lookup is complete.
803 PRCList cbs;
804 PR_INIT_CLIST(&cbs);
806 nsAutoLock lock(mLock);
808 // grab list of callbacks to notify
809 MoveCList(rec->callbacks, cbs);
811 // update record fields. We might have a rec->addr_info already if a
812 // previous lookup result expired and we're reresolving it..
813 PRAddrInfo *old_addr_info;
814 PR_Lock(rec->addr_info_lock);
815 old_addr_info = rec->addr_info;
816 rec->addr_info = result;
817 rec->addr_info_gencnt++;
818 PR_Unlock(rec->addr_info_lock);
819 if (old_addr_info)
820 PR_FreeAddrInfo(old_addr_info);
821 rec->expiration = NowInMinutes();
822 if (result) {
823 rec->expiration += mMaxCacheLifetime;
824 rec->negative = PR_FALSE;
826 else {
827 rec->expiration += 1; /* one minute for negative cache */
828 rec->negative = PR_TRUE;
830 rec->resolving = PR_FALSE;
832 if (rec->usingAnyThread) {
833 mActiveAnyThreadCount--;
834 rec->usingAnyThread = PR_FALSE;
837 if (rec->addr_info && !mShutdown) {
838 // add to mEvictionQ
839 PR_APPEND_LINK(rec, &mEvictionQ);
840 NS_ADDREF(rec);
841 if (mEvictionQSize < mMaxCacheEntries)
842 mEvictionQSize++;
843 else {
844 // remove first element on mEvictionQ
845 nsHostRecord *head =
846 static_cast<nsHostRecord *>(PR_LIST_HEAD(&mEvictionQ));
847 PR_REMOVE_AND_INIT_LINK(head);
848 PL_DHashTableOperate(&mDB, (nsHostKey *) head, PL_DHASH_REMOVE);
849 // release reference to rec owned by mEvictionQ
850 NS_RELEASE(head);
855 if (!PR_CLIST_IS_EMPTY(&cbs)) {
856 PRCList *node = cbs.next;
857 while (node != &cbs) {
858 nsResolveHostCallback *callback =
859 static_cast<nsResolveHostCallback *>(node);
860 node = node->next;
861 callback->OnLookupComplete(this, rec, status);
862 // NOTE: callback must not be dereferenced after this point!!
866 NS_RELEASE(rec);
869 //----------------------------------------------------------------------------
871 void
872 nsHostResolver::ThreadFunc(void *arg)
874 LOG(("nsHostResolver::ThreadFunc entering\n"));
875 #if defined(RES_RETRY_ON_FAILURE)
876 nsResState rs;
877 #endif
878 nsHostResolver *resolver = (nsHostResolver *)arg;
879 nsHostRecord *rec;
880 PRAddrInfo *ai;
881 while (resolver->GetHostToLookup(&rec)) {
882 LOG(("resolving %s ...\n", rec->host));
884 PRIntn flags = PR_AI_ADDRCONFIG;
885 if (!(rec->flags & RES_CANON_NAME))
886 flags |= PR_AI_NOCANONNAME;
888 ai = PR_GetAddrInfoByName(rec->host, rec->af, flags);
889 #if defined(RES_RETRY_ON_FAILURE)
890 if (!ai && rs.Reset())
891 ai = PR_GetAddrInfoByName(rec->host, rec->af, flags);
892 #endif
894 // convert error code to nsresult.
895 nsresult status = ai ? NS_OK : NS_ERROR_UNKNOWN_HOST;
896 resolver->OnLookupComplete(rec, status, ai);
897 LOG(("lookup complete for %s ...\n", rec->host));
899 NS_RELEASE(resolver);
900 LOG(("nsHostResolver::ThreadFunc exiting\n"));
903 //----------------------------------------------------------------------------
905 nsresult
906 nsHostResolver::Create(PRUint32 maxCacheEntries,
907 PRUint32 maxCacheLifetime,
908 nsHostResolver **result)
910 #if defined(PR_LOGGING)
911 if (!gHostResolverLog)
912 gHostResolverLog = PR_NewLogModule("nsHostResolver");
913 #endif
915 nsHostResolver *res = new nsHostResolver(maxCacheEntries,
916 maxCacheLifetime);
917 if (!res)
918 return NS_ERROR_OUT_OF_MEMORY;
919 NS_ADDREF(res);
921 nsresult rv = res->Init();
922 if (NS_FAILED(rv))
923 NS_RELEASE(res);
925 *result = res;
926 return rv;