/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache license, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the license for the specific language governing permissions and
 * limitations under the license.
 */

package org.apache.logging.log4j.core.async;

import java.util.Locale;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.BusySpinWaitStrategy;
import com.lmax.disruptor.ExceptionHandler;
import com.lmax.disruptor.SleepingWaitStrategy;
import com.lmax.disruptor.TimeoutBlockingWaitStrategy;
import com.lmax.disruptor.WaitStrategy;
import com.lmax.disruptor.YieldingWaitStrategy;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.core.util.Integers;
import org.apache.logging.log4j.core.util.Loader;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.PropertiesUtil;

Utility methods for getting Disruptor related configuration.
/** * Utility methods for getting Disruptor related configuration. */
final class DisruptorUtil { private static final Logger LOGGER = StatusLogger.getLogger(); private static final int RINGBUFFER_MIN_SIZE = 128; private static final int RINGBUFFER_DEFAULT_SIZE = 256 * 1024; private static final int RINGBUFFER_NO_GC_DEFAULT_SIZE = 4 * 1024;
LOG4J2-2606: Users encountered excessive CPU utilization with Disruptor v3.4.2 when the application was logging more than the underlying appender could keep up with and the ringbuffer became full, especially when the number of application threads vastly outnumbered the number of cores. CPU utilization is significantly reduced by restricting access to the enqueue operation.
/** * LOG4J2-2606: Users encountered excessive CPU utilization with Disruptor v3.4.2 when the application * was logging more than the underlying appender could keep up with and the ringbuffer became full, * especially when the number of application threads vastly outnumbered the number of cores. * CPU utilization is significantly reduced by restricting access to the enqueue operation. */
static final boolean ASYNC_LOGGER_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL = PropertiesUtil.getProperties() .getBooleanProperty("AsyncLogger.SynchronizeEnqueueWhenQueueFull", true); static final boolean ASYNC_CONFIG_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL = PropertiesUtil.getProperties() .getBooleanProperty("AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull", true); private DisruptorUtil() { } static long getTimeout(final String propertyName, final long defaultTimeout) { return PropertiesUtil.getProperties().getLongProperty(propertyName, defaultTimeout); } static WaitStrategy createWaitStrategy(final String propertyName) { final String key = propertyName.startsWith("AsyncLogger.") ? "AsyncLogger.Timeout" : "AsyncLoggerConfig.Timeout"; final long timeoutMillis = DisruptorUtil.getTimeout(key, 10L); return createWaitStrategy(propertyName, timeoutMillis); } static WaitStrategy createWaitStrategy(final String propertyName, final long timeoutMillis) { final String strategy = PropertiesUtil.getProperties().getStringProperty(propertyName, "TIMEOUT"); LOGGER.trace("property {}={}", propertyName, strategy); final String strategyUp = strategy.toUpperCase(Locale.ROOT); // TODO Refactor into Strings.toRootUpperCase(String) switch (strategyUp) { // TODO Define a DisruptorWaitStrategy enum? case "SLEEP": return new SleepingWaitStrategy(); case "YIELD": return new YieldingWaitStrategy(); case "BLOCK": return new BlockingWaitStrategy(); case "BUSYSPIN": return new BusySpinWaitStrategy(); case "TIMEOUT": return new TimeoutBlockingWaitStrategy(timeoutMillis, TimeUnit.MILLISECONDS); default: return new TimeoutBlockingWaitStrategy(timeoutMillis, TimeUnit.MILLISECONDS); } } static int calculateRingBufferSize(final String propertyName) { int ringBufferSize = Constants.ENABLE_THREADLOCALS ? RINGBUFFER_NO_GC_DEFAULT_SIZE : RINGBUFFER_DEFAULT_SIZE; final String userPreferredRBSize = PropertiesUtil.getProperties().getStringProperty(propertyName, String.valueOf(ringBufferSize)); try { int size = Integer.parseInt(userPreferredRBSize); if (size < RINGBUFFER_MIN_SIZE) { size = RINGBUFFER_MIN_SIZE; LOGGER.warn("Invalid RingBufferSize {}, using minimum size {}.", userPreferredRBSize, RINGBUFFER_MIN_SIZE); } ringBufferSize = size; } catch (final Exception ex) { LOGGER.warn("Invalid RingBufferSize {}, using default size {}.", userPreferredRBSize, ringBufferSize); } return Integers.ceilingNextPowerOfTwo(ringBufferSize); } static ExceptionHandler<RingBufferLogEvent> getAsyncLoggerExceptionHandler() { final String cls = PropertiesUtil.getProperties().getStringProperty("AsyncLogger.ExceptionHandler"); if (cls == null) { return new AsyncLoggerDefaultExceptionHandler(); } try { @SuppressWarnings("unchecked") final Class<? extends ExceptionHandler<RingBufferLogEvent>> klass = (Class<? extends ExceptionHandler<RingBufferLogEvent>>) Loader.loadClass(cls); return klass.newInstance(); } catch (final Exception ignored) { LOGGER.debug("Invalid AsyncLogger.ExceptionHandler value: error creating {}: ", cls, ignored); return new AsyncLoggerDefaultExceptionHandler(); } } static ExceptionHandler<AsyncLoggerConfigDisruptor.Log4jEventWrapper> getAsyncLoggerConfigExceptionHandler() { final String cls = PropertiesUtil.getProperties().getStringProperty("AsyncLoggerConfig.ExceptionHandler"); if (cls == null) { return new AsyncLoggerConfigDefaultExceptionHandler(); } try { @SuppressWarnings("unchecked") final Class<? extends ExceptionHandler<AsyncLoggerConfigDisruptor.Log4jEventWrapper>> klass = (Class<? extends ExceptionHandler<AsyncLoggerConfigDisruptor.Log4jEventWrapper>>) Loader.loadClass(cls); return klass.newInstance(); } catch (final Exception ignored) { LOGGER.debug("Invalid AsyncLoggerConfig.ExceptionHandler value: error creating {}: ", cls, ignored); return new AsyncLoggerConfigDefaultExceptionHandler(); } }
Returns the thread ID of the background appender thread. This allows us to detect Logger.log() calls initiated from the appender thread, which may cause deadlock when the RingBuffer is full. (LOG4J2-471)
Params:
  • executor – runs the appender thread
Returns:the thread ID of the background appender thread
/** * Returns the thread ID of the background appender thread. This allows us to detect Logger.log() calls initiated * from the appender thread, which may cause deadlock when the RingBuffer is full. (LOG4J2-471) * * @param executor runs the appender thread * @return the thread ID of the background appender thread */
public static long getExecutorThreadId(final ExecutorService executor) { final Future<Long> result = executor.submit(new Callable<Long>() { @Override public Long call() { return Thread.currentThread().getId(); } }); try { return result.get(); } catch (final Exception ex) { final String msg = "Could not obtain executor thread Id. " + "Giving up to avoid the risk of application deadlock."; throw new IllegalStateException(msg, ex); } } }