INDEX
@Slf4j
@Component
public class SpringRefreshedEnentListrner implements ApplicationListener {
private static final String HOOKS_CLASS_NAME = "java.lang.ApplicationShutdownHooks";
@Resource
private ConfigurableShutDownHooksSorter sorter;
@Value("${graceful.graceful:30}")
private long gracefulLimit;
@Resource
ApplicationContext applicationContext;
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (!(event instanceof ContextRefreshedEvent)) return;
if (!Objects.equals(
((ApplicationContextEvent)event).getApplicationContext(), this.applicationContext))
return;
log.info("[SpringRefreshedEnentListrner] on event start...");
log.info("[SpringRefreshedEnentListrner] rearrange application shutdown hooks");
rearrangeApplicationShutdownHooks();
log.info("[SpringRefreshedEnentListrner] register application shutdown hook trigger");
renewApplicationShutdownTrigger();
log.info("[SpringRefreshedEnentListrner] on event done...");
}
private void renewApplicationShutdownTrigger() {
TriggerSpringShutdownHook.register(
gracefulLimit, (ConfigurableApplicationContext) applicationContext);
}
private void rearrangeApplicationShutdownHooks() {
try {
Class hooksClz = Class.forName(HOOKS_CLASS_NAME);
Field hooksField = hooksClz.getDeclaredField("hooks");
hooksField.setAccessible(true);
IdentityHashMap<Thread, Thread> origHooks =
(IdentityHashMap<Thread, Thread>) hooksField.get(hooksClz);
if(MapUtils.isEmpty(origHooks)){
log.info("[rearrangeApplicationShutdownHooks] empty application hooks, skiped");
}
sorter.init();
log.info("[rearrangeApplicationShutdownHooks] sorting shutdownhook {} by {}"
,origHooks.size() ,sorter.toString());
synchronized (hooksClz) {
for (Thread hook : origHooks.keySet()) {
log.info("[rearrangeApplicationShutdownHooks] shutdownHook: [{}]-[{}]"
,hook.getClass().getName(), hook.getName());
SortedSerialRunningShutdownHookHolder.register(hook, sorter.sorting(hook.getName()));
}
origHooks.clear();
}
SortedSerialRunningShutdownHookHolder.report();
log.info("[rearrangeApplicationShutdownHooks] done");
} catch (Exception e) {
log.error("error on rearrange application shutdown hooks : ",e);
throw new RuntimeException(e);
}
}
}
@Slf4j
public class SortedSerialRunningShutdownHookHolder {
private static PriorityQueue<Pair<Thread,Integer>> HOOKS =
new PriorityQueue<>(Comparator.comparingInt(Pair::getValue));
public static synchronized void register(Thread hook,int score){
if (hook.isAlive())
throw new IllegalArgumentException("Hook already running");
if (hasBeenRegisted(hook))
throw new IllegalArgumentException("Hook previously registered");
HOOKS.add(Pair.of(hook,score));
}
public static synchronized void unregister(Thread hook){
if (hook == null)
throw new IllegalArgumentException();
Iterator<Pair<Thread,Integer>> hsi = HOOKS.iterator();
Pair<Thread,Integer> node;
while(hsi.hasNext()){
node = hsi.next();
if(node.getKey() == hook) hsi.remove();
break;
}
}
public static synchronized void runHooks(){
PriorityQueue<Pair<Thread,Integer>> toBeRuns;
synchronized(SortedSerialRunningShutdownHookHolder.class) {
toBeRuns = HOOKS;
HOOKS = null;
}
Pair<Thread,Integer> curr = null;
Thread t = null;
while( (curr=toBeRuns.poll()) !=null){
t = curr.getKey();
try {
log.info("[shutdown hook holder] run: {} ",t.getName());
t.start();
t.join();
log.info("[shutdown hook holder] finished: {} ",t.getName());
} catch (InterruptedException e) {
log.warn("SortedSerialRunningShutdownHooks has been interrupted");
}
}
}
public static void report() {
if(CollectionUtils.isEmpty(HOOKS)) {
log.info("[SortedSerialRunningShutdownHookHolder] report: {} ","");
return ;
}
StringBuilder report = new StringBuilder("[");
for(Pair<Thread,Integer> hook: HOOKS){
report.append("(").append(hook.getKey().getName())
.append(",").append(hook.getValue()).append("),");
}
report.deleteCharAt(report.length()-1);
report.append("]");
log.info("[SortedSerialRunningShutdownHookHolder] report: {} ",report);
}
private static boolean hasBeenRegisted(Thread target) {
for(Pair<Thread,Integer> hook: HOOKS){
if(hook.getKey()==target) return true;
}
return false;
}
}
@Component
@Data
public class ConfigurableShutDownHooksSorter {
@Value("${graceful.shutdownhook.sorted}")
private List<String> sorted;
@Value("${graceful.shutdownhook.outOfSorting:65535}")
private int outOfSorting;
private Function<Integer,Integer> processer;
public ConfigurableShutDownHooksSorter() { }
public ConfigurableShutDownHooksSorter(List<String> sorted, Function<Integer, Integer> processer) {
this.sorted = sorted;
this.processer = processer;
}
public void init(){
if(CollectionUtils.isEmpty(sorted)){
synchronized (ConfigurableShutDownHooksSorter.class){
if(CollectionUtils.isEmpty(sorted)){
sorted = new ArrayList<>();
sorted.add("as-shutdown-hooker");
sorted.add("DubboShutdownHook-NettyClient");
sorted.add("DubboShutdownHook");
}
}
}
}
public int sorting(String hookName){
int score = sorted.indexOf(hookName);
if(-1 == score) score = outOfSorting;
return processer==null?score:processer.apply(score);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append("\"sorted\":").append(sorted);
sb.append(",\"outOfSorting\":").append(outOfSorting);
sb.append('}');
return sb.toString();
}
}
@Slf4j
public class TriggerSpringShutdownHook extends Thread {
public static TriggerSpringShutdownHook INSTANCE = null;
public static final String DEFAULT_THREAD_NAME = "triggerSpringShutdownHook";
public static final String DEFAULT_FIELD_NAME = "shutdownHook";
private long gracefulLimit;
private ConfigurableApplicationContext context;
@Override
public void run() {
log.info("Application shutdown hooking for [flow truncate]");
AbstractRegistryFactory.destroyAll();
log.info("Application shutdown hooking for [graceful time]");
try {
TimeUnit.SECONDS.sleep(gracefulLimit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("interrupted :", e);
}
log.info("Application shutdown hooking for [other hooks holder]");
SortedSerialRunningShutdownHookHolder.runHooks();
log.info("Application shutdown hooking for [applicatoin hook]");
context.close();
}
public static void register(long gracefulLimit,ConfigurableApplicationContext context){
try {
TriggerSpringShutdownHook.get(DEFAULT_THREAD_NAME ,gracefulLimit, context);
log.info("[TriggerSpringShutdownHook] register: instanced");
Field hook = AbstractApplicationContext.class.getDeclaredField(DEFAULT_FIELD_NAME);
hook.setAccessible(true);
hook.set(context,TriggerSpringShutdownHook.get());
log.info("[TriggerSpringShutdownHook] register: spring context done");
Runtime.getRuntime().addShutdownHook(TriggerSpringShutdownHook.get());
log.info("[TriggerSpringShutdownHook] register: application hooks done");
} catch (Exception e) {
log.error("[TriggerSpringShutdownHook] exception: ",e);
}
}
public static TriggerSpringShutdownHook get(){
if(null == INSTANCE)
throw new IllegalStateException("TriggerSpringShutdownHook has not bean initialized");
return INSTANCE;
}
public static TriggerSpringShutdownHook get(String name,long gracefulLimit,ConfigurableApplicationContext context){
if(null == INSTANCE){
synchronized (TriggerSpringShutdownHook.class){
if(null == INSTANCE){
INSTANCE = new TriggerSpringShutdownHook(name);
INSTANCE.gracefulLimit = gracefulLimit;
INSTANCE.context = context;
}else{
log.warn("TriggerSpringShutdownHook is not updated");
}
}
}
return INSTANCE;
}
private TriggerSpringShutdownHook(String name) {
super(name);
}
}