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.config;
019
020import java.util.Objects;
021import java.util.concurrent.TimeUnit;
022import java.util.concurrent.atomic.AtomicBoolean;
023import java.util.concurrent.atomic.AtomicInteger;
024import java.util.concurrent.locks.Condition;
025import java.util.concurrent.locks.Lock;
026import java.util.concurrent.locks.ReentrantLock;
027
028import org.apache.logging.log4j.Level;
029import org.apache.logging.log4j.Marker;
030import org.apache.logging.log4j.core.LogEvent;
031import org.apache.logging.log4j.message.Message;
032import org.apache.logging.log4j.util.Supplier;
033
034/**
035 * ReliabilityStrategy that counts the number of threads that have started to log an event but have not completed yet,
036 * and waits for these threads to finish before allowing the appenders to be stopped.
037 */
038public class AwaitCompletionReliabilityStrategy implements ReliabilityStrategy {
039    private static final int MAX_RETRIES = 3;
040    private final AtomicInteger counter = new AtomicInteger();
041    private final AtomicBoolean shutdown = new AtomicBoolean(false);
042    private final Lock shutdownLock = new ReentrantLock();
043    private final Condition noLogEvents = shutdownLock.newCondition(); // should only be used when shutdown == true
044    private final LoggerConfig loggerConfig;
045
046    public AwaitCompletionReliabilityStrategy(final LoggerConfig loggerConfig) {
047        this.loggerConfig = Objects.requireNonNull(loggerConfig, "loggerConfig is null");
048    }
049
050    /*
051     * (non-Javadoc)
052     * 
053     * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier,
054     * java.lang.String, java.lang.String, org.apache.logging.log4j.Marker, org.apache.logging.log4j.Level,
055     * org.apache.logging.log4j.message.Message, java.lang.Throwable)
056     */
057    @Override
058    public void log(final Supplier<LoggerConfig> reconfigured, final String loggerName, final String fqcn,
059            final Marker marker, final Level level, final Message data, final Throwable t) {
060
061        final LoggerConfig config = getActiveLoggerConfig(reconfigured);
062        try {
063            config.log(loggerName, fqcn, marker, level, data, t);
064        } finally {
065            config.getReliabilityStrategy().afterLogEvent();
066        }
067    }
068
069    /*
070     * (non-Javadoc)
071     * 
072     * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier,
073     * org.apache.logging.log4j.core.LogEvent)
074     */
075    @Override
076    public void log(final Supplier<LoggerConfig> reconfigured, final LogEvent event) {
077        final LoggerConfig config = getActiveLoggerConfig(reconfigured);
078        try {
079            config.log(event);
080        } finally {
081            config.getReliabilityStrategy().afterLogEvent();
082        }
083    }
084
085    /*
086     * (non-Javadoc)
087     * 
088     * @see
089     * org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeLogEvent(org.apache.logging.log4j.core.config.
090     * LoggerConfig, org.apache.logging.log4j.util.Supplier)
091     */
092    @Override
093    public LoggerConfig getActiveLoggerConfig(final Supplier<LoggerConfig> next) {
094        LoggerConfig result = this.loggerConfig;
095        if (!beforeLogEvent()) {
096            result = next.get();
097            return result.getReliabilityStrategy().getActiveLoggerConfig(next);
098        }
099        return result;
100    }
101
102    private boolean beforeLogEvent() {
103        return counter.incrementAndGet() > 0;
104    }
105
106    @Override
107    public void afterLogEvent() {
108        if (counter.decrementAndGet() == 0 && shutdown.get()) {
109            signalCompletionIfShutdown();
110        }
111    }
112
113    private void signalCompletionIfShutdown() {
114        final Lock lock = shutdownLock;
115        lock.lock();
116        try {
117            noLogEvents.signalAll();
118        } finally {
119            lock.unlock();
120        }
121    }
122
123    /*
124     * (non-Javadoc)
125     * 
126     * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopAppenders()
127     */
128    @Override
129    public void beforeStopAppenders() {
130        waitForCompletion();
131    }
132
133    /**
134     * Waits for all log events to complete before returning.
135     */
136    private void waitForCompletion() {
137        shutdownLock.lock();
138        try {
139            if (shutdown.compareAndSet(false, true)) {
140                int retries = 0;
141                // repeat while counter is non-zero
142                while (!counter.compareAndSet(0, Integer.MIN_VALUE)) {
143
144                    // counter was non-zero
145                    if (counter.get() < 0) { // this should not happen
146                        return; // but if it does, we are already done
147                    }
148                    // counter greater than zero, wait for afterLogEvent to decrease count
149                    try {
150                        noLogEvents.await(retries + 1, TimeUnit.SECONDS);
151                    } catch (final InterruptedException ie) {
152                        if (++retries > MAX_RETRIES) {
153                            break;
154                        }
155                    }
156                }
157            }
158        } finally {
159            shutdownLock.unlock();
160        }
161    }
162
163    /*
164     * (non-Javadoc)
165     * 
166     * @see
167     * org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopConfiguration(org.apache.logging.log4j.core
168     * .config.Configuration)
169     */
170    @Override
171    public void beforeStopConfiguration(Configuration configuration) {
172        // no action
173    }
174
175}