001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.util;
019
020import org.apache.commons.logging.Log;
021import org.apache.commons.logging.LogFactory;
022
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.Comparator;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Set;
029import java.util.concurrent.atomic.AtomicBoolean;
030
031/**
032 * The <code>ShutdownHookManager</code> enables running shutdownHook
033 * in a deterministic order, higher priority first.
034 * <p/>
035 * The JVM runs ShutdownHooks in a non-deterministic order or in parallel.
036 * This class registers a single JVM shutdownHook and run all the
037 * shutdownHooks registered to it (to this class) in order based on their
038 * priority.
039 */
040public class ShutdownHookManager {
041
042  private static final ShutdownHookManager MGR = new ShutdownHookManager();
043
044  private static final Log LOG = LogFactory.getLog(ShutdownHookManager.class);
045
046  static {
047    Runtime.getRuntime().addShutdownHook(
048      new Thread() {
049        @Override
050        public void run() {
051          MGR.shutdownInProgress.set(true);
052          for (Runnable hook: MGR.getShutdownHooksInOrder()) {
053            try {
054              hook.run();
055            } catch (Throwable ex) {
056              LOG.warn("ShutdownHook '" + hook.getClass().getSimpleName() +
057                       "' failed, " + ex.toString(), ex);
058            }
059          }
060        }
061      }
062    );
063  }
064
065  /**
066   * Return <code>ShutdownHookManager</code> singleton.
067   *
068   * @return <code>ShutdownHookManager</code> singleton.
069   */
070  public static ShutdownHookManager get() {
071    return MGR;
072  }
073
074  /**
075   * Private structure to store ShutdownHook and its priority.
076   */
077  private static class HookEntry {
078    Runnable hook;
079    int priority;
080
081    public HookEntry(Runnable hook, int priority) {
082      this.hook = hook;
083      this.priority = priority;
084    }
085
086    @Override
087    public int hashCode() {
088      return hook.hashCode();
089    }
090
091    @Override
092    public boolean equals(Object obj) {
093      boolean eq = false;
094      if (obj != null) {
095        if (obj instanceof HookEntry) {
096          eq = (hook == ((HookEntry)obj).hook);
097        }
098      }
099      return eq;
100    }
101
102  }
103
104  private Set<HookEntry> hooks =
105    Collections.synchronizedSet(new HashSet<HookEntry>());
106
107  private AtomicBoolean shutdownInProgress = new AtomicBoolean(false);
108
109  //private to constructor to ensure singularity
110  private ShutdownHookManager() {
111  }
112
113  /**
114   * Returns the list of shutdownHooks in order of execution,
115   * Highest priority first.
116   *
117   * @return the list of shutdownHooks in order of execution.
118   */
119  List<Runnable> getShutdownHooksInOrder() {
120    List<HookEntry> list;
121    synchronized (MGR.hooks) {
122      list = new ArrayList<HookEntry>(MGR.hooks);
123    }
124    Collections.sort(list, new Comparator<HookEntry>() {
125
126      //reversing comparison so highest priority hooks are first
127      @Override
128      public int compare(HookEntry o1, HookEntry o2) {
129        return o2.priority - o1.priority;
130      }
131    });
132    List<Runnable> ordered = new ArrayList<Runnable>();
133    for (HookEntry entry: list) {
134      ordered.add(entry.hook);
135    }
136    return ordered;
137  }
138
139  /**
140   * Adds a shutdownHook with a priority, the higher the priority
141   * the earlier will run. ShutdownHooks with same priority run
142   * in a non-deterministic order.
143   *
144   * @param shutdownHook shutdownHook <code>Runnable</code>
145   * @param priority priority of the shutdownHook.
146   */
147  public void addShutdownHook(Runnable shutdownHook, int priority) {
148    if (shutdownHook == null) {
149      throw new IllegalArgumentException("shutdownHook cannot be NULL");
150    }
151    if (shutdownInProgress.get()) {
152      throw new IllegalStateException("Shutdown in progress, cannot add a shutdownHook");
153    }
154    hooks.add(new HookEntry(shutdownHook, priority));
155  }
156
157  /**
158   * Removes a shutdownHook.
159   *
160   * @param shutdownHook shutdownHook to remove.
161   * @return TRUE if the shutdownHook was registered and removed,
162   * FALSE otherwise.
163   */
164  public boolean removeShutdownHook(Runnable shutdownHook) {
165    if (shutdownInProgress.get()) {
166      throw new IllegalStateException("Shutdown in progress, cannot remove a shutdownHook");
167    }
168    return hooks.remove(new HookEntry(shutdownHook, 0));
169  }
170
171  /**
172   * Indicates if a shutdownHook is registered or not.
173   *
174   * @param shutdownHook shutdownHook to check if registered.
175   * @return TRUE/FALSE depending if the shutdownHook is is registered.
176   */
177  public boolean hasShutdownHook(Runnable shutdownHook) {
178    return hooks.contains(new HookEntry(shutdownHook, 0));
179  }
180  
181  /**
182   * Indicates if shutdown is in progress or not.
183   * 
184   * @return TRUE if the shutdown is in progress, otherwise FALSE.
185   */
186  public boolean isShutdownInProgress() {
187    return shutdownInProgress.get();
188  }
189
190}