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 com.google.common.util.concurrent.ThreadFactoryBuilder; 021import org.apache.commons.logging.Log; 022import org.apache.commons.logging.LogFactory; 023 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.Comparator; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Set; 030import java.util.concurrent.Executors; 031import java.util.concurrent.ExecutorService; 032import java.util.concurrent.Future; 033import java.util.concurrent.TimeoutException; 034import java.util.concurrent.TimeUnit; 035import java.util.concurrent.atomic.AtomicBoolean; 036 037/** 038 * The <code>ShutdownHookManager</code> enables running shutdownHook 039 * in a deterministic order, higher priority first. 040 * <p/> 041 * The JVM runs ShutdownHooks in a non-deterministic order or in parallel. 042 * This class registers a single JVM shutdownHook and run all the 043 * shutdownHooks registered to it (to this class) in order based on their 044 * priority. 045 */ 046public class ShutdownHookManager { 047 048 private static final ShutdownHookManager MGR = new ShutdownHookManager(); 049 050 private static final Log LOG = LogFactory.getLog(ShutdownHookManager.class); 051 private static final long TIMEOUT_DEFAULT = 10; 052 private static final TimeUnit TIME_UNIT_DEFAULT = TimeUnit.SECONDS; 053 054 private static final ExecutorService EXECUTOR = 055 Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() 056 .setDaemon(true).build()); 057 static { 058 try { 059 Runtime.getRuntime().addShutdownHook( 060 new Thread() { 061 @Override 062 public void run() { 063 MGR.shutdownInProgress.set(true); 064 for (HookEntry entry: MGR.getShutdownHooksInOrder()) { 065 Future<?> future = EXECUTOR.submit(entry.getHook()); 066 try { 067 future.get(entry.getTimeout(), entry.getTimeUnit()); 068 } catch (TimeoutException ex) { 069 future.cancel(true); 070 LOG.warn("ShutdownHook '" + entry.getHook().getClass(). 071 getSimpleName() + "' timeout, " + ex.toString(), ex); 072 } catch (Throwable ex) { 073 LOG.warn("ShutdownHook '" + entry.getHook().getClass(). 074 getSimpleName() + "' failed, " + ex.toString(), ex); 075 } 076 } 077 try { 078 EXECUTOR.shutdown(); 079 if (!EXECUTOR.awaitTermination(TIMEOUT_DEFAULT, 080 TIME_UNIT_DEFAULT)) { 081 LOG.error("ShutdownHookManger shutdown forcefully."); 082 EXECUTOR.shutdownNow(); 083 } 084 LOG.debug("ShutdownHookManger complete shutdown."); 085 } catch (InterruptedException ex) { 086 LOG.error("ShutdownHookManger interrupted while waiting for " + 087 "termination.", ex); 088 EXECUTOR.shutdownNow(); 089 Thread.currentThread().interrupt(); 090 } 091 } 092 } 093 ); 094 } catch (IllegalStateException ex) { 095 // JVM is being shut down. Ignore 096 LOG.warn("Failed to add the ShutdownHook", ex); 097 } 098 } 099 100 /** 101 * Return <code>ShutdownHookManager</code> singleton. 102 * 103 * @return <code>ShutdownHookManager</code> singleton. 104 */ 105 public static ShutdownHookManager get() { 106 return MGR; 107 } 108 109 /** 110 * Private structure to store ShutdownHook, its priority and timeout 111 * settings. 112 */ 113 static class HookEntry { 114 private final Runnable hook; 115 private final int priority; 116 private final long timeout; 117 private final TimeUnit unit; 118 119 HookEntry(Runnable hook, int priority) { 120 this(hook, priority, TIMEOUT_DEFAULT, TIME_UNIT_DEFAULT); 121 } 122 123 HookEntry(Runnable hook, int priority, long timeout, TimeUnit unit) { 124 this.hook = hook; 125 this.priority = priority; 126 this.timeout = timeout; 127 this.unit = unit; 128 } 129 130 @Override 131 public int hashCode() { 132 return hook.hashCode(); 133 } 134 135 @Override 136 public boolean equals(Object obj) { 137 boolean eq = false; 138 if (obj != null) { 139 if (obj instanceof HookEntry) { 140 eq = (hook == ((HookEntry)obj).hook); 141 } 142 } 143 return eq; 144 } 145 146 Runnable getHook() { 147 return hook; 148 } 149 150 int getPriority() { 151 return priority; 152 } 153 154 long getTimeout() { 155 return timeout; 156 } 157 158 TimeUnit getTimeUnit() { 159 return unit; 160 } 161 } 162 163 private final Set<HookEntry> hooks = 164 Collections.synchronizedSet(new HashSet<HookEntry>()); 165 166 private AtomicBoolean shutdownInProgress = new AtomicBoolean(false); 167 168 //private to constructor to ensure singularity 169 private ShutdownHookManager() { 170 } 171 172 /** 173 * Returns the list of shutdownHooks in order of execution, 174 * Highest priority first. 175 * 176 * @return the list of shutdownHooks in order of execution. 177 */ 178 List<HookEntry> getShutdownHooksInOrder() { 179 List<HookEntry> list; 180 synchronized (MGR.hooks) { 181 list = new ArrayList<HookEntry>(MGR.hooks); 182 } 183 Collections.sort(list, new Comparator<HookEntry>() { 184 185 //reversing comparison so highest priority hooks are first 186 @Override 187 public int compare(HookEntry o1, HookEntry o2) { 188 return o2.priority - o1.priority; 189 } 190 }); 191 return list; 192 } 193 194 /** 195 * Adds a shutdownHook with a priority, the higher the priority 196 * the earlier will run. ShutdownHooks with same priority run 197 * in a non-deterministic order. 198 * 199 * @param shutdownHook shutdownHook <code>Runnable</code> 200 * @param priority priority of the shutdownHook. 201 */ 202 public void addShutdownHook(Runnable shutdownHook, int priority) { 203 if (shutdownHook == null) { 204 throw new IllegalArgumentException("shutdownHook cannot be NULL"); 205 } 206 if (shutdownInProgress.get()) { 207 throw new IllegalStateException("Shutdown in progress, cannot add a " + 208 "shutdownHook"); 209 } 210 hooks.add(new HookEntry(shutdownHook, priority)); 211 } 212 213 /** 214 * 215 * Adds a shutdownHook with a priority and timeout the higher the priority 216 * the earlier will run. ShutdownHooks with same priority run 217 * in a non-deterministic order. The shutdown hook will be terminated if it 218 * has not been finished in the specified period of time. 219 * 220 * @param shutdownHook shutdownHook <code>Runnable</code> 221 * @param priority priority of the shutdownHook 222 * @param timeout timeout of the shutdownHook 223 * @param unit unit of the timeout <code>TimeUnit</code> 224 */ 225 public void addShutdownHook(Runnable shutdownHook, int priority, long timeout, 226 TimeUnit unit) { 227 if (shutdownHook == null) { 228 throw new IllegalArgumentException("shutdownHook cannot be NULL"); 229 } 230 if (shutdownInProgress.get()) { 231 throw new IllegalStateException("Shutdown in progress, cannot add a " + 232 "shutdownHook"); 233 } 234 hooks.add(new HookEntry(shutdownHook, priority, timeout, unit)); 235 } 236 237 /** 238 * Removes a shutdownHook. 239 * 240 * @param shutdownHook shutdownHook to remove. 241 * @return TRUE if the shutdownHook was registered and removed, 242 * FALSE otherwise. 243 */ 244 public boolean removeShutdownHook(Runnable shutdownHook) { 245 if (shutdownInProgress.get()) { 246 throw new IllegalStateException("Shutdown in progress, cannot remove a " + 247 "shutdownHook"); 248 } 249 return hooks.remove(new HookEntry(shutdownHook, 0)); 250 } 251 252 /** 253 * Indicates if a shutdownHook is registered or not. 254 * 255 * @param shutdownHook shutdownHook to check if registered. 256 * @return TRUE/FALSE depending if the shutdownHook is is registered. 257 */ 258 public boolean hasShutdownHook(Runnable shutdownHook) { 259 return hooks.contains(new HookEntry(shutdownHook, 0)); 260 } 261 262 /** 263 * Indicates if shutdown is in progress or not. 264 * 265 * @return TRUE if the shutdown is in progress, otherwise FALSE. 266 */ 267 public boolean isShutdownInProgress() { 268 return shutdownInProgress.get(); 269 } 270 271 /** 272 * clear all registered shutdownHooks. 273 */ 274 public void clearShutdownHooks() { 275 hooks.clear(); 276 } 277}