View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  
18  package org.apache.logging.log4j.core.async;
19  
20  import java.util.Locale;
21  import java.util.concurrent.Callable;
22  import java.util.concurrent.ExecutorService;
23  import java.util.concurrent.Future;
24  import java.util.concurrent.TimeUnit;
25  
26  import com.lmax.disruptor.BlockingWaitStrategy;
27  import com.lmax.disruptor.BusySpinWaitStrategy;
28  import com.lmax.disruptor.ExceptionHandler;
29  import com.lmax.disruptor.SleepingWaitStrategy;
30  import com.lmax.disruptor.TimeoutBlockingWaitStrategy;
31  import com.lmax.disruptor.WaitStrategy;
32  import com.lmax.disruptor.YieldingWaitStrategy;
33  import org.apache.logging.log4j.Logger;
34  import org.apache.logging.log4j.core.util.Constants;
35  import org.apache.logging.log4j.core.util.Integers;
36  import org.apache.logging.log4j.core.util.Loader;
37  import org.apache.logging.log4j.status.StatusLogger;
38  import org.apache.logging.log4j.util.PropertiesUtil;
39  
40  /**
41   * Utility methods for getting Disruptor related configuration.
42   */
43  final class DisruptorUtil {
44      private static final Logger LOGGER = StatusLogger.getLogger();
45      private static final int RINGBUFFER_MIN_SIZE = 128;
46      private static final int RINGBUFFER_DEFAULT_SIZE = 256 * 1024;
47      private static final int RINGBUFFER_NO_GC_DEFAULT_SIZE = 4 * 1024;
48  
49      /**
50       * LOG4J2-2606: Users encountered excessive CPU utilization with Disruptor v3.4.2 when the application
51       * was logging more than the underlying appender could keep up with and the ringbuffer became full,
52       * especially when the number of application threads vastly outnumbered the number of cores.
53       * CPU utilization is significantly reduced by restricting access to the enqueue operation.
54       */
55      static final boolean ASYNC_LOGGER_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL = PropertiesUtil.getProperties()
56              .getBooleanProperty("AsyncLogger.SynchronizeEnqueueWhenQueueFull", true);
57      static final boolean ASYNC_CONFIG_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL = PropertiesUtil.getProperties()
58              .getBooleanProperty("AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull", true);
59  
60      private DisruptorUtil() {
61      }
62  
63      static long getTimeout(final String propertyName, final long defaultTimeout) {
64          return PropertiesUtil.getProperties().getLongProperty(propertyName, defaultTimeout);
65      }
66  
67      static WaitStrategy createWaitStrategy(final String propertyName) {
68          final String key = propertyName.startsWith("AsyncLogger.")
69                  ? "AsyncLogger.Timeout"
70                  : "AsyncLoggerConfig.Timeout";
71          final long timeoutMillis = DisruptorUtil.getTimeout(key, 10L);
72          return createWaitStrategy(propertyName, timeoutMillis);
73      }
74  
75      static WaitStrategy createWaitStrategy(final String propertyName, final long timeoutMillis) {
76          final String strategy = PropertiesUtil.getProperties().getStringProperty(propertyName, "TIMEOUT");
77          LOGGER.trace("property {}={}", propertyName, strategy);
78          final String strategyUp = strategy.toUpperCase(Locale.ROOT); // TODO Refactor into Strings.toRootUpperCase(String)
79          switch (strategyUp) { // TODO Define a DisruptorWaitStrategy enum?
80          case "SLEEP":
81              return new SleepingWaitStrategy();
82          case "YIELD":
83              return new YieldingWaitStrategy();
84          case "BLOCK":
85              return new BlockingWaitStrategy();
86          case "BUSYSPIN":
87              return new BusySpinWaitStrategy();
88          case "TIMEOUT":
89              return new TimeoutBlockingWaitStrategy(timeoutMillis, TimeUnit.MILLISECONDS);
90          default:
91              return new TimeoutBlockingWaitStrategy(timeoutMillis, TimeUnit.MILLISECONDS);
92          }
93      }
94  
95      static int calculateRingBufferSize(final String propertyName) {
96          int ringBufferSize = Constants.ENABLE_THREADLOCALS ? RINGBUFFER_NO_GC_DEFAULT_SIZE : RINGBUFFER_DEFAULT_SIZE;
97          final String userPreferredRBSize = PropertiesUtil.getProperties().getStringProperty(propertyName,
98                  String.valueOf(ringBufferSize));
99          try {
100             int size = Integer.parseInt(userPreferredRBSize);
101             if (size < RINGBUFFER_MIN_SIZE) {
102                 size = RINGBUFFER_MIN_SIZE;
103                 LOGGER.warn("Invalid RingBufferSize {}, using minimum size {}.", userPreferredRBSize,
104                         RINGBUFFER_MIN_SIZE);
105             }
106             ringBufferSize = size;
107         } catch (final Exception ex) {
108             LOGGER.warn("Invalid RingBufferSize {}, using default size {}.", userPreferredRBSize, ringBufferSize);
109         }
110         return Integers.ceilingNextPowerOfTwo(ringBufferSize);
111     }
112 
113     static ExceptionHandler<RingBufferLogEvent> getAsyncLoggerExceptionHandler() {
114         final String cls = PropertiesUtil.getProperties().getStringProperty("AsyncLogger.ExceptionHandler");
115         if (cls == null) {
116             return new AsyncLoggerDefaultExceptionHandler();
117         }
118         try {
119             @SuppressWarnings("unchecked")
120             final Class<? extends ExceptionHandler<RingBufferLogEvent>> klass =
121                 (Class<? extends ExceptionHandler<RingBufferLogEvent>>) Loader.loadClass(cls);
122             return klass.newInstance();
123         } catch (final Exception ignored) {
124             LOGGER.debug("Invalid AsyncLogger.ExceptionHandler value: error creating {}: ", cls, ignored);
125             return new AsyncLoggerDefaultExceptionHandler();
126         }
127     }
128 
129     static ExceptionHandler<AsyncLoggerConfigDisruptor.Log4jEventWrapper> getAsyncLoggerConfigExceptionHandler() {
130         final String cls = PropertiesUtil.getProperties().getStringProperty("AsyncLoggerConfig.ExceptionHandler");
131         if (cls == null) {
132             return new AsyncLoggerConfigDefaultExceptionHandler();
133         }
134         try {
135             @SuppressWarnings("unchecked")
136             final Class<? extends ExceptionHandler<AsyncLoggerConfigDisruptor.Log4jEventWrapper>> klass =
137                     (Class<? extends ExceptionHandler<AsyncLoggerConfigDisruptor.Log4jEventWrapper>>) Loader.loadClass(cls);
138             return klass.newInstance();
139         } catch (final Exception ignored) {
140             LOGGER.debug("Invalid AsyncLoggerConfig.ExceptionHandler value: error creating {}: ", cls, ignored);
141             return new AsyncLoggerConfigDefaultExceptionHandler();
142         }
143     }
144 
145     /**
146      * Returns the thread ID of the background appender thread. This allows us to detect Logger.log() calls initiated
147      * from the appender thread, which may cause deadlock when the RingBuffer is full. (LOG4J2-471)
148      *
149      * @param executor runs the appender thread
150      * @return the thread ID of the background appender thread
151      */
152     public static long getExecutorThreadId(final ExecutorService executor) {
153         final Future<Long> result = executor.submit(new Callable<Long>() {
154             @Override
155             public Long call() {
156                 return Thread.currentThread().getId();
157             }
158         });
159         try {
160             return result.get();
161         } catch (final Exception ex) {
162             final String msg = "Could not obtain executor thread Id. "
163                     + "Giving up to avoid the risk of application deadlock.";
164             throw new IllegalStateException(msg, ex);
165         }
166     }
167 }