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

com.facebook.lifecycle.ShutdownManagerImpl Maven / Gradle / Ivy

/*
 * Copyright (C) 2012 Facebook, Inc.
 *
 * 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.facebook.lifecycle;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Runtime.addShutdownHook() has no guarantee of order.  This class
 * will run hooks by stage and in the order added within stages
 */
public class ShutdownManagerImpl implements ShutdownManager {
  private static final Logger LOG = LoggerFactory.getLogger(ShutdownManagerImpl.class);

  private final Map> shutdownHooksByStage = new ConcurrentHashMap<>();
  private final Thread thread;
  private final Object shutdownLock = new Object();
  private final T[] stages;
  private final T firstStage;
  private final T lastStage;
  private final T defaultStage;

  private T currentStage;
  private boolean isShutdown = false;

  public ShutdownManagerImpl(Class enumClazz, T defaultStage) {
    this.defaultStage = defaultStage;

    if (!enumClazz.isEnum()) {
      throw new IllegalArgumentException(
        String.format(
          "%s is not an enum class", enumClazz.getName()
        )
      );
    }

    stages = enumClazz.getEnumConstants();
    for (T stage : stages) {
      shutdownHooksByStage.put(stage, new ArrayList());
    }

    // three values : being/end sentinel and at least one usable value 
    if (stages.length < 3) {
      throw new IllegalArgumentException("enum class must have at least 3 values");
    }

    firstStage = stages[0];
    currentStage = firstStage;
    lastStage = stages[stages.length - 1];
    thread = new Thread(
      new Runnable() {
        @Override
        public void run() {
          internalShutdown();
        }
      }
    );
  }

  @Override
  public boolean tryAddShutdownHook(Runnable hook) {
    return tryAddShutdownHook(defaultStage, hook);
  }

  @Override
  public boolean tryAddShutdownHook(T stage, Runnable hook) {
    if (stage == firstStage || stage == lastStage) {
      throw new IllegalArgumentException(
        String.format("stage %s is reserved", stage)
      );
    }

    synchronized (shutdownLock) {
      //if the stage being added is our stage or earlier, we can't accept this
      if (stage.compareTo(currentStage) <= 0) {
        LOG.warn(
            "cannot add hook for stage {} when in stage {}",
            stage,
            currentStage
        );
        return false;
      }

      shutdownHooksByStage.get(stage).add(hook);

      return true;
    }
  }

  @Override
  public void addShutdownHook(Runnable hook) {
    addShutdownHook(defaultStage, hook);
  }

  @Override
  public void addShutdownHook(T stage, Runnable hook) {
    if (!tryAddShutdownHook(stage, hook)) {
      throw new IllegalStateException(
        "trying to add a hook after shutdown started"
      );
    }
  }

  @Override
  public void shutdown() {
    if (internalShutdown()) {
      // fb303.shutdown calls this, so remove the shutdown hook after we're done
      Runtime.getRuntime().removeShutdownHook(thread);
    }
  }

  @Override
  public Thread getAsThread() {
    return thread;
  }

  /**
   * @return true if this executed the shutdown
   */
  private boolean internalShutdown() {
    synchronized (shutdownLock) {
      if (isShutdown) {
        LOG.info("ignoring extra shutdown call");

        return false;
      }

      isShutdown = true;
    }

    LOG.info("starting service shutdown hooks");

    for (T stage : stages) {
      LOG.info("starting stage " + stage);
      synchronized (shutdownLock) {
        currentStage = stage;
      }

      for (Runnable hook : shutdownHooksByStage.get(stage)) {
        try {
          hook.run();
        } catch (Throwable t) {
          LOG.warn("error running hook", t);
        }
      }

      LOG.info("ending stage " + stage);
    }

    LOG.info("shutdown complete");

    return true;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy