/* * Copyright (c) 2015 Spotify AB. * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package com.spotify.heroic; import static com.google.common.base.Preconditions.checkNotNull; import static java.util.Optional.empty; import static java.util.Optional.of; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.spotify.heroic.analytics.AnalyticsComponent; import com.spotify.heroic.cache.CacheComponent; import com.spotify.heroic.cluster.CoreClusterComponent; import com.spotify.heroic.cluster.DaggerCoreClusterComponent; import com.spotify.heroic.common.Duration; import com.spotify.heroic.common.Optionals; import com.spotify.heroic.common.TypeNameMixin; import com.spotify.heroic.consumer.ConsumersComponent; import com.spotify.heroic.consumer.CoreConsumersModule; import com.spotify.heroic.consumer.DaggerCoreConsumersComponent; import com.spotify.heroic.dagger.CoreComponent; import com.spotify.heroic.dagger.CoreEarlyComponent; import com.spotify.heroic.dagger.CoreLoadingComponent; import com.spotify.heroic.dagger.CorePrimaryComponent; import com.spotify.heroic.dagger.DaggerCoreComponent; import com.spotify.heroic.dagger.DaggerCoreEarlyComponent; import com.spotify.heroic.dagger.DaggerCoreLoadingComponent; import com.spotify.heroic.dagger.DaggerCorePrimaryComponent; import com.spotify.heroic.dagger.DaggerStartupPingerComponent; import com.spotify.heroic.dagger.EarlyModule; import com.spotify.heroic.dagger.LoadingComponent; import com.spotify.heroic.dagger.LoadingModule; import com.spotify.heroic.dagger.PrimaryModule; import com.spotify.heroic.dagger.StartupPingerComponent; import com.spotify.heroic.dagger.StartupPingerModule; import com.spotify.heroic.generator.GeneratorComponent; import com.spotify.heroic.http.DaggerHttpServerComponent; import com.spotify.heroic.http.HttpServer; import com.spotify.heroic.http.HttpServerComponent; import com.spotify.heroic.http.HttpServerModule; import com.spotify.heroic.ingestion.IngestionComponent; import com.spotify.heroic.jetty.JettyConnectionFactory; import com.spotify.heroic.lifecycle.CoreLifeCycleRegistry; import com.spotify.heroic.lifecycle.LifeCycle; import com.spotify.heroic.lifecycle.LifeCycleHook; import com.spotify.heroic.lifecycle.LifeCycleNamedHook; import com.spotify.heroic.metadata.CoreMetadataComponent; import com.spotify.heroic.metadata.DaggerCoreMetadataComponent; import com.spotify.heroic.metric.CoreMetricComponent; import com.spotify.heroic.metric.DaggerCoreMetricComponent; import com.spotify.heroic.querylogging.QueryLoggingComponent; import com.spotify.heroic.shell.DaggerShellServerComponent; import com.spotify.heroic.shell.ShellServerComponent; import com.spotify.heroic.shell.ShellServerModule; import com.spotify.heroic.statistics.HeroicReporter; import com.spotify.heroic.statistics.StatisticsComponent; import com.spotify.heroic.suggest.CoreSuggestComponent; import com.spotify.heroic.suggest.DaggerCoreSuggestComponent; import eu.toolchain.async.AsyncFramework; import eu.toolchain.async.AsyncFuture; import eu.toolchain.async.ResolvableFuture; import java.io.InputStream; import java.lang.Thread.UncaughtExceptionHandler; import java.net.InetSocketAddress; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.Supplier; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.Pair; /** * Configure and bootstrap a Heroic application. * <p> * All public methods are thread-safe. * <p> * All fields are non-null. * * @author udoprog */ @Slf4j @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public class HeroicCore implements HeroicConfiguration { static final String DEFAULT_HOST = "0.0.0.0"; static final int DEFAULT_PORT = 8080; static final boolean DEFAULT_SETUP_SERVICE = true; static final boolean DEFAULT_ONESHOT = false; static final boolean DEFAULT_DISABLE_BACKENDS = false; static final boolean DEFAULT_SETUP_SHELL_SERVER = true; static final UncaughtExceptionHandler uncaughtExceptionHandler = (Thread t, Throwable e) -> { try { System.err.println( String.format("Uncaught exception caught in thread %s, exiting...", t)); e.printStackTrace(System.err); } finally { System.exit(1); } }; /** * Built-in modules that should always be loaded. */ // @formatter:off private static final HeroicModule[] BUILTIN_MODULES = new HeroicModule[] { new com.spotify.heroic.aggregation.Module(), new com.spotify.heroic.http.Module(), new com.spotify.heroic.jetty.Module(), new com.spotify.heroic.ws.Module(), new com.spotify.heroic.cache.Module(), new com.spotify.heroic.generator.Module() }; // @formatter:on private final Optional<String> id; private final Optional<String> host; private final Optional<Integer> port; private final Optional<Supplier<InputStream>> configStream; private final Optional<Path> configPath; private final Optional<URI> startupPing; private final Optional<String> startupId; /** * Additional dynamic parameters to pass into the configuration of a profile. These are * typically extracted from the commandline, or a properties file. */ private final ExtraParameters params; /* flags */ private final boolean setupService; private final boolean oneshot; private final boolean disableBackends; private final boolean setupShellServer; /* extensions */ private final List<HeroicModule> modules; private final List<HeroicProfile> profiles; private final List<HeroicBootstrap> early; private final List<HeroicBootstrap> late; private final List<HeroicConfig.Builder> configFragments; private final Optional<ExecutorService> executor; @Override public boolean isDisableLocal() { return disableBackends; } @Override public boolean isOneshot() { return oneshot; } /** * Start the Heroic core, step by step * <p> * <p> * It sets up the early injector which is responsible for loading all the necessary components * to parse a configuration file. * <p> * <p> * Load all the external modules, which are configured in {@link #modules}. * <p> * <p> * Load and build the configuration using the early injector * <p> * <p> * Setup the primary injector which will provide the dependencies to the entire application * <p> * <p> * Run all bootstraps that are configured in {@link #late} * <p> * <p> * Start all the external modules. {@link #startLifeCycles} */ public HeroicCoreInstance newInstance() throws Exception { final CoreLoadingComponent loading = loadingInjector(); loadModules(loading); final HeroicConfig config = config(loading); final CoreEarlyComponent early = earlyInjector(loading, config); runBootstrappers(early, this.early); // Initialize the instance injector with access to early components. final AtomicReference<CoreComponent> injector = new AtomicReference<>(); final HeroicCoreInstance instance = new Instance(loading.async(), injector, early, config, this.late); final CoreComponent primary = primaryInjector(early, config, instance); primary.loadingLifeCycle().install(); primary.internalLifeCycleRegistry().scoped("startup future").start(() -> { ((CoreHeroicContext) primary.context()).resolveCoreFuture(); return primary.async().resolved(null); }); // Update the instance injector, giving dynamic components initialized after this point // access to the primary // injector. injector.set(primary); return instance; } /** * This method basically goes through the list of bootstrappers registered by modules and runs * them. * * @param early Injector to inject boostrappers using. * @param bootstrappers Bootstraps to run. */ static void runBootstrappers( final CoreEarlyComponent early, final List<HeroicBootstrap> bootstrappers ) throws Exception { for (final HeroicBootstrap bootstrap : bootstrappers) { try { bootstrap.run(early); } catch (Exception e) { throw new Exception("Failed to run bootstrapper " + bootstrap, e); } } } /** * Setup early injector, which is responsible for sufficiently providing dependencies to runtime * components. */ private CoreLoadingComponent loadingInjector() { log.info("Building Loading Injector"); final ExecutorService executor; final boolean managedExecutor; if (this.executor.isPresent()) { executor = this.executor.get(); managedExecutor = false; } else { executor = setupExecutor(Runtime.getRuntime().availableProcessors() * 2); managedExecutor = true; } return DaggerCoreLoadingComponent .builder() .loadingModule(new LoadingModule(executor, managedExecutor, this, params)) .build(); } private CoreEarlyComponent earlyInjector( final CoreLoadingComponent loading, final HeroicConfig config ) { final Optional<String> id = Optionals.firstPresent(this.id, config.getId()); return DaggerCoreEarlyComponent .builder() .coreLoadingComponent(loading) .earlyModule(new EarlyModule(config, id)) .build(); } /** * Setup a fixed thread pool executor that correctly handles unhandled exceptions. * * @param threads Number of threads to configure. */ private ExecutorService setupExecutor(final int threads) { return new ThreadPoolExecutor(threads, threads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new ThreadFactoryBuilder() .setNameFormat("heroic-core-%d") .setUncaughtExceptionHandler(uncaughtExceptionHandler) .build()) { @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); if (t == null && (r instanceof Future<?>)) { try { ((Future<?>) r).get(); } catch (CancellationException e) { t = e; } catch (ExecutionException e) { t = e.getCause(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } if (t != null) { if (log.isErrorEnabled()) { log.error("Unhandled exception caught in core executor", t); log.error("Exiting (code=2)"); } else { System.err.println("Unhandled exception caught in core executor"); System.err.println("Exiting (code=2)"); t.printStackTrace(System.err); } System.exit(2); } } }; } /** * Setup primary injector, which will provide dependencies to the entire application. * * @param config The loaded configuration file. * @param early The early injector, which will act as a parent to the primary injector to bridge * all it's provided components. * @return The primary guice injector. */ private CoreComponent primaryInjector( final CoreEarlyComponent early, final HeroicConfig config, final HeroicCoreInstance instance ) { log.info("Building Primary Injector"); final List<LifeCycle> life = new ArrayList<>(); final StatisticsComponent statistics = config.getStatistics().module(early); life.add(statistics.life()); final HeroicReporter reporter = statistics.reporter(); // Register root components. final CorePrimaryComponent primary = DaggerCorePrimaryComponent .builder() .coreEarlyComponent(early) .primaryModule(new PrimaryModule(instance, config.getFeatures(), reporter)) .build(); final QueryLoggingComponent queryLogging = config.getQueryLogging().component(primary); final Optional<HttpServer> server; if (setupService) { final InetSocketAddress bindAddress = setupBindAddress(config); final HttpServerComponent serverComponent = DaggerHttpServerComponent .builder() .primaryComponent(primary) .httpServerModule(new HttpServerModule(bindAddress, config.isEnableCors(), config.getCorsAllowOrigin(), config.getConnectors())) .build(); // Trigger life cycle registration life.add(serverComponent.life()); server = Optional.of(serverComponent.server()); } else { server = Optional.empty(); } final CacheComponent cache = config.getCache().module(primary); life.add(cache.cacheLife()); final AnalyticsComponent analytics = config.getAnalytics().module(primary); life.add(analytics.analyticsLife()); final CoreMetadataComponent metadata = DaggerCoreMetadataComponent .builder() .primaryComponent(primary) .metadataManagerModule(config.getMetadata()) .build(); life.add(metadata.metadataLife()); final CoreSuggestComponent suggest = DaggerCoreSuggestComponent .builder() .primaryComponent(primary) .suggestManagerModule(config.getSuggest()) .build(); life.add(suggest.suggestLife()); final CoreMetricComponent metric = DaggerCoreMetricComponent .builder() .corePrimaryComponent(primary) .metadataComponent(metadata) .analyticsComponent(analytics) .metricManagerModule(config.getMetric()) .queryLoggingComponent(queryLogging) .build(); life.add(metric.metricLife()); final IngestionComponent ingestion = config.getIngestion().module(primary, suggest, metadata, metric); life.add(ingestion.ingestionLife()); buildShellServer(config).ifPresent(shellServerModule -> { final ShellServerComponent shellServer = DaggerShellServerComponent .builder() .primaryComponent(primary) .shellServerModule(shellServerModule) .build(); life.add(shellServer.shellServerLife()); }); final CoreClusterComponent cluster = DaggerCoreClusterComponent .builder() .primaryComponent(primary) .metricComponent(metric) .metadataComponent(metadata) .suggestComponent(suggest) .clusterManagerModule(config.getCluster()) .clusterDiscoveryComponent(config.getCluster().getDiscovery().module(primary)) .build(); life.add(cluster.clusterLife()); if (startupPing.isPresent() && startupId.isPresent()) { final StartupPingerComponent pinger = DaggerStartupPingerComponent .builder() .corePrimaryComponent(primary) .clusterComponent(cluster) .startupPingerModule( new StartupPingerModule(startupPing.get(), startupId.get(), server)) .build(); life.add(pinger.startupPingerLife()); } final ConsumersComponent consumer = DaggerCoreConsumersComponent .builder() .coreConsumersModule( new CoreConsumersModule(reporter, config.getConsumers(), primary, ingestion)) .corePrimaryComponent(primary) .build(); life.add(consumer.consumersLife()); final QueryComponent query = DaggerCoreQueryComponent .builder() .queryModule(new QueryModule(config.getMetric().getGroupLimit(), config.getMetric().getSmallQueryThreshold())) .corePrimaryComponent(primary) .clusterComponent(cluster) .cacheComponent(cache) .queryLoggingComponent(queryLogging) .build(); life.add(query.queryLife()); final GeneratorComponent generator = config.getGenerator().module(primary); life.add(generator.generatorLife()); // install all lifecycles final LifeCycle combined = LifeCycle.combined(life); combined.install(); return DaggerCoreComponent .builder() .primaryComponent(primary) .analyticsComponent(analytics) .consumersComponent(consumer) .metricComponent(metric) .metadataComponent(metadata) .suggestComponent(suggest) .queryComponent(query) .queryLoggingComponent(queryLogging) .ingestionComponent(ingestion) .clusterComponent(cluster) .generatorComponent(generator) .build(); } private Optional<ShellServerModule> buildShellServer(final HeroicConfig config) { final Optional<ShellServerModule> shellServer = config.getShellServer(); // a shell server is configured. if (shellServer.isPresent()) { return shellServer; } // must have a shell server if (setupShellServer) { return of(ShellServerModule.builder().build()); } return empty(); } private InetSocketAddress setupBindAddress(HeroicConfig config) { final String host = Optionals.pickOptional(config.getHost(), this.host).orElse(DEFAULT_HOST); final int port = Optionals.pickOptional(config.getPort(), this.port).orElse(DEFAULT_PORT); return new InetSocketAddress(host, port); } static void startLifeCycles( CoreLifeCycleRegistry registry, CoreComponent primary, HeroicConfig config ) throws Exception { if (!awaitLifeCycles("start", primary, config.getStartTimeout(), registry.starters())) { throw new Exception("Failed to start all life cycles"); } } static void stopLifeCycles( CoreLifeCycleRegistry registry, CoreComponent primary, HeroicConfig config ) throws Exception { if (!awaitLifeCycles("stop", primary, config.getStopTimeout(), registry.stoppers())) { log.warn("Failed to stop all life cycles"); } } static boolean awaitLifeCycles( final String op, final CoreComponent primary, final Duration await, final List<LifeCycleNamedHook<AsyncFuture<Void>>> hooks ) throws InterruptedException, ExecutionException { log.info("[{}] {} lifecycle(s)...", op, hooks.size()); final AsyncFramework async = primary.async(); final List<AsyncFuture<Void>> futures = new ArrayList<>(); final List<Pair<AsyncFuture<Void>, LifeCycleHook<AsyncFuture<Void>>>> pairs = new ArrayList<>(); for (final LifeCycleNamedHook<AsyncFuture<Void>> hook : hooks) { log.trace("[{}] {}", op, hook.id()); final AsyncFuture<Void> future; try { future = hook.get(); } catch (Exception e) { futures.add(async.failed(e)); break; } if (log.isTraceEnabled()) { final Stopwatch w = Stopwatch.createStarted(); future.onFinished(() -> { log.trace("[{}] {}, took {}us", op, hook.id(), w.elapsed(TimeUnit.MICROSECONDS)); }); } futures.add(future); pairs.add(Pair.of(future, hook)); } try { async.collect(futures).get(await.getDuration(), await.getUnit()); } catch (final TimeoutException e) { log.error("Operation timed out"); for (final Pair<AsyncFuture<Void>, LifeCycleHook<AsyncFuture<Void>>> pair : pairs) { if (!pair.getLeft().isDone()) { log.error("{}: did not finish in time: {}", op, pair.getRight()); } } return false; } log.info("[{}] {} lifecycle(s) done", op, hooks.size()); return true; } private HeroicConfig config(LoadingComponent loading) throws Exception { HeroicConfig.Builder builder = HeroicConfig.builder(); for (final HeroicProfile profile : profiles) { log.info("Loading profile '{}' (params: {})", profile.description(), params); final ExtraParameters p = profile.scope().map(params::scope).orElse(params); builder = builder.merge(profile.build(p)); } for (final HeroicConfig.Builder fragment : configFragments) { builder = builder.merge(fragment); } final ObjectMapper mapper = loading.configMapper().copy(); // TODO: figure out where to put this mapper.addMixIn(JettyConnectionFactory.Builder.class, TypeNameMixin.class); if (configPath.isPresent()) { builder = HeroicConfig .loadConfig(mapper, configPath.get()) .map(builder::merge) .orElse(builder); } if (configStream.isPresent()) { builder = HeroicConfig .loadConfigStream(mapper, configStream.get().get()) .map(builder::merge) .orElse(builder); } return builder.build(); } /** * Load modules from the specified modules configuration file and wire up those components with * early injection. * * @param loading Injector to wire up modules using. */ private void loadModules(final CoreLoadingComponent loading) { final List<HeroicModule> modules = new ArrayList<>(); for (final HeroicModule builtin : BUILTIN_MODULES) { modules.add(builtin); } modules.addAll(this.modules); for (final HeroicModule module : modules) { // inject members of an entry point and run them. module.setup(loading).run(); } log.info("Loaded {} module(s)", modules.size()); } public static Builder builder() { return new Builder(); } public static final class Builder { private Optional<String> id = empty(); private Optional<String> host = empty(); private Optional<Integer> port = empty(); private Optional<Supplier<InputStream>> configStream = empty(); private Optional<Path> configPath = empty(); private Optional<ExtraParameters> params = empty(); private Optional<HeroicReporter> reporter = empty(); private Optional<URI> startupPing = empty(); private Optional<String> startupId = empty(); /* flags */ private Optional<Boolean> setupService = empty(); private Optional<Boolean> oneshot = empty(); private Optional<Boolean> disableBackends = empty(); private Optional<Boolean> setupShellServer = empty(); /* extensions */ private final ImmutableList.Builder<HeroicModule> modules = ImmutableList.builder(); private final ImmutableList.Builder<HeroicProfile> profiles = ImmutableList.builder(); private final ImmutableList.Builder<HeroicBootstrap> early = ImmutableList.builder(); private final ImmutableList.Builder<HeroicBootstrap> late = ImmutableList.builder(); /* configuration fragments */ private final ImmutableList.Builder<HeroicConfig.Builder> configFragments = ImmutableList.builder(); private Optional<ExecutorService> executor = empty(); public Builder executor(final ExecutorService executor) { this.executor = of(executor); return this; } /** * Register a specific ID for this heroic instance. * * @param id Id of the instance. * @return This builder. */ public Builder id(final String id) { this.id = of(id); return this; } /** * If a shell server is not configured, setup the default shell server. */ public Builder setupShellServer(boolean setupShellServer) { this.setupShellServer = of(setupShellServer); return this; } /** * Port to bind service to. */ public Builder port(final int port) { this.port = of(port); return this; } /** * Host to bind service to. */ public Builder host(final String host) { checkNotNull(host, "host"); this.host = of(host); return this; } public Builder configStream(final Supplier<InputStream> configStream) { checkNotNull(configStream, "configStream"); this.configStream = of(configStream); return this; } public Builder configPath(final String configPath) { checkNotNull(configPath, "configPath"); return configPath(Paths.get(configPath)); } public Builder configPath(final Path configPath) { checkNotNull(configPath, "configPath"); if (!Files.isReadable(configPath)) { throw new IllegalArgumentException( "Configuration is not readable: " + configPath.toAbsolutePath()); } this.configPath = of(configPath); return this; } /** * Programmatically add a configuration fragment. * * @param config Configuration fragment to add. * @return This builder. */ public Builder configFragment(final HeroicConfig.Builder config) { checkNotNull(config, "config"); this.configFragments.add(config); return this; } public Builder startupPing(final String startupPing) { checkNotNull(startupPing, "startupPing"); this.startupPing = of(URI.create(startupPing)); return this; } public Builder startupId(final String startupId) { checkNotNull(startupId, "startupId"); this.startupId = of(startupId); return this; } public Builder parameters(final ExtraParameters params) { checkNotNull(params, "params"); this.params = of(params); return this; } public Builder reporter(final HeroicReporter reporter) { checkNotNull(reporter, "reporter"); this.reporter = of(reporter); return this; } /** * Configure setup of the server component of heroic or not. */ public Builder setupService(final boolean setupService) { this.setupService = of(setupService); return this; } /** * Do not perform any scheduled tasks, only perform then once during startup. */ public Builder oneshot(final boolean oneshot) { this.oneshot = of(oneshot); return this; } /** * Disable local backends. */ public Builder disableBackends(final boolean disableBackends) { this.disableBackends = of(disableBackends); return this; } public Builder modules(final List<HeroicModule> modules) { checkNotNull(modules, "modules"); this.modules.addAll(modules); return this; } public Builder module(final HeroicModule module) { checkNotNull(module, "module"); this.modules.add(module); return this; } public Builder profiles(final List<HeroicProfile> profiles) { checkNotNull(profiles, "profiles"); this.profiles.addAll(profiles); return this; } public Builder profile(final HeroicProfile profile) { checkNotNull(profile, "profile"); this.profiles.add(profile); return this; } public Builder earlyBootstrap(final HeroicBootstrap bootstrap) { checkNotNull(bootstrap, "bootstrap"); this.early.add(bootstrap); return this; } public Builder bootstrappers(final List<HeroicBootstrap> bootstrappers) { checkNotNull(bootstrappers, "bootstrappers"); this.late.addAll(bootstrappers); return this; } public Builder bootstrap(final HeroicBootstrap bootstrap) { checkNotNull(bootstrap, "bootstrap"); this.late.add(bootstrap); return this; } public HeroicCore build() { // @formatter:off return new HeroicCore( id, host, port, configStream, configPath, startupPing, startupId, params.orElseGet(ExtraParameters::empty), setupService.orElse(DEFAULT_SETUP_SERVICE), oneshot.orElse(DEFAULT_ONESHOT), disableBackends.orElse(DEFAULT_DISABLE_BACKENDS), setupShellServer.orElse(DEFAULT_SETUP_SHELL_SERVER), modules.build(), profiles.build(), early.build(), late.build(), configFragments.build(), executor ); // @formatter:on } } public static HeroicModule[] builtinModules() { return BUILTIN_MODULES; } public static class Instance implements HeroicCoreInstance { private final AtomicReference<CoreComponent> coreInjector; private final CoreEarlyComponent early; private final HeroicConfig config; private final List<HeroicBootstrap> late; private final ResolvableFuture<Void> start; private final ResolvableFuture<Void> stop; private final AsyncFuture<Void> started; private final AsyncFuture<Void> stopped; public Instance( final AsyncFramework async, final AtomicReference<CoreComponent> coreInjector, final CoreEarlyComponent early, final HeroicConfig config, final List<HeroicBootstrap> late ) { this.coreInjector = coreInjector; this.early = early; this.config = config; this.late = late; this.start = async.future(); this.stop = async.future(); this.started = start.directTransform(n -> { final CoreComponent core = coreInjector.get(); assert core != null; final CoreLifeCycleRegistry coreRegistry = (CoreLifeCycleRegistry) core.lifeCycleRegistry(); runBootstrappers(early, late); startLifeCycles(coreRegistry, core, config); final CoreLifeCycleRegistry internalRegistry = (CoreLifeCycleRegistry) core.internalLifeCycleRegistry(); startLifeCycles(internalRegistry, core, config); log.info("Startup finished, hello!"); return null; }); this.stopped = async.collect(ImmutableList.of(started, stop)).lazyTransform(n -> { return async.call(() -> { final CoreComponent core = coreInjector.get(); assert core != null; final CoreLifeCycleRegistry coreRegistry = (CoreLifeCycleRegistry) core.lifeCycleRegistry(); core.stopSignal().run(); log.info("Shutting down Heroic"); try { stopLifeCycles(coreRegistry, core, config); } catch (Exception e) { log.error("Failed to stop all lifecycles, continuing anyway...", e); } log.info("Stopping internal life cycle"); final CoreLifeCycleRegistry internalRegistry = (CoreLifeCycleRegistry) core.internalLifeCycleRegistry(); try { stopLifeCycles(internalRegistry, core, config); } catch (Exception e) { log.error("Failed to stop all internal lifecycles, continuing anyway...", e); } log.info("Done shutting down, bye bye!"); return null; }, Executors.newSingleThreadExecutor()); }); } /** * Inject fields to the provided injector using the primary injector. * * @param injector Injector to use. */ @Override public <T> T inject(Function<CoreComponent, T> injector) { final CoreComponent c = coreInjector.get(); if (c == null) { throw new IllegalStateException( "Injection attempted before instance has been fully initialize, use " + "LifeCycleRegistry#start(..) to register a hook instead"); } return injector.apply(c); } @Override public AsyncFuture<Void> start() { this.start.resolve(null); return this.started; } @Override public AsyncFuture<Void> shutdown() { this.stop.resolve(null); return this.stopped; } @Override public AsyncFuture<Void> join() { return this.stopped; } } }