001 /*
002 Copyright (c) 1996-2011, 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 log balancing decisions, etc. */
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 // Assume that all candidates will usually make it to the result.
384 final List<String> result = new ArrayList<String>(availableMirrors.size());
385
386 // First filter out any unsuitable mirrors.
387 // This may be any with non-positive bandwidth,
388 // or bandwidth too small to sustain a good user experience,
389 // or that a geographically inappropriate for the user.
390 for(final String tag : availableMirrors.keySet())
391 {
392 final Long bwL = availableMirrors.get(tag);
393 // Reject malformed or non-positive bandwidth entries...
394 if(bwL == null) { continue; }
395 if(bwL.longValue() <= 0) { continue; }
396
397 // // Reject mirrors with far too little bandwidth to be likely to be of use.
398 // // We filter for much lower than typical per-user bandwidth,
399 // // in case our number is somewhat wrong.
400 // if(_OMTBF_DROP_MIRRORS_BW_TOO_LOW &&
401 // (bwL.longValue() < WebConsts.DEFAULT_EXPECTED_USER_BW_BYTESPERSEC/10))
402 // { continue; }
403
404 final String fullHostname = makeMirrorNameFromTag(tag);
405
406 // If we don't know the mirror to be up
407 // then move on to the next possible mirror.
408 if(!Boolean.TRUE.equals(testIfHTTPServerIsUp(fullHostname, true)))
409 { continue; }
410
411 result.add(tag);
412 }
413
414 // If there are no remaining results then return immutable empty List to save space.
415 if(result.size() == 0)
416 {
417 final List<String> emptyList = Collections.emptyList();
418 return(emptyList);
419 }
420
421 // If more than one remaining candidate
422 // then sort the results into bandwidth order
423 // and trim any VERY slow hosts...
424 if(result.size() > 1)
425 {
426 Collections.sort(result, new BWOrder(availableMirrors));
427
428 // Get the bandwidth of the best entry...
429 final Long bestBW = availableMirrors.get(result.get(0));
430 if(bestBW == null) { throw new IllegalStateException(); }
431
432 // Compute minimum bandwidth to remain in the result.
433 // In the first instance a fraction of the fastest mirror's bandwidth
434 // that is about the same as the maximum as the maximum "closeness" benefit.
435 // The reasoning is that even being very close does not compensate for being very slow.
436 final long thresholdBW = bestBW.longValue() / (2*GeoProximity.VCLOSE.getCloseness());
437
438 // Nibble away entries from the end of the list that don't make the grade.
439 // Working backwards, as soon as we find one good enough then we can stop.
440 for(int i = result.size(); --i >= 1; )
441 {
442 final Long bw = availableMirrors.get(result.get(i));
443 if(bw == null) { throw new IllegalStateException(); }
444 if(bw.longValue() >= thresholdBW) { break; }
445 result.remove(i);
446 }
447 }
448
449 // Return the ordered results.
450 return(result);
451 }
452
453 /**Our simple sorting comparator for mirrors sorts by bandwidth (or other Long metric) then name. */
454 public static final class BWOrder implements Comparator<String>
455 {
456 /**Create an instance with the tag info map behind it. */
457 public BWOrder(final Map<String,Long> mirrorInfo)
458 { this.mirrorInfo = mirrorInfo; }
459 /**The map from tag name to bandwidth. */
460 private final Map<String,Long> mirrorInfo;
461
462 /**Sort in order by bandwidth (highest first), then by name to give a total ordering. */
463 public final int compare(final String s1, final String s2)
464 {
465 final long b1 = mirrorInfo.get(s1).longValue();
466 final long b2 = mirrorInfo.get(s2).longValue();
467
468 // Sort for largest-bandwidth first...
469 if(b1 > b2) { return(-1); }
470 if(b1 < b2) { return(+1); }
471
472 // Sort by name to break ties and thus to provide a total ordering.
473 return(s1.compareTo(s2));
474 }
475 }
476
477 /**Connection establishment timeout allowed by testIfHTTPServerIsUp(), in ms.
478 * Fairly short to make sure that a server is responding reasonably quickly,
479 * but long enough to allow for DNS lookup and the odd lost packet
480 * and some intermittent Net congestion.
481 * <p>
482 * Should be of the order of a few seconds to a few tens of seconds.
483 */
484 private static final int _tIHSIU_CONN_TIMEOUT_MS = 9001 + Rnd.fastRnd.nextInt(3001);
485
486 /**Number of connection attempts before deciding that remote host is dead; strictly positive. */
487 private static final int _tIHSIU_CONN_ATTEMPTS = 13 + Rnd.fastRnd.nextInt(3);
488
489
490 /**Private cache for testIfHTTPServerIsUp() for which mirrors are up.
491 * Map from mirror full host name component of URL (not just the mirror tag)
492 * to last time server at that URL was tested and found to respond properly
493 * (eg fast enough and with no error) or not.
494 * <p>
495 * Uses a thread-safe Map implementation.
496 * <p>
497 * TODO: must cap size and/or ensure that dead entries can be trimmed especially under memory stress.
498 */
499 private static final Map<String,MirrorInfo> _cache_tIHSIU_mirrorStatus = new Hashtable<String,MirrorInfo>();
500
501 /**Tests if an HTTP Gallery mirror server is up and running and responding quickly.
502 * The hostname or the hostname:port must be supplied;
503 * the "null" page will be polled
504 * (which should always work unless the host is down or busy).
505 * <p>
506 * Tests that the web server responds quickly and without error
507 * (we must get response code 200),
508 * to a request for its top-level page "/null.jsp"
509 * (redirections are allowed, eg to an index.html or index.jsp).
510 * <p>
511 * If an invalid (non-null) host name is supplied this will return false.
512 * <p>
513 * We should only do one of these at a time for a given host
514 * in order to avoid overloading any one remote mirror.
515 * <p>
516 * Note that even if we check synchronously,
517 * we may still record any result in the cache
518 * so that another caller may benefit.
519 * This also means that this routine can call itself recursively
520 * (with the checkAsynchronously argument false)
521 * via asynchronous threads.
522 * <p>
523 * Note that this may run as many threads as there are different hosts
524 * whose status is being requested.
525 * We could probably do better with non-blocking I/O
526 * and a single worker thread if there are many hosts to check,
527 * but the assumption is that there will not be.
528 *
529 * @param hostAndPort hostname or hostname:port for mirror; never null
530 * @param checkAsynchronously if false we check synchronously
531 * and block until we can return TRUE or FALSE,
532 * else we return immediately and check in the background
533 * @return TRUE if the specified server seems to be up and running,
534 * FALSE if the server seems to be down,
535 * and null if we do not know at the moment
536 * (never returns null for a synchronous check)
537 */
538 public static Boolean testIfHTTPServerIsUp(final String hostAndPort,
539 final boolean checkAsynchronously)
540 {
541 // If we are not using the cache then block here
542 // and update the cache if there is no thread updating
543 // or if the updating thread is us.
544 if(!checkAsynchronously)
545 {
546 boolean r; // = false;
547 try { r = testIfHTTPServerIsUp(new URL("http://" + hostAndPort + CoreConsts.DO_NOTHING_PAGE_RRURL)); }
548 catch(final MalformedURLException e)
549 {
550 e.printStackTrace(); // Unexpected error...
551 r = false;
552 }
553
554 final Boolean result = r ? Boolean.TRUE : Boolean.FALSE;
555 return(result);
556 }
557
558 // Removing expired entries...
559 // See if this thread is going to be picked to expire stale entries.
560 // We pick at random in inverse proportion to the size of the cache,
561 // in order to make the average cost per call constant.
562 // We do this under a lock on the cache to eliminate races,
563 // but we can release this lock again as soon as we are done.
564 synchronized(_cache_tIHSIU_mirrorStatus)
565 {
566 final int size = _cache_tIHSIU_mirrorStatus.size();
567 if((size > 0) && (Rnd.fastRnd.nextInt(2 + size) == 0))
568 {
569 // Expire old entries.
570 for(final Iterator<String> it = _cache_tIHSIU_mirrorStatus.keySet().iterator(); it.hasNext(); )
571 {
572 final String tag = it.next();
573 // Remove a stale entry as long as
574 // it does not still have a live thread updating it.
575 if(_cache_tIHSIU_mirrorStatus.get(tag).canExpire())
576 { it.remove(); }
577 }
578 }
579 }
580
581 // See what we currently have in the cache for this host.
582 // Make this, and the possible launch of a check thread, atomic.
583 synchronized(_cache_tIHSIU_mirrorStatus)
584 {
585 final MirrorInfo extant = _cache_tIHSIU_mirrorStatus.get(hostAndPort);
586
587 // If there is no entry or it has nearly expired
588 // then launch a background thread to get it updated.
589 // Create an entry that preserves the current value while we check;
590 // this limits us to at most one checker thread per host.
591 final Boolean result;
592 if((extant == null) || extant.expiresSoon())
593 {
594 // Compute result to return now.
595 result = (extant == null) ? null : extant.isUp;
596
597 // Create a checker Thread.
598 final Thread t = new Thread("checking liveness of server " + hostAndPort){
599 @Override
600 public final void run()
601 {
602 // Test synchronously...
603 final Boolean r = testIfHTTPServerIsUp(hostAndPort, false);
604 // This should always give a definite answer.
605 assert(r != null);
606
607 // If we can update the cache, then do it.
608 // We hold a lock to keep any test-and-update atomic.
609 synchronized(_cache_tIHSIU_mirrorStatus)
610 {
611 // If we can update the cache then do so.
612 // It can be updated if:
613 // * There is no entry.
614 // * There is no (live) checker thread.
615 // * There is a current entry but we are the checker thread.
616 final MirrorInfo extant = _cache_tIHSIU_mirrorStatus.get(hostAndPort);
617 if((extant == null) ||
618 (extant.checker == null) || !extant.checker.isAlive() ||
619 (extant.checker == Thread.currentThread()))
620 {
621 final MirrorInfo newInfo = new MirrorInfo(r, null);
622 final MirrorInfo oldInfo = _cache_tIHSIU_mirrorStatus.put(hostAndPort, newInfo);
623 // Report any change in status.
624 // Assumes that the Boolean can only be
625 // one of the fixed TRUE/FALSE/null
626 // so !=/== can be used rather than equals().
627 if((oldInfo == null) || (r != oldInfo.isUp))
628 {
629 System.out.println("[testIfHTTPServerIsUp("+hostAndPort+"): status change: now "+r+"; recheck after: "+(new Date(newInfo.validUntil))+"]");
630 }
631 }
632 }
633 }
634 };
635
636 _cache_tIHSIU_mirrorStatus.put(hostAndPort,
637 new MirrorInfo(result, t));
638 // Mark the the thread as a daemon and start it off.
639 t.setDaemon(true);
640 t.start();
641 }
642 else
643 {
644 result = extant.isUp;
645 }
646
647 return(result);
648 }
649 }
650
651 /**Tests if a Web server is up and running and responding quickly.
652 * A full URL must be supplied.
653 * <p>
654 * Tests that the web server responds quickly and without error
655 * (we must get response code 200),
656 * to a HEAD request for a special light-weight page "/null.jsp"
657 * (redirections are allowed, eg to an index.html or index.jsp).
658 * <p>
659 * If an invalid (non-null) host name is supplied this will return false.
660 * <p>
661 * We should only do one of these at a time for a given host
662 * in order to avoid overloading any one remote mirror.
663 *
664 * @param serverURL full URL for remote server (page) to test; never null
665 * @return true if the specified server seems to be up and running, false otherwise
666 */
667 public static boolean testIfHTTPServerIsUp(final URL serverURL)
668 {
669 if(serverURL == null)
670 { throw new IllegalArgumentException(); }
671
672 for(int attemptsLeft = _tIHSIU_CONN_ATTEMPTS; --attemptsLeft >= 0; )
673 {
674 HttpURLConnection uc = null;
675 try
676 {
677 uc = (HttpURLConnection) serverURL.openConnection();
678 if(!CoreConsts.AVOID_UNSAFE_TCP_TIMEOUTS)
679 {
680 uc.setConnectTimeout(_tIHSIU_CONN_TIMEOUT_MS); // Allow DNS lookup, TCP handshake, etc...
681 uc.setReadTimeout(_tIHSIU_CONN_TIMEOUT_MS); // Page may be expensive/slow to generate.
682 }
683 uc.setUseCaches(false); // Force a fresh fetch/connection.
684 uc.setAllowUserInteraction(false);
685 uc.setInstanceFollowRedirects(true);
686 uc.setRequestMethod("HEAD"); // Only need to get the status, not the data.
687 uc.connect(); // Try to force a connection...
688 final int responseCode = uc.getResponseCode();
689 if(200 == responseCode)
690 {
691 // All seems OK, so return immediately.
692 return(true);
693 }
694
695 //System.err.println("[testIfHTTPServerIsUp("+serverURL+") failed (attemptsLeft="+attemptsLeft+") with status code " + responseCode + ".]");
696 }
697 // Any IOException probably indicates that the server is NOT well.
698 catch(final IOException e)
699 {
700 //System.err.println("[testIfHTTPServerIsUp("+serverURL+") failed (attemptsLeft="+attemptsLeft+") with error " + e.getMessage() + ".]");
701 }
702
703 // Drop possibly-broken connection.
704 if(uc != null) { uc.disconnect(); }
705
706 // Pause a while before trying again...
707 // This may give DNS and other temporary glitches time to pass.
708 try { Thread.sleep(15101 + 2*_tIHSIU_CONN_TIMEOUT_MS); }
709 catch(final InterruptedException e)
710 {
711 Thread.currentThread().interrupt();
712 return(false); // Return immediately after interrupt.
713 }
714 }
715
716 // Failed to connect...
717 return(false);
718 }
719 }