private T internalGet()

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;
        }