JDK-4692867 : UnKnowHostException still occurs after network connection recovers
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.net
  • Affected Version: 1.4.1
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic,windows_2000
  • CPU: generic,x86
  • Submitted: 2002-05-29
  • Updated: 2003-03-13
  • Resolved: 2002-09-26
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other Other
1.4.1_03 03Fixed 1.4.2Fixed
Related Reports
Duplicate :  
Relates :  
Description
While an application is working, the network connection is terminated.
Then, even though the network connection recover from its termination.
the InetAddress.getByName() can not get the IP address and outputs "UnKnownHostException" 

Expectation is that the getByName() can get IP address after the network connection's recovery.


1. Reproducing

 1) Modify 2 lines of the attached sample code.

    Please specify host name of each which are connectted to the network.
  
    ex.
      String hostA = "srv1";
      String hostB = "srv2";

 2) Compile  the source code
 3) Invoke "Java InetAddressTP"
    -> You will see the following message.
   
** 1st resolution must be resolved **
srv1/111.222.33.44
** Thread will sleep, disable network by detaching the network cable **

 4) Disable the network connection
    ex. 
      by pulling out LAN connector

 5) Wait about 20 seconds, you will see the following.

java.net.UnknownHostException: srv2
        at java.net.InetAddress.getAllByName0(InetAddress.java:948)
        at java.net.InetAddress.getAllByName0(InetAddress.java:918)
        at java.net.InetAddress.getAllByName(InetAddress.java:912)
        at java.net.InetAddress.getByName(InetAddress.java:832)
        at InetAddressTP.main(InetAddressTP.java:36)
** Thread will sleep, enable network by connecting the network cable **

  6) Enable the netwrok connection
      ex. 
        by inserting  LAN connector
 
  7) You will see the following message

** 3rd resolution, must be resolved **
java.net.UnknownHostException: srv2
        at java.net.InetAddress.getAllByName0(InetAddress.java:953)
        at java.net.InetAddress.getAllByName0(InetAddress.java:918)
        at java.net.InetAddress.getAllByName(InetAddress.java:912)
        at java.net.InetAddress.getByName(InetAddress.java:832)
        at InetAddressTP.main(InetAddressTP.java:56)

  Here, we expect the following message shows up because the network connection
  is recovered and alive, 

** 3rd resolution, must be resolved **
srv2/111.222.33.55

but the above exception appears.


2. Configration
  
 MPU : Pentium IV 1.4 [GHz]
 Mem : 384 [MB]
 OS  : Windows2000 (SP2, Japanese)
 JDK : 1.4.1-beta-b13, 1.4.0fcs, 1.3.1_03


3. Note

 - Please imagine the case that network connection is terminated and 
   recover automatically.
   Besides, your application runs like server process and try to connect
   some nodes periodically.
   If your application is running on a server, you expect the application
   can reconnect automatically when the network connection recovers ?

   However, according to the above-mentioned behavior,
   once the network connection is terminated, the application 
   can not reconnect later on automatically even if it recovers.

   That looks big problem for the application on a server.


2002-05-29
===============================================================================

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: 1.4.1_03 mantis FIXED IN: 1.4.1_03 mantis INTEGRATED IN: 1.4.1_03 mantis mantis-b03
14-06-2004

SUGGESTED FIX ------- InetAddress.java ------- *** /tmp/geta8727 Thu Sep 19 14:25:32 2002 --- /tmp/getb8727 Thu Sep 19 14:25:32 2002 *************** *** 1,5 **** /* ! * %W% %E% * * Copyright 2002 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. --- 1,5 ---- /* ! * %W% %E% * * Copyright 2002 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. *************** *** 11,17 **** import java.util.LinkedHashMap; import java.util.Random; import java.util.Iterator; ! import java.util.Vector; import java.security.AccessController; import java.io.ObjectStreamException; import sun.security.action.*; --- 11,17 ---- import java.util.LinkedHashMap; import java.util.Random; import java.util.Iterator; ! import java.util.LinkedList; import java.security.AccessController; import java.io.ObjectStreamException; import sun.security.action.*; *************** *** 569,576 **** /* * Cached addresses - our own litle nis, not! */ ! private static LinkedHashMap addressCache = new LinkedHashMap(); ! private static boolean addressCacheInit = false; static InetAddress[] unknown_array; // put THIS in cache static InetAddressImpl impl; --- 569,582 ---- /* * Cached addresses - our own litle nis, not! */ ! private static Cache addressCache = ! new Cache(InetAddressCachePolicy.get()); ! ! private static Cache negativeCache = ! new Cache(InetAddressCachePolicy.getNegative()); ! ! private static boolean addressCacheInit = false; ! static InetAddress[] unknown_array; // put THIS in cache static InetAddressImpl impl; *************** *** 577,700 **** private static HashMap lookupTable = new HashMap(); static final class CacheEntry { ! CacheEntry(String hostname, Object address, long expiration) { ! this.hostname = hostname; ! this.address = address; ! this.expiration = expiration; ! } ! String hostname; ! Object address; ! long expiration; } ! /* ! * Initialize cache and insert anyLocalAddress into the ! * unknown array with no expiry. */ ! private static void cacheInitIfNeeded() { ! synchronized (addressCache) { ! if (addressCacheInit) { ! return; ! } ! unknown_array = new InetAddress[1]; ! unknown_array[0] = impl.anyLocalAddress(); ! String hostname = impl.anyLocalAddress().getHostName(); ! CacheEntry entry = new CacheEntry(hostname, ! unknown_array, ! InetAddressCachePolicy.FOREVER); ! addressCache.put(hostname, entry); ! addressCacheInit = true; ! } ! } ! private static void cacheAddress(String hostname, Object address, ! boolean success) { ! int policy = (success ? ! InetAddressCachePolicy.get() : ! InetAddressCachePolicy.getNegative()); ! // if the cache policy is to cache nothing, just return ! if (policy == 0) { ! return; } ! long expiration = -1; ! if (policy != InetAddressCachePolicy.FOREVER) { ! expiration = System.currentTimeMillis() + (policy * 1000); } - cacheAddress(hostname, address, expiration); } ! private static void cacheAddress(String hostname, Object address, long expiration) { ! hostname = hostname.toLowerCase(); ! /* init cache on first call */ ! cacheInitIfNeeded(); synchronized (addressCache) { ! CacheEntry entry = (CacheEntry)addressCache.get(hostname); ! if (entry == null) { ! entry = new CacheEntry(hostname, address, expiration); ! addressCache.put(hostname, entry); } else { ! if (!entry.address.equals(address)) { ! entry.address = address; ! entry.expiration = expiration; ! // remove the old entry from the cache ! // and add a new item ! addressCache.remove(hostname); ! addressCache.put(hostname, entry); ! } } } } private static Object getCachedAddress(String hostname) { hostname = hostname.toLowerCase(); - if ((InetAddressCachePolicy.get() == 0) && - (InetAddressCachePolicy.getNegative() == 0)) { - return null; - } ! /* init cache on first call */ ! cacheInitIfNeeded(); synchronized (addressCache) { ! // purge expired entries from cache ! Iterator itr = addressCache.keySet().iterator(); ! // pass the first entry, which is for unknown address ! itr.next(); ! Vector expired = new Vector(); ! while (itr.hasNext()) { ! Object key = itr.next(); ! CacheEntry value = (CacheEntry)addressCache.get(key); ! if (value != null && value.expiration < System.currentTimeMillis() && ! value.expiration >= 0) { ! expired.add(key); ! } else { ! break; ! } ! } ! for (int i = 0; i < expired.size(); i++) { ! Object key = expired.elementAt(i); ! addressCache.remove(key); ! } ! CacheEntry entry = (CacheEntry)addressCache.get(hostname); if (entry == null) { ! return null; } ! return entry.address; ! } } static { --- 583,752 ---- private static HashMap lookupTable = new HashMap(); + /** + * Represents a cache entry + */ static final class CacheEntry { ! CacheEntry(Object address, long expiration) { ! this.address = address; ! this.expiration = expiration; ! } ! Object address; ! long expiration; } ! /** ! * A cache that manages entries based on a policy specified ! * at creation time. */ ! static final class Cache { ! private int policy; ! private LinkedHashMap cache; ! /** ! * Create cache with specific policy ! */ ! public Cache(int policy) { ! this.policy = policy; ! cache = new LinkedHashMap(); ! } ! /** ! * Add an entry to the cache. If there's already an ! * entry then for this host then the entry will be ! * replaced. ! */ ! public Cache put(String host, Object address) { ! if (policy == InetAddressCachePolicy.NEVER) { ! return this; ! } ! // purge any expired entries ! if (policy != InetAddressCachePolicy.FOREVER) { ! // As we iterate in insertion order we can ! // terminate when a non-expired entry is found. ! LinkedList expired = new LinkedList(); ! Iterator i = cache.keySet().iterator(); ! long now = System.currentTimeMillis(); ! while (i.hasNext()) { ! String key = (String)i.next(); ! CacheEntry entry = (CacheEntry)cache.get(key); ! if (entry.expiration >= 0 && entry.expiration < now) { ! expired.add(key); ! } else { ! break; ! } ! } ! i = expired.iterator(); ! while (i.hasNext()) { ! cache.remove(i.next()); ! } ! } ! ! // create new entry and add it to the cache ! // -- as a HashMap replaces existing entries we ! // don't need to explicitly check if there is ! // already an entry for this host. ! long expiration; ! if (policy == InetAddressCachePolicy.FOREVER) { ! expiration = -1; ! } else { ! expiration = System.currentTimeMillis() + (policy * 1000); ! } ! CacheEntry entry = new CacheEntry(address, expiration); ! cache.put(host, entry); ! return this; } ! /** ! * Query the cache for the specific host. If found then ! * return its CacheEntry, or null if not found. ! */ ! public CacheEntry get(String host) { ! if (policy == InetAddressCachePolicy.NEVER) { ! return null; ! } ! CacheEntry entry = (CacheEntry)cache.get(host); ! ! // check if entry has expired ! if (entry != null && policy != InetAddressCachePolicy.FOREVER) { ! if (entry.expiration >= 0 && ! entry.expiration < System.currentTimeMillis()) { ! cache.remove(host); ! entry = null; ! } ! } ! ! return entry; } } ! /* ! * Initialize cache and insert anyLocalAddress into the ! * unknown array with no expiry. ! */ ! private static void cacheInitIfNeeded() { ! assert Thread.holdsLock(addressCache); ! if (addressCacheInit) { ! return; ! } ! unknown_array = new InetAddress[1]; ! unknown_array[0] = impl.anyLocalAddress(); ! addressCache.put(impl.anyLocalAddress().getHostName(), ! unknown_array); + addressCacheInit = true; + } + + /* + * Cache the given hostname and address. + */ + private static void cacheAddress(String hostname, Object address, + boolean success) { + synchronized (addressCache) { ! cacheInitIfNeeded(); ! if (success) { ! addressCache.put(hostname, address); } else { ! negativeCache.put(hostname, address); } } } + /* + * Lookup hostname in cache (positive & negative cache). If + * found return address, null if not found. + */ private static Object getCachedAddress(String hostname) { hostname = hostname.toLowerCase(); ! // search both positive & negative caches synchronized (addressCache) { ! CacheEntry entry; ! cacheInitIfNeeded(); ! entry = (CacheEntry)addressCache.get(hostname); if (entry == null) { ! entry = (CacheEntry)negativeCache.get(hostname); } ! ! if (entry != null) { ! return entry.address; ! } ! } ! ! // not found ! return null; } static { ###@###.### 2002-09-19
19-09-2002

EVALUATION Yes, there is a bug here - when flushing expired entries from the cache we bail out when a non-expired entry is encountered. The result is that we can falsely report stale results (in the case of positive lookups), or continue to report UHE for expired negative lookups. Too late to fix in hopper but will fix in mantis. Note that the problem isn't too serious as caching of negative lookups can be disabled via the specified networkaddress.cache.negative property (ie: in lib/security/java.security you can set networkaddress.cache.negative to 0). ###@###.### 2002-05-29 The positive & negative caches are now seperated so we can flush expired entries from the negative cache without a complete iteration over the entries. ###@###.### 2002-09-19
19-09-2002