| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache license, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the license for the specific language governing permissions and |
| * limitations under the license. |
| */ |
| |
| package org.apache.logging.log4j.core.util; |
| |
| import java.lang.ref.Reference; |
| import java.lang.ref.SoftReference; |
| import java.lang.ref.WeakReference; |
| import java.util.Collection; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.ThreadFactory; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import org.apache.logging.log4j.Logger; |
| import org.apache.logging.log4j.core.AbstractLifeCycle; |
| import org.apache.logging.log4j.core.LifeCycle2; |
| import org.apache.logging.log4j.status.StatusLogger; |
| |
| /** |
| * ShutdownRegistrationStrategy that simply uses {@link Runtime#addShutdownHook(Thread)}. If no strategy is specified, |
| * this one is used for shutdown hook registration. |
| * |
| * @since 2.1 |
| */ |
| public class DefaultShutdownCallbackRegistry implements ShutdownCallbackRegistry, LifeCycle2, Runnable { |
| /** Status logger. */ |
| protected static final Logger LOGGER = StatusLogger.getLogger(); |
| |
| private final AtomicReference<State> state = new AtomicReference<>(State.INITIALIZED); |
| private final ThreadFactory threadFactory; |
| private final Collection<Cancellable> hooks = new CopyOnWriteArrayList<>(); |
| private Reference<Thread> shutdownHookRef; |
| |
| /** |
| * Constructs a DefaultShutdownRegistrationStrategy. |
| */ |
| public DefaultShutdownCallbackRegistry() { |
| this(Executors.defaultThreadFactory()); |
| } |
| |
| /** |
| * Constructs a DefaultShutdownRegistrationStrategy using the given {@link ThreadFactory}. |
| * |
| * @param threadFactory the ThreadFactory to use to create a {@link Runtime} shutdown hook thread |
| */ |
| protected DefaultShutdownCallbackRegistry(final ThreadFactory threadFactory) { |
| this.threadFactory = threadFactory; |
| } |
| |
| /** |
| * Executes the registered shutdown callbacks. |
| */ |
| @Override |
| public void run() { |
| if (state.compareAndSet(State.STARTED, State.STOPPING)) { |
| for (final Runnable hook : hooks) { |
| try { |
| hook.run(); |
| } catch (final Throwable t1) { |
| try { |
| LOGGER.error(SHUTDOWN_HOOK_MARKER, "Caught exception executing shutdown hook {}", hook, t1); |
| } catch (final Throwable t2) { |
| System.err.println("Caught exception " + t2.getClass() + " logging exception " + t1.getClass()); |
| t1.printStackTrace(); |
| } |
| } |
| } |
| state.set(State.STOPPED); |
| } |
| } |
| |
| private static class RegisteredCancellable implements Cancellable { |
| // use a reference to prevent memory leaks |
| private final Reference<Runnable> hook; |
| private Collection<Cancellable> registered; |
| |
| RegisteredCancellable(final Runnable callback, final Collection<Cancellable> registered) { |
| this.registered = registered; |
| hook = new SoftReference<>(callback); |
| } |
| |
| @Override |
| public void cancel() { |
| hook.clear(); |
| registered.remove(this); |
| registered = null; |
| } |
| |
| @Override |
| public void run() { |
| final Runnable runnableHook = this.hook.get(); |
| if (runnableHook != null) { |
| runnableHook.run(); |
| this.hook.clear(); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return String.valueOf(hook.get()); |
| } |
| } |
| |
| @Override |
| public Cancellable addShutdownCallback(final Runnable callback) { |
| if (isStarted()) { |
| final Cancellable receipt = new RegisteredCancellable(callback, hooks); |
| hooks.add(receipt); |
| return receipt; |
| } |
| throw new IllegalStateException("Cannot add new shutdown hook as this is not started. Current state: " + |
| state.get().name()); |
| } |
| |
| @Override |
| public void initialize() { |
| } |
| |
| /** |
| * Registers the shutdown thread only if this is initialized. |
| */ |
| @Override |
| public void start() { |
| if (state.compareAndSet(State.INITIALIZED, State.STARTING)) { |
| try { |
| addShutdownHook(threadFactory.newThread(this)); |
| state.set(State.STARTED); |
| } catch (final IllegalStateException ex) { |
| state.set(State.STOPPED); |
| throw ex; |
| } catch (final Exception e) { |
| LOGGER.catching(e); |
| state.set(State.STOPPED); |
| } |
| } |
| } |
| |
| private void addShutdownHook(final Thread thread) { |
| shutdownHookRef = new WeakReference<>(thread); |
| Runtime.getRuntime().addShutdownHook(thread); |
| } |
| |
| @Override |
| public void stop() { |
| stop(AbstractLifeCycle.DEFAULT_STOP_TIMEOUT, AbstractLifeCycle.DEFAULT_STOP_TIMEUNIT); |
| } |
| |
| /** |
| * Cancels the shutdown thread only if this is started. |
| */ |
| @Override |
| public boolean stop(final long timeout, final TimeUnit timeUnit) { |
| if (state.compareAndSet(State.STARTED, State.STOPPING)) { |
| try { |
| removeShutdownHook(); |
| } finally { |
| state.set(State.STOPPED); |
| } |
| } |
| return true; |
| } |
| |
| private void removeShutdownHook() { |
| final Thread shutdownThread = shutdownHookRef.get(); |
| if (shutdownThread != null) { |
| Runtime.getRuntime().removeShutdownHook(shutdownThread); |
| shutdownHookRef.enqueue(); |
| } |
| } |
| |
| @Override |
| public State getState() { |
| return state.get(); |
| } |
| |
| /** |
| * Indicates if this can accept shutdown hooks. |
| * |
| * @return true if this can accept shutdown hooks |
| */ |
| @Override |
| public boolean isStarted() { |
| return state.get() == State.STARTED; |
| } |
| |
| @Override |
| public boolean isStopped() { |
| return state.get() == State.STOPPED; |
| } |
| |
| } |