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, LocationAwareReliabilityStrategy {
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     * java.lang.String, java.lang.String, java.lang.StackTraceElement, org.apache.logging.log4j.Marker,
074     * org.apache.logging.log4j.Level, org.apache.logging.log4j.message.Message, java.lang.Throwable)
075     */
076    @Override
077    public void log(final Supplier<LoggerConfig> reconfigured, final String loggerName, final String fqcn,
078        final StackTraceElement location, final Marker marker, final Level level, final Message data,
079        final Throwable t) {
080        final LoggerConfig config = getActiveLoggerConfig(reconfigured);
081        try {
082            config.log(loggerName, fqcn, location, marker, level, data, t);
083        } finally {
084            config.getReliabilityStrategy().afterLogEvent();
085        }
086    }
087
088    /*
089     * (non-Javadoc)
090     *
091     * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier,
092     * org.apache.logging.log4j.core.LogEvent)
093     */
094    @Override
095    public void log(final Supplier<LoggerConfig> reconfigured, final LogEvent event) {
096        final LoggerConfig config = getActiveLoggerConfig(reconfigured);
097        try {
098            config.log(event);
099        } finally {
100            config.getReliabilityStrategy().afterLogEvent();
101        }
102    }
103
104    /*
105     * (non-Javadoc)
106     *
107     * @see
108     * org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeLogEvent(org.apache.logging.log4j.core.config.
109     * LoggerConfig, org.apache.logging.log4j.util.Supplier)
110     */
111    @Override
112    public LoggerConfig getActiveLoggerConfig(final Supplier<LoggerConfig> next) {
113        LoggerConfig result = this.loggerConfig;
114        if (!beforeLogEvent()) {
115            result = next.get();
116            return result == this.loggerConfig ? result : result.getReliabilityStrategy().getActiveLoggerConfig(next);
117        }
118        return result;
119    }
120
121    private boolean beforeLogEvent() {
122        return counter.incrementAndGet() > 0;
123    }
124
125    @Override
126    public void afterLogEvent() {
127        if (counter.decrementAndGet() == 0 && shutdown.get()) {
128            signalCompletionIfShutdown();
129        }
130    }
131
132    private void signalCompletionIfShutdown() {
133        final Lock lock = shutdownLock;
134        lock.lock();
135        try {
136            noLogEvents.signalAll();
137        } finally {
138            lock.unlock();
139        }
140    }
141
142    /*
143     * (non-Javadoc)
144     *
145     * @see org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopAppenders()
146     */
147    @Override
148    public void beforeStopAppenders() {
149        waitForCompletion();
150    }
151
152    /**
153     * Waits for all log events to complete before returning.
154     */
155    private void waitForCompletion() {
156        shutdownLock.lock();
157        try {
158            if (shutdown.compareAndSet(false, true)) {
159                int retries = 0;
160                // repeat while counter is non-zero
161                while (!counter.compareAndSet(0, Integer.MIN_VALUE)) {
162
163                    // counter was non-zero
164                    if (counter.get() < 0) { // this should not happen
165                        return; // but if it does, we are already done
166                    }
167                    // counter greater than zero, wait for afterLogEvent to decrease count
168                    try {
169                        noLogEvents.await(retries + 1, TimeUnit.SECONDS);
170                    } catch (final InterruptedException ie) {
171                        if (++retries > MAX_RETRIES) {
172                            break;
173                        }
174                    }
175                }
176            }
177        } finally {
178            shutdownLock.unlock();
179        }
180    }
181
182    /*
183     * (non-Javadoc)
184     *
185     * @see
186     * org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopConfiguration(org.apache.logging.log4j.core
187     * .config.Configuration)
188     */
189    @Override
190    public void beforeStopConfiguration(final Configuration configuration) {
191        // no action
192    }
193
194}