mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 14:54:52 +02:00
8304885: Reuse stale data to improve DNS resolver resiliency
Reviewed-by: michaelm, dfuchs
This commit is contained in:
parent
beec734fdd
commit
bdd81b3182
16 changed files with 461 additions and 87 deletions
|
@ -52,6 +52,7 @@ import java.util.concurrent.ConcurrentMap;
|
|||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -191,9 +192,9 @@ import static java.net.spi.InetAddressResolver.LookupPolicy.IPV6_FIRST;
|
|||
* <p> If the default behavior is not desired, then a Java security property
|
||||
* can be set to a different Time-to-live (TTL) value for positive
|
||||
* caching. Likewise, a system admin can configure a different
|
||||
* negative caching TTL value when needed.
|
||||
* negative caching TTL value when needed or extend the usage of the stale data.
|
||||
*
|
||||
* <p> Two Java security properties control the TTL values used for
|
||||
* <p> Three Java security properties control the TTL values used for
|
||||
* positive and negative host name resolution caching:
|
||||
*
|
||||
* <dl style="margin-left:2em">
|
||||
|
@ -205,6 +206,25 @@ import static java.net.spi.InetAddressResolver.LookupPolicy.IPV6_FIRST;
|
|||
* <p>
|
||||
* A value of -1 indicates "cache forever".
|
||||
* </dd>
|
||||
* <dt><b>networkaddress.cache.stale.ttl</b></dt>
|
||||
* <dd>Indicates the caching policy for stale names. The value is specified as
|
||||
* an integer to indicate the number of seconds that stale names will be kept in
|
||||
* the cache. A name is considered stale if the TTL has expired and an attempt
|
||||
* to lookup the host name again was not successful. This property is useful if
|
||||
* it is preferable to use a stale name rather than fail due to an unsuccessful
|
||||
* lookup. The default setting is to cache for an implementation specific period
|
||||
* of time.
|
||||
* <p>
|
||||
* If the value of this property is larger than "networkaddress.cache.ttl" then
|
||||
* "networkaddress.cache.ttl" will be used as a refresh interval of the name in
|
||||
* the cache. For example, if this property is set to 1 day and
|
||||
* "networkaddress.cache.ttl" is set to 30 seconds, then the positive response
|
||||
* will be cached for 1 day but an attempt to refresh it will be done every
|
||||
* 30 seconds.
|
||||
* <p>
|
||||
* A value of 0 (zero) or if the property is not set means do not use stale
|
||||
* names. Negative values are ignored.
|
||||
* </dd>
|
||||
* <dt><b>networkaddress.cache.negative.ttl</b> (default: 10)</dt>
|
||||
* <dd>Indicates the caching policy for un-successful name lookups
|
||||
* from the name service. The value is specified as an integer to
|
||||
|
@ -933,7 +953,7 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
|
|||
|
||||
// CachedAddresses that have to expire are kept ordered in this NavigableSet
|
||||
// which is scanned on each access
|
||||
private static final NavigableSet<CachedAddresses> expirySet =
|
||||
private static final NavigableSet<CachedLookup> expirySet =
|
||||
new ConcurrentSkipListSet<>();
|
||||
|
||||
// common interface
|
||||
|
@ -941,15 +961,22 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
|
|||
InetAddress[] get() throws UnknownHostException;
|
||||
}
|
||||
|
||||
// a holder for cached addresses with required metadata
|
||||
private static final class CachedAddresses implements Addresses, Comparable<CachedAddresses> {
|
||||
/**
|
||||
* A cached result of a name service lookup. The result can be either valid
|
||||
* addresses or invalid (ie a failed lookup) containing no addresses.
|
||||
*/
|
||||
private static class CachedLookup implements Addresses, Comparable<CachedLookup> {
|
||||
private static final AtomicLong seq = new AtomicLong();
|
||||
final String host;
|
||||
final InetAddress[] inetAddresses;
|
||||
final long expiryTime; // time of expiry (in terms of System.nanoTime())
|
||||
volatile InetAddress[] inetAddresses;
|
||||
/**
|
||||
* Time of expiry (in terms of System.nanoTime()). Can be modified only
|
||||
* when the record is not added to the "expirySet".
|
||||
*/
|
||||
volatile long expiryTime;
|
||||
final long id = seq.incrementAndGet(); // each instance is unique
|
||||
|
||||
CachedAddresses(String host, InetAddress[] inetAddresses, long expiryTime) {
|
||||
CachedLookup(String host, InetAddress[] inetAddresses, long expiryTime) {
|
||||
this.host = host;
|
||||
this.inetAddresses = inetAddresses;
|
||||
this.expiryTime = expiryTime;
|
||||
|
@ -964,7 +991,7 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
|
|||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(CachedAddresses other) {
|
||||
public int compareTo(CachedLookup other) {
|
||||
// natural order is expiry time -
|
||||
// compare difference of expiry times rather than
|
||||
// expiry times directly, to avoid possible overflow.
|
||||
|
@ -975,6 +1002,106 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
|
|||
// ties are broken using unique id
|
||||
return Long.compare(this.id, other.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current cache record is expired or not. Expired records
|
||||
* are removed from the expirySet and cache.
|
||||
*
|
||||
* @return {@code true} if the record was removed
|
||||
*/
|
||||
public boolean tryRemoveExpiredAddress(long now) {
|
||||
// compare difference of time instants rather than
|
||||
// time instants directly, to avoid possible overflow.
|
||||
// (see System.nanoTime() recommendations...)
|
||||
if ((expiryTime - now) < 0L) {
|
||||
// ConcurrentSkipListSet uses weakly consistent iterator,
|
||||
// so removing while iterating is OK...
|
||||
if (expirySet.remove(this)) {
|
||||
// ... remove from cache
|
||||
cache.remove(host, this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cached valid lookup containing addresses whose validity may be
|
||||
* temporarily extended by an additional stale period pending the mapping
|
||||
* being refreshed or updated.
|
||||
*/
|
||||
private static final class ValidCachedLookup extends CachedLookup {
|
||||
/**
|
||||
* Time to refresh (in terms of System.nanoTime()).
|
||||
*/
|
||||
private volatile long refreshTime;
|
||||
/**
|
||||
* For how long the stale data should be used after TTL expiration.
|
||||
* Initially equal to the expiryTime, but increased over time after each
|
||||
* successful lookup.
|
||||
*/
|
||||
private volatile long staleTime;
|
||||
|
||||
/**
|
||||
* only one thread is doing lookup to name service
|
||||
* for particular host at any time.
|
||||
*/
|
||||
private final Lock lookupLock = new ReentrantLock();
|
||||
|
||||
ValidCachedLookup(String host, InetAddress[] inetAddresses,
|
||||
long staleTime, long refreshTime)
|
||||
{
|
||||
super(host, inetAddresses, staleTime);
|
||||
this.refreshTime = refreshTime;
|
||||
this.staleTime = staleTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetAddress[] get() {
|
||||
long now = System.nanoTime();
|
||||
if ((refreshTime - now) < 0L && lookupLock.tryLock()) {
|
||||
try {
|
||||
// cachePolicy is in [s] - we need [ns]
|
||||
refreshTime = now + InetAddressCachePolicy.get() * 1000_000_000L;
|
||||
// getAddressesFromNameService returns non-empty/non-null value
|
||||
inetAddresses = getAddressesFromNameService(host);
|
||||
// don't update the "expirySet", will do that later
|
||||
staleTime = refreshTime + InetAddressCachePolicy.getStale() * 1000_000_000L;
|
||||
} catch (UnknownHostException ignore) {
|
||||
} finally {
|
||||
lookupLock.unlock();
|
||||
}
|
||||
}
|
||||
return inetAddresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the parent method to skip deleting the record from the
|
||||
* cache if the stale data can still be used. Note to update the
|
||||
* "expiryTime" field we have to remove the record from the expirySet
|
||||
* and add it back. It is not necessary to remove/add it here, we can do
|
||||
* that in the "get()" method above, but extracting it minimizes
|
||||
* contention on "expirySet".
|
||||
*/
|
||||
@Override
|
||||
public boolean tryRemoveExpiredAddress(long now) {
|
||||
// compare difference of time instants rather than
|
||||
// time instants directly, to avoid possible overflow.
|
||||
// (see System.nanoTime() recommendations...)
|
||||
if ((expiryTime - now) < 0L) {
|
||||
if ((staleTime - now) < 0L) {
|
||||
return super.tryRemoveExpiredAddress(now);
|
||||
}
|
||||
// ConcurrentSkipListSet uses weakly consistent iterator,
|
||||
// so removing while iterating is OK...
|
||||
if (expirySet.remove(this)) {
|
||||
expiryTime = staleTime;
|
||||
expirySet.add(this);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// a name service lookup based Addresses implementation which replaces itself
|
||||
|
@ -1021,18 +1148,33 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
|
|||
if (cachePolicy == InetAddressCachePolicy.NEVER) {
|
||||
cache.remove(host, this);
|
||||
} else {
|
||||
CachedAddresses cachedAddresses = new CachedAddresses(
|
||||
host,
|
||||
inetAddresses,
|
||||
cachePolicy == InetAddressCachePolicy.FOREVER
|
||||
? 0L
|
||||
// cachePolicy is in [s] - we need [ns]
|
||||
: System.nanoTime() + 1000_000_000L * cachePolicy
|
||||
);
|
||||
if (cache.replace(host, this, cachedAddresses) &&
|
||||
long now = System.nanoTime();
|
||||
long expiryTime =
|
||||
cachePolicy == InetAddressCachePolicy.FOREVER ?
|
||||
0L
|
||||
// cachePolicy is in [s] - we need [ns]
|
||||
: now + 1000_000_000L * cachePolicy;
|
||||
CachedLookup cachedLookup;
|
||||
if (InetAddressCachePolicy.getStale() > 0 &&
|
||||
ex == null && expiryTime > 0)
|
||||
{
|
||||
long refreshTime = expiryTime;
|
||||
// staleCachePolicy is in [s] - we need [ns]
|
||||
expiryTime = refreshTime + 1000_000_000L *
|
||||
InetAddressCachePolicy.getStale();
|
||||
cachedLookup = new ValidCachedLookup(host,
|
||||
inetAddresses,
|
||||
expiryTime,
|
||||
refreshTime);
|
||||
} else {
|
||||
cachedLookup = new CachedLookup(host,
|
||||
inetAddresses,
|
||||
expiryTime);
|
||||
}
|
||||
if (cache.replace(host, this, cachedLookup) &&
|
||||
cachePolicy != InetAddressCachePolicy.FOREVER) {
|
||||
// schedule expiry
|
||||
expirySet.add(cachedAddresses);
|
||||
expirySet.add(cachedLookup);
|
||||
}
|
||||
}
|
||||
if (inetAddresses == null || inetAddresses.length == 0) {
|
||||
|
@ -1638,18 +1780,8 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
|
|||
// remove expired addresses from cache - expirySet keeps them ordered
|
||||
// by expiry time so we only need to iterate the prefix of the NavigableSet...
|
||||
long now = System.nanoTime();
|
||||
for (CachedAddresses caddrs : expirySet) {
|
||||
// compare difference of time instants rather than
|
||||
// time instants directly, to avoid possible overflow.
|
||||
// (see System.nanoTime() recommendations...)
|
||||
if ((caddrs.expiryTime - now) < 0L) {
|
||||
// ConcurrentSkipListSet uses weakly consistent iterator,
|
||||
// so removing while iterating is OK...
|
||||
if (expirySet.remove(caddrs)) {
|
||||
// ... remove from cache
|
||||
cache.remove(caddrs.host, caddrs);
|
||||
}
|
||||
} else {
|
||||
for (CachedLookup caddrs : expirySet) {
|
||||
if (!caddrs.tryRemoveExpiredAddress(now)) {
|
||||
// we encountered 1st element that expires in future
|
||||
break;
|
||||
}
|
||||
|
@ -1662,7 +1794,7 @@ public sealed class InetAddress implements Serializable permits Inet4Address, In
|
|||
} else {
|
||||
addrs = cache.remove(host);
|
||||
if (addrs != null) {
|
||||
if (addrs instanceof CachedAddresses) {
|
||||
if (addrs instanceof CachedLookup) {
|
||||
// try removing from expirySet too if CachedAddresses
|
||||
expirySet.remove(addrs);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<!DOCTYPE HTML>
|
||||
<!--
|
||||
Copyright (c) 1998, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
|
||||
This code is free software; you can redistribute it and/or modify it
|
||||
|
@ -267,13 +267,22 @@ tuning on how the cache is operating.</P>
|
|||
policy, while a value of 0 (zero) means no caching. The default value
|
||||
is -1 (forever) if a security manager is installed, and implementation-specific
|
||||
when no security manager is installed.</P>
|
||||
<LI><P><B>{@systemProperty networkaddress.cache.stale.ttl}</B> (default: see below)<BR>
|
||||
Value is an integer corresponding to the number of seconds that stale names
|
||||
will be kept in the cache. A name is considered stale if the TTL has expired
|
||||
and an attempt to lookup the host name again was not successful. This
|
||||
property is useful if it is preferable to use a stale name rather than
|
||||
fail due to an unsuccessful lookup.
|
||||
A value of 0 (zero) or if the property is not set means do not use stale
|
||||
names. Negative values are ignored.
|
||||
The default value is implementation-specific.</P>
|
||||
<LI><P><B>{@systemProperty networkaddress.cache.negative.ttl}</B> (default: {@code 10})<BR>
|
||||
Value is an integer corresponding to the number of seconds an
|
||||
unsuccessful name lookup will be kept in the cache. A value of -1,
|
||||
or any negative value, means “cache forever”, while a
|
||||
value of 0 (zero) means no caching.</P>
|
||||
</UL>
|
||||
<P>Since these 2 properties are part of the security policy, they are
|
||||
<P>Since these 3 properties are part of the security policy, they are
|
||||
not set by either the -D option or the {@code System.setProperty()} API,
|
||||
instead they are set as security properties.</P>
|
||||
<a id="Unixdomain"></a>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue