All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.palantir.async.initializer.AsyncInitializer Maven / Gradle / Ivy

There is a newer version: 0.1152.0
Show newest version
/*
 * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved.
 *
 * Licensed 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.palantir.async.initializer;

import com.palantir.common.concurrent.NamedThreadFactory;
import com.palantir.common.concurrent.PTExecutors;
import com.palantir.exception.NotInitializedException;
import com.palantir.logsafe.SafeArg;
import com.palantir.logsafe.logger.SafeLogger;
import com.palantir.logsafe.logger.SafeLoggerFactory;
import java.time.Duration;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.concurrent.ThreadSafe;

/**
 * Implements basic infrastructure to allow an object to be asynchronously initialized.
 * In order to be ThreadSafe, the abstract methods of the inheriting class need to be synchronized.
 */
@ThreadSafe
public abstract class AsyncInitializer {
    private static final SafeLogger log = SafeLoggerFactory.get(AsyncInitializer.class);

    private final ScheduledExecutorService singleThreadedExecutor = createExecutorService();
    private final AtomicBoolean isInitializing = new AtomicBoolean(false);
    private AsyncInitializationState state = new AsyncInitializationState();
    private int numberOfInitializationAttempts = 1;
    private Long initializationStartTime;

    /**
     * Initialization method that must be called to initialize the object before it is used.
     *
     * @param initializeAsync If true, the object will be initialized asynchronously when synchronous initialization
     * fails.
     */
    public final void initialize(boolean initializeAsync) {
        assertNeverCalledInitialize();

        initializationStartTime = System.currentTimeMillis();

        if (initializeAsync) {
            scheduleInitialization(Duration.ZERO);
        } else {
            attemptInitializationOnce();
        }
    }

    private void attemptInitializationOnce() {
        try {
            log.info("Attempting to initialize {}", SafeArg.of("className", getInitializingClassName()));
            tryInitializeInternal();
            log.info(
                    "Successfully initialized {} after {} milliseconds",
                    SafeArg.of("className", getInitializingClassName()),
                    SafeArg.of("initializationDuration", getMillisecondsSinceInitialization()));
        } catch (RuntimeException | Error throwable) {
            log.info(
                    "Failed to initialize {} after {} milliseconds",
                    SafeArg.of("className", getInitializingClassName()),
                    SafeArg.of("initializationDuration", getMillisecondsSinceInitialization()),
                    throwable);
            try {
                cleanUpOnInitFailure();
            } catch (RuntimeException | Error cleanupThrowable) {
                log.error(
                        "Failed to cleanup when initialization of {} failed after {} milliseconds",
                        SafeArg.of("className", getInitializingClassName()),
                        SafeArg.of("initializationDuration", getMillisecondsSinceInitialization()),
                        cleanupThrowable);
                throwable.addSuppressed(cleanupThrowable);
            }
            throw throwable;
        }
    }

    private void tryInitializationLoop() {
        if (state.isCancelled()) {
            log.info(
                    "Shutting down executor associated with asynchronous initialisation, as it was cancelled",
                    SafeArg.of("className", getInitializingClassName()),
                    SafeArg.of("numberOfAttempts", numberOfInitializationAttempts),
                    SafeArg.of("durationBeforeCancellation", getMillisecondsSinceInitialization()));
            singleThreadedExecutor.shutdown();
            return;
        }

        try {
            log.info(
                    "Attempting to initialize {} on the attempt {}. The amount of time elapsed since we began was {}"
                            + " milliseconds.",
                    SafeArg.of("className", getInitializingClassName()),
                    SafeArg.of("numberOfAttempts", numberOfInitializationAttempts),
                    SafeArg.of("initializationDuration", getMillisecondsSinceInitialization()));
            tryInitializeInternal();
            log.info(
                    "Initialized {} on the attempt {} in {} milliseconds",
                    SafeArg.of("className", getInitializingClassName()),
                    SafeArg.of("numberOfAttempts", numberOfInitializationAttempts),
                    SafeArg.of("initializationDuration", getMillisecondsSinceInitialization()));
        } catch (RuntimeException | Error throwable) {
            log.info(
                    "Failed to initialize {} on the attempt {}",
                    SafeArg.of("className", getInitializingClassName()),
                    SafeArg.of("numberOfAttempts", numberOfInitializationAttempts++),
                    throwable);
            try {
                cleanUpOnInitFailure();
            } catch (RuntimeException | Error cleanupThrowable) {
                log.error(
                        "Failed to cleanup when initialization of {} failed on attempt {} with {} milliseconds",
                        SafeArg.of("className", getInitializingClassName()),
                        SafeArg.of("numberOfAttempts", numberOfInitializationAttempts),
                        SafeArg.of("initializationDuration", getMillisecondsSinceInitialization()),
                        cleanupThrowable);
            }
            scheduleInitialization(sleepInterval());
        }
    }

    private long getMillisecondsSinceInitialization() {
        return System.currentTimeMillis() - initializationStartTime;
    }

    // Not final for tests.
    void scheduleInitialization(Duration delay) {
        singleThreadedExecutor.schedule(this::tryInitializationLoop, delay.toMillis(), TimeUnit.MILLISECONDS);
    }

    // Not final for tests.
    ScheduledExecutorService createExecutorService() {
        return PTExecutors.newSingleThreadScheduledExecutor(
                new NamedThreadFactory("AsyncInitializer-" + getInitializingClassName(), true));
    }

    // Not final for tests.
    void assertNeverCalledInitialize() {
        if (!isInitializing.compareAndSet(false, true)) {
            throw new IllegalStateException("Multiple calls tried to initialize the same instance.\n"
                    + "Each instance should have a single thread trying to initialize it.\n"
                    + "Object being initialized multiple times: " + getInitializingClassName());
        }
    }

    protected Duration sleepInterval() {
        return Duration.ofSeconds(10);
    }

    /**
     * Cancels future initializations and registers a callback to be called if the initialization is happening and
     * succeeds. If the initialization has already successfully completed, runs the cleanup task synchronously.
     * 

* If the instance is closeable, it's recommended that the this method is invoked in a close call, and the callback * contains a call to the instance's close method. */ protected final void cancelInitialization(Runnable handler) { if (state.initToCancelWithCleanupTask(handler) == AsyncInitializationState.State.DONE) { handler.run(); } } protected final void checkInitialized() { if (!isInitialized()) { throw new NotInitializedException(getInitializingClassName()); } } private void tryInitializeInternal() { tryInitialize(); if (state.initToDone() == AsyncInitializationState.State.CANCELLED) { state.performCleanupTask(); } singleThreadedExecutor.shutdown(); } // Not final for tests. public boolean isInitialized() { return state.isDone(); } /** * Override this method if there's anything to be cleaned up on initialization failure. * Default implementation is no-op. */ protected void cleanUpOnInitFailure() { // no-op } /** * Override this method with the calls to initialize an object that may fail. * This method will be retried if any exception is thrown on its execution. * If there's any follow up action to clean any state left by a previous initialization failure, see * {@link AsyncInitializer#cleanUpOnInitFailure}. */ protected abstract void tryInitialize(); /** * This method should contain the name of the initializing class. It's used for logging and exception propagation. */ protected abstract String getInitializingClassName(); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy