aboutsummaryrefslogtreecommitdiffstats
path: root/container-disc/src/main/java/com/yahoo/container/jdisc/component/Deconstructor.java
blob: 72ba72403614b44d2ac81088886ee4d73d16ae16 (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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.container.jdisc.component;

import com.yahoo.component.AbstractComponent;
import com.yahoo.component.Deconstructable;
import com.yahoo.concurrent.ThreadFactoryFactory;
import com.yahoo.container.di.ComponentDeconstructor;
import com.yahoo.container.di.componentgraph.Provider;
import com.yahoo.jdisc.SharedResource;
import com.yahoo.yolean.UncheckedInterruptedException;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;

/**
* @author Tony Vaagenes
* @author gv
*/
public class Deconstructor implements ComponentDeconstructor {

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

    private final ExecutorService executor =
            Executors.newFixedThreadPool(1, ThreadFactoryFactory.getThreadFactory("component-deconstructor"));

    // This must be smaller than the shutdownDeadlineExecutor delay in ConfiguredApplication
    private final Duration shutdownTimeout;

    public Deconstructor(Duration shutdownTimeout) {
        this.shutdownTimeout = shutdownTimeout;
    }

    public Deconstructor() { this(Duration.ofSeconds(45)); }

    @Override
    public void deconstruct(long generation, List<Object> components, Collection<Bundle> bundles) {
        Collection<Deconstructable> destructibleComponents = new ArrayList<>();
        for (var component : components) {
            if (component instanceof AbstractComponent) {
                AbstractComponent abstractComponent = (AbstractComponent) component;
                if (abstractComponent.isDeconstructable()) {
                    destructibleComponents.add(abstractComponent);
                }
            } else if (component instanceof Provider) {
                destructibleComponents.add((Deconstructable) component);
            } else if (component instanceof SharedResource) {
                // Release shared resources in same order as other components in case of usage without reference counting
                destructibleComponents.add(new SharedResourceReleaser(component));
            }
        }
        if (!destructibleComponents.isEmpty() || !bundles.isEmpty()) {
            executor.execute(new DestructComponentTask(generation, destructibleComponents, bundles));
        }
    }

    @Override
    public void shutdown() {
        executor.shutdown();
        try {
            log.info("Waiting up to " + shutdownTimeout.toSeconds() + " seconds for all previous components graphs to deconstruct.");
            if (!executor.awaitTermination(shutdownTimeout.getSeconds(), TimeUnit.SECONDS)) {
                log.warning("Waiting for deconstruction timed out.");
            }
        } catch (InterruptedException e) {
            log.info("Interrupted while waiting for component deconstruction to finish.");
            throw new UncheckedInterruptedException(e, true);
        }
    }

    private static class SharedResourceReleaser implements Deconstructable {
        final SharedResource resource;

        private SharedResourceReleaser(Object resource) { this.resource = (SharedResource) resource; }

        @Override public void deconstruct() { resource.release(); }
    }

    private static class DestructComponentTask implements Runnable {

        private final Random random = new Random(System.nanoTime());
        private final long generation;
        private final Collection<Deconstructable> components;
        private final Collection<Bundle> bundles;

        DestructComponentTask(long generation, Collection<Deconstructable> components, Collection<Bundle> bundles) {
            this.generation = generation;
            this.components = components;
            this.bundles = bundles;
        }

        /**
        * Returns a random delay between 0 and 10 minutes which will be different across identical containers invoking this at the same time.
        * Used to randomize restart to avoid simultaneous cluster restarts.
        */
        private Duration getRandomizedShutdownDelay() {
            long seconds = (long) (random.nextDouble() * 60 * 10);
            return Duration.ofSeconds(seconds);
        }

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            log.info(String.format("Starting deconstruction of %d components and %d bundles from generation %d",
                    components.size(), bundles.size(), generation));
            for (var component : components) {
                log.log(FINE, () -> "Starting deconstruction of " + component);
                try {
                    component.deconstruct();
                    log.log(FINE, () -> "Finished deconstructing of " + component);
                } catch (Exception | NoClassDefFoundError e) { // May get class not found due to it being already unloaded
                    log.log(WARNING, "Exception thrown when deconstructing component " + component, e);
                } catch (Error e) {
                    try {
                        Duration shutdownDelay = getRandomizedShutdownDelay();
                        log.log(Level.SEVERE, "Error when deconstructing component " + component + ". Will sleep for " +
                                shutdownDelay.getSeconds() + " seconds then restart", e);
                        Thread.sleep(shutdownDelay.toMillis());
                    } catch (InterruptedException exception) {
                        log.log(WARNING, "Randomized wait before dying disrupted. Dying now.");
                    }
                    com.yahoo.protect.Process.logAndDie("Shutting down due to error when deconstructing component " + component);
                } catch (Throwable e) {
                    log.log(WARNING, "Non-error not exception throwable thrown when deconstructing component  " + component, e);
                }
            }
            // It should now be safe to uninstall the old bundles.
            for (var bundle : bundles) {
                try {
                    log.log(INFO, "Uninstalling bundle " + bundle);
                    bundle.uninstall();
                } catch (BundleException e) {
                    log.log(SEVERE, "Could not uninstall bundle " + bundle);
                }
            }
            log.info(String.format("Completed deconstruction in %.3f seconds", (System.currentTimeMillis() - start) / 1000D));
            // NOTE: It could be tempting to refresh packages here, but if active bundles were using any of
            //       the removed ones, they would switch wiring in the middle of a generation's lifespan.
            //       This would happen if the dependent active bundle has not been rebuilt with a new version
            //       of its dependency(ies).
        }
    }

}