001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017 018package org.apache.logging.log4j.core.util; 019 020import java.io.Serializable; 021import java.lang.ref.Reference; 022import java.lang.ref.SoftReference; 023import java.lang.ref.WeakReference; 024import java.util.Collection; 025import java.util.concurrent.CopyOnWriteArrayList; 026import java.util.concurrent.Executors; 027import java.util.concurrent.ThreadFactory; 028import java.util.concurrent.atomic.AtomicReference; 029 030import org.apache.logging.log4j.Logger; 031import org.apache.logging.log4j.core.LifeCycle; 032import org.apache.logging.log4j.status.StatusLogger; 033 034/** 035 * ShutdownRegistrationStrategy that simply uses {@link Runtime#addShutdownHook(Thread)}. If no strategy is specified, 036 * this one is used for shutdown hook registration. 037 * 038 * @since 2.1 039 */ 040public class DefaultShutdownCallbackRegistry implements ShutdownCallbackRegistry, LifeCycle, Runnable, Serializable { 041 042 private static final long serialVersionUID = 1L; 043 protected static final Logger LOGGER = StatusLogger.getLogger(); 044 045 private final AtomicReference<State> state = new AtomicReference<>(State.INITIALIZED); 046 private final ThreadFactory threadFactory; 047 private final Collection<Cancellable> hooks = new CopyOnWriteArrayList<>(); 048 private Reference<Thread> shutdownHookRef; 049 050 /** 051 * Constructs a DefaultShutdownRegistrationStrategy. 052 */ 053 public DefaultShutdownCallbackRegistry() { 054 this(Executors.defaultThreadFactory()); 055 } 056 057 /** 058 * Constructs a DefaultShutdownRegistrationStrategy using the given {@link ThreadFactory}. 059 * 060 * @param threadFactory the ThreadFactory to use to create a {@link Runtime} shutdown hook thread 061 */ 062 protected DefaultShutdownCallbackRegistry(final ThreadFactory threadFactory) { 063 this.threadFactory = threadFactory; 064 } 065 066 /** 067 * Executes the registered shutdown callbacks. 068 */ 069 @Override 070 public void run() { 071 if (state.compareAndSet(State.STARTED, State.STOPPING)) { 072 for (final Runnable hook : hooks) { 073 try { 074 hook.run(); 075 } catch (final Throwable t) { 076 LOGGER.error(SHUTDOWN_HOOK_MARKER, "Caught exception executing shutdown hook {}", hook, t); 077 } 078 } 079 state.set(State.STOPPED); 080 } 081 } 082 083 @Override 084 public Cancellable addShutdownCallback(final Runnable callback) { 085 if (isStarted()) { 086 final Cancellable receipt = new Cancellable() { 087 // use a reference to prevent memory leaks 088 private final Reference<Runnable> hook = new SoftReference<>(callback); 089 090 @Override 091 public void cancel() { 092 hook.clear(); 093 hooks.remove(this); 094 } 095 096 @Override 097 public void run() { 098 final Runnable hook = this.hook.get(); 099 if (hook != null) { 100 hook.run(); 101 this.hook.clear(); 102 } 103 } 104 105 @Override 106 public String toString() { 107 return String.valueOf(hook.get()); 108 } 109 }; 110 hooks.add(receipt); 111 return receipt; 112 } 113 throw new IllegalStateException("Cannot add new shutdown hook as this is not started. Current state: " + 114 state.get().name()); 115 } 116 117 @Override 118 public void initialize() { 119 } 120 121 /** 122 * Registers the shutdown thread only if this is initialized. 123 */ 124 @Override 125 public void start() { 126 if (state.compareAndSet(State.INITIALIZED, State.STARTING)) { 127 try { 128 addShutdownHook(threadFactory.newThread(this)); 129 state.set(State.STARTED); 130 } catch (final Exception e) { 131 LOGGER.catching(e); 132 state.set(State.STOPPED); 133 } 134 } 135 } 136 137 private void addShutdownHook(final Thread thread) { 138 shutdownHookRef = new WeakReference<>(thread); 139 Runtime.getRuntime().addShutdownHook(thread); 140 } 141 142 /** 143 * Cancels the shutdown thread only if this is started. 144 */ 145 @Override 146 public void stop() { 147 if (state.compareAndSet(State.STARTED, State.STOPPING)) { 148 try { 149 removeShutdownHook(); 150 } finally { 151 state.set(State.STOPPED); 152 } 153 } 154 } 155 156 private void removeShutdownHook() { 157 final Thread shutdownThread = shutdownHookRef.get(); 158 if (shutdownThread != null) { 159 Runtime.getRuntime().removeShutdownHook(shutdownThread); 160 shutdownHookRef.enqueue(); 161 } 162 } 163 164 @Override 165 public State getState() { 166 return state.get(); 167 } 168 169 /** 170 * Indicates if this can accept shutdown hooks. 171 * 172 * @return true if this can accept shutdown hooks 173 */ 174 @Override 175 public boolean isStarted() { 176 return state.get() == State.STARTED; 177 } 178 179 @Override 180 public boolean isStopped() { 181 return state.get() == State.STOPPED; 182 } 183 184}