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