/*
 * 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 Runtime.addShutdownHook(Thread). If no strategy is specified, this one is used for shutdown hook registration.
Since:2.1
/** * 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.
/** 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.
/** * Constructs a DefaultShutdownRegistrationStrategy. */
public DefaultShutdownCallbackRegistry() { this(Executors.defaultThreadFactory()); }
Constructs a DefaultShutdownRegistrationStrategy using the given ThreadFactory.
Params:
  • threadFactory – the ThreadFactory to use to create a Runtime shutdown hook thread
/** * 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.
/** * 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.
/** * 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.
/** * 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.
Returns:true if this can accept shutdown hooks
/** * 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; } }