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 048 // use references to prevent memory leaks 049 private final Collection<Reference<Cancellable>> hooks = new CopyOnWriteArrayList<>(); 050 private Reference<Thread> shutdownHookRef; 051 052 /** 053 * Constructs a DefaultShutdownRegistrationStrategy. 054 */ 055 public DefaultShutdownCallbackRegistry() { 056 this(Executors.defaultThreadFactory()); 057 } 058 059 /** 060 * Constructs a DefaultShutdownRegistrationStrategy using the given {@link ThreadFactory}. 061 * 062 * @param threadFactory the ThreadFactory to use to create a {@link Runtime} shutdown hook thread 063 */ 064 protected DefaultShutdownCallbackRegistry(final ThreadFactory threadFactory) { 065 this.threadFactory = threadFactory; 066 } 067 068 /** 069 * Executes the registered shutdown callbacks. 070 */ 071 @Override 072 public void run() { 073 if (state.compareAndSet(State.STARTED, State.STOPPING)) { 074 for (final Reference<Cancellable> hookRef : hooks) { 075 Cancellable hook = hookRef.get(); 076 if (hook != null) { 077 try { 078 hook.run(); 079 } catch (final Throwable t1) { 080 try { 081 LOGGER.error(SHUTDOWN_HOOK_MARKER, "Caught exception executing shutdown hook {}", hook, t1); 082 } catch (final Throwable t2) { 083 System.err.println("Caught exception " + t2.getClass() + " logging exception " + t1.getClass()); 084 t1.printStackTrace(); 085 } 086 } 087 } 088 } 089 state.set(State.STOPPED); 090 } 091 } 092 093 private static class RegisteredCancellable implements Cancellable { 094 private Runnable callback; 095 private Collection<Reference<Cancellable>> registered; 096 097 RegisteredCancellable(final Runnable callback, final Collection<Reference<Cancellable>> registered) { 098 this.callback = callback; 099 this.registered = registered; 100 } 101 102 @Override 103 public void cancel() { 104 callback = null; 105 Collection<Reference<Cancellable>> references = registered; 106 if (references != null) { 107 registered = null; 108 references.removeIf(ref -> { 109 Cancellable value = ref.get(); 110 return value == null || value == RegisteredCancellable.this; 111 }); 112 } 113 } 114 115 @Override 116 public void run() { 117 final Runnable runnableHook = callback; 118 if (runnableHook != null) { 119 runnableHook.run(); 120 callback = null; 121 } 122 } 123 124 @Override 125 public String toString() { 126 return String.valueOf(callback); 127 } 128 } 129 130 @Override 131 public Cancellable addShutdownCallback(final Runnable callback) { 132 if (isStarted()) { 133 final Cancellable receipt = new RegisteredCancellable(callback, hooks); 134 hooks.add(new SoftReference<>(receipt)); 135 return receipt; 136 } 137 throw new IllegalStateException("Cannot add new shutdown hook as this is not started. Current state: " + 138 state.get().name()); 139 } 140 141 @Override 142 public void initialize() { 143 } 144 145 /** 146 * Registers the shutdown thread only if this is initialized. 147 */ 148 @Override 149 public void start() { 150 if (state.compareAndSet(State.INITIALIZED, State.STARTING)) { 151 try { 152 addShutdownHook(threadFactory.newThread(this)); 153 state.set(State.STARTED); 154 } catch (final IllegalStateException ex) { 155 state.set(State.STOPPED); 156 throw ex; 157 } catch (final Exception e) { 158 LOGGER.catching(e); 159 state.set(State.STOPPED); 160 } 161 } 162 } 163 164 private void addShutdownHook(final Thread thread) { 165 shutdownHookRef = new WeakReference<>(thread); 166 Runtime.getRuntime().addShutdownHook(thread); 167 } 168 169 @Override 170 public void stop() { 171 stop(AbstractLifeCycle.DEFAULT_STOP_TIMEOUT, AbstractLifeCycle.DEFAULT_STOP_TIMEUNIT); 172 } 173 174 /** 175 * Cancels the shutdown thread only if this is started. 176 */ 177 @Override 178 public boolean stop(final long timeout, final TimeUnit timeUnit) { 179 if (state.compareAndSet(State.STARTED, State.STOPPING)) { 180 try { 181 removeShutdownHook(); 182 } finally { 183 state.set(State.STOPPED); 184 } 185 } 186 return true; 187 } 188 189 private void removeShutdownHook() { 190 final Thread shutdownThread = shutdownHookRef.get(); 191 if (shutdownThread != null) { 192 Runtime.getRuntime().removeShutdownHook(shutdownThread); 193 shutdownHookRef.enqueue(); 194 } 195 } 196 197 @Override 198 public State getState() { 199 return state.get(); 200 } 201 202 /** 203 * Indicates if this can accept shutdown hooks. 204 * 205 * @return true if this can accept shutdown hooks 206 */ 207 @Override 208 public boolean isStarted() { 209 return state.get() == State.STARTED; 210 } 211 212 @Override 213 public boolean isStopped() { 214 return state.get() == State.STOPPED; 215 } 216 217}