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.atomic.AtomicReference; 028 029import org.apache.logging.log4j.Logger; 030import org.apache.logging.log4j.core.LifeCycle; 031import org.apache.logging.log4j.status.StatusLogger; 032 033/** 034 * ShutdownRegistrationStrategy that simply uses {@link Runtime#addShutdownHook(Thread)}. If no strategy is specified, 035 * this one is used for shutdown hook registration. 036 * 037 * @since 2.1 038 */ 039public class DefaultShutdownCallbackRegistry implements ShutdownCallbackRegistry, LifeCycle, Runnable { 040 /** Status logger. */ 041 protected static final Logger LOGGER = StatusLogger.getLogger(); 042 043 private final AtomicReference<State> state = new AtomicReference<>(State.INITIALIZED); 044 private final ThreadFactory threadFactory; 045 private final Collection<Cancellable> hooks = new CopyOnWriteArrayList<>(); 046 private Reference<Thread> shutdownHookRef; 047 048 /** 049 * Constructs a DefaultShutdownRegistrationStrategy. 050 */ 051 public DefaultShutdownCallbackRegistry() { 052 this(Executors.defaultThreadFactory()); 053 } 054 055 /** 056 * Constructs a DefaultShutdownRegistrationStrategy using the given {@link ThreadFactory}. 057 * 058 * @param threadFactory the ThreadFactory to use to create a {@link Runtime} shutdown hook thread 059 */ 060 protected DefaultShutdownCallbackRegistry(final ThreadFactory threadFactory) { 061 this.threadFactory = threadFactory; 062 } 063 064 /** 065 * Executes the registered shutdown callbacks. 066 */ 067 @Override 068 public void run() { 069 if (state.compareAndSet(State.STARTED, State.STOPPING)) { 070 for (final Runnable hook : hooks) { 071 try { 072 hook.run(); 073 } catch (final Throwable t) { 074 LOGGER.error(SHUTDOWN_HOOK_MARKER, "Caught exception executing shutdown hook {}", hook, t); 075 } 076 } 077 state.set(State.STOPPED); 078 } 079 } 080 081 private static class RegisteredCancellable implements Cancellable { 082 // use a reference to prevent memory leaks 083 private final Reference<Runnable> hook; 084 private Collection<Cancellable> registered; 085 086 RegisteredCancellable(final Runnable callback, final Collection<Cancellable> registered) { 087 this.registered = registered; 088 hook = new SoftReference<>(callback); 089 } 090 091 @Override 092 public void cancel() { 093 hook.clear(); 094 registered.remove(this); 095 registered = null; 096 } 097 098 @Override 099 public void run() { 100 final Runnable runnableHook = this.hook.get(); 101 if (runnableHook != null) { 102 runnableHook.run(); 103 this.hook.clear(); 104 } 105 } 106 107 @Override 108 public String toString() { 109 return String.valueOf(hook.get()); 110 } 111 } 112 113 @Override 114 public Cancellable addShutdownCallback(final Runnable callback) { 115 if (isStarted()) { 116 final Cancellable receipt = new RegisteredCancellable(callback, hooks); 117 hooks.add(receipt); 118 return receipt; 119 } 120 throw new IllegalStateException("Cannot add new shutdown hook as this is not started. Current state: " + 121 state.get().name()); 122 } 123 124 @Override 125 public void initialize() { 126 } 127 128 /** 129 * Registers the shutdown thread only if this is initialized. 130 */ 131 @Override 132 public void start() { 133 if (state.compareAndSet(State.INITIALIZED, State.STARTING)) { 134 try { 135 addShutdownHook(threadFactory.newThread(this)); 136 state.set(State.STARTED); 137 } catch (final IllegalStateException ex) { 138 state.set(State.STOPPED); 139 throw ex; 140 } catch (final Exception e) { 141 LOGGER.catching(e); 142 state.set(State.STOPPED); 143 } 144 } 145 } 146 147 private void addShutdownHook(final Thread thread) { 148 shutdownHookRef = new WeakReference<>(thread); 149 Runtime.getRuntime().addShutdownHook(thread); 150 } 151 152 /** 153 * Cancels the shutdown thread only if this is started. 154 */ 155 @Override 156 public void stop() { 157 if (state.compareAndSet(State.STARTED, State.STOPPING)) { 158 try { 159 removeShutdownHook(); 160 } finally { 161 state.set(State.STOPPED); 162 } 163 } 164 } 165 166 private void removeShutdownHook() { 167 final Thread shutdownThread = shutdownHookRef.get(); 168 if (shutdownThread != null) { 169 Runtime.getRuntime().removeShutdownHook(shutdownThread); 170 shutdownHookRef.enqueue(); 171 } 172 } 173 174 @Override 175 public State getState() { 176 return state.get(); 177 } 178 179 /** 180 * Indicates if this can accept shutdown hooks. 181 * 182 * @return true if this can accept shutdown hooks 183 */ 184 @Override 185 public boolean isStarted() { 186 return state.get() == State.STARTED; 187 } 188 189 @Override 190 public boolean isStopped() { 191 return state.get() == State.STOPPED; 192 } 193 194}