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.TimeUnit;
028import java.util.concurrent.atomic.AtomicReference;
029
030import org.apache.logging.log4j.Logger;
031import org.apache.logging.log4j.core.AbstractLifeCycle;
032import org.apache.logging.log4j.core.LifeCycle2;
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, LifeCycle2, Runnable {
042    /** Status logger. */
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
048    // use references to prevent memory leaks
049    private final Collection<Reference<Cancellable>> hooks = new CopyOnWriteArrayList<>();
050    private Reference<Thread> shutdownHookRef;
051
052    /**
053     * Constructs a DefaultShutdownRegistrationStrategy.
054     */
055    public DefaultShutdownCallbackRegistry() {
056        this(Executors.defaultThreadFactory());
057    }
058
059    /**
060     * Constructs a DefaultShutdownRegistrationStrategy using the given {@link ThreadFactory}.
061     *
062     * @param threadFactory the ThreadFactory to use to create a {@link Runtime} shutdown hook thread
063     */
064    protected DefaultShutdownCallbackRegistry(final ThreadFactory threadFactory) {
065        this.threadFactory = threadFactory;
066    }
067
068    /**
069     * Executes the registered shutdown callbacks.
070     */
071    @Override
072    public void run() {
073        if (state.compareAndSet(State.STARTED, State.STOPPING)) {
074            for (final Reference<Cancellable> hookRef : hooks) {
075                Cancellable hook = hookRef.get();
076                if (hook != null) {
077                    try {
078                        hook.run();
079                    } catch (final Throwable t1) {
080                        try {
081                            LOGGER.error(SHUTDOWN_HOOK_MARKER, "Caught exception executing shutdown hook {}", hook, t1);
082                        } catch (final Throwable t2) {
083                            System.err.println("Caught exception " + t2.getClass() + " logging exception " + t1.getClass());
084                            t1.printStackTrace();
085                        }
086                    }
087                }
088            }
089            state.set(State.STOPPED);
090        }
091    }
092
093    private static class RegisteredCancellable implements Cancellable {
094        private Runnable callback;
095        private Collection<Reference<Cancellable>> registered;
096
097        RegisteredCancellable(final Runnable callback, final Collection<Reference<Cancellable>> registered) {
098            this.callback = callback;
099            this.registered = registered;
100        }
101
102        @Override
103        public void cancel() {
104            callback = null;
105            Collection<Reference<Cancellable>> references = registered;
106            if (references != null) {
107                registered = null;
108                references.removeIf(ref -> {
109                    Cancellable value = ref.get();
110                    return value == null || value == RegisteredCancellable.this;
111                });
112            }
113        }
114
115        @Override
116        public void run() {
117            final Runnable runnableHook = callback;
118            if (runnableHook != null) {
119                runnableHook.run();
120                callback = null;
121            }
122        }
123
124        @Override
125        public String toString() {
126            return String.valueOf(callback);
127        }
128    }
129
130    @Override
131    public Cancellable addShutdownCallback(final Runnable callback) {
132        if (isStarted()) {
133            final Cancellable receipt = new RegisteredCancellable(callback, hooks);
134            hooks.add(new SoftReference<>(receipt));
135            return receipt;
136        }
137        throw new IllegalStateException("Cannot add new shutdown hook as this is not started. Current state: " +
138            state.get().name());
139    }
140
141    @Override
142    public void initialize() {
143    }
144
145    /**
146     * Registers the shutdown thread only if this is initialized.
147     */
148    @Override
149    public void start() {
150        if (state.compareAndSet(State.INITIALIZED, State.STARTING)) {
151            try {
152                addShutdownHook(threadFactory.newThread(this));
153                state.set(State.STARTED);
154            } catch (final IllegalStateException ex) {
155                state.set(State.STOPPED);
156                throw ex;
157            } catch (final Exception e) {
158                LOGGER.catching(e);
159                state.set(State.STOPPED);
160            }
161        }
162    }
163
164    private void addShutdownHook(final Thread thread) {
165        shutdownHookRef = new WeakReference<>(thread);
166        Runtime.getRuntime().addShutdownHook(thread);
167    }
168
169    @Override
170    public void stop() {
171        stop(AbstractLifeCycle.DEFAULT_STOP_TIMEOUT, AbstractLifeCycle.DEFAULT_STOP_TIMEUNIT);
172    }
173
174    /**
175     * Cancels the shutdown thread only if this is started.
176     */
177    @Override
178    public boolean stop(final long timeout, final TimeUnit timeUnit) {
179        if (state.compareAndSet(State.STARTED, State.STOPPING)) {
180            try {
181                removeShutdownHook();
182            } finally {
183                state.set(State.STOPPED);
184            }
185        }
186        return true;
187    }
188
189    private void removeShutdownHook() {
190        final Thread shutdownThread = shutdownHookRef.get();
191        if (shutdownThread != null) {
192            Runtime.getRuntime().removeShutdownHook(shutdownThread);
193            shutdownHookRef.enqueue();
194        }
195    }
196
197    @Override
198    public State getState() {
199        return state.get();
200    }
201
202    /**
203     * Indicates if this can accept shutdown hooks.
204     *
205     * @return true if this can accept shutdown hooks
206     */
207    @Override
208    public boolean isStarted() {
209        return state.get() == State.STARTED;
210    }
211
212    @Override
213    public boolean isStopped() {
214        return state.get() == State.STOPPED;
215    }
216
217}