in archaius2-core/src/main/java/com/netflix/archaius/DefaultPropertyFactory.java [184:231]
private T internalGet(boolean ignoreErrors) {
int currentMasterVersion = masterVersion.get();
CachedValue<T> currentCachedValue = this.cachedValue;
// Happy path. We have an up-to-date cached value, so just return that.
// We check for >= in case an upstream update happened between getting the version and the cached value AND
// another thread came and updated the cache.
if (currentCachedValue != null && currentCachedValue.version >= currentMasterVersion) {
return currentCachedValue.value;
}
// No valid cache, let's try to update it. Multiple threads may get here and try to update. That's fine,
// the worst case is wasted effort. A hidden assumption here is that the supplier is idempotent and relatively
// cheap, which should be true unless the user installed badly behaving interpolators or converters in
// the Config object.
// The tricky edge case is if another update came in between the check above to get the version and
// the call to the supplier. In that case we'll tag the updated value with an old version number. That's fine,
// since the next call to get() will see the old version and try again.
CachedValue<T> newValue;
try {
// Get the new value from the supplier. This call could fail.
newValue = new CachedValue<>(supplier.get(), currentMasterVersion);
} catch (RuntimeException e) {
if (!ignoreErrors) {
// Oh, no, something went wrong while trying to get the new value. Log the error and return null.
// Upstream users may return that null unchanged or substitute it by a defaultValue.
// We leave the cache unchanged, which means the next caller will try again.
LOG.error("Unable to update value for property '{}'", keyAndType.key, e);
}
return null;
}
/*
* We successfully got the new value, so now we update the cache. We use an atomic CAS operation to guard
* from edge cases where another thread could have updated to a higher version than we have, in a flow like this:
* Assume currentVersion started at 1., property cache is set to 1 too.
* 1. Upstream update bumps version to 2.
* 2. Thread A reads currentVersion at 2, cachedValue at 1, proceeds to start update, gets interrupted and yields the cpu.
* 3. Thread C bumps version to 3, yields the cpu.
* 4. Thread B is scheduled, reads currentVersion at 3, cachedValue still at 1, proceeds to start update.
* 5. Thread B keeps running, updates cache to 3, yields.
* 6. Thread A resumes, tries to write cache with version 2.
*/
CACHED_VALUE_UPDATER.compareAndSet(this, currentCachedValue, newValue);
return newValue.value;
}