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.hbase.regionserver; 019 020import java.io.IOException; 021import java.lang.reflect.Field; 022import java.util.HashMap; 023import java.util.Map; 024import org.apache.hadoop.conf.Configuration; 025import org.apache.hadoop.fs.FileSystem; 026import org.apache.hadoop.hbase.HBaseConfiguration; 027import org.apache.hadoop.hbase.Stoppable; 028import org.apache.hadoop.hbase.log.HBaseMarkers; 029import org.apache.hadoop.hbase.util.ShutdownHookManager; 030import org.apache.hadoop.hbase.util.Threads; 031import org.apache.yetus.audience.InterfaceAudience; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035/** 036 * Manage regionserver shutdown hooks. 037 * @see #install(Configuration, FileSystem, Stoppable, Thread) 038 */ 039@InterfaceAudience.Private 040public class ShutdownHook { 041 private static final Logger LOG = LoggerFactory.getLogger(ShutdownHook.class); 042 private static final String CLIENT_FINALIZER_DATA_METHOD = "clientFinalizer"; 043 044 /** 045 * Key for boolean configuration whose default is true. 046 */ 047 public static final String RUN_SHUTDOWN_HOOK = "hbase.shutdown.hook"; 048 049 /** 050 * Key for a long configuration on how much time to wait on the fs shutdown hook. Default is 30 051 * seconds. 052 */ 053 public static final String FS_SHUTDOWN_HOOK_WAIT = "hbase.fs.shutdown.hook.wait"; 054 055 /** 056 * A place for keeping track of all the filesystem shutdown hooks that need to be executed after 057 * the last regionserver referring to a given filesystem stops. We keep track of the # of 058 * regionserver references in values of the map. 059 */ 060 private final static Map<Runnable, Integer> fsShutdownHooks = new HashMap<>(); 061 062 /** 063 * Install a shutdown hook that calls stop on the passed Stoppable and then thread joins against 064 * the passed <code>threadToJoin</code>. When this thread completes, it then runs the hdfs thread 065 * (This install removes the hdfs shutdown hook keeping a handle on it to run it after 066 * <code>threadToJoin</code> has stopped). 067 * <p> 068 * To suppress all shutdown hook handling -- both the running of the regionserver hook and of the 069 * hdfs hook code -- set {@link ShutdownHook#RUN_SHUTDOWN_HOOK} in {@link Configuration} to 070 * <code>false</code>. This configuration value is checked when the hook code runs. 071 * @param fs Instance of Filesystem used by the RegionServer 072 * @param stop Installed shutdown hook will call stop against this passed 073 * <code>Stoppable</code> instance. 074 * @param threadToJoin After calling stop on <code>stop</code> will then join this thread. 075 */ 076 public static void install(final Configuration conf, final FileSystem fs, final Stoppable stop, 077 final Thread threadToJoin) { 078 Runnable fsShutdownHook = suppressHdfsShutdownHook(fs); 079 Thread t = new ShutdownHookThread(conf, stop, threadToJoin, fsShutdownHook); 080 ShutdownHookManager.affixShutdownHook(t, 0); 081 LOG.debug("Installed shutdown hook thread: " + t.getName()); 082 } 083 084 /* 085 * Thread run by shutdown hook. 086 */ 087 private static class ShutdownHookThread extends Thread { 088 private final Stoppable stop; 089 private final Thread threadToJoin; 090 private final Runnable fsShutdownHook; 091 private final Configuration conf; 092 093 ShutdownHookThread(final Configuration conf, final Stoppable stop, final Thread threadToJoin, 094 final Runnable fsShutdownHook) { 095 super("Shutdownhook:" + threadToJoin.getName()); 096 this.stop = stop; 097 this.threadToJoin = threadToJoin; 098 this.conf = conf; 099 this.fsShutdownHook = fsShutdownHook; 100 } 101 102 @Override 103 public void run() { 104 boolean b = this.conf.getBoolean(RUN_SHUTDOWN_HOOK, true); 105 LOG.info("Shutdown hook starting; " + RUN_SHUTDOWN_HOOK + "=" + b + "; fsShutdownHook=" 106 + this.fsShutdownHook); 107 if (b) { 108 this.stop.stop("Shutdown hook"); 109 Threads.shutdown(this.threadToJoin); 110 if (this.fsShutdownHook != null) { 111 synchronized (fsShutdownHooks) { 112 int refs = fsShutdownHooks.get(fsShutdownHook); 113 if (refs == 1) { 114 LOG.info("Starting fs shutdown hook thread."); 115 Thread fsShutdownHookThread = (fsShutdownHook instanceof Thread) 116 ? (Thread) fsShutdownHook 117 : new Thread(fsShutdownHook, 118 fsShutdownHook.getClass().getSimpleName() + "-shutdown-hook"); 119 fsShutdownHookThread.start(); 120 Threads.shutdown(fsShutdownHookThread, 121 this.conf.getLong(FS_SHUTDOWN_HOOK_WAIT, 30000)); 122 } 123 if (refs > 0) { 124 fsShutdownHooks.put(fsShutdownHook, refs - 1); 125 } 126 } 127 } 128 } 129 LOG.info("Shutdown hook finished."); 130 } 131 } 132 133 /* 134 * So, HDFS keeps a static map of all FS instances. In order to make sure things are cleaned up on 135 * our way out, it also creates a shutdown hook so that all filesystems can be closed when the 136 * process is terminated; it calls FileSystem.closeAll. This inconveniently runs concurrently with 137 * our own shutdown handler, and therefore causes all the filesystems to be closed before the 138 * server can do all its necessary cleanup. <p>The dirty reflection in this method sneaks into the 139 * FileSystem class and grabs the shutdown hook, removes it from the list of active shutdown 140 * hooks, and returns the hook for the caller to run at its convenience. <p>This seems quite 141 * fragile and susceptible to breaking if Hadoop changes anything about the way this cleanup is 142 * managed. Keep an eye on things. 143 * @return The fs shutdown hook 144 * @throws RuntimeException if we fail to find or grap the shutdown hook. 145 */ 146 private static Runnable suppressHdfsShutdownHook(final FileSystem fs) { 147 try { 148 // This introspection has been updated to work for hadoop 0.20, 0.21 and for 149 // cloudera 0.20. 0.21 and cloudera 0.20 both have hadoop-4829. With the 150 // latter in place, things are a little messy in that there are now two 151 // instances of the data member clientFinalizer; an uninstalled one in 152 // FileSystem and one in the innner class named Cache that actually gets 153 // registered as a shutdown hook. If the latter is present, then we are 154 // on 0.21 or cloudera patched 0.20. 155 Runnable hdfsClientFinalizer = null; 156 // Look into the FileSystem#Cache class for clientFinalizer 157 Class<?>[] classes = FileSystem.class.getDeclaredClasses(); 158 Class<?> cache = null; 159 for (Class<?> c : classes) { 160 if (c.getSimpleName().equals("Cache")) { 161 cache = c; 162 break; 163 } 164 } 165 166 if (cache == null) { 167 throw new RuntimeException( 168 "This should not happen. Could not find the cache class in FileSystem."); 169 } 170 171 Field field = null; 172 try { 173 field = cache.getDeclaredField(CLIENT_FINALIZER_DATA_METHOD); 174 } catch (NoSuchFieldException e) { 175 // We can get here if the Cache class does not have a clientFinalizer 176 // instance: i.e. we're running on straight 0.20 w/o hadoop-4829. 177 } 178 if (field != null) { 179 field.setAccessible(true); 180 Field cacheField = FileSystem.class.getDeclaredField("CACHE"); 181 cacheField.setAccessible(true); 182 Object cacheInstance = cacheField.get(fs); 183 hdfsClientFinalizer = (Runnable) field.get(cacheInstance); 184 } else { 185 // Then we didnt' find clientFinalizer in Cache. Presume clean 0.20 hadoop. 186 field = FileSystem.class.getDeclaredField(CLIENT_FINALIZER_DATA_METHOD); 187 field.setAccessible(true); 188 hdfsClientFinalizer = (Runnable) field.get(null); 189 } 190 if (hdfsClientFinalizer == null) { 191 throw new RuntimeException("Client finalizer is null, can't suppress!"); 192 } 193 synchronized (fsShutdownHooks) { 194 boolean isFSCacheDisabled = fs.getConf().getBoolean("fs.hdfs.impl.disable.cache", false); 195 if ( 196 !isFSCacheDisabled && !fsShutdownHooks.containsKey(hdfsClientFinalizer) 197 && !ShutdownHookManager.deleteShutdownHook(hdfsClientFinalizer) 198 ) { 199 throw new RuntimeException( 200 "Failed suppression of fs shutdown hook: " + hdfsClientFinalizer); 201 } 202 Integer refs = fsShutdownHooks.get(hdfsClientFinalizer); 203 fsShutdownHooks.put(hdfsClientFinalizer, refs == null ? 1 : refs + 1); 204 } 205 return hdfsClientFinalizer; 206 } catch (NoSuchFieldException nsfe) { 207 LOG.error(HBaseMarkers.FATAL, "Couldn't find field 'clientFinalizer' in FileSystem!", nsfe); 208 throw new RuntimeException("Failed to suppress HDFS shutdown hook"); 209 } catch (IllegalAccessException iae) { 210 LOG.error(HBaseMarkers.FATAL, "Couldn't access field 'clientFinalizer' in FileSystem!", iae); 211 throw new RuntimeException("Failed to suppress HDFS shutdown hook"); 212 } 213 } 214 215 // Thread that does nothing. Used in below main testing. 216 static class DoNothingThread extends Thread { 217 DoNothingThread() { 218 super("donothing"); 219 } 220 221 @Override 222 public void run() { 223 super.run(); 224 } 225 } 226 227 // Stoppable with nothing to stop. Used below in main testing. 228 static class DoNothingStoppable implements Stoppable { 229 @Override 230 public boolean isStopped() { 231 // TODO Auto-generated method stub 232 return false; 233 } 234 235 @Override 236 public void stop(String why) { 237 // TODO Auto-generated method stub 238 } 239 } 240 241 /** 242 * Main to test basic functionality. Run with clean hadoop 0.20 and hadoop 0.21 and cloudera 243 * patched hadoop to make sure our shutdown hook handling works for all compbinations. Pass 244 * '-Dhbase.shutdown.hook=false' to test turning off the running of shutdown hooks. 245 */ 246 public static void main(final String[] args) throws IOException { 247 Configuration conf = HBaseConfiguration.create(); 248 String prop = System.getProperty(RUN_SHUTDOWN_HOOK); 249 if (prop != null) { 250 conf.setBoolean(RUN_SHUTDOWN_HOOK, Boolean.parseBoolean(prop)); 251 } 252 // Instantiate a FileSystem. This will register the fs shutdown hook. 253 FileSystem fs = FileSystem.get(conf); 254 Thread donothing = new DoNothingThread(); 255 donothing.start(); 256 ShutdownHook.install(conf, fs, new DoNothingStoppable(), donothing); 257 } 258}