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 018 package org.apache.logging.log4j.core.util; 019 020 import java.io.Serializable; 021 import java.lang.ref.Reference; 022 import java.lang.ref.SoftReference; 023 import java.lang.ref.WeakReference; 024 import java.util.Collection; 025 import java.util.concurrent.CopyOnWriteArrayList; 026 import java.util.concurrent.Executors; 027 import java.util.concurrent.ThreadFactory; 028 import java.util.concurrent.atomic.AtomicReference; 029 030 import org.apache.logging.log4j.Logger; 031 import org.apache.logging.log4j.core.LifeCycle; 032 import 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 */ 040 public 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>(State.INITIALIZED); 046 private final ThreadFactory threadFactory; 047 private final Collection<Cancellable> hooks = new CopyOnWriteArrayList<Cancellable>(); 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<Runnable>(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 /** 118 * Registers the shutdown thread only if this is initialized. 119 */ 120 @Override 121 public void start() { 122 if (state.compareAndSet(State.INITIALIZED, State.STARTING)) { 123 try { 124 addShutdownHook(threadFactory.newThread(this)); 125 state.set(State.STARTED); 126 } catch (final Exception e) { 127 LOGGER.catching(e); 128 state.set(State.STOPPED); 129 } 130 } 131 } 132 133 private void addShutdownHook(final Thread thread) { 134 shutdownHookRef = new WeakReference<Thread>(thread); 135 Runtime.getRuntime().addShutdownHook(thread); 136 } 137 138 /** 139 * Cancels the shutdown thread only if this is started. 140 */ 141 @Override 142 public void stop() { 143 if (state.compareAndSet(State.STARTED, State.STOPPING)) { 144 try { 145 removeShutdownHook(); 146 } finally { 147 state.set(State.STOPPED); 148 } 149 } 150 } 151 152 private void removeShutdownHook() { 153 final Thread shutdownThread = shutdownHookRef.get(); 154 if (shutdownThread != null) { 155 Runtime.getRuntime().removeShutdownHook(shutdownThread); 156 shutdownHookRef.enqueue(); 157 } 158 } 159 160 /** 161 * Indicates if this can accept shutdown hooks. 162 * 163 * @return true if this can accept shutdown hooks 164 */ 165 @Override 166 public boolean isStarted() { 167 return state.get() == State.STARTED; 168 } 169 170 @Override 171 public boolean isStopped() { 172 return state.get() == State.STOPPED; 173 } 174 175 }