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 }