001 /*
002 Copyright (c) 1996-2012, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029
030 package org.hd.d.pg2k.svrCore.location;
031
032 import java.io.IOException;
033 import java.net.HttpURLConnection;
034 import java.net.MalformedURLException;
035 import java.net.URL;
036 import java.util.ArrayList;
037 import java.util.Collections;
038 import java.util.Comparator;
039 import java.util.Date;
040 import java.util.Hashtable;
041 import java.util.Iterator;
042 import java.util.List;
043 import java.util.Map;
044 import java.util.SortedMap;
045 import java.util.TreeMap;
046
047 import org.hd.d.pg2k.svrCore.CoreConsts;
048 import org.hd.d.pg2k.svrCore.GenUtils;
049 import org.hd.d.pg2k.svrCore.HostUtils;
050 import org.hd.d.pg2k.svrCore.Rnd;
051 import org.hd.d.pg2k.svrCore.VarTools;
052 import org.hd.d.pg2k.svrCore.vars.InstanceID;
053 import org.hd.d.pg2k.svrCore.vars.SimpleVariablePipelineIF;
054 import org.hd.d.pg2k.svrCore.vars.SimpleVariableValue;
055 import org.hd.d.pg2k.svrCore.vars.SystemVariables;
056
057
058 /**Load-balancing utility functions for stand-alone and Web apps.
059 */
060 public final class LoadBalancingUtils
061 {
062 /**Prevent construction of an instance. */
063 private LoadBalancingUtils() { }
064
065
066 /**If true then always log balancing decisions. */
067 public static final boolean LOG_BALANCING_DECISIONS = false;
068
069 /**Private cache for getActiveMirrors().
070 * A map from mirror tag (String) to available bandwidth
071 * in bytes-per-second-per-new-client (Long).
072 * <p>
073 * A Hashtable is used for thread safety,
074 * but also a lock on this value is used
075 * as a lock by getActiveMirrors() where needed.
076 * <p>
077 * TODO: ensure that size is bounded and/or dead entries can be trimmed on memory shortage.
078 */
079 private static final Hashtable<String,Long> _cache_gAM = new Hashtable<String,Long>();
080
081 /**Time _cache_gAM is valid until; initially zero marking the cache as invalid. */
082 private static long _cache_gAM_valid_until;
083
084 /**Roughly how stale getActiveMirrors() is prepared for its answers to be, ms. */
085 private static final int _MAX_STALE_GET_ACTIVE_MIRRORS_MS = VarTools.MIN_AGE_MS;
086
087 /**Get active mirrors listed by mirror tag; may be empty but never null.
088 * Retrieves the tags (cc-xxxxx) of active mirrors from system variables,
089 * filtering out stale entries and those without significant free bandwidth.
090 * <p>
091 * Returns a Map from the mirror tag to the per-client bandwidth
092 * that the mirror claims to have available.
093 * <p>
094 * This does <em>not</em> check names in DNS,
095 * or that mirrors are actually responding,
096 * just which mirrors claim to be up as recorded in the system variables.
097 * <p>
098 * If this cannot retrieve the variable values that it needs,
099 * then this routine returns an (immutable) empty result.
100 * <p>
101 * This does not re-compute its value on each call,
102 * but caches it for a while since it may be expensive to compute.
103 * <p>
104 * It is possible to optionally leave in "stale" entries
105 * so as to be able to "coast" for a little longer
106 * after loss of contact with the master.
107 *
108 * @param vars the system variables; never null
109 * @param filterStale if true, filter out stale entries;
110 * this result is not cached
111 *
112 * @return non-null immutable empty,
113 * or new mutable non-empty Map that is private to the caller
114 */
115 public static final SortedMap<String,Long> getActiveMirrors(final SimpleVariablePipelineIF vars,
116 final boolean filterStale)
117 {
118 if(vars == null)
119 { throw new IllegalArgumentException(); }
120
121 synchronized(_cache_gAM)
122 {
123 // If the cached value is still valid,
124 // then return a *copy* of it.
125 final long now = System.currentTimeMillis();
126 if(!filterStale)
127 {
128 // Not filtering, so we can use the cache.
129
130 // Returned cached content if still valid.
131 if(now <= _cache_gAM_valid_until)
132 { return(new TreeMap<String,Long>(_cache_gAM)); }
133 else // clear and recompute stale values...
134 {
135 _cache_gAM.clear(); // Zap the now-invalid cache content...
136 // We'll set a valid (short) cache life
137 // so that we don't keep trying to recompute this
138 // even when we encounter errors.
139 _cache_gAM_valid_until = now + Math.min(17000, _MAX_STALE_GET_ACTIVE_MIRRORS_MS/4);
140 }
141 }
142
143 try
144 {
145 // Get set of systems claiming to be alive...
146 final SimpleVariableValue mirrorAvailableBandwidthRaw =
147 vars.getVariable(SystemVariables.ThroughputMonitorFilter_AVAIL_BPS_PER_CLIENT);
148 if(mirrorAvailableBandwidthRaw == null)
149 { return(_NO_LIVE_MIRRORS); }
150 final SimpleVariableValue mirrorAvailableBandwidth = (!filterStale) ? mirrorAvailableBandwidthRaw :
151 mirrorAvailableBandwidthRaw.removeAllKeysOlder(System.currentTimeMillis() - _MAX_STALE_GET_ACTIVE_MIRRORS_MS);
152 final Map<InstanceID,SimpleVariableValue> mABMap = mirrorAvailableBandwidth.getGlobalMap();
153 if(mABMap == null)
154 { return(_NO_LIVE_MIRRORS); }
155
156 // Get the map of mirror tags...
157 final SimpleVariableValue mirrorTagsRaw =
158 vars.getVariable(SystemVariables.ThroughputMonitorFilter_ACTIVE_MIRROR_NAME);
159 if(mirrorTagsRaw == null)
160 { return(_NO_LIVE_MIRRORS); }
161 final SimpleVariableValue mirrorTags = (!filterStale) ? mirrorTagsRaw :
162 mirrorTagsRaw.removeAllKeysOlder(System.currentTimeMillis() - _MAX_STALE_GET_ACTIVE_MIRRORS_MS);
163 final Map<InstanceID,SimpleVariableValue> mTMap = mirrorTags.getGlobalMap();
164 if(mTMap == null)
165 { return(_NO_LIVE_MIRRORS); }
166
167 // First get a list of all active mirrors.
168 // Create a map from system ID
169 // to non-zero available per-client bandwidth.
170 final SortedMap<String,Long> liveMirrors = new TreeMap<String,Long>();
171
172 // Scan the global map of available bandwidth,
173 // and for every positive value,
174 // note it with the mirror tag (assuming that there is one)
175 // of the system concerned.
176 for(final InstanceID id : mABMap.keySet())
177 {
178 final SimpleVariableValue svvAvailBandwidth = mABMap.get(id);
179 if((svvAvailBandwidth != null) && (svvAvailBandwidth.getValue() != null) && (((Number) svvAvailBandwidth.getValue()).longValue() > 0))
180 {
181 final SimpleVariableValue svvTag = mTMap.get(id);
182
183 if((svvTag != null) && (svvTag.getValue() != null))
184 {
185 // Got a live mirror and its tag!
186 final String tag = (String) svvTag.getValue();
187 final Long bw = new Long(((Number) svvAvailBandwidth.getValue()).longValue());
188 liveMirrors.put(tag, bw);
189 }
190 }
191 }
192
193 // Memory use (heap-churn) optimisation:
194 // return the single "no mirrors" value if none found...
195 if(liveMirrors.isEmpty())
196 { return(_NO_LIVE_MIRRORS); }
197
198 // Return details (and cache a copy) of one or more mirrors...
199 // Don't cache filtered data.
200 if(!filterStale)
201 {
202 // Cache the unfiltered data.
203 _cache_gAM.putAll(liveMirrors);
204 // Set a reasonable life on the non-empty cache...
205 _cache_gAM_valid_until = now + _MAX_STALE_GET_ACTIVE_MIRRORS_MS/2;
206 }
207 return(liveMirrors);
208 }
209 // If we have a problem getting the variable values that we need,
210 // then return a value indicating no currently-active mirrors.
211 catch(final IOException e)
212 {
213 e.printStackTrace(); // Whinge, but absorb the error.
214 return(_NO_LIVE_MIRRORS);
215 }
216 }
217 }
218
219 /**Immutable empty Map to indicate no live mirrors. */
220 private static final SortedMap<String,Long> _NO_LIVE_MIRRORS =
221 Collections.unmodifiableSortedMap(new TreeMap<String,Long>());
222
223
224 /**Make full hostname of site given a mirror tag; never null/empty.
225 * The host name returned is relative to the given domain name.
226 * <p>
227 * No checking is done that the resulting name actually refers to a valid host.
228 * <p>
229 * Result is undefined if the tag is not a valid mirror tag.
230 */
231 public static String makeMirrorHostnameFromTag(final String mirrorTag,
232 final String domainName)
233 {
234 final String baseName = HostUtils.normaliseVirtualHostName(domainName);
235 return("mirror-" + mirrorTag + "." + baseName);
236 }
237
238
239 /**Immutable info on interaction with one remote mirror.
240 */
241 private static final class MirrorInfo
242 {
243 /**Make a new instance with a suitable lifetime... */
244 MirrorInfo(final Boolean isUp,
245 final Thread t)
246 {
247 // Set random cache expiry time with mean specified,
248 // plus a bit extra for positive results
249 // and when we're (temporarily) conserving energy.
250 validUntil = System.currentTimeMillis() +
251 (Boolean.TRUE.equals(isUp) ?
252 (MIRROR_RECHECK_MS) : (MIRROR_RECHECK_MS/2)) +
253 (GenUtils.mustConservePower() ? MIRROR_RECHECK_MS/2 : 0) +
254 Rnd.fastRnd.nextInt(MIRROR_RECHECK_MS);
255 this.isUp = isUp;
256 checker = t;
257 }
258
259 /**Time that this info is valid until. */
260 final long validUntil;
261
262 /**TRUE if the mirror seems to be up and working and accessible.
263 * FALSE if the mirror seems to be down.
264 * null if we do not yet know, because we are checking asynchronously.
265 */
266 final Boolean isUp;
267
268 /**Non-null if being checked in background by thread; null if not being checked.
269 * If any referred-to thread is not alive then this entry is not being checked.
270 */
271 final Thread checker;
272
273 /**If true then this entry can be expired.
274 * This is true if the validUntil time has passed
275 * and there is no thread working on this host/entry.
276 */
277 boolean canExpire()
278 {
279 if(System.currentTimeMillis() <= validUntil) { return(false); }
280 if(checker == null) { return(true); }
281 return(!checker.isAlive());
282 }
283
284 /**If true then this entry will expire soon.
285 * This can be used to decide if we should start a new checker thread.
286 * <p>
287 * This is true if the validUntil time will pass soon
288 * and there is no thread working on this host/entry.
289 */
290 boolean expiresSoon()
291 {
292 if(System.currentTimeMillis() <= validUntil - (MIRROR_RECHECK_MS/4))
293 { return(false); }
294 if(checker == null) { return(true); }
295 return(!checker.isAlive());
296 }
297 }
298
299
300 /**Target/normal/mean time before we (re)test that a mirror is working, in ms.
301 * Should be of the order of minutes to tens of minutes,
302 * (and the results may be effectively cached in pages' HTML for a while)
303 * but not so long as to make the system insensitive to extended outages
304 * or gross configuration errors.
305 * <p>
306 * We randomise this a little to help avoid (re)testing collisions
307 * between peers.
308 * <p>
309 * Testing may be slow and/or consume significant network resources.
310 * <p>
311 * We may asymmetrically retain positive results longer than negative ones.
312 */
313 private static final int MIRROR_RECHECK_MS = 13 * 60 * 1000 +
314 Rnd.fastRnd.nextInt(3 * 60 * 1000);
315
316
317 /**Construct a full mirror host name from the mirror tag.
318 * Result is undefined if the tag is invalid.
319 */
320 public static String makeMirrorNameFromTag(final String tag)
321 {
322 return("mirror-" + tag + "." + CoreConsts.MAIN_DATA_HOST);
323 }
324
325 /**Try the requested mirrors in turn, returning the full hostname of the first that is up, or null if none is available.
326 * The mirrors are tried in the order in the list.
327 * <p>
328 * The cache of mirror state is used, and updated if need be.
329 * <p>
330 * This routine may take considerable time to complete
331 * if this has to actually test the mirrors
332 * and they are down and/or network connectivity is poor.
333 *
334 * @param orderedMirrorTags valid mirror tags in the order the mirrors should be tried
335 *
336 * @return the full hostname of the first working mirror found,
337 * or null if none of the mirrors specified appear to be up
338 */
339 public static String findFirstWorkingMirror(final List<String> orderedMirrorTags)
340 {
341 // Try the tags in order, asynchronously.
342 for(final String tag : orderedMirrorTags)
343 {
344 final String fullHostname = makeMirrorNameFromTag(tag);
345
346 // If the server is up then our search is over.
347 // Don't block to find out though.
348 if(Boolean.TRUE.equals(testIfHTTPServerIsUp(fullHostname, true)))
349 { return(fullHostname); }
350 }
351
352 return(null); // No available mirror.
353 }
354
355 // /**If true then we automatically filter out mirrors with too little bandwidth in orderMirrorTagsBestBandwidthFirst(). */
356 // private static final boolean _OMTBF_DROP_MIRRORS_BW_TOO_LOW = true;
357
358 /**Orders mirrors into "fastest first" order, returning a List of mirror tags and dropping any known unusable; never null.
359 * This accepts as input a set of mirror details as returned from getActiveMirrors()
360 * (a mapping from mirror tag to claimed available bandwidth per client).
361 * <p>
362 * The results will be ordered to be highest-bandwidth first, and then by name.
363 * <p>
364 * This will drop mirrors known from the cache to be dead/unreachable,
365 * and any mirrors that are <em>much</em> slower than the best available.
366 * <p>
367 * The result will be smaller when inappropriate/slow/dead mirrors
368 * are dropped from the result.
369 * <p>
370 * This does <em>not</em> synchronously attempt
371 * to verify the accuracy of the data that it has been passed,
372 * eg it does not do blocking DNS lookups nor make HTTP connections,
373 * so should always be reasonably fast.
374 * (It does however check the status of remote mirrors asynchronously,
375 * and won't return a candidate unless verified to be working.)
376 *
377 * @param availableMirrors mapping of mirror tags to available per-client bandwidth; never null
378 * @return private, mutable-unless-empty, best-first list of mirrors;
379 * may be empty and/or smaller than the input set but never null
380 */
381 public static List<String> orderMirrorTagsBestBandwidthFirst(final Map<String, Long> availableMirrors)
382 {
383 if(null == availableMirrors) { throw new IllegalArgumentException(); }
384
385 // Assume that all candidates will usually make it to the result.
386 final List<String> result = new ArrayList<String>(availableMirrors.size());
387
388 // First filter out any unsuitable mirrors.
389 // This may be any with non-positive bandwidth,
390 // or bandwidth too small to sustain a good user experience,
391 // or that a geographically inappropriate for the user.
392 for(final String tag : availableMirrors.keySet())
393 {
394 final Long bwL = availableMirrors.get(tag);
395 // Reject malformed or non-positive bandwidth entries...
396 if(bwL == null) { continue; }
397 if(bwL.longValue() <= 0) { continue; }
398
399 // // Reject mirrors with far too little bandwidth to be likely to be of use.
400 // // We filter for much lower than typical per-user bandwidth,
401 // // in case our number is somewhat wrong.
402 // if(_OMTBF_DROP_MIRRORS_BW_TOO_LOW &&
403 // (bwL.longValue() < WebConsts.DEFAULT_EXPECTED_USER_BW_BYTESPERSEC/10))
404 // { continue; }
405
406 final String fullHostname = makeMirrorNameFromTag(tag);
407
408 // If we don't know the mirror to be up
409 // then move on to the next possible mirror.
410 if(!Boolean.TRUE.equals(testIfHTTPServerIsUp(fullHostname, true)))
411 { continue; }
412
413 result.add(tag);
414 }
415
416 // If there are no remaining results then return immutable empty List to save space.
417 if(result.isEmpty())
418 { return(Collections.emptyList()); }
419
420 // If more than one remaining candidate
421 // then sort the results into bandwidth order
422 // and trim any VERY slow hosts...
423 if(result.size() > 1)
424 {
425 Collections.sort(result, new BWOrder(availableMirrors));
426
427 // Get the bandwidth of the best entry...
428 final Long bestBW = availableMirrors.get(result.get(0));
429 if(bestBW == null) { throw new IllegalStateException(); }
430
431 // Compute minimum bandwidth to remain in the result.
432 // In the first instance a fraction of the fastest mirror's bandwidth
433 // that is about the same as the maximum as the maximum "closeness" benefit.
434 // The reasoning is that even being very close does not compensate for being very slow.
435 final long thresholdBW = bestBW.longValue() / (GeoProximity.VCLOSE.getCloseness() << 1);
436
437 // Nibble away entries from the end of the list that don't make the grade.
438 // Working backwards, as soon as we find one good enough then we can stop.
439 for(int i = result.size(); --i >= 1; )
440 {
441 final Long bw = availableMirrors.get(result.get(i));
442 if(bw == null) { throw new IllegalStateException(); }
443 if(bw.longValue() >= thresholdBW) { break; }
444 result.remove(i);
445 }
446 }
447
448 // Return the ordered results.
449 return(result);
450 }
451
452 /**Our simple sorting comparator for mirrors sorts by bandwidth (or other Long metric) then name. */
453 public static final class BWOrder implements Comparator<String>
454 {
455 /**Create an instance with the tag info map behind it. */
456 public BWOrder(final Map<String,Long> mirrorInfo)
457 { this.mirrorInfo = mirrorInfo; }
458 /**The map from tag name to bandwidth. */
459 private final Map<String,Long> mirrorInfo;
460
461 /**Sort in order by bandwidth (highest first), then by name to give a total ordering. */
462 public final int compare(final String s1, final String s2)
463 {
464 final long b1 = mirrorInfo.get(s1).longValue();
465 final long b2 = mirrorInfo.get(s2).longValue();
466
467 // Sort for largest-bandwidth first...
468 if(b1 > b2) { return(-1); }
469 if(b1 < b2) { return(+1); }
470
471 // Sort by name to break ties and thus to provide a total ordering.
472 return(s1.compareTo(s2));
473 }
474 }
475
476 /**Connection establishment timeout allowed by testIfHTTPServerIsUp(), in ms.
477 * Fairly short to make sure that a server is responding reasonably quickly,
478 * but long enough to allow for DNS lookup and the odd lost packet
479 * and some intermittent Net congestion.
480 * <p>
481 * Should be of the order of a few seconds to a few tens of seconds.
482 */
483 private static final int _tIHSIU_CONN_TIMEOUT_MS = 9001 + Rnd.fastRnd.nextInt(3001);
484
485 /**Number of connection attempts before deciding that remote host is dead; strictly positive. */
486 private static final int _tIHSIU_CONN_ATTEMPTS = 13 + Rnd.fastRnd.nextInt(3);
487
488
489 /**Private cache for testIfHTTPServerIsUp() for which mirrors are up.
490 * Map from mirror full host name component of URL (not just the mirror tag)
491 * to last time server at that URL was tested and found to respond properly
492 * (eg fast enough and with no error) or not.
493 * <p>
494 * Uses a thread-safe Map implementation.
495 * <p>
496 * TODO: must cap size and/or ensure that dead entries can be trimmed especially under memory stress.
497 */
498 private static final Map<String,MirrorInfo> _cache_tIHSIU_mirrorStatus = new Hashtable<String,MirrorInfo>();
499
500 /**Tests if an HTTP Gallery mirror server is up and running and responding quickly.
501 * The hostname or the hostname:port must be supplied;
502 * the "null" page will be polled
503 * (which should always work unless the host is down or busy).
504 * <p>
505 * Tests that the web server responds quickly and without error
506 * (we must get response code 200),
507 * to a request for its top-level page "/null.jsp"
508 * (redirections are allowed, eg to an index.html or index.jsp).
509 * <p>
510 * If an invalid (non-null) host name is supplied this will return false.
511 * <p>
512 * We should only do one of these at a time for a given host
513 * in order to avoid overloading any one remote mirror.
514 * <p>
515 * Note that even if we check synchronously,
516 * we may still record any result in the cache
517 * so that another caller may benefit.
518 * This also means that this routine can call itself recursively
519 * (with the checkAsynchronously argument false)
520 * via asynchronous threads.
521 * <p>
522 * Note that this may run as many threads as there are different hosts
523 * whose status is being requested.
524 * We could probably do better with non-blocking I/O
525 * and a single worker thread if there are many hosts to check,
526 * but the assumption is that there will not be.
527 *
528 * @param hostAndPort hostname or hostname:port for mirror; never null
529 * @param checkAsynchronously if false we check synchronously
530 * and block until we can return TRUE or FALSE,
531 * else we return immediately and check in the background
532 * @return TRUE if the specified server seems to be up and running,
533 * FALSE if the server seems to be down,
534 * and null if we do not know at the moment
535 * (never returns null for a synchronous check)
536 */
537 public static Boolean testIfHTTPServerIsUp(final String hostAndPort,
538 final boolean checkAsynchronously)
539 {
540 // If we are not using the cache then block here
541 // and update the cache if there is no thread updating
542 // or if the updating thread is us.
543 if(!checkAsynchronously)
544 {
545 boolean r; // = false;
546 try { r = testIfHTTPServerIsUp(new URL("http://" + hostAndPort + CoreConsts.DO_NOTHING_PAGE_RRURL)); }
547 catch(final MalformedURLException e)
548 {
549 e.printStackTrace(); // Unexpected error...
550 r = false;
551 }
552
553 final Boolean result = r ? Boolean.TRUE : Boolean.FALSE;
554 return(result);
555 }
556
557 // Removing expired entries...
558 // See if this thread is going to be picked to expire stale entries.
559 // We pick at random in inverse proportion to the size of the cache,
560 // in order to make the average cost per call constant.
561 // We do this under a lock on the cache to eliminate races,
562 // but we can release this lock again as soon as we are done.
563 synchronized(_cache_tIHSIU_mirrorStatus)
564 {
565 final int size = _cache_tIHSIU_mirrorStatus.size();
566 if((size > 0) && (Rnd.fastRnd.nextInt(2 + size) == 0))
567 {
568 // Expire old entries.
569 for(final Iterator<String> it = _cache_tIHSIU_mirrorStatus.keySet().iterator(); it.hasNext(); )
570 {
571 final String tag = it.next();
572 // Remove a stale entry as long as
573 // it does not still have a live thread updating it.
574 if(_cache_tIHSIU_mirrorStatus.get(tag).canExpire())
575 { it.remove(); }
576 }
577 }
578 }
579
580 // See what we currently have in the cache for this host.
581 // Make this, and the possible launch of a check thread, atomic.
582 synchronized(_cache_tIHSIU_mirrorStatus)
583 {
584 final MirrorInfo extant = _cache_tIHSIU_mirrorStatus.get(hostAndPort);
585
586 // If there is no entry or it has nearly expired
587 // then launch a background thread to get it updated.
588 // Create an entry that preserves the current value while we check;
589 // this limits us to at most one checker thread per host.
590 final Boolean result;
591 if((extant == null) || extant.expiresSoon())
592 {
593 // Compute result to return now.
594 result = (extant == null) ? null : extant.isUp;
595
596 // Create a checker Thread.
597 final Thread t = new Thread("checking liveness of server " + hostAndPort){
598 @Override
599 public final void run()
600 {
601 // Test synchronously...
602 final Boolean r = testIfHTTPServerIsUp(hostAndPort, false);
603 // This should always give a definite answer.
604 assert(r != null);
605
606 // If we can update the cache, then do it.
607 // We hold a lock to keep any test-and-update atomic.
608 synchronized(_cache_tIHSIU_mirrorStatus)
609 {
610 // If we can update the cache then do so.
611 // It can be updated if:
612 // * There is no entry.
613 // * There is no (live) checker thread.
614 // * There is a current entry but we are the checker thread.
615 final MirrorInfo extant = _cache_tIHSIU_mirrorStatus.get(hostAndPort);
616 if((extant == null) ||
617 (extant.checker == null) || !extant.checker.isAlive() ||
618 (extant.checker == Thread.currentThread()))
619 {
620 final MirrorInfo newInfo = new MirrorInfo(r, null);
621 final MirrorInfo oldInfo = _cache_tIHSIU_mirrorStatus.put(hostAndPort, newInfo);
622 // Report any change in status.
623 // Assumes that the Boolean can only be
624 // one of the fixed TRUE/FALSE/null
625 // so !=/== can be used rather than equals().
626 if((oldInfo == null) || (r != oldInfo.isUp))
627 {
628 System.out.println("[testIfHTTPServerIsUp("+hostAndPort+"): status change: now "+r+"; recheck after: "+(new Date(newInfo.validUntil))+"]");
629 }
630 }
631 }
632 }
633 };
634
635 _cache_tIHSIU_mirrorStatus.put(hostAndPort,
636 new MirrorInfo(result, t));
637 // Mark the the thread as a daemon and start it off.
638 t.setDaemon(true);
639 t.start();
640 }
641 else
642 {
643 result = extant.isUp;
644 }
645
646 return(result);
647 }
648 }
649
650 /**Tests if a Web server is up and running and responding quickly.
651 * A full URL must be supplied.
652 * <p>
653 * Tests that the web server responds quickly and without error
654 * (we must get response code 200),
655 * to a HEAD request for a special light-weight page "/null.jsp"
656 * (redirections are allowed, eg to an index.html or index.jsp).
657 * <p>
658 * If an invalid (non-null) host name is supplied this will return false.
659 * <p>
660 * We should only do one of these at a time for a given host
661 * in order to avoid overloading any one remote mirror.
662 *
663 * @param serverURL full URL for remote server (page) to test; never null
664 * @return true if the specified server seems to be up and running, false otherwise
665 */
666 public static boolean testIfHTTPServerIsUp(final URL serverURL)
667 {
668 if(serverURL == null)
669 { throw new IllegalArgumentException(); }
670
671 for(int attemptsLeft = _tIHSIU_CONN_ATTEMPTS; --attemptsLeft >= 0; )
672 {
673 HttpURLConnection uc = null;
674 try
675 {
676 uc = (HttpURLConnection) serverURL.openConnection();
677 if(!CoreConsts.AVOID_UNSAFE_TCP_TIMEOUTS)
678 {
679 uc.setConnectTimeout(_tIHSIU_CONN_TIMEOUT_MS); // Allow DNS lookup, TCP handshake, etc...
680 uc.setReadTimeout(_tIHSIU_CONN_TIMEOUT_MS); // Page may be expensive/slow to generate.
681 }
682 uc.setUseCaches(false); // Force a fresh fetch/connection.
683 uc.setAllowUserInteraction(false);
684 uc.setInstanceFollowRedirects(true);
685 uc.setRequestMethod("HEAD"); // Only need to get the status, not the data.
686 uc.connect(); // Try to force a connection...
687 final int responseCode = uc.getResponseCode();
688 if(200 == responseCode)
689 {
690 // All seems OK, so return immediately.
691 return(true);
692 }
693
694 //System.err.println("[testIfHTTPServerIsUp("+serverURL+") failed (attemptsLeft="+attemptsLeft+") with status code " + responseCode + ".]");
695 }
696 // Any IOException probably indicates that the server is NOT well.
697 catch(final IOException e)
698 {
699 //System.err.println("[testIfHTTPServerIsUp("+serverURL+") failed (attemptsLeft="+attemptsLeft+") with error " + e.getMessage() + ".]");
700 }
701
702 // Drop possibly-broken connection.
703 if(uc != null) { uc.disconnect(); }
704
705 // Pause a while before trying again...
706 // This may give DNS and other temporary glitches time to pass.
707 try { Thread.sleep(15101 + 2*_tIHSIU_CONN_TIMEOUT_MS); }
708 catch(final InterruptedException e)
709 {
710 Thread.currentThread().interrupt();
711 return(false); // Return immediately after interrupt.
712 }
713 }
714
715 // Failed to connect...
716 return(false);
717 }
718 }