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