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.lang.ref.Reference; 021import java.lang.ref.SoftReference; 022import java.lang.ref.WeakReference; 023import java.util.Collection; 024import java.util.concurrent.CopyOnWriteArrayList; 025import java.util.concurrent.Executors; 026import java.util.concurrent.ThreadFactory; 027import java.util.concurrent.TimeUnit; 028import java.util.concurrent.atomic.AtomicReference; 029 030import org.apache.logging.log4j.Logger; 031import org.apache.logging.log4j.core.AbstractLifeCycle; 032import org.apache.logging.log4j.core.LifeCycle2; 033import org.apache.logging.log4j.status.StatusLogger; 034 035/** 036 * ShutdownRegistrationStrategy that simply uses {@link Runtime#addShutdownHook(Thread)}. If no strategy is specified, 037 * this one is used for shutdown hook registration. 038 * 039 * @since 2.1 040 */ 041public class DefaultShutdownCallbackRegistry implements ShutdownCallbackRegistry, LifeCycle2, Runnable { 042 /** Status logger. */ 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 t1) { 076 try { 077 LOGGER.error(SHUTDOWN_HOOK_MARKER, "Caught exception executing shutdown hook {}", hook, t1); 078 } catch (final Throwable t2) { 079 System.err.println("Caught exception " + t2.getClass() + " logging exception " + t1.getClass()); 080 t1.printStackTrace(); 081 } 082 } 083 } 084 state.set(State.STOPPED); 085 } 086 } 087 088 private static class RegisteredCancellable implements Cancellable { 089 // use a reference to prevent memory leaks 090 private final Reference<Runnable> hook; 091 private Collection<Cancellable> registered; 092 093 RegisteredCancellable(final Runnable callback, final Collection<Cancellable> registered) { 094 this.registered = registered; 095 hook = new SoftReference<>(callback); 096 } 097 098 @Override 099 public void cancel() { 100 hook.clear(); 101 registered.remove(this); 102 registered = null; 103 } 104 105 @Override 106 public void run() { 107 final Runnable runnableHook = this.hook.get(); 108 if (runnableHook != null) { 109 runnableHook.run(); 110 this.hook.clear(); 111 } 112 } 113 114 @Override 115 public String toString() { 116 return String.valueOf(hook.get()); 117 } 118 } 119 120 @Override 121 public Cancellable addShutdownCallback(final Runnable callback) { 122 if (isStarted()) { 123 final Cancellable receipt = new RegisteredCancellable(callback, hooks); 124 hooks.add(receipt); 125 return receipt; 126 } 127 throw new IllegalStateException("Cannot add new shutdown hook as this is not started. Current state: " + 128 state.get().name()); 129 } 130 131 @Override 132 public void initialize() { 133 } 134 135 /** 136 * Registers the shutdown thread only if this is initialized. 137 */ 138 @Override 139 public void start() { 140 if (state.compareAndSet(State.INITIALIZED, State.STARTING)) { 141 try { 142 addShutdownHook(threadFactory.newThread(this)); 143 state.set(State.STARTED); 144 } catch (final IllegalStateException ex) { 145 state.set(State.STOPPED); 146 throw ex; 147 } catch (final Exception e) { 148 LOGGER.catching(e); 149 state.set(State.STOPPED); 150 } 151 } 152 } 153 154 private void addShutdownHook(final Thread thread) { 155 shutdownHookRef = new WeakReference<>(thread); 156 Runtime.getRuntime().addShutdownHook(thread); 157 } 158 159 @Override 160 public void stop() { 161 stop(AbstractLifeCycle.DEFAULT_STOP_TIMEOUT, AbstractLifeCycle.DEFAULT_STOP_TIMEUNIT); 162 } 163 164 /** 165 * Cancels the shutdown thread only if this is started. 166 */ 167 @Override 168 public boolean stop(final long timeout, final TimeUnit timeUnit) { 169 if (state.compareAndSet(State.STARTED, State.STOPPING)) { 170 try { 171 removeShutdownHook(); 172 } finally { 173 state.set(State.STOPPED); 174 } 175 } 176 return true; 177 } 178 179 private void removeShutdownHook() { 180 final Thread shutdownThread = shutdownHookRef.get(); 181 if (shutdownThread != null) { 182 Runtime.getRuntime().removeShutdownHook(shutdownThread); 183 shutdownHookRef.enqueue(); 184 } 185 } 186 187 @Override 188 public State getState() { 189 return state.get(); 190 } 191 192 /** 193 * Indicates if this can accept shutdown hooks. 194 * 195 * @return true if this can accept shutdown hooks 196 */ 197 @Override 198 public boolean isStarted() { 199 return state.get() == State.STARTED; 200 } 201 202 @Override 203 public boolean isStopped() { 204 return state.get() == State.STOPPED; 205 } 206 207}