// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.container.di; import com.google.common.collect.Sets; import com.yahoo.config.ConfigInstance; import com.yahoo.container.di.componentgraph.core.Keys; import com.yahoo.container.di.config.Subscriber; import com.yahoo.container.di.config.SubscriberFactory; import com.yahoo.vespa.config.ConfigKey; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; import static java.util.logging.Level.FINE; /** * @author Tony Vaagenes * @author gjoranv * @author ollivir */ public final class ConfigRetriever { private static final Logger log = Logger.getLogger(ConfigRetriever.class.getName()); private final Set> bootstrapKeys; private Set> componentSubscriberKeys; private final SubscriberFactory subscriberFactory; private final Subscriber bootstrapSubscriber; private Subscriber componentSubscriber; private int componentSubscriberIndex; public ConfigRetriever(Set> bootstrapKeys, SubscriberFactory subscriberFactory) { this.bootstrapKeys = bootstrapKeys; this.componentSubscriberKeys = new HashSet<>(); this.subscriberFactory = subscriberFactory; if (bootstrapKeys.isEmpty()) { throw new IllegalArgumentException("Bootstrap key set is empty"); } this.bootstrapSubscriber = this.subscriberFactory.getSubscriber(bootstrapKeys, "bootstrap"); this.componentSubscriber = this.subscriberFactory.getSubscriber(componentSubscriberKeys, "component_" + ++componentSubscriberIndex); } public ConfigSnapshot getConfigs(Set> componentConfigKeys, long leastGeneration, boolean isInitializing) { // Loop until we get config. while (true) { Optional maybeSnapshot = getConfigsOnce(componentConfigKeys, leastGeneration, isInitializing); if (maybeSnapshot.isPresent()) { var configSnapshot = maybeSnapshot.get(); resetComponentSubscriberIfBootstrap(configSnapshot); return configSnapshot; } } } private Optional getConfigsOnce(Set> componentConfigKeys, long leastGeneration, boolean isInitializing) { if (!Sets.intersection(componentConfigKeys, bootstrapKeys).isEmpty()) { throw new IllegalArgumentException( "Component config keys [" + componentConfigKeys + "] overlaps with bootstrap config keys [" + bootstrapKeys + "]"); } Set> allKeys = new HashSet<>(componentConfigKeys); allKeys.addAll(bootstrapKeys); setupComponentSubscriber(allKeys); var maybeSnapshot = getConfigsOptional(leastGeneration, isInitializing); log.log(FINE, () -> "getConfigsOnce returning " + maybeSnapshot); return maybeSnapshot; } private Optional getConfigsOptional(long leastGeneration, boolean isInitializing) { if (componentSubscriber.generation() < bootstrapSubscriber.generation()) { return getComponentsSnapshot(leastGeneration, isInitializing); } long newestBootstrapGeneration = bootstrapSubscriber.waitNextGeneration(isInitializing); log.log(FINE, () -> "getConfigsOptional: new bootstrap generation: " + newestBootstrapGeneration); // leastGeneration is used to ensure newer generation (than the latest bootstrap or component gen) // when the previous generation was invalidated due to an exception upon creating the component graph. if (newestBootstrapGeneration < leastGeneration) { return Optional.empty(); } return bootstrapConfigIfChanged(); } private Optional getComponentsSnapshot(long leastGeneration, boolean isInitializing) { long newestBootstrapGeneration = bootstrapSubscriber.generation(); long newestComponentGeneration = componentSubscriber.waitNextGeneration(isInitializing); if (newestComponentGeneration < leastGeneration) { log.log(FINE, () -> "Component generation too old: " + componentSubscriber.generation() + " < " + leastGeneration); return Optional.empty(); } if (newestComponentGeneration == newestBootstrapGeneration) { log.log(FINE, () -> "getConfigsOptional: new component generation: " + componentSubscriber.generation()); return componentsConfigIfChanged(); } else { // Should not be a normal case, and hence a warning to allow investigation. log.warning("Did not get same generation for bootstrap (" + newestBootstrapGeneration + ") and components configs (" + newestComponentGeneration + ")."); return Optional.empty(); } } private Optional bootstrapConfigIfChanged() { return configIfChanged(bootstrapSubscriber, BootstrapConfigs::new); } private Optional componentsConfigIfChanged() { return configIfChanged(componentSubscriber, ComponentsConfigs::new); } private Optional configIfChanged(Subscriber subscriber, Function, ConfigInstance>, ConfigSnapshot> constructor) { if (subscriber.configChanged()) { return Optional.of(constructor.apply(Keys.covariantCopy(subscriber.config()))); } else { return Optional.empty(); } } private void resetComponentSubscriberIfBootstrap(ConfigSnapshot configSnapshot) { if (configSnapshot instanceof BootstrapConfigs) { setupComponentSubscriber(Collections.emptySet()); } } private void setupComponentSubscriber(Set> keys) { if (! componentSubscriberKeys.equals(keys)) { componentSubscriber.close(); log.log(FINE, () -> "Closed " + componentSubscriber); componentSubscriberKeys = keys; try { componentSubscriber = subscriberFactory.getSubscriber(keys, "component_" + ++componentSubscriberIndex); log.log(FINE, () -> "Set up new subscriber " + componentSubscriber + " for keys: " + keys); } catch (Throwable e) { log.log(Level.WARNING, "Failed setting up subscriptions for component configs: " + e.getMessage()); log.log(Level.WARNING, "Config keys: " + keys); throw e; } } } public void shutdown() { bootstrapSubscriber.close(); componentSubscriber.close(); } //TODO: check if these are really needed public long getBootstrapGeneration() { return bootstrapSubscriber.generation(); } public long getComponentsGeneration() { return componentSubscriber.generation(); } public static class ConfigSnapshot { private final Map, ConfigInstance> configs; ConfigSnapshot(Map, ConfigInstance> configs) { this.configs = configs; } public Map, ConfigInstance> configs() { return configs; } public int size() { return configs.size(); } } public static class BootstrapConfigs extends ConfigSnapshot { BootstrapConfigs(Map, ConfigInstance> configs) { super(configs); } } public static class ComponentsConfigs extends ConfigSnapshot { ComponentsConfigs(Map, ConfigInstance> configs) { super(configs); } } }