aboutsummaryrefslogtreecommitdiffstats
path: root/container-core/src/main/java/com/yahoo/container/di/CloudSubscriberFactory.java
blob: 065733a719aa609091c831685f917274b5122a2d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.container.di;

import com.yahoo.config.ConfigInstance;
import com.yahoo.config.subscription.ConfigHandle;
import com.yahoo.config.subscription.ConfigSource;
import com.yahoo.config.subscription.ConfigSourceSet;
import com.yahoo.config.subscription.ConfigSubscriber;
import com.yahoo.container.di.config.Subscriber;
import com.yahoo.container.di.config.SubscriberFactory;
import com.yahoo.vespa.config.ConfigKey;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author Tony Vaagenes
 * @author ollivir
 */
public class CloudSubscriberFactory implements SubscriberFactory {

    private static final Logger log = Logger.getLogger(CloudSubscriberFactory.class.getName());

    private final ConfigSource configSource;
    private final Map<CloudSubscriber, Integer> activeSubscribers = new WeakHashMap<>();

    private Optional<Long> testGeneration = Optional.empty();

    public CloudSubscriberFactory(ConfigSource configSource) {
        this.configSource = configSource;
    }

    @Override
    public Subscriber getSubscriber(Set<? extends ConfigKey<?>> configKeys) {
        Set<ConfigKey<ConfigInstance>> subscriptionKeys = new HashSet<>();
        for(ConfigKey<?> key: configKeys) {
            @SuppressWarnings("unchecked") // ConfigKey is defined as <CONFIGCLASS extends ConfigInstance>
            ConfigKey<ConfigInstance> invariant = (ConfigKey<ConfigInstance>) key;
            subscriptionKeys.add(invariant);
        }
        CloudSubscriber subscriber = new CloudSubscriber(subscriptionKeys, configSource);

        testGeneration.ifPresent(subscriber.subscriber::reload); // TODO: test specific code, remove
        activeSubscribers.put(subscriber, 0);

        return subscriber;
    }

    //TODO: test specific code, remove
    @Override
    public void reloadActiveSubscribers(long generation) {
        testGeneration = Optional.of(generation);

        List<CloudSubscriber> subscribers = new ArrayList<>(activeSubscribers.keySet());
        subscribers.forEach(s -> s.subscriber.reload(generation));
    }

    private static class CloudSubscriber implements Subscriber {

        private final ConfigSubscriber subscriber;
        private final Map<ConfigKey<ConfigInstance>, ConfigHandle<ConfigInstance>> handles = new HashMap<>();

        // if waitNextGeneration has not yet been called, -1 should be returned
        private long generation = -1L;

        private CloudSubscriber(Set<ConfigKey<ConfigInstance>> keys, ConfigSource configSource) {
            this.subscriber = new ConfigSubscriber(configSource);
            keys.forEach(k -> handles.put(k, subscriber.subscribe(k.getConfigClass(), k.getConfigId())));
        }

        @Override
        public boolean configChanged() {
            return handles.values().stream().anyMatch(ConfigHandle::isChanged);
        }

        @Override
        public long generation() {
            return generation;
        }

        //mapValues returns a view,, so we need to force evaluation of it here to prevent deferred evaluation.
        @Override
        public Map<ConfigKey<ConfigInstance>, ConfigInstance> config() {
            Map<ConfigKey<ConfigInstance>, ConfigInstance> ret = new HashMap<>();
            handles.forEach((k, v) -> ret.put(k, v.getConfig()));
            return ret;
        }

        @Override
        public long waitNextGeneration(boolean isInitializing) {
            if (handles.isEmpty())
                throw new IllegalStateException("No config keys registered");

            // Catch and just log config exceptions due to missing config values for parameters that do
            // not have a default value. These exceptions occur when the user has removed a component
            // from services.xml, and the component takes a config that has parameters without a
            // default value in the def-file. There is a new 'components' config underway, where the
            // component is removed, so this old config generation will soon be replaced by a new one.
            boolean gotNextGen = false;
            int numExceptions = 0;
            while ( ! gotNextGen) {
                try {
                    if (subscriber.nextGeneration(isInitializing))
                        gotNextGen = true;
                }
                catch (IllegalArgumentException e) {
                    numExceptions++;
                    log.log(Level.WARNING, "Got exception from the config system (ignore if you just removed a " +
                                           "component from your application that used the mentioned config) Subscriber info: " +
                                           subscriber.toString(), e);
                    if (numExceptions >= 5)
                        throw new IllegalArgumentException("Failed retrieving the next config generation", e);
                }
            }

            generation = subscriber.getGeneration();
            return generation;
        }

        @Override
        public void close() {
            subscriber.close();
        }

    }

    public static class Provider implements com.google.inject.Provider<SubscriberFactory> {
        @Override
        public SubscriberFactory get() {
            return new CloudSubscriberFactory(ConfigSourceSet.createDefault());
        }
    }

}