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     */
018    package org.apache.hadoop.util;
019    
020    import org.apache.commons.logging.Log;
021    import org.apache.commons.logging.LogFactory;
022    
023    import java.util.ArrayList;
024    import java.util.Collections;
025    import java.util.Comparator;
026    import java.util.HashSet;
027    import java.util.List;
028    import java.util.Set;
029    import 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     */
040    public 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    }