Follow-on fix for bug 457825. Use sheet principal for agent and user sheets. r=dbaron...
[wine-gecko.git] / netwerk / dns / src / nsHostResolver.cpp
bloba6cdafd748a856dfc58737b3fed3a7332dc9ba81
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 //----------------------------------------------------------------------------
71 // Use a persistent thread pool in order to avoid spinning up new threads all the time.
72 // In particular, thread creation results in a res_init() call from libc which is
73 // quite expensive.
75 // The pool dynamically grows between 0 and MAX_RESOLVER_THREADS in size. New requests
76 // go first to an idle thread. If that cannot be found and there are fewer than MAX_RESOLVER_THREADS
77 // currently in the pool a new thread is created for high priority requests. If
78 // the new request is at a lower priority a new thread will only be created if
79 // there are fewer than HighThreadThreshold currently outstanding. If a thread cannot be
80 // created or an idle thread located for the request it is queued.
82 // When the pool is greater than HighThreadThreshold in size a thread will be destroyed after
83 // ShortIdleTimeoutSeconds of idle time. Smaller pools use LongIdleTimeoutSeconds for a
84 // timeout period.
86 #define MAX_NON_PRIORITY_REQUESTS 150
88 #define HighThreadThreshold MAX_RESOLVER_THREADS_FOR_ANY_PRIORITY
89 #define LongIdleTimeoutSeconds 300 // for threads 1 -> HighThreadThreshold
90 #define ShortIdleTimeoutSeconds 60 // for threads HighThreadThreshold+1 -> MAX_RESOLVER_THREADS
92 PR_STATIC_ASSERT (HighThreadThreshold <= MAX_RESOLVER_THREADS);
94 //----------------------------------------------------------------------------
96 #if defined(PR_LOGGING)
97 static PRLogModuleInfo *gHostResolverLog = nsnull;
98 #define LOG(args) PR_LOG(gHostResolverLog, PR_LOG_DEBUG, args)
99 #else
100 #define LOG(args)
101 #endif
103 //----------------------------------------------------------------------------
105 static inline void
106 MoveCList(PRCList &from, PRCList &to)
108 if (!PR_CLIST_IS_EMPTY(&from)) {
109 to.next = from.next;
110 to.prev = from.prev;
111 to.next->prev = &to;
112 to.prev->next = &to;
113 PR_INIT_CLIST(&from);
117 static PRUint32
118 NowInMinutes()
120 PRTime now = PR_Now(), minutes, factor;
121 LL_I2L(factor, 60 * PR_USEC_PER_SEC);
122 LL_DIV(minutes, now, factor);
123 PRUint32 result;
124 LL_L2UI(result, minutes);
125 return result;
128 //----------------------------------------------------------------------------
130 #if defined(RES_RETRY_ON_FAILURE)
132 // this class represents the resolver state for a given thread. if we
133 // encounter a lookup failure, then we can invoke the Reset method on an
134 // instance of this class to reset the resolver (in case /etc/resolv.conf
135 // for example changed). this is mainly an issue on GNU systems since glibc
136 // only reads in /etc/resolv.conf once per thread. it may be an issue on
137 // other systems as well.
139 class nsResState
141 public:
142 nsResState()
143 // initialize mLastReset to the time when this object
144 // is created. this means that a reset will not occur
145 // if a thread is too young. the alternative would be
146 // to initialize this to the beginning of time, so that
147 // the first failure would cause a reset, but since the
148 // thread would have just started up, it likely would
149 // already have current /etc/resolv.conf info.
150 : mLastReset(PR_IntervalNow())
154 PRBool Reset()
156 // reset no more than once per second
157 if (PR_IntervalToSeconds(PR_IntervalNow() - mLastReset) < 1)
158 return PR_FALSE;
160 LOG(("calling res_ninit\n"));
162 mLastReset = PR_IntervalNow();
163 return (res_ninit(&_res) == 0);
166 private:
167 PRIntervalTime mLastReset;
170 #endif // RES_RETRY_ON_FAILURE
172 //----------------------------------------------------------------------------
174 // this macro filters out any flags that are not used when constructing the
175 // host key. the significant flags are those that would affect the resulting
176 // host record (i.e., the flags that are passed down to PR_GetAddrInfoByName).
177 #define RES_KEY_FLAGS(_f) ((_f) & nsHostResolver::RES_CANON_NAME)
179 nsresult
180 nsHostRecord::Create(const nsHostKey *key, nsHostRecord **result)
182 PRLock *lock = PR_NewLock();
183 if (!lock)
184 return NS_ERROR_OUT_OF_MEMORY;
186 size_t hostLen = strlen(key->host) + 1;
187 size_t size = hostLen + sizeof(nsHostRecord);
189 nsHostRecord *rec = (nsHostRecord*) ::operator new(size);
190 if (!rec) {
191 PR_DestroyLock(lock);
192 return NS_ERROR_OUT_OF_MEMORY;
195 rec->host = ((char *) rec) + sizeof(nsHostRecord);
196 rec->flags = key->flags;
197 rec->af = key->af;
199 rec->_refc = 1; // addref
200 NS_LOG_ADDREF(rec, 1, "nsHostRecord", sizeof(nsHostRecord));
201 rec->addr_info_lock = lock;
202 rec->addr_info = nsnull;
203 rec->addr_info_gencnt = 0;
204 rec->addr = nsnull;
205 rec->expiration = NowInMinutes();
206 rec->resolving = PR_FALSE;
207 rec->onQueue = PR_FALSE;
208 PR_INIT_CLIST(rec);
209 PR_INIT_CLIST(&rec->callbacks);
210 rec->negative = PR_FALSE;
211 memcpy((char *) rec->host, key->host, hostLen);
213 *result = rec;
214 return NS_OK;
217 nsHostRecord::~nsHostRecord()
219 if (addr_info_lock)
220 PR_DestroyLock(addr_info_lock);
221 if (addr_info)
222 PR_FreeAddrInfo(addr_info);
223 if (addr)
224 free(addr);
227 //----------------------------------------------------------------------------
229 struct nsHostDBEnt : PLDHashEntryHdr
231 nsHostRecord *rec;
234 static PLDHashNumber
235 HostDB_HashKey(PLDHashTable *table, const void *key)
237 const nsHostKey *hk = static_cast<const nsHostKey *>(key);
238 return PL_DHashStringKey(table, hk->host) ^ RES_KEY_FLAGS(hk->flags) ^ hk->af;
241 static PRBool
242 HostDB_MatchEntry(PLDHashTable *table,
243 const PLDHashEntryHdr *entry,
244 const void *key)
246 const nsHostDBEnt *he = static_cast<const nsHostDBEnt *>(entry);
247 const nsHostKey *hk = static_cast<const nsHostKey *>(key);
249 return !strcmp(he->rec->host, hk->host) &&
250 RES_KEY_FLAGS (he->rec->flags) == RES_KEY_FLAGS(hk->flags) &&
251 he->rec->af == hk->af;
254 static void
255 HostDB_MoveEntry(PLDHashTable *table,
256 const PLDHashEntryHdr *from,
257 PLDHashEntryHdr *to)
259 static_cast<nsHostDBEnt *>(to)->rec =
260 static_cast<const nsHostDBEnt *>(from)->rec;
263 static void
264 HostDB_ClearEntry(PLDHashTable *table,
265 PLDHashEntryHdr *entry)
267 LOG(("evicting record\n"));
268 nsHostDBEnt *he = static_cast<nsHostDBEnt *>(entry);
269 #if defined(DEBUG) && defined(PR_LOGGING)
270 if (!he->rec->addr_info)
271 LOG(("%s: => no addr_info\n", he->rec->host));
272 else {
273 PRInt32 now = (PRInt32) NowInMinutes();
274 PRInt32 diff = (PRInt32) he->rec->expiration - now;
275 LOG(("%s: exp=%d => %s\n",
276 he->rec->host, diff,
277 PR_GetCanonNameFromAddrInfo(he->rec->addr_info)));
278 void *iter = nsnull;
279 PRNetAddr addr;
280 char buf[64];
281 for (;;) {
282 iter = PR_EnumerateAddrInfo(iter, he->rec->addr_info, 0, &addr);
283 if (!iter)
284 break;
285 PR_NetAddrToString(&addr, buf, sizeof(buf));
286 LOG((" %s\n", buf));
289 #endif
290 NS_RELEASE(he->rec);
293 static PRBool
294 HostDB_InitEntry(PLDHashTable *table,
295 PLDHashEntryHdr *entry,
296 const void *key)
298 nsHostDBEnt *he = static_cast<nsHostDBEnt *>(entry);
299 nsHostRecord::Create(static_cast<const nsHostKey *>(key), &he->rec);
300 return PR_TRUE;
303 static PLDHashTableOps gHostDB_ops =
305 PL_DHashAllocTable,
306 PL_DHashFreeTable,
307 HostDB_HashKey,
308 HostDB_MatchEntry,
309 HostDB_MoveEntry,
310 HostDB_ClearEntry,
311 PL_DHashFinalizeStub,
312 HostDB_InitEntry,
315 static PLDHashOperator
316 HostDB_RemoveEntry(PLDHashTable *table,
317 PLDHashEntryHdr *hdr,
318 PRUint32 number,
319 void *arg)
321 return PL_DHASH_REMOVE;
324 //----------------------------------------------------------------------------
326 nsHostResolver::nsHostResolver(PRUint32 maxCacheEntries,
327 PRUint32 maxCacheLifetime)
328 : mMaxCacheEntries(maxCacheEntries)
329 , mMaxCacheLifetime(maxCacheLifetime)
330 , mLock(nsnull)
331 , mIdleThreadCV(nsnull)
332 , mNumIdleThreads(0)
333 , mThreadCount(0)
334 , mAnyPriorityThreadCount(0)
335 , mEvictionQSize(0)
336 , mPendingCount(0)
337 , mShutdown(PR_TRUE)
339 mCreationTime = PR_Now();
340 PR_INIT_CLIST(&mHighQ);
341 PR_INIT_CLIST(&mMediumQ);
342 PR_INIT_CLIST(&mLowQ);
343 PR_INIT_CLIST(&mEvictionQ);
345 mHighPriorityInfo.self = this;
346 mHighPriorityInfo.onlyHighPriority = PR_TRUE;
347 mAnyPriorityInfo.self = this;
348 mAnyPriorityInfo.onlyHighPriority = PR_FALSE;
350 mLongIdleTimeout = PR_SecondsToInterval(LongIdleTimeoutSeconds);
351 mShortIdleTimeout = PR_SecondsToInterval(ShortIdleTimeoutSeconds);
354 nsHostResolver::~nsHostResolver()
356 if (mIdleThreadCV)
357 PR_DestroyCondVar(mIdleThreadCV);
359 if (mLock)
360 PR_DestroyLock(mLock);
362 PL_DHashTableFinish(&mDB);
365 nsresult
366 nsHostResolver::Init()
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;
610 if (result)
611 callback->OnLookupComplete(this, result, status);
612 return rv;
615 void
616 nsHostResolver::DetachCallback(const char *host,
617 PRUint16 flags,
618 PRUint16 af,
619 nsResolveHostCallback *callback,
620 nsresult status)
622 nsRefPtr<nsHostRecord> rec;
624 nsAutoLock lock(mLock);
626 nsHostKey key = { host, flags, af };
627 nsHostDBEnt *he = static_cast<nsHostDBEnt *>
628 (PL_DHashTableOperate(&mDB, &key, PL_DHASH_LOOKUP));
629 if (he && he->rec) {
630 // walk list looking for |callback|... we cannot assume
631 // that it will be there!
632 PRCList *node = he->rec->callbacks.next;
633 while (node != &he->rec->callbacks) {
634 if (static_cast<nsResolveHostCallback *>(node) == callback) {
635 PR_REMOVE_LINK(callback);
636 rec = he->rec;
637 break;
639 node = node->next;
644 // complete callback with the given status code; this would only be done if
645 // the record was in the process of being resolved.
646 if (rec)
647 callback->OnLookupComplete(this, rec, status);
650 nsresult
651 nsHostResolver::ConditionallyCreateThread(nsHostRecord *rec)
653 if (mNumIdleThreads) {
654 // wake up idle thread to process this lookup
655 PR_NotifyCondVar(mIdleThreadCV);
657 else if ((mThreadCount < HighThreadThreshold) ||
658 (IsHighPriority(rec->flags) && mThreadCount < MAX_RESOLVER_THREADS)) {
659 // dispatch new worker thread
660 NS_ADDREF_THIS(); // owning reference passed to thread
662 struct nsHostResolverThreadInfo *info;
664 if (mAnyPriorityThreadCount < HighThreadThreshold) {
665 info = &mAnyPriorityInfo;
666 mAnyPriorityThreadCount++;
668 else
669 info = &mHighPriorityInfo;
671 mThreadCount++;
672 PRThread *thr = PR_CreateThread(PR_SYSTEM_THREAD,
673 ThreadFunc,
674 info,
675 PR_PRIORITY_NORMAL,
676 PR_GLOBAL_THREAD,
677 PR_UNJOINABLE_THREAD,
679 if (!thr) {
680 mThreadCount--;
681 if (info == &mAnyPriorityInfo)
682 mAnyPriorityThreadCount--;
683 NS_RELEASE_THIS();
684 return NS_ERROR_OUT_OF_MEMORY;
687 #if defined(PR_LOGGING)
688 else
689 LOG(("lookup waiting for thread - %s ...\n", rec->host));
690 #endif
691 return NS_OK;
694 nsresult
695 nsHostResolver::IssueLookup(nsHostRecord *rec)
697 nsresult rv = NS_OK;
698 NS_ASSERTION(!rec->resolving, "record is already being resolved");
700 // Add rec to one of the pending queues, possibly removing it from mEvictionQ.
701 // If rec is on mEvictionQ, then we can just move the owning
702 // reference over to the new active queue.
703 if (rec->next == rec)
704 NS_ADDREF(rec);
705 else {
706 PR_REMOVE_LINK(rec);
707 mEvictionQSize--;
710 if (IsHighPriority(rec->flags))
711 PR_APPEND_LINK(rec, &mHighQ);
712 else if (IsMediumPriority(rec->flags))
713 PR_APPEND_LINK(rec, &mMediumQ);
714 else
715 PR_APPEND_LINK(rec, &mLowQ);
716 mPendingCount++;
718 rec->resolving = PR_TRUE;
719 rec->onQueue = PR_TRUE;
721 rv = ConditionallyCreateThread(rec);
723 LOG (("DNS Thread Counters: total=%d any=%d high=%d idle=%d pending=%d\n",
724 mThreadCount,
725 mAnyPriorityThreadCount,
726 mThreadCount - mAnyPriorityThreadCount,
727 mNumIdleThreads,
728 mPendingCount));
730 return rv;
733 void
734 nsHostResolver::DeQueue(PRCList &aQ, nsHostRecord **aResult)
736 *aResult = static_cast<nsHostRecord *>(aQ.next);
737 PR_REMOVE_AND_INIT_LINK(*aResult);
738 mPendingCount--;
739 (*aResult)->onQueue = PR_FALSE;
742 PRBool
743 nsHostResolver::GetHostToLookup(nsHostRecord **result, struct nsHostResolverThreadInfo *aID)
745 nsAutoLock lock(mLock);
747 PRIntervalTime start = PR_IntervalNow(), timeout;
749 while (!mShutdown) {
750 // remove next record from Q; hand over owning reference. Check high, then med, then low
752 if (!PR_CLIST_IS_EMPTY(&mHighQ)) {
753 DeQueue (mHighQ, result);
754 return PR_TRUE;
757 if (! aID->onlyHighPriority) {
758 if (!PR_CLIST_IS_EMPTY(&mMediumQ)) {
759 DeQueue (mMediumQ, result);
760 return PR_TRUE;
763 if (!PR_CLIST_IS_EMPTY(&mLowQ)) {
764 DeQueue (mLowQ, result);
765 return PR_TRUE;
769 timeout = (mNumIdleThreads >= HighThreadThreshold) ? mShortIdleTimeout : mLongIdleTimeout;
770 // wait for one or more of the following to occur:
771 // (1) the pending queue has a host record to process
772 // (2) the shutdown flag has been set
773 // (3) the thread has been idle for too long
775 // PR_WaitCondVar will return when any of these conditions is true.
776 // become the idle thread and wait for a lookup
778 mNumIdleThreads++;
779 PR_WaitCondVar(mIdleThreadCV, timeout);
780 mNumIdleThreads--;
782 PRIntervalTime delta = PR_IntervalNow() - start;
783 if (delta >= timeout)
784 break;
785 timeout -= delta;
786 start += delta;
789 // tell thread to exit...
790 mThreadCount--;
791 if (!aID->onlyHighPriority)
792 mAnyPriorityThreadCount--;
793 return PR_FALSE;
796 void
797 nsHostResolver::OnLookupComplete(nsHostRecord *rec, nsresult status, PRAddrInfo *result)
799 // get the list of pending callbacks for this lookup, and notify
800 // them that the lookup is complete.
801 PRCList cbs;
802 PR_INIT_CLIST(&cbs);
804 nsAutoLock lock(mLock);
806 // grab list of callbacks to notify
807 MoveCList(rec->callbacks, cbs);
809 // update record fields. We might have a rec->addr_info already if a
810 // previous lookup result expired and we're reresolving it..
811 PRAddrInfo *old_addr_info;
812 PR_Lock(rec->addr_info_lock);
813 old_addr_info = rec->addr_info;
814 rec->addr_info = result;
815 rec->addr_info_gencnt++;
816 PR_Unlock(rec->addr_info_lock);
817 if (old_addr_info)
818 PR_FreeAddrInfo(old_addr_info);
819 rec->expiration = NowInMinutes();
820 if (result) {
821 rec->expiration += mMaxCacheLifetime;
822 rec->negative = PR_FALSE;
824 else {
825 rec->expiration += 1; /* one minute for negative cache */
826 rec->negative = PR_TRUE;
828 rec->resolving = PR_FALSE;
830 if (rec->addr_info && !mShutdown) {
831 // add to mEvictionQ
832 PR_APPEND_LINK(rec, &mEvictionQ);
833 NS_ADDREF(rec);
834 if (mEvictionQSize < mMaxCacheEntries)
835 mEvictionQSize++;
836 else {
837 // remove first element on mEvictionQ
838 nsHostRecord *head =
839 static_cast<nsHostRecord *>(PR_LIST_HEAD(&mEvictionQ));
840 PR_REMOVE_AND_INIT_LINK(head);
841 PL_DHashTableOperate(&mDB, (nsHostKey *) head, PL_DHASH_REMOVE);
842 // release reference to rec owned by mEvictionQ
843 NS_RELEASE(head);
848 if (!PR_CLIST_IS_EMPTY(&cbs)) {
849 PRCList *node = cbs.next;
850 while (node != &cbs) {
851 nsResolveHostCallback *callback =
852 static_cast<nsResolveHostCallback *>(node);
853 node = node->next;
854 callback->OnLookupComplete(this, rec, status);
855 // NOTE: callback must not be dereferenced after this point!!
859 NS_RELEASE(rec);
862 //----------------------------------------------------------------------------
864 void
865 nsHostResolver::ThreadFunc(void *arg)
867 LOG(("nsHostResolver::ThreadFunc entering\n"));
868 #if defined(RES_RETRY_ON_FAILURE)
869 nsResState rs;
870 #endif
871 struct nsHostResolverThreadInfo *info = (struct nsHostResolverThreadInfo *) arg;
872 nsHostResolver *resolver = info->self;
873 nsHostRecord *rec;
874 PRAddrInfo *ai;
875 while (resolver->GetHostToLookup(&rec, info)) {
876 LOG(("resolving %s ...\n", rec->host));
878 PRIntn flags = PR_AI_ADDRCONFIG;
879 if (!(rec->flags & RES_CANON_NAME))
880 flags |= PR_AI_NOCANONNAME;
882 ai = PR_GetAddrInfoByName(rec->host, rec->af, flags);
883 #if defined(RES_RETRY_ON_FAILURE)
884 if (!ai && rs.Reset())
885 ai = PR_GetAddrInfoByName(rec->host, rec->af, flags);
886 #endif
888 // convert error code to nsresult.
889 nsresult status = ai ? NS_OK : NS_ERROR_UNKNOWN_HOST;
890 resolver->OnLookupComplete(rec, status, ai);
891 LOG(("lookup complete for %s ...\n", rec->host));
893 NS_RELEASE(resolver);
894 LOG(("nsHostResolver::ThreadFunc exiting\n"));
897 //----------------------------------------------------------------------------
899 nsresult
900 nsHostResolver::Create(PRUint32 maxCacheEntries,
901 PRUint32 maxCacheLifetime,
902 nsHostResolver **result)
904 #if defined(PR_LOGGING)
905 if (!gHostResolverLog)
906 gHostResolverLog = PR_NewLogModule("nsHostResolver");
907 #endif
909 nsHostResolver *res = new nsHostResolver(maxCacheEntries,
910 maxCacheLifetime);
911 if (!res)
912 return NS_ERROR_OUT_OF_MEMORY;
913 NS_ADDREF(res);
915 nsresult rv = res->Init();
916 if (NS_FAILED(rv))
917 NS_RELEASE(res);
919 *result = res;
920 return rv;