Index: pom.xml =================================================================== --- pom.xml (revision 1458654) +++ pom.xml (working copy) @@ -115,8 +115,8 @@ 2.9 2.12.4 target/osgi/MANIFEST.MF - 1.5 - 1.5 + 1.6 + 1.6 Site Documentation @@ -556,6 +556,7 @@ flume-ng web samples + log4j-async Index: core/src/main/java/org/apache/logging/log4j/core/LogEvent.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/LogEvent.java (revision 1458654) +++ core/src/main/java/org/apache/logging/log4j/core/LogEvent.java (working copy) @@ -104,4 +104,47 @@ */ String getFQCN(); + /** + * Returns whether the source of the logging request is required downstream. + * Asynchronous Loggers and Appenders use this flag to determine whether + * to take a {@code StackTrace} snapshot or not before handing off this + * event to another thread. + * @return {@code true} if the source of the logging request is required + * downstream, {@code false} otherwise. + * @see #getSource() + */ + // see also LOG4J2-153 + boolean isLocationRequired(); + + /** + * Sets whether the source of the logging request is required downstream. + * Asynchronous Loggers and Appenders use this flag to determine whether + * to take a {@code StackTrace} snapshot or not before handing off this + * event to another thread. + * @param locationRequired {@code true} if the source of the logging request + * is required downstream, {@code false} otherwise. + * @see #getSource() + */ + void setLocationRequired(boolean locationRequired); + + /** + * Returns {@code true} if this event is the last one in a batch, + * {@code false} otherwise. Used by asynchronous Loggers and Appenders to + * signal to buffered downstream components when to flush to disk, as a + * more efficient alternative to the {@code immediateFlush=true} + * configuration. + * @return whether this event is the last one in a batch. + */ + // see also LOG4J2-164 + boolean isEndOfBatch(); + + /** + * Sets whether this event is the last one in a batch. + * Used by asynchronous Loggers and Appenders to signal to buffered + * downstream components when to flush to disk, as a more efficient + * alternative to the {@code immediateFlush=true} configuration. + * @param endOfBatch {@code true} if this event is the last one in a batch, + * {@code false} otherwise. + */ + void setEndOfBatch(boolean endOfBatch); } Index: core/src/main/java/org/apache/logging/log4j/core/Logger.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/Logger.java (revision 1458654) +++ core/src/main/java/org/apache/logging/log4j/core/Logger.java (working copy) @@ -234,8 +234,9 @@ * The binding between a Logger and its configuration. */ protected class PrivateConfig { - private final LoggerConfig loggerConfig; - private final Configuration config; + // config fields are public to make them visible to Logger subclasses + public final LoggerConfig loggerConfig; + public final Configuration config; private final Level level; private final int intLevel; private final Logger logger; @@ -264,7 +265,8 @@ this.logger = pc.logger; } - protected void logEvent(final LogEvent event) { + // LOG4J2-151: changed visibility to public + public void logEvent(LogEvent event) { config.getConfigurationMonitor().checkConfiguration(); loggerConfig.log(event); } Index: core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java (revision 1458654) +++ core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java (working copy) @@ -326,8 +326,8 @@ } } - - private Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) { + // LOG4J2-151: changed visibility from private to protected + protected Logger newInstance(final LoggerContext ctx, final String name, final MessageFactory messageFactory) { return new Logger(ctx, name, messageFactory); } Index: core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java (revision 1458654) +++ core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java (working copy) @@ -28,6 +28,7 @@ import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.LogEventFactory; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginAttr; import org.apache.logging.log4j.core.config.plugins.PluginFactory; @@ -38,6 +39,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -61,6 +63,7 @@ private LogEventFactory logEventFactory; private Level level; private boolean additive = true; + private boolean isLocationRequired = true; private LoggerConfig parent; private final AtomicInteger counter = new AtomicInteger(); private boolean shutdown = false; @@ -94,14 +97,17 @@ this.config = null; } - protected LoggerConfig(final String name, final List appenders, final Filter filter, final Level level, - final boolean additive, final Property[] properties, final Configuration config) { + protected LoggerConfig(final String name, final List appenders, + final Filter filter, final Level level, final boolean additive, + final Property[] properties, final Configuration config, + boolean isLocationRequired) { super(filter); this.logEventFactory = this; this.name = name; this.appenderRefs = appenders; this.level = level; this.additive = additive; + this.isLocationRequired = isLocationRequired; this.config = config; if (properties != null && properties.length > 0) { this.properties = new HashMap(properties.length); @@ -250,11 +256,34 @@ /** * Sets the additive setting. - * @param additive true if thee LoggerConfig should be additive, false otherwise. + * @param additive true if the LoggerConfig should be additive, false otherwise. */ public void setAdditive(final boolean additive) { this.additive = additive; } + + public boolean isLocationRequired() { + return isLocationRequired; + } + + /** + * Returns an unmodifiable map with the configuration properties, or + * {@code null} if this {@code LoggerConfig} does not have any configuration + * properties. + *

+ * For each {@code Property} key in the map, the value is {@code true} if + * the property value has a variable that needs to be substituted. + * + * @return an unmodifiable map with the configuration properties, or + * {@code null} + * @see Configuration#getSubst() + * @see StrSubstitutor + */ + // LOG4J2-157 + public Map getProperties() { + return properties == null ? null : + Collections.unmodifiableMap(properties); + } /** * Logs an event. @@ -313,6 +342,8 @@ return; } + event.setLocationRequired(isLocationRequired); + callAppenders(event); if (additive && parent != null) { @@ -330,7 +361,7 @@ } } - private void callAppenders(final LogEvent event) { + protected void callAppenders(final LogEvent event) { for (final AppenderControl control : appenders.values()) { control.callAppender(event); } @@ -372,6 +403,7 @@ public static LoggerConfig createLogger(@PluginAttr("additivity") final String additivity, @PluginAttr("level") final String levelName, @PluginAttr("name") final String loggerName, + @PluginAttr("needsLocation") final String needsLocation, @PluginElement("appender-ref") final AppenderRef[] refs, @PluginElement("properties") final Property[] properties, @PluginConfiguration final Configuration config, @@ -391,8 +423,11 @@ } final String name = loggerName.equals("root") ? "" : loggerName; final boolean additive = additivity == null ? true : Boolean.parseBoolean(additivity); + final boolean isLocationRequired = needsLocation == null ? true + : Boolean.parseBoolean(needsLocation); - return new LoggerConfig(name, appenderRefs, filter, level, additive, properties, config); + return new LoggerConfig(name, appenderRefs, filter, level, additive, + properties, config, isLocationRequired); } /** @@ -404,6 +439,7 @@ @PluginFactory public static LoggerConfig createLogger(@PluginAttr("additivity") final String additivity, @PluginAttr("level") final String levelName, + @PluginAttr("needsLocation") final String needsLocation, @PluginElement("appender-ref") final AppenderRef[] refs, @PluginElement("properties") final Property[] properties, @PluginConfiguration final Configuration config, @@ -417,9 +453,11 @@ level = Level.ERROR; } final boolean additive = additivity == null ? true : Boolean.parseBoolean(additivity); + final boolean isLocationRequired = needsLocation == null ? true + : Boolean.parseBoolean(needsLocation); - return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, filter, level, additive, properties, - config); + return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, + filter, level, additive, properties, config, isLocationRequired); } } Index: core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java (revision 1458654) +++ core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java (working copy) @@ -49,6 +49,8 @@ private final ThreadContext.ContextStack ndc; private String threadName = null; private StackTraceElement location; + private boolean locationRequired; + private boolean endOfBatch = false; /** * Constructor. @@ -222,33 +224,56 @@ * @return the StackTraceElement for the caller. */ public StackTraceElement getSource() { + if (location != null) { + return location; + } + if (fqcnOfLogger == null || !locationRequired) { + return null; + } + location = calcLocation(fqcnOfLogger); + return location; + } + + public static StackTraceElement calcLocation(String fqcnOfLogger) { if (fqcnOfLogger == null) { return null; } - if (location == null) { - final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); - boolean next = false; - for (final StackTraceElement element : stackTrace) { - final String className = element.getClassName(); - if (next) { - if (fqcnOfLogger.equals(className)) { - continue; - } - location = element; - break; - } - + final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + boolean next = false; + for (final StackTraceElement element : stackTrace) { + final String className = element.getClassName(); + if (next) { if (fqcnOfLogger.equals(className)) { - next = true; - } else if (NOT_AVAIL.equals(className)) { - break; + continue; } + return element; } + + if (fqcnOfLogger.equals(className)) { + next = true; + } else if (NOT_AVAIL.equals(className)) { + break; + } } + return null; + } - return location; + public boolean isLocationRequired() { + return locationRequired; } + public void setLocationRequired(boolean locationRequired) { + this.locationRequired = locationRequired; + } + + public boolean isEndOfBatch() { + return endOfBatch; + } + + public void setEndOfBatch(boolean endOfBatch) { + this.endOfBatch = endOfBatch; + } + /** * Creates a LogEventProxy that can be serialized. * @return a LogEventProxy. @@ -267,8 +292,13 @@ } if (event instanceof LogEventProxy) { final LogEventProxy proxy = (LogEventProxy) event; - return new Log4jLogEvent(proxy.name, proxy.marker, proxy.fqcnOfLogger, proxy.level, proxy.message, - proxy.throwable, proxy.mdc, proxy.ndc, proxy.threadName, proxy.location, proxy.timestamp); + Log4jLogEvent result = new Log4jLogEvent(proxy.name, proxy.marker, + proxy.fqcnOfLogger, proxy.level, proxy.message, + proxy.throwable, proxy.mdc, proxy.ndc, proxy.threadName, + proxy.location, proxy.timestamp); + result.setEndOfBatch(proxy.isEndOfBatch); + result.setLocationRequired(proxy.isLocationRequired); + return result; } throw new IllegalArgumentException("Event is not a serialized LogEvent: " + event.toString()); } @@ -304,6 +334,8 @@ private final ThreadContext.ContextStack ndc; private final String threadName; private final StackTraceElement location; + private final boolean isLocationRequired; + private final boolean isEndOfBatch; public LogEventProxy(final Log4jLogEvent event) { this.fqcnOfLogger = event.fqcnOfLogger; @@ -317,6 +349,8 @@ this.ndc = event.ndc; this.location = event.getSource(); this.threadName = event.getThreadName(); + this.isLocationRequired = event.locationRequired; + this.isEndOfBatch = event.endOfBatch; } /** @@ -324,10 +358,12 @@ * @return Log4jLogEvent. */ protected Object readResolve() { - return new Log4jLogEvent(name, marker, fqcnOfLogger, level, message, throwable, mdc, ndc, threadName, - location, timestamp); + Log4jLogEvent result = new Log4jLogEvent(name, marker, fqcnOfLogger, + level, message, throwable, mdc, ndc, threadName, location, + timestamp); + result.setEndOfBatch(isEndOfBatch); + result.setLocationRequired(isLocationRequired); + return result; } - } - } Index: core/src/test/java/org/apache/logging/log4j/core/lookup/DateLookupTest.java =================================================================== --- core/src/test/java/org/apache/logging/log4j/core/lookup/DateLookupTest.java (revision 1458654) +++ core/src/test/java/org/apache/logging/log4j/core/lookup/DateLookupTest.java (working copy) @@ -94,5 +94,19 @@ public String getFQCN() { return null; } + + public boolean isEndOfBatch() { + return false; + } + + public void setEndOfBatch(boolean endOfBatch) { + } + + public boolean isLocationRequired() { + return false; + } + + public void setLocationRequired(boolean locationRequired) { + } } } Index: flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeEvent.java =================================================================== --- flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeEvent.java (revision 1458654) +++ flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeEvent.java (working copy) @@ -277,4 +277,24 @@ public ThreadContext.ContextStack getContextStack() { return event.getContextStack(); } + + @Override + public boolean isLocationRequired() { + return event.isLocationRequired(); + } + + @Override + public void setLocationRequired(boolean locationRequired) { + event.setLocationRequired(locationRequired); + } + + @Override + public boolean isEndOfBatch() { + return event.isEndOfBatch(); + } + + @Override + public void setEndOfBatch(boolean endOfBatch) { + event.setEndOfBatch(endOfBatch); + } } Index: log4j-async/pom.xml =================================================================== --- log4j-async/pom.xml (revision 0) +++ log4j-async/pom.xml (working copy) @@ -0,0 +1,224 @@ + + + + 4.0.0 + + log4j + org.apache.logging.log4j + 2.0-beta5-SNAPSHOT + ../ + + + org.apache.logging.log4j + log4j-async + jar + Apache Log4j Async + Log4j 2.0 Asynchronous Loggers for Low Latency Logging + + UTF-8 + ${basedir}/.. + Asynchronous Logging Documentation + /log4j-async + + + + org.apache.logging.log4j + log4j-api + + + org.apache.logging.log4j + log4j-core + + + org.apache.logging.log4j + log4j-core + test-jar + test + + + junit + junit + test + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + always + + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + process-classes + + java + + + + + org.apache.logging.log4j.core.config.plugins.PluginManager + + ${project.build.outputDirectory} + org.apache.logging.log4j.async + + + + + + + + + org.apache.maven.plugins + maven-changes-plugin + ${changes.plugin.version} + + + + changes-report + + + + + %URL%/show_bug.cgi?id=%ISSUE% + true + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.7 + + + ${log4jParentDir}/checkstyle.xml + ${log4jParentDir}/checkstyle-suppressions.xml + false + basedir=${basedir} + licensedir=${log4jParentDir}/checkstyle-header.txt + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${javadoc.plugin.version} + + Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved. Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, and the + Apache Logging project logo are trademarks of The Apache Software Foundation. + + false + true + + + issue + a + JIRA issue: + + + doubt + a + Troublesome: + + + compare + a + Compare with: + + + + + + non-aggregate + + javadoc + + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 2.3 + + + non-aggregate + + jxr + + + + aggregate + + aggregate + + + + + + org.apache.maven.plugins + maven-pmd-plugin + ${pmd.plugin.version} + + 1.5 + + + + org.codehaus.mojo + cobertura-maven-plugin + 2.2 + + + + + + + + + + + \ No newline at end of file Index: log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLogger.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLogger.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLogger.java (working copy) @@ -0,0 +1,237 @@ +/* + * 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.async; + +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.async.com.lmax.disruptor.BlockingWaitStrategy; +import org.apache.logging.log4j.async.com.lmax.disruptor.EventHandler; +import org.apache.logging.log4j.async.com.lmax.disruptor.ExceptionHandler; +import org.apache.logging.log4j.async.com.lmax.disruptor.RingBuffer; +import org.apache.logging.log4j.async.com.lmax.disruptor.SleepingWaitStrategy; +import org.apache.logging.log4j.async.com.lmax.disruptor.WaitStrategy; +import org.apache.logging.log4j.async.com.lmax.disruptor.YieldingWaitStrategy; +import org.apache.logging.log4j.async.com.lmax.disruptor.dsl.Disruptor; +import org.apache.logging.log4j.async.com.lmax.disruptor.dsl.ProducerType; +import org.apache.logging.log4j.async.com.lmax.disruptor.util.Util; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * AsyncLogger is a logger designed for high throughput and low latency logging. + * It does not perform any I/O in the calling (application) thread, but instead + * hands off the work to another thread as soon as possible. The actual logging + * is performed in the background thread. It uses the LMAX Disruptor library for + * inter-thread communication. (http://lmax-exchange.github.com/disruptor/) + *

+ * To use AsyncLogger, specify the System property + * -DLog4jContextSelector=log4j.async.AsyncLoggerContextSelector before you + * obtain a Logger, and all Loggers returned by LogManager.getLogger will be + * AsyncLoggers. + *

+ * Note that for performance reasons, this logger does not support source + * location. Any %class, %location or %line conversion patterns in your + * log4j.xml configuration will produce either a "?" character or no output at + * all. + *

+ * For best performance, use AsyncLogger with the FastFileAppender or + * FastRollingFileAppender, with immediateFlush=false. These appenders have + * built-in support for the batching mechanism used by the Disruptor library, + * and they will flush to disk at the end of each batch. This means that even + * with immediateFlush=false, there will never be any items left in the buffer; + * all log events will all be written to disk in a very efficient manner. + * + * @author Remko Popma + */ +public class AsyncLogger extends Logger { // depends on LOG4J2-151 + private static final StatusLogger LOGGER = StatusLogger.getLogger(); + private static volatile Disruptor disruptor; + private static Clock clock = ClockFactory.getClock(); + + private static ExecutorService executor = Executors + .newSingleThreadExecutor(); + private ThreadLocal threadlocalInfo = new ThreadLocal(); + + static { + int ringBufferSize = calculateRingBufferSize(); + + WaitStrategy waitStrategy = createWaitStrategy(); + disruptor = new Disruptor( + RingBufferLogEvent.FACTORY, ringBufferSize, executor, + ProducerType.MULTI, waitStrategy); + EventHandler[] handlers = new RingBufferLogEventHandler[] { new RingBufferLogEventHandler() }; + disruptor.handleExceptionsWith(getExceptionHandler()); + disruptor.handleEventsWith(handlers); + + LOGGER.debug( + "Starting AsyncLogger disruptor with ringbuffer size {}...", + disruptor.getRingBuffer().getBufferSize()); + disruptor.start(); + } + + private static int calculateRingBufferSize() { + String userPreferredRBSize = System.getProperty( + "AsyncLogger.RingBufferSize", "256000"); + int ringBufferSize = 256000; // default + try { + int size = Integer.parseInt(userPreferredRBSize); + if (size < 128) { + size = 128; + LOGGER.warn( + "Invalid RingBufferSize {}, using minimum size 128.", + userPreferredRBSize); + } + ringBufferSize = size; + } catch (Exception ex) { + LOGGER.warn("Invalid RingBufferSize {}, using default size.", + userPreferredRBSize); + } + return Util.ceilingNextPowerOfTwo(ringBufferSize); + } + + private static WaitStrategy createWaitStrategy() { + String strategy = System.getProperty("AsyncLogger.WaitStrategy"); + LOGGER.debug("property AsyncLogger.WaitStrategy={}", strategy); + if ("Sleep".equals(strategy)) { + LOGGER.debug("disruptor event handler uses SleepingWaitStrategy"); + return new SleepingWaitStrategy(); + } else if ("Yield".equals(strategy)) { + LOGGER.debug("disruptor event handler uses YieldingWaitStrategy"); + return new YieldingWaitStrategy(); + } else if ("Block".equals(strategy)) { + LOGGER.debug("disruptor event handler uses BlockingWaitStrategy"); + return new BlockingWaitStrategy(); + } + LOGGER.debug("disruptor event handler uses SleepingWaitStrategy"); + return new SleepingWaitStrategy(); + } + + private static ExceptionHandler getExceptionHandler() { + String cls = System.getProperty("AsyncLogger.ExceptionHandler"); + if (cls == null) { + return null; + } + try { + @SuppressWarnings("unchecked") + Class klass = (Class) Class + .forName(cls); + return klass.newInstance(); + } catch (Exception ignored) { + return null; + } + } + + private static class Info { + RingBufferLogEventTranslator translator; + String cachedThreadName; + } + + public AsyncLogger(LoggerContext context, String name, + MessageFactory messageFactory) { + super(context, name, messageFactory); + } + + @Override + public void log(Marker marker, String fqcn, Level level, Message data, + Throwable t) { + Info info = threadlocalInfo.get(); + if (info == null) { + info = new Info(); + info.translator = new RingBufferLogEventTranslator(); + info.cachedThreadName = Thread.currentThread().getName(); + threadlocalInfo.set(info); + } + + info.translator.setValues(this, getName(), marker, fqcn, level, data, + t, // + + // config properties are taken care of in the EventHandler + // thread in the #actualAsyncLog method + + // needs shallow copy to be fast (LOG4J2-154) + ThreadContext.getImmutableContext(),// + + // needs shallow copy to be fast (LOG4J2-154) + ThreadContext.getImmutableStack(), // + + // Thread.currentThread().getName(), // + info.cachedThreadName, // + + // location: very expensive without LOG4J2-153 + config.loggerConfig.isLocationRequired() ? location(fqcn) + : null, + + // System.currentTimeMillis()); + // CoarseCachedClock: 20% faster than system clock, 16ms gaps + // CachedClock: 10% faster than system clock, smaller gaps + clock.currentTimeMillis()); + + disruptor.publishEvent(info.translator); + } + + private StackTraceElement location(String fqcnOfLogger) { + return Log4jLogEvent.calcLocation(fqcnOfLogger); + } + + /** + * This method is called by the EventHandler that processes the + * RingBufferLogEvent in a separate thread. + * + * @param event the event to log + */ + public void actualAsyncLog(RingBufferLogEvent event) { + Map properties = config.loggerConfig.getProperties(); + event.mergePropertiesIntoContextMap(properties, + config.config.getSubst()); + config.logEvent(event); + } + + public static void stop() { + Disruptor temp = disruptor; + + // Must guarantee that publishing to the RingBuffer has stopped + // before we call disruptor.shutdown() + disruptor = null; // client code fails with NPE if log after stop = OK + temp.shutdown(); + + // wait up to 10 seconds for the ringbuffer to drain + RingBuffer ringBuffer = temp.getRingBuffer(); + for (int i = 0; i < 20; i++) { + if (ringBuffer.hasAvailableCapacity(ringBuffer.getBufferSize())) { + break; + } + try { + Thread.sleep(500); // give ringbuffer some time to drain... + } catch (InterruptedException e) { + } + } + executor.shutdown(); // finally, kill the processor thread + } + +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLoggerContext.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLoggerContext.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLoggerContext.java (working copy) @@ -0,0 +1,57 @@ +/* + * 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.async; + +import java.net.URI; + +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.message.MessageFactory; + +// depends on LOG4J2-151 +public class AsyncLoggerContext extends LoggerContext { + + public AsyncLoggerContext(String name) { + super(name); + } + + public AsyncLoggerContext(String name, Object externalContext) { + super(name, externalContext); + } + + public AsyncLoggerContext(String name, Object externalContext, + URI configLocn) { + super(name, externalContext, configLocn); + } + + public AsyncLoggerContext(String name, Object externalContext, + String configLocn) { + super(name, externalContext, configLocn); + } + + // @Override + protected Logger newInstance(LoggerContext ctx, String name, + MessageFactory messageFactory) { + return new AsyncLogger(ctx, name, messageFactory); + } + + @Override + public void stop() { + AsyncLogger.stop(); + super.stop(); + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLoggerContextSelector.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLoggerContextSelector.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/AsyncLoggerContextSelector.java (working copy) @@ -0,0 +1,48 @@ +/* + * 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.async; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.selector.ContextSelector; + +public class AsyncLoggerContextSelector implements ContextSelector { + + private static final AsyncLoggerContext context = new AsyncLoggerContext( + "Default"); + + public LoggerContext getContext(String fqcn, ClassLoader loader, + boolean currentContext) { + return context; + } + + public List getLoggerContexts() { + List list = new ArrayList(); + list.add(context); + return Collections.unmodifiableList(list); + } + + public LoggerContext getContext(String fqcn, ClassLoader loader, + boolean currentContext, URI configLocation) { + return context; + } + +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/CachedClock.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/CachedClock.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/CachedClock.java (working copy) @@ -0,0 +1,55 @@ +/* + * 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.async; + +import org.apache.logging.log4j.async.com.lmax.disruptor.util.Util; + +public class CachedClock implements Clock { + private static CachedClock instance = new CachedClock(); + private volatile long millis = System.currentTimeMillis(); + private volatile short count = 0; + private final Thread updater = new Thread("Clock Updater Thread") { + public void run() { + while (true) { + long time = System.currentTimeMillis(); + millis = time; + Util.getUnsafe().park(true, time + 1); // abs (millis) + // Util.getUnsafe().park(false, 1000 * 1000);// relative(nanos) + } + } + }; + + public static CachedClock instance() { + return instance; + } + + private CachedClock() { + updater.setDaemon(true); + updater.start(); + } + + // @Override + public long currentTimeMillis() { + + // improve granularity: also update time field every 1024 calls. + // (the bit fiddling means we don't need to worry about overflows) + if ((++count & 0x3FF) == 0x3FF) { + millis = System.currentTimeMillis(); + } + return millis; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/Clock.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/Clock.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/Clock.java (working copy) @@ -0,0 +1,21 @@ +/* + * 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.async; + +public interface Clock { + long currentTimeMillis(); +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/ClockFactory.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/ClockFactory.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/ClockFactory.java (working copy) @@ -0,0 +1,41 @@ +package org.apache.logging.log4j.async; + +import org.apache.logging.log4j.status.StatusLogger; + +public class ClockFactory { + + public static final String PROPERTY_NAME = "AsyncLogger.Clock"; + //private static final Clock clock = createClock(); + + public static Clock getClock() { + return createClock(); + } + + private static Clock createClock() { + final StatusLogger LOGGER = StatusLogger.getLogger(); + String userRequest = System.getProperty(PROPERTY_NAME); + if (userRequest == null || "SystemClock".equals(userRequest)) { + LOGGER.debug("Using default SystemClock for timestamps"); + return new SystemClock(); + } + if ("org.apache.logging.log4j.async.CachedClock".equals(userRequest) + || "CachedClock".equals(userRequest)) { + LOGGER.debug("Using specified CachedClock for timestamps"); + return CachedClock.instance(); + } + if ("org.apache.logging.log4j.async.CoarseCachedClock" + .equals(userRequest) || "CoarseCachedClock".equals(userRequest)) { + LOGGER.debug("Using specified CoarseCachedClock for timestamps"); + return CoarseCachedClock.instance(); + } + try { + Clock result = (Clock) Class.forName(userRequest).newInstance(); + LOGGER.debug("Using {} for timestamps", userRequest); + return result; + } catch (Exception e) { + String fmt = "Could not create {}: {}, using default SystemClock for timestamps"; + LOGGER.error(fmt, userRequest, e); + return new SystemClock(); + } + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/CoarseCachedClock.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/CoarseCachedClock.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/CoarseCachedClock.java (working copy) @@ -0,0 +1,54 @@ +/* + * 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.async; + +import org.apache.logging.log4j.async.com.lmax.disruptor.util.Util; + +/** + * This Clock implementation is similar to CachedClock. It is slightly faster at + * the cost of some accuracy. + * + * @author Remko Popma + */ +public class CoarseCachedClock implements Clock { + private static CoarseCachedClock instance = new CoarseCachedClock(); + private volatile long millis = System.currentTimeMillis(); + private final Thread updater = new Thread("Clock Updater Thread") { + public void run() { + while (true) { + long time = System.currentTimeMillis(); + millis = time; + Util.getUnsafe().park(true, time + 1); // abs (millis) + // Util.getUnsafe().park(false, 1000 * 1000);// relative(nanos) + } + } + }; + + public static CoarseCachedClock instance() { + return instance; + } + + private CoarseCachedClock() { + updater.setDaemon(true); + updater.start(); + } + + // @Override + public long currentTimeMillis() { + return millis; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEvent.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEvent.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEvent.java (working copy) @@ -0,0 +1,208 @@ +/* + * 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.async; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.ThreadContext.ContextStack; +import org.apache.logging.log4j.async.com.lmax.disruptor.EventFactory; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.lookup.StrSubstitutor; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.SimpleMessage; + +/** + * When the Disruptor is started, the RingBuffer is populated with event + * objects. These objects are then re-used during the life of the RingBuffer. + * + * @author Remko Popma + */ +public class RingBufferLogEvent implements LogEvent { + private static final long serialVersionUID = 8462119088943934758L; + + public static final Factory FACTORY = new Factory(); + + /** + * Creates the events that will be put in the RingBuffer. + */ + private static class Factory implements EventFactory { + // @Override + public RingBufferLogEvent newInstance() { + return new RingBufferLogEvent(); + } + } + + private AsyncLogger asyncLogger; + private String loggerName; + private Marker marker; + private String fqcn; + private Level level; + private Message message; + private Throwable thrown; + private Map contextMap; + private ContextStack contextStack; + private String threadName; + private StackTraceElement location; + private long currentTimeMillis; + private boolean endOfBatch; + private boolean locationRequired; + + public void setValues(AsyncLogger asyncLogger, String loggerName, + Marker marker, String fqcn, Level level, Message data, Throwable t, + Map map, ContextStack contextStack, + String threadName, StackTraceElement location, + long currentTimeMillis) { + this.asyncLogger = asyncLogger; + this.loggerName = loggerName; + this.marker = marker; + this.fqcn = fqcn; + this.level = level; + this.message = data; + this.thrown = t; + this.contextMap = map; + this.contextStack = contextStack; + this.threadName = threadName; + this.location = location; + this.currentTimeMillis = currentTimeMillis; + } + + /** + * Event processor that reads the event from the ringbuffer can call this + * method. + * + * @param endOfBatch flag to indicate if this is the last event in a batch + * from the RingBuffer + */ + public void execute(boolean endOfBatch) { + this.endOfBatch = endOfBatch; + asyncLogger.actualAsyncLog(this); + } + + /** + * Returns {@code true} if this event is the end of a batch, {@code false} + * otherwise. + * + * @return {@code true} if this event is the end of a batch, {@code false} + * otherwise + */ + public boolean isEndOfBatch() { + return endOfBatch; + } + + public void setEndOfBatch(boolean endOfBatch) { + this.endOfBatch = endOfBatch; + } + + public boolean isLocationRequired() { + return locationRequired; + } + + public void setLocationRequired(boolean locationRequired) { + this.locationRequired = locationRequired; + } + + // @Override + public String getLoggerName() { + return loggerName; + } + + // @Override + public Marker getMarker() { + return marker; + } + + // @Override + public String getFQCN() { + return fqcn; + } + + // @Override + public Level getLevel() { + return level; + } + + // @Override + public Message getMessage() { + if (message == null) { + message = new SimpleMessage(""); + } + return message; + } + + // @Override + public Throwable getThrown() { + return thrown; + } + + // @Override + public Map getContextMap() { + return contextMap; + } + + // @Override + public ContextStack getContextStack() { + return contextStack; + } + + // @Override + public String getThreadName() { + return threadName; + } + + // @Override + public StackTraceElement getSource() { + return location; + } + + // @Override + public long getMillis() { + return currentTimeMillis; + } + + /** + * Merges the contents of the specified map into the contextMap, after + * replacing any variables in the property values with the + * StrSubstitutor-supplied actual values. + * + * @param properties configured properties + * @param strSubstitutor used to lookup values of variables in properties + */ + public void mergePropertiesIntoContextMap( + Map properties, StrSubstitutor strSubstitutor) { + if (properties == null) { + return; // nothing to do + } + + Map map = (contextMap == null) ? new HashMap() + : new HashMap(contextMap); + + for (Map.Entry entry : properties.entrySet()) { + Property prop = entry.getKey(); + if (map.containsKey(prop.getName())) { + continue; // contextMap overrides config properties + } + String value = entry.getValue() ? strSubstitutor.replace(prop + .getValue()) : prop.getValue(); + map.put(prop.getName(), value); + } + contextMap = map; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEventHandler.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEventHandler.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEventHandler.java (working copy) @@ -0,0 +1,38 @@ +/* + * 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.async; + +import org.apache.logging.log4j.async.com.lmax.disruptor.EventHandler; + +/** + * This event handler gets passed messages from the RingBuffer as they become + * available. Processing of these messages is done in a separate thread, + * controlled by the {@code Executor} passed to the {@code Disruptor} + * constructor. + * + * @author Remko Popma + */ +public class RingBufferLogEventHandler implements + EventHandler { + + // @Override + public void onEvent(RingBufferLogEvent event, long sequence, + boolean endOfBatch) throws Exception { + event.execute(endOfBatch); + } + +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEventTranslator.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEventTranslator.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/RingBufferLogEventTranslator.java (working copy) @@ -0,0 +1,69 @@ +/* + * 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.async; + +import java.util.Map; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.ThreadContext.ContextStack; +import org.apache.logging.log4j.async.com.lmax.disruptor.EventTranslator; +import org.apache.logging.log4j.message.Message; + +public class RingBufferLogEventTranslator implements + EventTranslator { + + private AsyncLogger asyncLogger; + private String loggerName; + private Marker marker; + private String fqcn; + private Level level; + private Message message; + private Throwable thrown; + private Map contextMap; + private ContextStack contextStack; + private String threadName; + private StackTraceElement location; + private long currentTimeMillis; + + // @Override + public void translateTo(RingBufferLogEvent event, long sequence) { + event.setValues(asyncLogger, loggerName, marker, fqcn, level, message, + thrown, contextMap, contextStack, threadName, location, + currentTimeMillis); + } + + public void setValues(AsyncLogger asyncLogger, String loggerName, + Marker marker, String fqcn, Level level, Message message, + Throwable thrown, Map contextMap, + ContextStack contextStack, String threadName, + StackTraceElement location, long currentTimeMillis) { + this.asyncLogger = asyncLogger; + this.loggerName = loggerName; + this.marker = marker; + this.fqcn = fqcn; + this.level = level; + this.message = message; + this.thrown = thrown; + this.contextMap = contextMap; + this.contextStack = contextStack; + this.threadName = threadName; + this.location = location; + this.currentTimeMillis = currentTimeMillis; + } + +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/SystemClock.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/SystemClock.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/SystemClock.java (working copy) @@ -0,0 +1,26 @@ +/* + * 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.async; + +public class SystemClock implements Clock { + + // @Override + public long currentTimeMillis() { + return System.currentTimeMillis(); + } + +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastFileAppender.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastFileAppender.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastFileAppender.java (working copy) @@ -0,0 +1,165 @@ +/* + * 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.async.appender; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttr; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.net.Advertiser; + +/** + * File Appender. + */ +@Plugin(name = "FastFile", type = "Core", elementType = "appender", printObject = true) +public final class FastFileAppender extends AbstractOutputStreamAppender { + + private final String fileName; + private Object advertisement; + private final Advertiser advertiser; + + private FastFileAppender(String name, Layout layout, Filter filter, + FastFileManager manager, String filename, boolean handleException, + boolean immediateFlush, Advertiser advertiser) { + super(name, layout, filter, handleException, immediateFlush, manager); + if (advertiser != null) { + Map configuration = new HashMap( + layout.getContentFormat()); + configuration.putAll(manager.getContentFormat()); + configuration.put("contentType", layout.getContentType()); + configuration.put("name", name); + advertisement = advertiser.advertise(configuration); + } + this.fileName = filename; + this.advertiser = advertiser; + } + + @Override + public void stop() { + super.stop(); + if (advertiser != null) { + advertiser.unadvertise(advertisement); + } + } + + /** + * Write the log entry rolling over the file when required. + * + * @param event The LogEvent. + */ + @Override + public void append(LogEvent event) { + + // Leverage the nice batching behaviour of async Loggers/Appenders: + // we can signal the file manager that it needs to flush the buffer + // to disk at the end of a batch. + // From a user's point of view, this means that all log events are + // _always_ available in the log file, without incurring the overhead + // of immediateFlush=true. + // + // without LOG4J2-164: + // if (event.getClass() == RingBufferLogEvent.class) { + // boolean isEndOfBatch = ((RingBufferLogEvent) event).isEndOfBatch(); + // ((FastFileManager) getManager()).setEndOfBatch(isEndOfBatch); + // } + ((FastFileManager) getManager()).setEndOfBatch(event.isEndOfBatch()); + super.append(event); + } + + /** + * Returns the file name this appender is associated with. + * + * @return The File name. + */ + public String getFileName() { + return this.fileName; + } + + // difference from standard File Appender: + // locking is not supported and buffering cannot be switched off + /** + * Create a File Appender. + * + * @param fileName The name and path of the file. + * @param append "True" if the file should be appended to, "false" if it + * should be overwritten. The default is "true". + * @param name The name of the Appender. + * @param immediateFlush "true" if the contents should be flushed on every + * write, "false" otherwise. The default is "true". + * @param suppress "true" if exceptions should be hidden from the + * application, "false" otherwise. The default is "true". + * @param layout The layout to use to format the event. If no layout is + * provided the default PatternLayout will be used. + * @param filter The filter, if any, to use. + * @param advertise "true" if the appender configuration should be + * advertised, "false" otherwise. + * @param advertiseURI The advertised URI which can be used to retrieve the + * file contents. + * @param config The Configuration. + * @return The FileAppender. + */ + @PluginFactory + public static FastFileAppender createAppender( + @PluginAttr("fileName") String fileName, + @PluginAttr("append") String append, + @PluginAttr("name") String name, + @PluginAttr("immediateFlush") String immediateFlush, + @PluginAttr("suppressExceptions") String suppress, + @PluginElement("layout") Layout layout, + @PluginElement("filters") final Filter filter, + @PluginAttr("advertise") final String advertise, + @PluginAttr("advertiseURI") final String advertiseURI, + @PluginConfiguration final Configuration config) { + + boolean isAppend = append == null ? true : Boolean.valueOf(append); + boolean isFlush = immediateFlush == null ? true : Boolean.valueOf(immediateFlush); + boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress); + boolean isAdvertise = advertise == null ? false : Boolean.valueOf(advertise); + + if (name == null) { + LOGGER.error("No name provided for FileAppender"); + return null; + } + + if (fileName == null) { + LOGGER.error("No filename provided for FileAppender with name " + + name); + return null; + } + + FastFileManager manager = FastFileManager.getFileManager(fileName, + isAppend, isFlush, advertiseURI); + if (manager == null) { + return null; + } + if (layout == null) { + layout = PatternLayout.createLayout(null, null, null, null); + } + return new FastFileAppender(name, layout, filter, manager, fileName, + handleExceptions, isFlush, isAdvertise ? config.getAdvertiser() : null); + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastFileManager.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastFileManager.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastFileManager.java (working copy) @@ -0,0 +1,195 @@ +/* + * 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.async.appender; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import org.apache.logging.log4j.core.appender.AppenderRuntimeException; +import org.apache.logging.log4j.core.appender.ManagerFactory; +import org.apache.logging.log4j.core.appender.OutputStreamManager; + +/** + * Manages actual File I/O for File Appenders. + */ +public class FastFileManager extends OutputStreamManager { + + private static final FastFileManagerFactory factory = new FastFileManagerFactory(); + + private final boolean isImmediateFlush; + private final String advertiseURI; + private final RandomAccessFile randomAccessFile; + private final ByteBuffer buffer; + private ThreadLocal isEndOfBatch = new ThreadLocal(); + + protected FastFileManager(RandomAccessFile file, String fileName, + OutputStream os, boolean immediateFlush, String advertiseURI) { + super(os, fileName); + this.isImmediateFlush = immediateFlush; + this.randomAccessFile = file; + this.advertiseURI = advertiseURI; + isEndOfBatch.set(Boolean.FALSE); + buffer = ByteBuffer.allocate(256 * 1024); // TODO make configurable? + } + + /** + * Returns the FastFileManager. + * + * @param fileName + * The name of the file to manage. + * @param append + * true if the file should be appended to, false if it should be + * overwritten. + * @param isFlush + * true if the contents should be flushed to disk on every write + * @param advertiseURI the URI to use when advertising the file + * @return A FastFileManager for the File. + */ + public static FastFileManager getFileManager(String fileName, + boolean append, boolean isFlush, String advertiseURI) { + return (FastFileManager) getManager(fileName, new FactoryData(append, + isFlush, advertiseURI), factory); + } + + public Boolean isEndOfBatch() { + return isEndOfBatch.get(); + } + + public void setEndOfBatch(boolean isEndOfBatch) { + this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch)); + } + + @Override + protected synchronized void write(byte[] bytes, int offset, int length) { + super.write(bytes, offset, length); // writes to dummy output stream + + if (length > buffer.remaining()) { + flush(); + } + buffer.put(bytes, offset, length); + if (isImmediateFlush || isEndOfBatch.get() == Boolean.TRUE) { + flush(); + } + } + + @Override + public void flush() { + buffer.flip(); + try { + randomAccessFile.write(buffer.array(), 0, buffer.limit()); + } catch (IOException ex) { + String msg = "Error writing to RandomAccessFile " + getName(); + throw new AppenderRuntimeException(msg, ex); + } + buffer.clear(); + } + + /** + * Returns the name of the File being managed. + * + * @return The name of the File being managed. + */ + public String getFileName() { + return getName(); + } + + private static class DummyOutputStream extends OutputStream { + @Override + public void write(int b) throws IOException { + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + } + } + + /** + * FileManager's content format is specified by:

+ * Key: "fileURI" Value: provided "advertiseURI" param + * @return Map of content format keys supporting FileManager + */ + public Map getContentFormat() { + Map result = new HashMap(super.getContentFormat()); + result.put("fileURI", advertiseURI); + return result; + } + + /** + * Factory Data. + */ + private static class FactoryData { + private final boolean append; + private final boolean immediateFlush; + private final String advertiseURI; + + /** + * Constructor. + * + * @param append + * Append status. + */ + public FactoryData(boolean append, boolean immediateFlush, String advertiseURI) { + this.append = append; + this.immediateFlush = immediateFlush; + this.advertiseURI = advertiseURI; + } + } + + /** + * Factory to create a FastFileManager. + */ + private static class FastFileManagerFactory implements + ManagerFactory { + + /** + * Create a FastFileManager. + * + * @param name + * The name of the File. + * @param data + * The FactoryData + * @return The FastFileManager for the File. + */ + public FastFileManager createManager(String name, FactoryData data) { + File file = new File(name); + final File parent = file.getParentFile(); + if (null != parent && !parent.exists()) { + parent.mkdirs(); + } + if (!data.append) { + file.delete(); + } + + OutputStream os = new DummyOutputStream(); + RandomAccessFile raf; + try { + raf = new RandomAccessFile(name, "rw"); + return new FastFileManager(raf, name, os, data.immediateFlush, + data.advertiseURI); + } catch (Exception ex) { + LOGGER.error("FastFileManager (" + name + ") " + ex); + } + return null; + } + } + +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastRollingFileAppender.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastRollingFileAppender.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastRollingFileAppender.java (working copy) @@ -0,0 +1,193 @@ +/* + * 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.async.appender; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender; +import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy; +import org.apache.logging.log4j.core.appender.rolling.RollingFileManager; +import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy; +import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttr; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.net.Advertiser; + +/** + * An appender that writes to random access files and can roll over at + * intervals. + */ +@Plugin(name = "FastRollingFile", type = "Core", elementType = "appender", printObject = true) +public final class FastRollingFileAppender extends AbstractOutputStreamAppender { + + private final String fileName; + private final String filePattern; + private Object advertisement; + private final Advertiser advertiser; + + private FastRollingFileAppender(String name, Layout layout, Filter filter, + RollingFileManager manager, String fileName, + String filePattern, boolean handleException, + boolean immediateFlush, Advertiser advertiser) { + super(name, layout, filter, handleException, immediateFlush, manager); + if (advertiser != null) { + Map configuration = new HashMap(layout.getContentFormat()); + configuration.put("contentType", layout.getContentType()); + configuration.put("name", name); + advertisement = advertiser.advertise(configuration); + } + this.fileName = fileName; + this.filePattern = filePattern; + this.advertiser = advertiser; + } + + @Override + public void stop() { + super.stop(); + if (advertiser != null) { + advertiser.unadvertise(advertisement); + } + } + + /** + * Write the log entry rolling over the file when required. + + * @param event The LogEvent. + */ + @Override + public void append(final LogEvent event) { + ((RollingFileManager) getManager()).checkRollover(event); + + // Leverage the nice batching behaviour of async Loggers/Appenders: + // we can signal the file manager that it needs to flush the buffer + // to disk at the end of a batch. + // From a user's point of view, this means that all log events are + // _always_ available in the log file, without incurring the overhead + // of immediateFlush=true. + // + // without LOG4J2-164: + // if (event.getClass() == RingBufferLogEvent.class) { + // boolean isEndOfBatch = ((RingBufferLogEvent) event).isEndOfBatch(); + // ((FastRollingFileManager) getManager()).setEndOfBatch(isEndOfBatch); + // } + ((FastRollingFileManager) getManager()).setEndOfBatch(event.isEndOfBatch()); + super.append(event); + } + + /** + * Returns the File name for the Appender. + * @return The file name. + */ + public String getFileName() { + return fileName; + } + + /** + * Returns the file pattern used when rolling over. + * @return The file pattern. + */ + public String getFilePattern() { + return filePattern; + } + + /** + * Create a FastRollingFileAppender. + * @param fileName The name of the file that is actively written to. (required). + * @param filePattern The pattern of the file name to use on rollover. (required). + * @param append If true, events are appended to the file. If false, the file + * is overwritten when opened. Defaults to "true" + * @param name The name of the Appender (required). + * @param immediateFlush When true, events are immediately flushed. Defaults to "true". + * @param policy The triggering policy. (required). + * @param strategy The rollover strategy. Defaults to DefaultRolloverStrategy. + * @param layout The layout to use (defaults to the default PatternLayout). + * @param filter The Filter or null. + * @param suppress "true" if exceptions should be hidden from the application, "false" otherwise. + * The default is "true". + * @param advertise "true" if the appender configuration should be advertised, "false" otherwise. + * @param advertiseURI The advertised URI which can be used to retrieve the file contents. + * @param config The Configuration. + * @return A FastRollingFileAppender. + */ + @PluginFactory + public static FastRollingFileAppender createAppender(@PluginAttr("fileName") final String fileName, + @PluginAttr("filePattern") final String filePattern, + @PluginAttr("append") final String append, + @PluginAttr("name") final String name, + @PluginAttr("immediateFlush") final String immediateFlush, + @PluginElement("policy") final TriggeringPolicy policy, + @PluginElement("strategy") RolloverStrategy strategy, + @PluginElement("layout") Layout layout, + @PluginElement("filter") final Filter filter, + @PluginAttr("suppressExceptions") final String suppress, + @PluginAttr("advertise") final String advertise, + @PluginAttr("advertiseURI") final String advertiseURI, + @PluginConfiguration final Configuration config) { + + final boolean isAppend = append == null ? true : Boolean.valueOf(append); + final boolean handleExceptions = suppress == null ? true : Boolean.valueOf(suppress); + final boolean isFlush = immediateFlush == null ? true : Boolean.valueOf(immediateFlush); + boolean isAdvertise = advertise == null ? false : Boolean.valueOf(advertise); + + if (name == null) { + LOGGER.error("No name provided for FileAppender"); + return null; + } + + if (fileName == null) { + LOGGER.error("No filename was provided for FileAppender with name " + name); + return null; + } + + if (filePattern == null) { + LOGGER.error("No filename pattern provided for FileAppender with name " + name); + return null; + } + + if (policy == null) { + LOGGER.error("A TriggeringPolicy must be provided"); + return null; + } + + if (strategy == null) { + strategy = DefaultRolloverStrategy.createStrategy(null, null, "true", config); + } + + final FastRollingFileManager manager = FastRollingFileManager + .getFastRollingFileManager(fileName, filePattern, isAppend, + isFlush, policy, strategy, advertiseURI); + if (manager == null) { + return null; + } + + if (layout == null) { + layout = PatternLayout.createLayout(null, null, null, null); + } + + return new FastRollingFileAppender(name, layout, filter, manager, fileName, filePattern, + handleExceptions, isFlush, isAdvertise ? config.getAdvertiser() : null); + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastRollingFileManager.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastRollingFileManager.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/appender/FastRollingFileManager.java (working copy) @@ -0,0 +1,177 @@ +/* + * 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.async.appender; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; + +import org.apache.logging.log4j.core.appender.AppenderRuntimeException; +import org.apache.logging.log4j.core.appender.ManagerFactory; +import org.apache.logging.log4j.core.appender.rolling.RollingFileManager; +import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy; +import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy; + +public class FastRollingFileManager extends RollingFileManager { + private static FastRollingFileManagerFactory factory = new FastRollingFileManagerFactory(); + + private final boolean isImmediateFlush; + private final RandomAccessFile randomAccessFile; + private final ByteBuffer buffer; + private ThreadLocal isEndOfBatch = new ThreadLocal(); + + public FastRollingFileManager(RandomAccessFile raf, String fileName, + String pattern, OutputStream os, boolean append, + boolean immediateFlush, long size, long time, TriggeringPolicy policy, + RolloverStrategy strategy, String advertiseURI) { + super(fileName, pattern, os, append, size, time, policy, strategy, advertiseURI); + this.isImmediateFlush = immediateFlush; + this.randomAccessFile = raf; + isEndOfBatch.set(Boolean.FALSE); + buffer = ByteBuffer.allocate(256 * 1024); // TODO make configurable? + } + + public static FastRollingFileManager getFastRollingFileManager( + String fileName, String filePattern, boolean isAppend, + boolean immediateFlush, TriggeringPolicy policy, + RolloverStrategy strategy, String advertiseURI) { + return (FastRollingFileManager) getManager(fileName, new FactoryData( + filePattern, isAppend, immediateFlush, policy, strategy, + advertiseURI), factory); + } + + public Boolean isEndOfBatch() { + return isEndOfBatch.get(); + } + + public void setEndOfBatch(boolean isEndOfBatch) { + this.isEndOfBatch.set(Boolean.valueOf(isEndOfBatch)); + } + + @Override + protected synchronized void write(byte[] bytes, int offset, int length) { + super.write(bytes, offset, length); // writes to dummy output stream + + if (length > buffer.remaining()) { + flush(); + } + buffer.put(bytes, offset, length); + if (isImmediateFlush || isEndOfBatch.get() == Boolean.TRUE) { + flush(); + } + } + + @Override + public void flush() { + buffer.flip(); + try { + randomAccessFile.write(buffer.array(), 0, buffer.limit()); + } catch (IOException ex) { + String msg = "Error writing to RandomAccessFile " + getName(); + throw new AppenderRuntimeException(msg, ex); + } + buffer.clear(); + } + + /** + * Factory to create a FastRollingFileManager. + */ + private static class FastRollingFileManagerFactory implements + ManagerFactory { + + /** + * Create the FastRollingFileManager. + * + * @param name + * The name of the entity to manage. + * @param data + * The data required to create the entity. + * @return a RollingFileManager. + */ + public FastRollingFileManager createManager(String name, + FactoryData data) { + File file = new File(name); + final File parent = file.getParentFile(); + if (null != parent && !parent.exists()) { + parent.mkdirs(); + } + if (!data.append) { + file.delete(); + } + long size = data.append ? file.length() : 0; + long time = file.lastModified(); + + RandomAccessFile raf; + try { + raf = new RandomAccessFile(name, "rw"); + return new FastRollingFileManager(raf, name, data.pattern, + new DummyOutputStream(), data.append, + data.immediateFlush, size, time, data.policy, + data.strategy, data.advertiseURI); + } catch (FileNotFoundException ex) { + LOGGER.error("FastRollingFileManager (" + name + ") " + ex); + } + return null; + } + } + + private static class DummyOutputStream extends OutputStream { + @Override + public void write(int b) throws IOException { + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + } + } + + /** + * Factory data. + */ + private static class FactoryData { + private final String pattern; + private final boolean append; + private final boolean immediateFlush; + private final TriggeringPolicy policy; + private final RolloverStrategy strategy; + private final String advertiseURI; + + /** + * Create the data for the factory. + * + * @param pattern + * The pattern. + * @param append + * The append flag. + * @param immediateFlush + */ + public FactoryData(String pattern, boolean append, + boolean immediateFlush, TriggeringPolicy policy, + RolloverStrategy strategy, String advertiseURI) { + this.pattern = pattern; + this.append = append; + this.immediateFlush = immediateFlush; + this.policy = policy; + this.strategy = strategy; + this.advertiseURI = advertiseURI; + } + } + +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/AggregateEventHandler.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/AggregateEventHandler.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/AggregateEventHandler.java (working copy) @@ -0,0 +1,71 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * An aggregate collection of {@link EventHandler}s that get called in sequence for each event. + * + * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. + */ +public final class AggregateEventHandler + implements EventHandler, LifecycleAware +{ + private final EventHandler[] eventHandlers; + + /** + * Construct an aggregate collection of {@link EventHandler}s to be called in sequence. + * + * @param eventHandlers to be called in sequence. + */ + public AggregateEventHandler(final EventHandler... eventHandlers) + { + this.eventHandlers = eventHandlers; + } + + @Override + public void onEvent(final T event, final long sequence, final boolean endOfBatch) + throws Exception + { + for (final EventHandler eventHandler : eventHandlers) + { + eventHandler.onEvent(event, sequence, endOfBatch); + } + } + + @Override + public void onStart() + { + for (final EventHandler eventHandler : eventHandlers) + { + if (eventHandler instanceof LifecycleAware) + { + ((LifecycleAware)eventHandler).onStart(); + } + } + } + + @Override + public void onShutdown() + { + for (final EventHandler eventHandler : eventHandlers) + { + if (eventHandler instanceof LifecycleAware) + { + ((LifecycleAware)eventHandler).onShutdown(); + } + } + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/AlertException.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/AlertException.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/AlertException.java (working copy) @@ -0,0 +1,46 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * Used to alert {@link EventProcessor}s waiting at a {@link SequenceBarrier} of status changes. + *

+ * It does not fill in a stack trace for performance reasons. + */ +@SuppressWarnings("serial") +public class AlertException extends Exception +{ + /** Pre-allocated exception to avoid garbage generation */ + public static final AlertException INSTANCE = new AlertException(); + + /** + * Private constructor so only a single instance exists. + */ + private AlertException() + { + } + + /** + * Overridden so the stack trace is not filled in for this exception for performance reasons. + * + * @return this instance. + */ + @Override + public Throwable fillInStackTrace() + { + return this; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/BatchEventProcessor.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/BatchEventProcessor.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/BatchEventProcessor.java (working copy) @@ -0,0 +1,199 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +import java.util.concurrent.atomic.AtomicBoolean; + + +/** + * Convenience class for handling the batching semantics of consuming entries from a {@link RingBuffer} + * and delegating the available events to an {@link EventHandler}. + * + * If the {@link EventHandler} also implements {@link LifecycleAware} it will be notified just after the thread + * is started and just before the thread is shutdown. + * + * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. + */ +public final class BatchEventProcessor + implements EventProcessor +{ + private final AtomicBoolean running = new AtomicBoolean(false); + private ExceptionHandler exceptionHandler = new FatalExceptionHandler(); + private final RingBuffer ringBuffer; + private final SequenceBarrier sequenceBarrier; + private final EventHandler eventHandler; + private final Sequence sequence = new Sequence(Sequencer.INITIAL_CURSOR_VALUE); + private final TimeoutHandler timeoutHandler; + + /** + * Construct a {@link EventProcessor} that will automatically track the progress by updating its sequence when + * the {@link EventHandler#onEvent(Object, long, boolean)} method returns. + * + * @param ringBuffer to which events are published. + * @param sequenceBarrier on which it is waiting. + * @param eventHandler is the delegate to which events are dispatched. + */ + public BatchEventProcessor(final RingBuffer ringBuffer, + final SequenceBarrier sequenceBarrier, + final EventHandler eventHandler) + { + this.ringBuffer = ringBuffer; + this.sequenceBarrier = sequenceBarrier; + this.eventHandler = eventHandler; + + if (eventHandler instanceof SequenceReportingEventHandler) + { + ((SequenceReportingEventHandler)eventHandler).setSequenceCallback(sequence); + } + + timeoutHandler = (eventHandler instanceof TimeoutHandler) ? (TimeoutHandler) eventHandler : null; + } + + @Override + public Sequence getSequence() + { + return sequence; + } + + @Override + public void halt() + { + running.set(false); + sequenceBarrier.alert(); + } + + /** + * Set a new {@link ExceptionHandler} for handling exceptions propagated out of the {@link BatchEventProcessor} + * + * @param exceptionHandler to replace the existing exceptionHandler. + */ + public void setExceptionHandler(final ExceptionHandler exceptionHandler) + { + if (null == exceptionHandler) + { + throw new NullPointerException(); + } + + this.exceptionHandler = exceptionHandler; + } + + /** + * It is ok to have another thread rerun this method after a halt(). + * + * @throws IllegalStateException if this object instance is already running in a thread + */ + @Override + public void run() + { + if (!running.compareAndSet(false, true)) + { + throw new IllegalStateException("Thread is already running"); + } + sequenceBarrier.clearAlert(); + + notifyStart(); + + T event = null; + long nextSequence = sequence.get() + 1L; + while (true) + { + try + { + final long availableSequence = sequenceBarrier.waitFor(nextSequence); + + if (availableSequence < nextSequence && timeoutHandler != null) + { + notifyTimeout(availableSequence); + continue; + } + + while (nextSequence <= availableSequence) + { + event = ringBuffer.getPublished(nextSequence); + eventHandler.onEvent(event, nextSequence, nextSequence == availableSequence); + nextSequence++; + } + + sequence.set(availableSequence); + } + catch (final AlertException ex) + { + if (!running.get()) + { + break; + } + } + catch (final Throwable ex) + { + exceptionHandler.handleEventException(ex, nextSequence, event); + sequence.set(nextSequence); + nextSequence++; + } + } + + notifyShutdown(); + + running.set(false); + } + + private void notifyTimeout(final long availableSequence) throws Exception + { + try + { + timeoutHandler.onTimeout(availableSequence); + } + catch (Throwable e) + { + exceptionHandler.handleEventException(e, availableSequence, null); + } + } + + /** + * Notifies the EventHandler when this processor is starting up + */ + private void notifyStart() + { + if (eventHandler instanceof LifecycleAware) + { + try + { + ((LifecycleAware)eventHandler).onStart(); + } + catch (final Throwable ex) + { + exceptionHandler.handleOnStartException(ex); + } + } + } + + /** + * Notifies the EventHandler immediately prior to this processor shutting down + */ + private void notifyShutdown() + { + if (eventHandler instanceof LifecycleAware) + { + try + { + ((LifecycleAware)eventHandler).onShutdown(); + } + catch (final Throwable ex) + { + exceptionHandler.handleOnShutdownException(ex); + } + } + } +} \ No newline at end of file Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/BlockingWaitStrategy.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/BlockingWaitStrategy.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/BlockingWaitStrategy.java (working copy) @@ -0,0 +1,75 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Blocking strategy that uses a lock and condition variable for {@link EventProcessor}s waiting on a barrier. + * + * This strategy can be used when throughput and low-latency are not as important as CPU resource. + */ +public final class BlockingWaitStrategy implements WaitStrategy +{ + private final Lock lock = new ReentrantLock(); + private final Condition processorNotifyCondition = lock.newCondition(); + + @Override + public long waitFor(long sequence, Sequence cursorSequence, Sequence dependentSequence, SequenceBarrier barrier) + throws AlertException, InterruptedException + { + long availableSequence; + if ((availableSequence = cursorSequence.get()) < sequence) + { + lock.lock(); + try + { + while ((availableSequence = cursorSequence.get()) < sequence) + { + barrier.checkAlert(); + processorNotifyCondition.await(); + } + } + finally + { + lock.unlock(); + } + } + + while ((availableSequence = dependentSequence.get()) < sequence) + { + barrier.checkAlert(); + } + + return availableSequence; + } + + @Override + public void signalAllWhenBlocking() + { + lock.lock(); + try + { + processorNotifyCondition.signalAll(); + } + finally + { + lock.unlock(); + } + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/BusySpinWaitStrategy.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/BusySpinWaitStrategy.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/BusySpinWaitStrategy.java (working copy) @@ -0,0 +1,45 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + + +/** + * Busy Spin strategy that uses a busy spin loop for {@link org.apache.logging.log4j.async.com.lmax.disruptor.EventProcessor}s waiting on a barrier. + * + * This strategy will use CPU resource to avoid syscalls which can introduce latency jitter. It is best + * used when threads can be bound to specific CPU cores. + */ +public final class BusySpinWaitStrategy implements WaitStrategy +{ + @Override + public long waitFor(final long sequence, Sequence cursor, final Sequence dependentSequence, final SequenceBarrier barrier) + throws AlertException, InterruptedException + { + long availableSequence; + + while ((availableSequence = dependentSequence.get()) < sequence) + { + barrier.checkAlert(); + } + + return availableSequence; + } + + @Override + public void signalAllWhenBlocking() + { + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventFactory.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventFactory.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventFactory.java (working copy) @@ -0,0 +1,29 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * Called by the {@link RingBuffer} to pre-populate all the events to fill the RingBuffer. + * + * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. + */ +public interface EventFactory +{ + /* + * Implementations should instantiate an event object, with all memory already allocated where possible. + */ + T newInstance(); +} \ No newline at end of file Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventHandler.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventHandler.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventHandler.java (working copy) @@ -0,0 +1,36 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * Callback interface to be implemented for processing events as they become available in the {@link RingBuffer} + * + * @see BatchEventProcessor#setExceptionHandler(ExceptionHandler) if you want to handle exceptions propagated out of the handler. + * + * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. + */ +public interface EventHandler +{ + /** + * Called when a publisher has published an event to the {@link RingBuffer} + * + * @param event published to the {@link RingBuffer} + * @param sequence of the event being processed + * @param endOfBatch flag to indicate if this is the last event in a batch from the {@link RingBuffer} + * @throws Exception if the EventHandler would like the exception handled further up the chain. + */ + void onEvent(T event, long sequence, boolean endOfBatch) throws Exception; +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventProcessor.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventProcessor.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventProcessor.java (working copy) @@ -0,0 +1,37 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * EventProcessors waitFor events to become available for consumption from the {@link RingBuffer} + * + * An EventProcessor will generally be associated with a Thread for execution. + */ +public interface EventProcessor extends Runnable +{ + /** + * Get a reference to the {@link Sequence} being used by this {@link EventProcessor}. + * + * @return reference to the {@link Sequence} for this {@link EventProcessor} + */ + Sequence getSequence(); + + /** + * Signal that this EventProcessor should stop when it has finished consuming at the next clean break. + * It will call {@link SequenceBarrier#alert()} to notify the thread to check status. + */ + void halt(); +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventTranslator.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventTranslator.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventTranslator.java (working copy) @@ -0,0 +1,36 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * Implementations translate (write) data representations into events claimed from the {@link RingBuffer}.

+ * + * When publishing to the RingBuffer, provide an EventTranslator. The RingBuffer will select the next available + * event by sequence and provide it to the EventTranslator (which should update the event), before publishing + * the sequence update. + * + * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. + */ +public interface EventTranslator +{ + /** + * Translate a data representation into fields set in given event + * + * @param event into which the data should be translated. + * @param sequence that is assigned to event. + */ + void translateTo(final T event, long sequence); +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventTranslatorOneArg.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventTranslatorOneArg.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventTranslatorOneArg.java (working copy) @@ -0,0 +1,34 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * Implementations translate another data representations into events claimed from the {@link RingBuffer} + * + * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. + * @see EventTranslator + */ +public interface EventTranslatorOneArg +{ + /** + * Translate a data representation into fields set in given event + * + * @param event into which the data should be translated. + * @param sequence that is assigned to event. + * @param arg0 The first user specified argument to the translator + */ + void translateTo(final T event, long sequence, final A arg0); +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventTranslatorThreeArg.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventTranslatorThreeArg.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventTranslatorThreeArg.java (working copy) @@ -0,0 +1,36 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * Implementations translate another data representations into events claimed from the {@link RingBuffer} + * + * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. + * @see EventTranslator + */ +public interface EventTranslatorThreeArg +{ + /** + * Translate a data representation into fields set in given event + * + * @param event into which the data should be translated. + * @param sequence that is assigned to event. + * @param arg0 The first user specified argument to the translator + * @param arg1 The second user specified argument to the translator + * @param arg2 The third user specified argument to the translator + */ + void translateTo(final T event, long sequence, final A arg0, final B arg1, final C arg2); +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventTranslatorTwoArg.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventTranslatorTwoArg.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventTranslatorTwoArg.java (working copy) @@ -0,0 +1,35 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * Implementations translate another data representations into events claimed from the {@link RingBuffer} + * + * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. + * @see EventTranslator + */ +public interface EventTranslatorTwoArg +{ + /** + * Translate a data representation into fields set in given event + * + * @param event into which the data should be translated. + * @param sequence that is assigned to event. + * @param arg0 The first user specified argument to the translator + * @param arg1 The second user specified argument to the translator + */ + void translateTo(final T event, long sequence, final A arg0, final B arg1); +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventTranslatorVararg.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventTranslatorVararg.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventTranslatorVararg.java (working copy) @@ -0,0 +1,34 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * Implementations translate another data representations into events claimed from the {@link RingBuffer} + * + * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. + * @see EventTranslator + */ +public interface EventTranslatorVararg +{ + /** + * Translate a data representation into fields set in given event + * + * @param event into which the data should be translated. + * @param sequence that is assigned to event. + * @param args The array of user arguments. + */ + void translateTo(final T event, long sequence, final Object...args); +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/ExceptionHandler.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/ExceptionHandler.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/ExceptionHandler.java (working copy) @@ -0,0 +1,48 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * Callback handler for uncaught exceptions in the event processing cycle of the {@link BatchEventProcessor} + */ +public interface ExceptionHandler +{ + /** + * Strategy for handling uncaught exceptions when processing an event.

+ * + * If the strategy wishes to terminate further processing by the {@link BatchEventProcessor} + * then it should throw a {@link RuntimeException}.

+ * + * @param ex the exception that propagated from the {@link EventHandler}. + * @param sequence of the event which cause the exception. + * @param event being processed when the exception occurred. This can be null. + */ + void handleEventException(Throwable ex, long sequence, Object event); + + /** + * Callback to notify of an exception during {@link LifecycleAware#onStart()} + * + * @param ex throw during the starting process. + */ + void handleOnStartException(Throwable ex); + + /** + * Callback to notify of an exception during {@link LifecycleAware#onShutdown()} + * + * @param ex throw during the shutdown process. + */ + void handleOnShutdownException(Throwable ex); +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/FatalExceptionHandler.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/FatalExceptionHandler.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/FatalExceptionHandler.java (working copy) @@ -0,0 +1,59 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Convenience implementation of an exception handler that using standard JDK logging to log + * the exception as {@link Level}.SEVERE and re-throw it wrapped in a {@link RuntimeException} + */ +public final class FatalExceptionHandler implements ExceptionHandler +{ + private final static Logger LOGGER = Logger.getLogger(FatalExceptionHandler.class.getName()); + private final Logger logger; + + public FatalExceptionHandler() + { + this.logger = LOGGER; + } + + public FatalExceptionHandler(final Logger logger) + { + this.logger = logger; + } + + @Override + public void handleEventException(final Throwable ex, final long sequence, final Object event) + { + logger.log(Level.SEVERE, "Exception processing: " + sequence + " " + event, ex); + + throw new RuntimeException(ex); + } + + @Override + public void handleOnStartException(final Throwable ex) + { + logger.log(Level.SEVERE, "Exception during onStart()", ex); + } + + @Override + public void handleOnShutdownException(final Throwable ex) + { + logger.log(Level.SEVERE, "Exception during onShutdown()", ex); + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/FixedSequenceGroup.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/FixedSequenceGroup.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/FixedSequenceGroup.java (working copy) @@ -0,0 +1,92 @@ +/* + * Copyright 2012 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +import java.util.Arrays; + +import org.apache.logging.log4j.async.com.lmax.disruptor.util.Util; + + +/** + * Hides a group of Sequences behind a single Sequence + */ +public final class FixedSequenceGroup extends Sequence +{ + private final Sequence[] sequences; + + /** + * Constructor + * + * @param sequences the list of sequences to be tracked under this sequence group + */ + public FixedSequenceGroup(Sequence[] sequences) + { + this.sequences = Arrays.copyOf(sequences, sequences.length); + } + + /** + * Get the minimum sequence value for the group. + * + * @return the minimum sequence value for the group. + */ + @Override + public long get() + { + return Util.getMinimumSequence(sequences); + } + + @Override + public String toString() + { + return Arrays.toString(sequences); + } + + /** + * Not supported. + */ + @Override + public void set(long value) + { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. + */ + @Override + public boolean compareAndSet(long expectedValue, long newValue) + { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. + */ + @Override + public long incrementAndGet() + { + throw new UnsupportedOperationException(); + } + + /** + * Not supported. + */ + @Override + public long addAndGet(long increment) + { + throw new UnsupportedOperationException(); + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/IgnoreExceptionHandler.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/IgnoreExceptionHandler.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/IgnoreExceptionHandler.java (working copy) @@ -0,0 +1,57 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Convenience implementation of an exception handler that using standard JDK logging to log + * the exception as {@link Level}.INFO + */ +public final class IgnoreExceptionHandler implements ExceptionHandler +{ + private final static Logger LOGGER = Logger.getLogger(IgnoreExceptionHandler.class.getName()); + private final Logger logger; + + public IgnoreExceptionHandler() + { + this.logger = LOGGER; + } + + public IgnoreExceptionHandler(final Logger logger) + { + this.logger = logger; + } + + @Override + public void handleEventException(final Throwable ex, final long sequence, final Object event) + { + logger.log(Level.INFO, "Exception processing: " + sequence + " " + event, ex); + } + + @Override + public void handleOnStartException(final Throwable ex) + { + logger.log(Level.INFO, "Exception during onStart()", ex); + } + + @Override + public void handleOnShutdownException(final Throwable ex) + { + logger.log(Level.INFO, "Exception during onShutdown()", ex); + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/InsufficientCapacityException.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/InsufficientCapacityException.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/InsufficientCapacityException.java (working copy) @@ -0,0 +1,42 @@ +/* + * Copyright 2012 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + *

Exception thrown when the it is not possible to insert a value into + * the ring buffer without it wrapping the consuming sequenes. Used + * specifically when claiming with the {@link RingBuffer#tryNext()} call. + * + *

For efficiency this exception will not have a stack trace. + * @author mikeb01 + * + */ +@SuppressWarnings("serial") +public class InsufficientCapacityException extends Exception +{ + public static final InsufficientCapacityException INSTANCE = new InsufficientCapacityException(); + + private InsufficientCapacityException() + { + // Singleton + } + + @Override + public synchronized Throwable fillInStackTrace() + { + return this; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/LifecycleAware.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/LifecycleAware.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/LifecycleAware.java (working copy) @@ -0,0 +1,36 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * Implement this interface in your {@link EventHandler} to be notified when a thread for the + * {@link BatchEventProcessor} starts and shuts down.

+ */ +public interface LifecycleAware +{ + /** + * Called once on thread start before first event is available. + */ + void onStart(); + + /** + * Called once just before the thread is shutdown.

+ * + * Sequence event processing will already have stopped before this method is called. No events will + * be processed after this message. + */ + void onShutdown(); +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/MultiProducerPublisher.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/MultiProducerPublisher.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/MultiProducerPublisher.java (working copy) @@ -0,0 +1,130 @@ +/* + * Copyright 2012 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +import org.apache.logging.log4j.async.com.lmax.disruptor.util.Util; + +import sun.misc.Unsafe; + + +/** + * A Publisher optimised for use from multiple threads.

+ * + * Suitable for use for publishing from multiple threads. + */ +class MultiProducerPublisher implements Publisher +{ + private static final Unsafe UNSAFE = Util.getUnsafe(); + private static final long base = UNSAFE.arrayBaseOffset(int[].class); + private static final long scale = UNSAFE.arrayIndexScale(int[].class); + + private final WaitStrategy waitStrategy; + // availableBuffer tracks the state of each ringbuffer slot + // see below for more details on the approach + private final int[] availableBuffer; + private final int indexMask; + private final int indexShift; + + public MultiProducerPublisher(int bufferSize, WaitStrategy waitStrategy) + { + this.waitStrategy = waitStrategy; + availableBuffer = new int[bufferSize]; + indexMask = bufferSize - 1; + indexShift = Util.log2(bufferSize); + + initialiseAvailableBuffer(); + } + + private void initialiseAvailableBuffer() + { + for (int i = availableBuffer.length - 1; i != 0; i--) + { + setAvailableBufferValue(i, -1); + } + + setAvailableBufferValue(0, -1); + } + + @Override + public void publish(final long sequence) + { + setAvailable(sequence); + waitStrategy.signalAllWhenBlocking(); + } + + /** + * The below methods work on the availableBuffer flag. + * + * The prime reason is to avoid a shared sequence object between publisher threads. + * (Keeping single pointers tracking start and end would require coordination + * between the threads). + * + * -- Firstly we have the constraint that the delta between the cursor and minimum + * gating sequence will never be larger than the buffer size (the code in + * next/tryNext in the Sequence takes care of that). + * -- Given that; take the sequence value and mask off the lower portion of the + * sequence as the index into the buffer (indexMask). (aka modulo operator) + * -- The upper portion of the sequence becomes the value to check for availability. + * ie: it tells us how many times around the ring buffer we've been (aka division) + * -- Beause we can't wrap without the gating sequences moving forward (i.e. the + * minimum gating sequence is effectively our last available position in the + * buffer), when we have new data and successfully claimed a slot we can simply + * write over the top. + */ + private void setAvailable(final long sequence) + { + setAvailableBufferValue(calculateIndex(sequence), calculateAvailabilityFlag(sequence)); + } + + private void setAvailableBufferValue(int index, int flag) + { + long bufferAddress = (index * scale) + base; + UNSAFE.putOrderedInt(availableBuffer, bufferAddress, flag); + } + + @Override + public void ensureAvailable(long sequence) + { + int index = calculateIndex(sequence); + int flag = calculateAvailabilityFlag(sequence); + long bufferAddress = (index * scale) + base; + + while (UNSAFE.getIntVolatile(availableBuffer, bufferAddress) != flag) + { + assert UNSAFE.getIntVolatile(availableBuffer, bufferAddress) <= flag; + // spin + } + } + + @Override + public boolean isAvailable(long sequence) + { + int index = calculateIndex(sequence); + int flag = calculateAvailabilityFlag(sequence); + long bufferAddress = (index * scale) + base; + return UNSAFE.getIntVolatile(availableBuffer, bufferAddress) == flag; + } + + private int calculateAvailabilityFlag(final long sequence) + { + return (int) (sequence >>> indexShift); + } + + private int calculateIndex(final long sequence) + { + return ((int) sequence) & indexMask; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/MultiProducerSequencer.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/MultiProducerSequencer.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/MultiProducerSequencer.java (working copy) @@ -0,0 +1,158 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +import static org.apache.logging.log4j.async.com.lmax.disruptor.util.Util.*; + +import java.util.concurrent.locks.LockSupport; + +import org.apache.logging.log4j.async.com.lmax.disruptor.util.Util; + + + +/** + * Coordinator for claiming sequences for access to a data structure while tracking dependent {@link Sequence}s + * + * Suitable for use for sequencing across multiple publisher threads. + */ +class MultiProducerSequencer implements Sequencer +{ + private final int bufferSize; + @SuppressWarnings("unused") + private final WaitStrategy waitStrategy; + private final Sequence cursor = new Sequence(Sequencer.INITIAL_CURSOR_VALUE); + private final Sequence gatingSequenceCache = new Sequence(Sequencer.INITIAL_CURSOR_VALUE); + + /** + * Construct a Sequencer with the selected wait strategy and buffer size. + * + * @param bufferSize the size of the buffer that this will sequence over. + * @param waitStrategy for those waiting on sequences. + */ + public MultiProducerSequencer(int bufferSize, final WaitStrategy waitStrategy) + { + this.bufferSize = bufferSize; + this.waitStrategy = waitStrategy; + } + + @Override + public int getBufferSize() + { + return bufferSize; + } + + Sequence getCursorSequence() + { + return cursor; + } + + @Override + public boolean hasAvailableCapacity(Sequence[] gatingSequences, final int requiredCapacity) + { + return hasAvailableCapacity(gatingSequences, requiredCapacity, cursor.get()); + } + + private boolean hasAvailableCapacity(Sequence[] gatingSequences, final int requiredCapacity, long cursorValue) + { + long wrapPoint = (cursorValue + requiredCapacity) - bufferSize; + long cachedGatingSequence = gatingSequenceCache.get(); + + if (wrapPoint > cachedGatingSequence || cachedGatingSequence > cursorValue) + { + long minSequence = getMinimumSequence(gatingSequences, cursorValue); + gatingSequenceCache.set(minSequence); + + if (wrapPoint > minSequence) + { + return false; + } + } + + return true; + } + + @Override + public void claim(long sequence) + { + cursor.set(sequence); + } + + @Override + public long next(Sequence[] gatingSequences) + { + long current; + long next; + + do + { + current = cursor.get(); + next = current + 1; + + long wrapPoint = next - bufferSize; + long cachedGatingSequence = gatingSequenceCache.get(); + + if (wrapPoint > cachedGatingSequence || cachedGatingSequence > current) + { + long gatingSequence = getMinimumSequence(gatingSequences, current); + + if (wrapPoint > gatingSequence) + { + LockSupport.parkNanos(1); // TODO, should we spin based on the wait strategy? + continue; + } + + gatingSequenceCache.set(gatingSequence); + } + + else if (cursor.compareAndSet(current, next)) + { + break; + } + } + while (true); + + return next; + } + + @Override + public long tryNext(Sequence[] gatingSequences) throws InsufficientCapacityException + { + long current; + long next; + + do + { + current = cursor.get(); + next = current + 1; + + if (!hasAvailableCapacity(gatingSequences, 1, current)) + { + throw InsufficientCapacityException.INSTANCE; + } + } + while (!cursor.compareAndSet(current, next)); + + return next; + } + + @Override + public long remainingCapacity(Sequence[] gatingSequences) + { + long consumed = Util.getMinimumSequence(gatingSequences, cursor.get()); + long produced = cursor.get(); + return getBufferSize() - (produced - consumed); + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/NoOpEventProcessor.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/NoOpEventProcessor.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/NoOpEventProcessor.java (working copy) @@ -0,0 +1,72 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * No operation version of a {@link EventProcessor} that simply tracks a {@link Sequence}. + * + * This is useful in tests or for pre-filling a {@link RingBuffer} from a publisher. + */ +public final class NoOpEventProcessor implements EventProcessor +{ + private final SequencerFollowingSequence sequence; + + /** + * Construct a {@link EventProcessor} that simply tracks a {@link Sequence} object. + * + * @param sequencer to track. + */ + public NoOpEventProcessor(final RingBuffer sequencer) + { + sequence = new SequencerFollowingSequence(sequencer); + } + + @Override + public Sequence getSequence() + { + return sequence; + } + + @Override + public void halt() + { + } + + @Override + public void run() + { + } + + /** + * Sequence that follows (by wrapping) another sequence + */ + private static final class SequencerFollowingSequence extends Sequence + { + private final RingBuffer sequencer; + + private SequencerFollowingSequence(final RingBuffer sequencer) + { + super(Sequencer.INITIAL_CURSOR_VALUE); + this.sequencer = sequencer; + } + + @Override + public long get() + { + return sequencer.getCursor(); + } + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/PhasedBackoffWaitStrategy.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/PhasedBackoffWaitStrategy.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/PhasedBackoffWaitStrategy.java (working copy) @@ -0,0 +1,202 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Phased wait strategy for waiting {@link EventProcessor}s on a barrier.

+ * + * This strategy can be used when throughput and low-latency are not as important as CPU resource.

+ * + * Spins, then yields, then blocks on the configured BlockingStrategy. + */ +public final class PhasedBackoffWaitStrategy implements WaitStrategy +{ + private static final int SPIN_TRIES = 10000; + private final long spinTimeoutNanos; + private final long yieldTimeoutNanos; + private BlockingStrategy lockingStrategy; + + public PhasedBackoffWaitStrategy(long spinTimeoutMillis, + long yieldTimeoutMillis, + TimeUnit units, + BlockingStrategy lockingStrategy) + { + this.spinTimeoutNanos = units.toNanos(spinTimeoutMillis); + this.yieldTimeoutNanos = spinTimeoutNanos + units.toNanos(yieldTimeoutMillis); + this.lockingStrategy = lockingStrategy; + } + + /** + * Block with wait/notifyAll semantics + */ + public static PhasedBackoffWaitStrategy withLock(long spinTimeoutMillis, + long yieldTimeoutMillis, + TimeUnit units) + { + return new PhasedBackoffWaitStrategy(spinTimeoutMillis, yieldTimeoutMillis, + units, new LockBlockingStrategy()); + } + + /** + * Block by sleeping in a loop + */ + public static PhasedBackoffWaitStrategy withSleep(long spinTimeoutMillis, + long yieldTimeoutMillis, + TimeUnit units) + { + return new PhasedBackoffWaitStrategy(spinTimeoutMillis, yieldTimeoutMillis, + units, new SleepBlockingStrategy()); + } + + @Override + public long waitFor(long sequence, Sequence cursor, Sequence dependentSequence, SequenceBarrier barrier) + throws AlertException, InterruptedException + { + long availableSequence; + long startTime = 0; + int counter = SPIN_TRIES; + + do + { + if ((availableSequence = dependentSequence.get()) >= sequence) + { + return availableSequence; + } + + if (0 == --counter) + { + if (0 == startTime) + { + startTime = System.nanoTime(); + } + else + { + long timeDelta = System.nanoTime() - startTime; + if (timeDelta > yieldTimeoutNanos) + { + return lockingStrategy.waitOnLock(sequence, cursor, dependentSequence, barrier); + } + else if (timeDelta > spinTimeoutNanos) + { + Thread.yield(); + } + } + counter = SPIN_TRIES; + } + } + while (true); + } + + @Override + public void signalAllWhenBlocking() + { + lockingStrategy.signalAllWhenBlocking(); + } + + private interface BlockingStrategy + { + long waitOnLock(long sequence, + Sequence cursorSequence, + Sequence dependentSequence, SequenceBarrier barrier) + throws AlertException, InterruptedException; + + void signalAllWhenBlocking(); + } + + private static class LockBlockingStrategy implements BlockingStrategy + { + private final Lock lock = new ReentrantLock(); + private final Condition processorNotifyCondition = lock.newCondition(); + private volatile int numWaiters = 0; + + @Override + public long waitOnLock(long sequence, + Sequence cursorSequence, + Sequence dependentSequence, + SequenceBarrier barrier) throws AlertException, InterruptedException + { + long availableSequence; + lock.lock(); + try + { + ++numWaiters; + while ((availableSequence = cursorSequence.get()) < sequence) + { + barrier.checkAlert(); + processorNotifyCondition.await(1, TimeUnit.MILLISECONDS); + } + } + finally + { + --numWaiters; + lock.unlock(); + } + + while ((availableSequence = dependentSequence.get()) < sequence) + { + barrier.checkAlert(); + } + + return availableSequence; + } + + @Override + public void signalAllWhenBlocking() + { + if (0 != numWaiters) + { + lock.lock(); + try + { + processorNotifyCondition.signalAll(); + } + finally + { + lock.unlock(); + } + } + } + } + + private static class SleepBlockingStrategy implements BlockingStrategy + { + public long waitOnLock(final long sequence, + Sequence cursorSequence, + final Sequence dependentSequence, final SequenceBarrier barrier) + throws AlertException, InterruptedException + { + long availableSequence; + + while ((availableSequence = dependentSequence.get()) < sequence) + { + LockSupport.parkNanos(1); + } + + return availableSequence; + } + + @Override + public void signalAllWhenBlocking() + { + } + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/ProcessingSequenceBarrier.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/ProcessingSequenceBarrier.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/ProcessingSequenceBarrier.java (working copy) @@ -0,0 +1,88 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + + +/** + * {@link SequenceBarrier} handed out for gating {@link EventProcessor}s on a cursor sequence and optional dependent {@link EventProcessor}(s), + * using the given WaitStrategy. + */ +final class ProcessingSequenceBarrier implements SequenceBarrier +{ + private final WaitStrategy waitStrategy; + private final Sequence dependentSequence; + private volatile boolean alerted = false; + private Sequence cursorSequence; + + public ProcessingSequenceBarrier(final WaitStrategy waitStrategy, + final Sequence cursorSequence, + final Sequence[] dependentSequences) + { + this.waitStrategy = waitStrategy; + this.cursorSequence = cursorSequence; + if (0 == dependentSequences.length) + { + dependentSequence = cursorSequence; + } + else + { + dependentSequence = new FixedSequenceGroup(dependentSequences); + } + } + + @Override + public long waitFor(final long sequence) + throws AlertException, InterruptedException + { + checkAlert(); + + return waitStrategy.waitFor(sequence, cursorSequence, dependentSequence, this); + } + + @Override + public long getCursor() + { + return dependentSequence.get(); + } + + @Override + public boolean isAlerted() + { + return alerted; + } + + @Override + public void alert() + { + alerted = true; + waitStrategy.signalAllWhenBlocking(); + } + + @Override + public void clearAlert() + { + alerted = false; + } + + @Override + public void checkAlert() throws AlertException + { + if (alerted) + { + throw AlertException.INSTANCE; + } + } +} \ No newline at end of file Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/Publisher.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/Publisher.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/Publisher.java (working copy) @@ -0,0 +1,47 @@ +/* + * Copyright 2012 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * The Publisher interface allows different strategies for tracking the slots in the RingBuffer + */ +interface Publisher +{ + /** + * Publishes a sequence to the buffer. Call when the event has been filled. + * + * @param sequence + */ + void publish(long sequence); + + /** + * Confirms if a sequence is published and the event is available for use; non-blocking. + * + * @param sequence of the buffer to check + * @return true if the sequence is available for use, false if not + */ + boolean isAvailable(long sequence); + + /** + * Ensure a given sequence has been published and the event is now available.

+ * + * Blocks if the sequence is not available yet. + * + * @param sequence of the event to wait for + */ + void ensureAvailable(long sequence); + +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/RingBuffer.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/RingBuffer.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/RingBuffer.java (working copy) @@ -0,0 +1,655 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import org.apache.logging.log4j.async.com.lmax.disruptor.dsl.ProducerType; +import org.apache.logging.log4j.async.com.lmax.disruptor.util.Util; + + +/** + * Ring based store of reusable entries containing the data representing + * an event being exchanged between event publisher and {@link EventProcessor}s. + * + * @param implementation storing the data for sharing during exchange or parallel coordination of an event. + */ +public final class RingBuffer +{ + public static final long INITIAL_CURSOR_VALUE = Sequence.INITIAL_VALUE; + @SuppressWarnings("rawtypes") + private static final AtomicReferenceFieldUpdater SEQUENCE_UPDATER = + AtomicReferenceFieldUpdater.newUpdater(RingBuffer.class, Sequence[].class, "gatingSequences"); + private final int indexMask; + private final Object[] entries; + private final Sequence cursor; + private final int bufferSize; + private final Publisher publisher; + private final Sequencer sequencer; + private final WaitStrategy waitStrategy; + protected volatile Sequence[] gatingSequences = new Sequence[0]; + + /** + * Construct a RingBuffer with the full option set. + * + * @param eventFactory to newInstance entries for filling the RingBuffer + * @param sequencer sequencer to handle the ordering of events moving through the RingBuffer. + * @param waitStrategy + * + * @throws IllegalArgumentException if bufferSize is less than 1 and not a power of 2 + */ + private RingBuffer(EventFactory eventFactory, + Sequence cursor, + Sequencer sequencer, + Publisher publisher, + WaitStrategy waitStrategy) + { + this.sequencer = sequencer; + this.waitStrategy = waitStrategy; + this.bufferSize = sequencer.getBufferSize(); + this.cursor = cursor; + + if (bufferSize < 1) + { + throw new IllegalArgumentException("bufferSize must not be less than 1"); + } + if (Integer.bitCount(bufferSize) != 1) + { + throw new IllegalArgumentException("bufferSize must be a power of 2"); + } + + this.indexMask = bufferSize - 1; + this.publisher = publisher; + this.entries = new Object[sequencer.getBufferSize()]; + fill(eventFactory); + } + + /** + * Create a new multiple producer RingBuffer with the specified wait strategy. + * + * @see MultiProducerSequencer + * @param factory used to create the events within the ring buffer. + * @param bufferSize number of elements to create within the ring buffer. + * @param waitStrategy used to determine how to wait for new elements to become available. + * @throws IllegalArgumentException if bufferSize is less than 1 and not a power of 2 + */ + public static RingBuffer createMultiProducer(EventFactory factory, + int bufferSize, + WaitStrategy waitStrategy) + { + MultiProducerSequencer sequencer = new MultiProducerSequencer(bufferSize, waitStrategy); + MultiProducerPublisher publisher = new MultiProducerPublisher(bufferSize, waitStrategy); + + RingBuffer ringBuffer = new RingBuffer(factory, sequencer.getCursorSequence(), + sequencer, publisher, waitStrategy); + + return ringBuffer; + } + + /** + * Create a new multiple producer RingBuffer using the default wait strategy {@link BlockingWaitStrategy}. + * + * @see MultiProducerSequencer + * @param factory used to create the events within the ring buffer. + * @param bufferSize number of elements to create within the ring buffer. + * @throws IllegalArgumentException if bufferSize is less than 1 and not a power of 2 + */ + public static RingBuffer createMultiProducer(EventFactory factory, int bufferSize) + { + return createMultiProducer(factory, bufferSize, new BlockingWaitStrategy()); + } + + /** + * Create a new single producer RingBuffer with the specified wait strategy. + * + * @see SingleProducerSequencer + * @param factory used to create the events within the ring buffer. + * @param bufferSize number of elements to create within the ring buffer. + * @param waitStrategy used to determine how to wait for new elements to become available. + * @throws IllegalArgumentException if bufferSize is less than 1 and not a power of 2 + */ + public static RingBuffer createSingleProducer(EventFactory factory, + int bufferSize, + WaitStrategy waitStrategy) + { + SingleProducerSequencer sequencer = new SingleProducerSequencer(bufferSize, waitStrategy); + SingleProducerPublisher publisher = new SingleProducerPublisher(waitStrategy); + + RingBuffer ringBuffer = new RingBuffer(factory, publisher.getCursorSequence(), + sequencer, publisher, waitStrategy); + + return ringBuffer; + } + + /** + * Create a new single producer RingBuffer using the default wait strategy {@link BlockingWaitStrategy}. + * + * @see MultiProducerSequencer + * @param factory used to create the events within the ring buffer. + * @param bufferSize number of elements to create within the ring buffer. + * @throws IllegalArgumentException if bufferSize is less than 1 and not a power of 2 + */ + public static RingBuffer createSingleProducer(EventFactory factory, int bufferSize) + { + return createSingleProducer(factory, bufferSize, new BlockingWaitStrategy()); + } + + /** + * Create a new Ring Buffer with the specified producer type (SINGLE or MULTI) + * + * @param producerType producer type to use {@link ProducerType}. + * @param factory used to create events within the ring buffer. + * @param bufferSize number of elements to create within the ring buffer. + * @param waitStrategy used to determine how to wait for new elements to become available. + * @throws IllegalArgumentException if bufferSize is less than 1 and not a power of 2 + */ + public static RingBuffer create(ProducerType producerType, + EventFactory factory, + int bufferSize, + WaitStrategy waitStrategy) + { + switch (producerType) + { + case SINGLE: + return createSingleProducer(factory, bufferSize, waitStrategy); + case MULTI: + return createMultiProducer(factory, bufferSize, waitStrategy); + default: + throw new IllegalStateException(producerType.toString()); + } + } + + /** + *

Get the event for a given sequence in the RingBuffer. This method will wait until the + * value is published before returning. This method should only be used by {@link EventProcessor}s + * that are reading values out of the ring buffer. Publishing code should use the + * {@link RingBuffer#getPreallocated(long)} call to get a handle onto the preallocated event. + * + *

The call implements the appropriate load fence to ensure that the data within the event + * is visible after this call completes. + * + * @param sequence for the event + * @return the event that visibily published by the producer + */ + @SuppressWarnings("unchecked") + public E getPublished(long sequence) + { + publisher.ensureAvailable(sequence); + return (E)entries[(int)sequence & indexMask]; + } + + /** + * Increment and return the next sequence for the ring buffer. Calls of this + * method should ensure that they always publish the sequence afterward. E.g. + *

+     * long sequence = ringBuffer.next();
+     * try {
+     *     Event e = ringBuffer.getPreallocated(sequence);
+     *     // Do some work with the event.
+     * } finally {
+     *     ringBuffer.publish(sequence);
+     * }
+     * 
+ * @see RingBuffer#publish(long) + * @see RingBuffer#getPreallocated(long) + * @return The next sequence to publish to. + */ + public long next() + { + return sequencer.next(gatingSequences); + } + + /** + *

Increment and return the next sequence for the ring buffer. Calls of this + * method should ensure that they always publish the sequence afterward. E.g. + *

+     * long sequence = ringBuffer.next();
+     * try {
+     *     Event e = ringBuffer.getPreallocated(sequence);
+     *     // Do some work with the event.
+     * } finally {
+     *     ringBuffer.publish(sequence);
+     * }
+     * 
+ *

This method will not block if there is not space available in the ring + * buffer, instead it will throw an {@link InsufficientCapacityException}. + * + * + * @see RingBuffer#publish(long) + * @see RingBuffer#getPreallocated(long) + * @return The next sequence to publish to. + * @throws InsufficientCapacityException + */ + public long tryNext() throws InsufficientCapacityException + { + return sequencer.tryNext(gatingSequences); + } + + /** + * Resets the cursor to a specific value. This can be applied at any time, but it is worth not + * that it is a racy thing to do and should only be used in controlled circumstances. E.g. during + * initialisation. + * + * @param sequence The sequence to reset too. + * @throws IllegalStateException If any gating sequences have already been specified. + */ + public void resetTo(long sequence) + { + sequencer.claim(sequence); + publisher.publish(sequence); + } + + /** + * Sets the cursor to a specific sequence and returns the preallocated entry that is stored there. This + * is another deliberatly racy call, that should only be done in controlled circumstances, e.g. initialisation. + * + * @param sequence The sequence to claim. + * @return The preallocated event. + */ + public E claimAndGetPreallocated(long sequence) + { + sequencer.claim(sequence); + return getPreallocated(sequence); + } + + /** + * Determines if a particular entry has been published. + * + * @param sequence The sequence to identify the entry. + * @return If the value has been published or not. + */ + public boolean isPublished(long sequence) + { + return publisher.isAvailable(sequence); + } + + /** + * Add the specified gating sequences to this instance of the Disruptor. They will + * safely and atomically added to the list of gating sequences. + * + * @param gatingSequences The sequences to add. + */ + public void addGatingSequences(Sequence... gatingSequences) + { + SequenceGroups.addSequences(this, SEQUENCE_UPDATER, this, gatingSequences); + } + + /** + * Get the minimum sequence value from all of the gating sequences + * added to this ringBuffer. + * + * @return The minimum gating sequence or the cursor sequence if + * no sequences have been added. + */ + public long getMinimumGatingSequence() + { + return Util.getMinimumSequence(gatingSequences, cursor.get()); + } + + /** + * Remove the specified sequence from this ringBuffer. + * + * @param sequence to be removed. + * @return true if this sequence was found, false otherwise. + */ + public boolean removeGatingSequence(Sequence sequence) + { + return SequenceGroups.removeSequence(this, SEQUENCE_UPDATER, sequence); + } + + /** + * Create a new SequenceBarrier to be used by an EventProcessor to track which messages + * are available to be read from the ring buffer given a list of sequences to track. + * + * @see SequenceBarrier + * @param sequencesToTrack + * @return A sequence barrier that will track the specified sequences. + */ + public SequenceBarrier newBarrier(Sequence... sequencesToTrack) + { + return new ProcessingSequenceBarrier(waitStrategy, cursor, sequencesToTrack); + } + + /** + * Get the current cursor value for the ring buffer. The cursor value is + * the last value that was published, or the highest available sequence + * that can be consumed. + */ + public final long getCursor() + { + return cursor.get(); + } + + /** + * The size of the buffer. + */ + public int getBufferSize() + { + return bufferSize; + } + + /** + * Given specified requiredCapacity determines if that amount of space + * is available. Note, you can not assume that if this method returns true + * that a call to {@link RingBuffer#next()} will not block. Especially true if this + * ring buffer is set up to handle multiple producers. + * + * @param requiredCapacity The capacity to check for. + * @return true If the specified requiredCapacity is available + * false if now. + */ + public boolean hasAvailableCapacity(int requiredCapacity) + { + return sequencer.hasAvailableCapacity(gatingSequences, requiredCapacity); + } + + + /** + * Publishes an event to the ring buffer. It handles + * claiming the next sequence, getting the current (uninitialised) + * event from the ring buffer and publishing the claimed sequence + * after translation. + * + * @param translator The user specified translation for the event + */ + public void publishEvent(EventTranslator translator) + { + final long sequence = sequencer.next(gatingSequences); + translateAndPublish(translator, sequence); + } + + /** + * Attempts to publish an event to the ring buffer. It handles + * claiming the next sequence, getting the current (uninitialised) + * event from the ring buffer and publishing the claimed sequence + * after translation. Will return false if specified capacity + * was not available. + * + * @param translator The user specified translation for the event + * @param capacity The capacity that should be available before publishing + * @return true if the value was published, false if there was insufficient + * capacity. + */ + public boolean tryPublishEvent(EventTranslator translator, int capacity) + { + try + { + final long sequence = sequencer.tryNext(gatingSequences); + translateAndPublish(translator, sequence); + return true; + } + catch (InsufficientCapacityException e) + { + return false; + } + } + + /** + * Allows one user supplied argument. + * + * @see #publishEvent(EventTranslator) + * @param translator The user specified translation for the event + * @param arg0 A user supplied argument. + */ + public void publishEvent(EventTranslatorOneArg translator, A arg0) + { + final long sequence = sequencer.next(gatingSequences); + translateAndPublish(translator, sequence, arg0); + } + + /** + * Allows one user supplied argument. + * + * @see #tryPublishEvent(EventTranslator, int) + * @param translator The user specified translation for the event + * @param capacity The capacity that should be available before publishing + * @param arg0 A user supplied argument. + * @return true if the value was published, false if there was insufficient + * capacity. + */ + public boolean tryPublishEvent(EventTranslatorOneArg translator, int capacity, A arg0) + { + try + { + final long sequence = sequencer.tryNext(gatingSequences); + translateAndPublish(translator, sequence, arg0); + return true; + } + catch (InsufficientCapacityException e) + { + return false; + } + } + + /** + * Allows two user supplied arguments. + * + * @see #publishEvent(EventTranslator) + * @param translator The user specified translation for the event + * @param arg0 A user supplied argument. + * @param arg1 A user supplied argument. + */ + public void publishEvent(EventTranslatorTwoArg translator, A arg0, B arg1) + { + final long sequence = sequencer.next(gatingSequences); + translateAndPublish(translator, sequence, arg0, arg1); + } + + /** + * Allows two user supplied arguments. + * + * @see #tryPublishEvent(EventTranslator, int) + * @param translator The user specified translation for the event + * @param capacity The capacity that should be available before publishing + * @param arg0 A user supplied argument. + * @param arg1 A user supplied argument. + * @return true if the value was published, false if there was insufficient + * capacity. + */ + public boolean tryPublishEvent(EventTranslatorTwoArg translator, + int capacity, A arg0, B arg1) + { + try + { + final long sequence = sequencer.tryNext(gatingSequences); + translateAndPublish(translator, sequence, arg0, arg1); + return true; + } + catch (InsufficientCapacityException e) + { + return false; + } + } + + /** + * Allows three user supplied arguments + * + * @see #publishEvent(EventTranslator) + * @param translator The user specified translation for the event + * @param arg0 A user supplied argument. + * @param arg1 A user supplied argument. + * @param arg2 A user supplied argument. + */ + public void publishEvent(EventTranslatorThreeArg translator, A arg0, B arg1, C arg2) + { + final long sequence = sequencer.next(gatingSequences); + translateAndPublish(translator, sequence, arg0, arg1, arg2); + } + + /** + * Allows three user supplied arguments + * + * @see #publishEvent(EventTranslator) + * @param translator The user specified translation for the event + * @param capacity The capacity that should be available before publishing + * @param arg0 A user supplied argument. + * @param arg1 A user supplied argument. + * @param arg2 A user supplied argument. + * @return true if the value was published, false if there was insufficient + * capacity. + */ + public boolean tryPublishEvent(EventTranslatorThreeArg translator, + int capacity, A arg0, B arg1, C arg2) + { + try + { + final long sequence = sequencer.tryNext(gatingSequences); + translateAndPublish(translator, sequence, arg0, arg1, arg2); + return true; + } + catch (InsufficientCapacityException e) + { + return false; + } + } + + /** + * Allows a variable number of user supplied arguments + * + * @see #publishEvent(EventTranslator) + * @param translator The user specified translation for the event + * @param args User supplied arguments. + */ + public void publishEvent(EventTranslatorVararg translator, Object...args) + { + final long sequence = sequencer.next(gatingSequences); + translateAndPublish(translator, sequence, args); + } + + /** + * Allows a variable number of user supplied arguments + * + * @see #publishEvent(EventTranslator) + * @param translator The user specified translation for the event + * @param capacity The capacity that should be available before publishing + * @param args User supplied arguments. + * @return true if the value was published, false if there was insufficient + * capacity. + */ + public boolean tryPublishEvent(EventTranslatorVararg translator, int capacity, Object...args) + { + try + { + final long sequence = sequencer.tryNext(gatingSequences); + translateAndPublish(translator, sequence, args); + return true; + } + catch (InsufficientCapacityException e) + { + return false; + } + } + + /** + * Get the object that is preallocated within the ring buffer. This differs from the {@link #getPublished(long)} \ + * in that is does not wait until the publisher indicates that object is available. This method should only be used + * by the publishing thread to get a handle on the preallocated event in order to fill it with data. + * + * @param sequence for the event + * @return event for the sequence + */ + @SuppressWarnings("unchecked") + public E getPreallocated(long sequence) + { + return (E)entries[(int)sequence & indexMask]; + } + + /** + * Publish the specified sequence. This action marks this particular + * message as being available to be read. + * + * @param sequence the sequence to publish. + */ + public void publish(long sequence) + { + publisher.publish(sequence); + } + + private void translateAndPublish(EventTranslator translator, long sequence) + { + try + { + translator.translateTo(getPreallocated(sequence), sequence); + } + finally + { + publisher.publish(sequence); + } + } + + private void translateAndPublish(EventTranslatorOneArg translator, long sequence, A arg0) + { + try + { + translator.translateTo(getPreallocated(sequence), sequence, arg0); + } + finally + { + publisher.publish(sequence); + } + } + + private void translateAndPublish(EventTranslatorTwoArg translator, long sequence, A arg0, B arg1) + { + try + { + translator.translateTo(getPreallocated(sequence), sequence, arg0, arg1); + } + finally + { + publisher.publish(sequence); + } + } + + private void translateAndPublish(EventTranslatorThreeArg translator, long sequence, + A arg0, B arg1, C arg2) + { + try + { + translator.translateTo(getPreallocated(sequence), sequence, arg0, arg1, arg2); + } + finally + { + publisher.publish(sequence); + } + } + + private void translateAndPublish(EventTranslatorVararg translator, long sequence, Object...args) + { + try + { + translator.translateTo(getPreallocated(sequence), sequence, args); + } + finally + { + publisher.publish(sequence); + } + } + + private void fill(EventFactory eventFactory) + { + for (int i = 0; i < entries.length; i++) + { + entries[i] = eventFactory.newInstance(); + } + } + + long remainingCapacity() + { + return sequencer.remainingCapacity(gatingSequences); + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/Sequence.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/Sequence.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/Sequence.java (working copy) @@ -0,0 +1,146 @@ +/* + * Copyright 2012 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +import org.apache.logging.log4j.async.com.lmax.disruptor.util.Util; + +import sun.misc.Unsafe; + +/** + *

Concurrent sequence class used for tracking the progress of + * the ring buffer and event processors. Support a number + * of concurrent operations including CAS and order writes. + * + *

Also attempts to be more efficient with regards to false + * sharing by adding padding around the volatile field. + */ +public class Sequence +{ + static final long INITIAL_VALUE = -1L; + private static final Unsafe unsafe; + private static final long valueOffset; + + static + { + unsafe = Util.getUnsafe(); + final int base = unsafe.arrayBaseOffset(long[].class); + final int scale = unsafe.arrayIndexScale(long[].class); + valueOffset = base + (scale * 7); + } + + private final long[] paddedValue = new long[15]; + + /** + * Create a sequence initialised to -1. + */ + public Sequence() + { + this(INITIAL_VALUE); + } + + /** + * Create a sequence with a specified initial value. + * + * @param initialValue The initial value for this sequence. + */ + public Sequence(final long initialValue) + { + unsafe.putOrderedLong(paddedValue, valueOffset, initialValue); + } + + /** + * Perform a volatile read of this sequence's value. + * + * @return The current value of the sequence. + */ + public long get() + { + return unsafe.getLongVolatile(paddedValue, valueOffset); + } + + /** + * Perform an ordered write of this sequence. The intent is + * a Store/Store barrier between this write and any previous + * store. + * + * @param value The new value for the sequence. + */ + public void set(final long value) + { + unsafe.putOrderedLong(paddedValue, valueOffset, value); + } + + /** + * Performs a volatile write of this sequence. The intent is + * a Store/Store barrier between this write and any previous + * write and a Store/Load barrier between this write and any + * subsequent volatile read. + * + * @param value The new value for the sequence. + */ + public void setVolatile(final long value) + { + unsafe.putLongVolatile(paddedValue, valueOffset, value); + } + + /** + * Perform a compare and set operation on the sequence. + * + * @param expectedValue The expected current value. + * @param newValue The value to update to. + * @return true if the operation succeeds, false otherwise. + */ + public boolean compareAndSet(final long expectedValue, final long newValue) + { + return unsafe.compareAndSwapLong(paddedValue, valueOffset, expectedValue, newValue); + } + + /** + * Atomically increment the sequence by one. + * + * @return The value after the increment + */ + public long incrementAndGet() + { + return addAndGet(1L); + } + + /** + * Atomically add the supplied value. + * + * @param increment The value to add to the sequence. + * @return The value after the increment. + */ + public long addAndGet(final long increment) + { + long currentValue; + long newValue; + + do + { + currentValue = get(); + newValue = currentValue + increment; + } + while (!compareAndSet(currentValue, newValue)); + + return newValue; + } + + public String toString() + { + return Long.toString(get()); + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SequenceBarrier.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SequenceBarrier.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SequenceBarrier.java (working copy) @@ -0,0 +1,65 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + + +/** + * Coordination barrier for tracking the cursor for publishers and sequence of + * dependent {@link EventProcessor}s for processing a data structure + */ +public interface SequenceBarrier +{ + /** + * Wait for the given sequence to be available for consumption. + * + * @param sequence to wait for + * @return the sequence up to which is available + * @throws AlertException if a status change has occurred for the Disruptor + * @throws InterruptedException if the thread needs awaking on a condition variable. + */ + long waitFor(long sequence) throws AlertException, InterruptedException; + + /** + * Get the current cursor value that can be read. + * + * @return value of the cursor for entries that have been published. + */ + long getCursor(); + + /** + * The current alert status for the barrier. + * + * @return true if in alert otherwise false. + */ + boolean isAlerted(); + + /** + * Alert the {@link EventProcessor}s of a status change and stay in this status until cleared. + */ + void alert(); + + /** + * Clear the current alert status. + */ + void clearAlert(); + + /** + * Check if an alert has been raised and throw an {@link AlertException} if it has. + * + * @throws AlertException if alert has been raised. + */ + void checkAlert() throws AlertException; +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SequenceGroup.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SequenceGroup.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SequenceGroup.java (working copy) @@ -0,0 +1,126 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import org.apache.logging.log4j.async.com.lmax.disruptor.util.Util; + + +/** + * A {@link Sequence} group that can dynamically have {@link Sequence}s added and removed while being + * thread safe. + *

+ * The {@link SequenceGroup#get()} and {@link SequenceGroup#set(long)} methods are lock free and can be + * concurrently be called with the {@link SequenceGroup#add(Sequence)} and {@link SequenceGroup#remove(Sequence)}. + */ +public final class SequenceGroup extends Sequence +{ + private static final AtomicReferenceFieldUpdater SEQUENCE_UPDATER = + AtomicReferenceFieldUpdater.newUpdater(SequenceGroup.class, Sequence[].class, "sequences"); + private volatile Sequence[] sequences = new Sequence[0]; + + /** + * Default Constructor + */ + public SequenceGroup() + { + super(-1); + } + + /** + * Get the minimum sequence value for the group. + * + * @return the minimum sequence value for the group. + */ + @Override + public long get() + { + return Util.getMinimumSequence(sequences); + } + + /** + * Set all {@link Sequence}s in the group to a given value. + * + * @param value to set the group of sequences to. + */ + @Override + public void set(final long value) + { + final Sequence[] sequences = this.sequences; + for (int i = 0, size = sequences.length; i < size; i++) + { + sequences[i].set(value); + } + } + + /** + * Add a {@link Sequence} into this aggregate. This should only be used during + * initialisation. Use {@link SequenceGroup#addWhileRunning(RingBuffer, Sequence)} + * + * @see SequenceGroup#addWhileRunning(RingBuffer, Sequence) + * @param sequence to be added to the aggregate. + */ + public void add(final Sequence sequence) + { + Sequence[] oldSequences; + Sequence[] newSequences; + do + { + oldSequences = sequences; + final int oldSize = oldSequences.length; + newSequences = new Sequence[oldSize + 1]; + System.arraycopy(oldSequences, 0, newSequences, 0, oldSize); + newSequences[oldSize] = sequence; + } + while (!SEQUENCE_UPDATER.compareAndSet(this, oldSequences, newSequences)); + } + + /** + * Remove the first occurrence of the {@link Sequence} from this aggregate. + * + * @param sequence to be removed from this aggregate. + * @return true if the sequence was removed otherwise false. + */ + public boolean remove(final Sequence sequence) + { + return SequenceGroups.removeSequence(this, SEQUENCE_UPDATER, sequence); + } + + /** + * Get the size of the group. + * + * @return the size of the group. + */ + public int size() + { + return sequences.length; + } + + /** + * Adds a sequence to the sequence group after threads have started to publish to + * the Disruptor. It will set the sequences to cursor value of the ringBuffer + * just after adding them. This should prevent any nasty rewind/wrapping effects. + * + * @param ringBuffer The ringBuffer that the owner of this sequence group will + * be pulling it's events from. + * @param sequence The sequence to add. + */ + public void addWhileRunning(RingBuffer ringBuffer, Sequence sequence) + { + SequenceGroups.addSequences(this, SEQUENCE_UPDATER, ringBuffer, sequence); + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SequenceGroups.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SequenceGroups.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SequenceGroups.java (working copy) @@ -0,0 +1,106 @@ +/* + * Copyright 2012 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +import static java.util.Arrays.copyOf; + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +/** + * Provides static methods for managing a {@link SequenceGroup} object. + */ +class SequenceGroups +{ + static void addSequences(final T holder, + final AtomicReferenceFieldUpdater updater, + final RingBuffer cursor, + final Sequence... sequencesToAdd) + { + long cursorSequence; + Sequence[] updatedSequences; + Sequence[] currentSequences; + + do + { + currentSequences = updater.get(holder); + updatedSequences = copyOf(currentSequences, currentSequences.length + sequencesToAdd.length); + cursorSequence = cursor.getCursor(); + + int index = currentSequences.length; + for (Sequence sequence : sequencesToAdd) + { + sequence.set(cursorSequence); + updatedSequences[index++] = sequence; + } + } + while (!updater.compareAndSet(holder, currentSequences, updatedSequences)); + + cursorSequence = cursor.getCursor(); + for (Sequence sequence : sequencesToAdd) + { + sequence.set(cursorSequence); + } + } + + static boolean removeSequence(final T holder, + final AtomicReferenceFieldUpdater sequenceUpdater, + final Sequence sequence) + { + int numToRemove; + Sequence[] oldSequences; + Sequence[] newSequences; + + do + { + oldSequences = sequenceUpdater.get(holder); + + numToRemove = countMatching(oldSequences, sequence); + + if (0 == numToRemove) + { + break; + } + + final int oldSize = oldSequences.length; + newSequences = new Sequence[oldSize - numToRemove]; + + for (int i = 0, pos = 0; i < oldSize; i++) + { + final Sequence testSequence = oldSequences[i]; + if (sequence != testSequence) + { + newSequences[pos++] = testSequence; + } + } + } + while (!sequenceUpdater.compareAndSet(holder, oldSequences, newSequences)); + + return numToRemove != 0; + } + + private static int countMatching(T[] values, final T toMatch) + { + int numToRemove = 0; + for (T value : values) + { + if (value == toMatch) // Specifically uses identity + { + numToRemove++; + } + } + return numToRemove; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SequenceReportingEventHandler.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SequenceReportingEventHandler.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SequenceReportingEventHandler.java (working copy) @@ -0,0 +1,37 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * Used by the {@link BatchEventProcessor} to set a callback allowing the {@link EventHandler} to notify + * when it has finished consuming an event if this happens after the {@link EventHandler#onEvent(Object, long, boolean)} call. + *

+ * Typically this would be used when the handler is performing some sort of batching operation such as writing to an IO + * device; after the operation has completed, the implementation should call {@link Sequence#set} to update the + * sequence and allow other processes that are dependent on this handler to progress. + * + * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. + */ +public interface SequenceReportingEventHandler + extends EventHandler +{ + /** + * Call by the {@link BatchEventProcessor} to setup the callback. + * + * @param sequenceCallback callback on which to notify the {@link BatchEventProcessor} that the sequence has progressed. + */ + void setSequenceCallback(final Sequence sequenceCallback); +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/Sequencer.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/Sequencer.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/Sequencer.java (working copy) @@ -0,0 +1,77 @@ +/* + * Copyright 2012 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * Coordinates claiming sequences for access to a data structure while tracking dependent {@link Sequence}s + */ +interface Sequencer +{ + /** Set to -1 as sequence starting point */ + public static final long INITIAL_CURSOR_VALUE = -1L; + + /** + * The capacity of the data structure to hold entries. + * + * @return the size of the RingBuffer. + */ + int getBufferSize(); + + /** + * Has the buffer got capacity to allocate another sequence. This is a concurrent + * method so the response should only be taken as an indication of available capacity. + * @param gatingSequences to gate on + * @param requiredCapacity in the buffer + * + * @return true if the buffer has the capacity to allocate the next sequence otherwise false. + */ + boolean hasAvailableCapacity(Sequence[] gatingSequences, final int requiredCapacity); + + /** + * Claim the next event in sequence for publishing. + * @param gatingSequences to gate on + * + * @return the claimed sequence value + */ + long next(Sequence[] gatingSequences); + + /** + * Attempt to claim the next event in sequence for publishing. Will return the + * number of the slot if there is at least requiredCapacity slots + * available. + * @param gatingSequences to gate on + * + * @return the claimed sequence value + * @throws InsufficientCapacityException + */ + long tryNext(Sequence[] gatingSequences) throws InsufficientCapacityException; + + /** + * Get the remaining capacity for this sequencer. + * @param gatingSequences to gate on + * + * @return The number of slots remaining. + */ + long remainingCapacity(Sequence[] gatingSequences); + + /** + * Claim a specific sequence. Only used if initialising the ring buffer to + * a specific value. + * + * @param sequence The sequence to initialise too. + */ + void claim(long sequence); +} \ No newline at end of file Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SingleProducerPublisher.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SingleProducerPublisher.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SingleProducerPublisher.java (working copy) @@ -0,0 +1,55 @@ +/* + * Copyright 2012 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * A Publisher optimised for publishing events from a single thread.

+ * + * Generally not safe for use from multiple threads as it does not implement any barriers. + */ +class SingleProducerPublisher implements Publisher +{ + private final WaitStrategy waitStrategy; + private final Sequence cursor = new Sequence(Sequencer.INITIAL_CURSOR_VALUE); + + public SingleProducerPublisher(WaitStrategy waitStrategy) + { + this.waitStrategy = waitStrategy; + } + + @Override + public void publish(long sequence) + { + cursor.set(sequence); + waitStrategy.signalAllWhenBlocking(); + } + + @Override + public void ensureAvailable(long sequence) + { + } + + @Override + public boolean isAvailable(long sequence) + { + return sequence <= cursor.get(); + } + + Sequence getCursorSequence() + { + return cursor; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SingleProducerSequencer.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SingleProducerSequencer.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SingleProducerSequencer.java (working copy) @@ -0,0 +1,144 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +import static org.apache.logging.log4j.async.com.lmax.disruptor.util.Util.*; + +import java.util.concurrent.locks.LockSupport; + +import org.apache.logging.log4j.async.com.lmax.disruptor.util.Util; + + + +/** + * Coordinator for claiming sequences for access to a data structure while tracking dependent {@link Sequence}s.

+ * + * Generally not safe for use from multiple threads as it does not implement any barriers. + */ +class SingleProducerSequencer implements Sequencer +{ + @SuppressWarnings("unused") + private final WaitStrategy waitStrategy; + private final int bufferSize; + + @SuppressWarnings("unused") + private static class Padding + { + /** Set to -1 as sequence starting point */ + public long nextValue = Sequence.INITIAL_VALUE, cachedValue = Sequence.INITIAL_VALUE, p2, p3, p4, p5, p6, p7; + } + + private final Padding pad = new Padding(); + + /** + * Construct a Sequencer with the selected wait strategy and buffer size. + * + * @param bufferSize the size of the buffer that this will sequence over. + * @param waitStrategy for those waiting on sequences. + */ + public SingleProducerSequencer(int bufferSize, final WaitStrategy waitStrategy) + { + this.bufferSize = bufferSize; + this.waitStrategy = waitStrategy; + } + + @Override + public int getBufferSize() + { + return bufferSize; + } + + long getNextValue() + { + return pad.nextValue; + } + + @Override + public boolean hasAvailableCapacity(Sequence[] gatingSequences, final int requiredCapacity) + { + long nextValue = pad.nextValue; + + long wrapPoint = (nextValue + requiredCapacity) - bufferSize; + long cachedGatingSequence = pad.cachedValue; + + if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue) + { + long minSequence = getMinimumSequence(gatingSequences, nextValue); + pad.cachedValue = minSequence; + + if (wrapPoint > minSequence) + { + return false; + } + } + + return true; + } + + @Override + public long next(Sequence[] gatingSequences) + { + long nextValue = pad.nextValue; + + long nextSequence = nextValue + 1; + long wrapPoint = nextSequence - bufferSize; + long cachedGatingSequence = pad.cachedValue; + + if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue) + { + long minSequence; + while (wrapPoint > (minSequence = getMinimumSequence(gatingSequences, nextValue))) + { + LockSupport.parkNanos(1L); // TODO: Use waitStrategy to spin? + } + + pad.cachedValue = minSequence; + } + + pad.nextValue = nextSequence; + + return nextSequence; + } + + @Override + public long tryNext(Sequence[] gatingSequences) throws InsufficientCapacityException + { + if (!hasAvailableCapacity(gatingSequences, 1)) + { + throw InsufficientCapacityException.INSTANCE; + } + + long nextSequence = ++pad.nextValue; + + return nextSequence; + } + + @Override + public long remainingCapacity(Sequence[] gatingSequences) + { + long nextValue = pad.nextValue; + + long consumed = Util.getMinimumSequence(gatingSequences, nextValue); + long produced = nextValue; + return getBufferSize() - (produced - consumed); + } + + @Override + public void claim(long sequence) + { + pad.nextValue = sequence; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SleepingWaitStrategy.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SleepingWaitStrategy.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SleepingWaitStrategy.java (working copy) @@ -0,0 +1,71 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +import java.util.concurrent.locks.LockSupport; + +/** + * Sleeping strategy that initially spins, then uses a Thread.yield(), and eventually for the minimum number of nanos + * the OS and JVM will allow while the {@link org.apache.logging.log4j.async.com.lmax.disruptor.EventProcessor}s are waiting on a barrier. + * + * This strategy is a good compromise between performance and CPU resource. Latency spikes can occur after quiet periods. + */ +public final class SleepingWaitStrategy implements WaitStrategy +{ + private static final int RETRIES = 200; + + @Override + public long waitFor(final long sequence, Sequence cursor, final Sequence dependentSequence, final SequenceBarrier barrier) + throws AlertException, InterruptedException + { + long availableSequence; + int counter = RETRIES; + + while ((availableSequence = dependentSequence.get()) < sequence) + { + counter = applyWaitMethod(barrier, counter); + } + + return availableSequence; + } + + @Override + public void signalAllWhenBlocking() + { + } + + private int applyWaitMethod(final SequenceBarrier barrier, int counter) + throws AlertException + { + barrier.checkAlert(); + + if (counter > 100) + { + --counter; + } + else if (counter > 0) + { + --counter; + Thread.yield(); + } + else + { + LockSupport.parkNanos(1L); + } + + return counter; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/TimeoutBlockingWaitStrategy.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/TimeoutBlockingWaitStrategy.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/TimeoutBlockingWaitStrategy.java (working copy) @@ -0,0 +1,78 @@ +package org.apache.logging.log4j.async.com.lmax.disruptor; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.logging.log4j.async.com.lmax.disruptor.AlertException; +import org.apache.logging.log4j.async.com.lmax.disruptor.Sequence; +import org.apache.logging.log4j.async.com.lmax.disruptor.SequenceBarrier; +import org.apache.logging.log4j.async.com.lmax.disruptor.WaitStrategy; + + +public class TimeoutBlockingWaitStrategy implements WaitStrategy +{ + private final Lock lock = new ReentrantLock(); + private final Condition processorNotifyCondition = lock.newCondition(); + private final long timeoutInNanos; + + public TimeoutBlockingWaitStrategy(final long timeout, final TimeUnit units) + { + timeoutInNanos = units.toNanos(timeout); + } + + @Override + public long waitFor(final long sequence, + final Sequence cursorSequence, + final Sequence dependentSequence, + final SequenceBarrier barrier) + throws AlertException, InterruptedException + { + long nanos = timeoutInNanos; + + long availableSequence; + if ((availableSequence = cursorSequence.get()) < sequence) + { + lock.lock(); + try + { + while ((availableSequence = cursorSequence.get()) < sequence) + { + barrier.checkAlert(); + nanos = processorNotifyCondition.awaitNanos(nanos); + if (nanos <= 0) + { + return availableSequence; + } + } + } + finally + { + lock.unlock(); + } + } + + while ((availableSequence = dependentSequence.get()) < sequence) + { + barrier.checkAlert(); + } + + return availableSequence; + } + + @Override + public void signalAllWhenBlocking() + { + lock.lock(); + try + { + processorNotifyCondition.signalAll(); + } + finally + { + lock.unlock(); + } + } + +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/TimeoutHandler.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/TimeoutHandler.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/TimeoutHandler.java (working copy) @@ -0,0 +1,6 @@ +package org.apache.logging.log4j.async.com.lmax.disruptor; + +public interface TimeoutHandler +{ + void onTimeout(long sequence) throws Exception; +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/WaitStrategy.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/WaitStrategy.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/WaitStrategy.java (working copy) @@ -0,0 +1,47 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + + +/** + * Strategy employed for making {@link EventProcessor}s wait on a cursor {@link Sequence}. + */ +public interface WaitStrategy +{ + /** + * Wait for the given sequence to be available. It is possible for this method to return a value + * less than the sequence number supplied depending on the implementation of the WaitStrategy. A common + * use for this is to signal a timeout. Any EventProcessor that is using a WaitStragegy to get notifications + * about message becoming available should remember to handle this case. The {@link BatchEventProcessor} explicitly + * handles this case and will signal a timeout if required. + * + * @param sequence to be waited on. + * @param cursor the main sequence from ringbuffer. Wait/notify strategies will + * need this as it's the only sequence that is also notified upon update. + * @param dependentSequence on which to wait. + * @param barrier the processor is waiting on. + * @return the sequence that is available which may be greater than the requested sequence. + * @throws AlertException if the status of the Disruptor has changed. + * @throws InterruptedException if the thread is interrupted. + */ + long waitFor(long sequence, Sequence cursor, Sequence dependentSequence, SequenceBarrier barrier) + throws AlertException, InterruptedException; + + /** + * Implementations should signal the waiting {@link EventProcessor}s that the cursor has advanced. + */ + void signalAllWhenBlocking(); +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/WorkHandler.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/WorkHandler.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/WorkHandler.java (working copy) @@ -0,0 +1,33 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +/** + * Callback interface to be implemented for processing units of work as they become available in the {@link RingBuffer}. + * + * @param event implementation storing the data for sharing during exchange or parallel coordination of an event. + * @see WorkerPool + */ +public interface WorkHandler +{ + /** + * Callback to indicate a unit of work needs to be processed. + * + * @param event published to the {@link RingBuffer} + * @throws Exception if the {@link WorkHandler} would like the exception handled further up the chain. + */ + void onEvent(T event) throws Exception; +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/WorkProcessor.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/WorkProcessor.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/WorkProcessor.java (working copy) @@ -0,0 +1,165 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A {@link WorkProcessor} wraps a single {@link WorkHandler}, effectively consuming the sequence + * and ensuring appropriate barriers.

+ * + * Generally, this will be used as part of a {@link WorkerPool}. + * + * @param event implementation storing the details for the work to processed. + */ +public final class WorkProcessor + implements EventProcessor +{ + private final AtomicBoolean running = new AtomicBoolean(false); + private final Sequence sequence = new Sequence(Sequencer.INITIAL_CURSOR_VALUE); + private final RingBuffer ringBuffer; + private final SequenceBarrier sequenceBarrier; + private final WorkHandler workHandler; + private final ExceptionHandler exceptionHandler; + private final Sequence workSequence; + + /** + * Construct a {@link WorkProcessor}. + * + * @param ringBuffer to which events are published. + * @param sequenceBarrier on which it is waiting. + * @param workHandler is the delegate to which events are dispatched. + * @param exceptionHandler to be called back when an error occurs + * @param workSequence from which to claim the next event to be worked on. It should always be initialised + * as {@link Sequencer#INITIAL_CURSOR_VALUE} + */ + public WorkProcessor(final RingBuffer ringBuffer, + final SequenceBarrier sequenceBarrier, + final WorkHandler workHandler, + final ExceptionHandler exceptionHandler, + final Sequence workSequence) + { + this.ringBuffer = ringBuffer; + this.sequenceBarrier = sequenceBarrier; + this.workHandler = workHandler; + this.exceptionHandler = exceptionHandler; + this.workSequence = workSequence; + } + + @Override + public Sequence getSequence() + { + return sequence; + } + + @Override + public void halt() + { + running.set(false); + sequenceBarrier.alert(); + } + + /** + * It is ok to have another thread re-run this method after a halt(). + * + * @throws IllegalStateException if this processor is already running + */ + @Override + public void run() + { + if (!running.compareAndSet(false, true)) + { + throw new IllegalStateException("Thread is already running"); + } + sequenceBarrier.clearAlert(); + + notifyStart(); + + boolean processedSequence = true; + long nextSequence = sequence.get(); + T event = null; + while (true) + { + try + { + // if previous sequence was processed - fetch the next sequence and set + // that we have successfully processed the previous sequence + // typically, this will be true + // this prevents the sequence getting too far forward if an exception + // is thrown from the WorkHandler + if (processedSequence) + { + processedSequence = false; + nextSequence = workSequence.incrementAndGet(); + sequence.set(nextSequence - 1L); + } + + sequenceBarrier.waitFor(nextSequence); + event = ringBuffer.getPublished(nextSequence); + workHandler.onEvent(event); + + processedSequence = true; + } + catch (final AlertException ex) + { + if (!running.get()) + { + break; + } + } + catch (final Throwable ex) + { + // handle, mark as procesed, unless the exception handler threw an exception + exceptionHandler.handleEventException(ex, nextSequence, event); + processedSequence = true; + } + } + + notifyShutdown(); + + running.set(false); + } + + private void notifyStart() + { + if (workHandler instanceof LifecycleAware) + { + try + { + ((LifecycleAware)workHandler).onStart(); + } + catch (final Throwable ex) + { + exceptionHandler.handleOnStartException(ex); + } + } + } + + private void notifyShutdown() + { + if (workHandler instanceof LifecycleAware) + { + try + { + ((LifecycleAware)workHandler).onShutdown(); + } + catch (final Throwable ex) + { + exceptionHandler.handleOnShutdownException(ex); + } + } + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/WorkerPool.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/WorkerPool.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/WorkerPool.java (working copy) @@ -0,0 +1,171 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.logging.log4j.async.com.lmax.disruptor.util.Util; + + +/** + * WorkerPool contains a pool of {@link WorkProcessor}s that will consume sequences so jobs can be farmed out across a pool of workers. + * Each of the {@link WorkProcessor}s manage and calls a {@link WorkHandler} to process the events. + * + * @param event to be processed by a pool of workers + */ +public final class WorkerPool +{ + private final AtomicBoolean started = new AtomicBoolean(false); + private final Sequence workSequence = new Sequence(Sequencer.INITIAL_CURSOR_VALUE); + private final RingBuffer ringBuffer; + // WorkProcessors are created to wrap each of the provided WorkHandlers + private final WorkProcessor[] workProcessors; + + /** + * Create a worker pool to enable an array of {@link WorkHandler}s to consume published sequences. + * + * This option requires a pre-configured {@link RingBuffer} which must have {@link RingBuffer#addGatingSequences(Sequence...)} + * called before the work pool is started. + * + * @param ringBuffer of events to be consumed. + * @param sequenceBarrier on which the workers will depend. + * @param exceptionHandler to callback when an error occurs which is not handled by the {@link WorkHandler}s. + * @param workHandlers to distribute the work load across. + */ + public WorkerPool(final RingBuffer ringBuffer, + final SequenceBarrier sequenceBarrier, + final ExceptionHandler exceptionHandler, + final WorkHandler... workHandlers) + { + this.ringBuffer = ringBuffer; + final int numWorkers = workHandlers.length; + workProcessors = new WorkProcessor[numWorkers]; + + for (int i = 0; i < numWorkers; i++) + { + workProcessors[i] = new WorkProcessor(ringBuffer, + sequenceBarrier, + workHandlers[i], + exceptionHandler, + workSequence); + } + } + + /** + * Construct a work pool with an internal {@link RingBuffer} for convenience. + * + * This option does not require {@link RingBuffer#addGatingSequences(Sequence...)} to be called before the work pool is started. + * + * @param eventFactory for filling the {@link RingBuffer} + * @param exceptionHandler to callback when an error occurs which is not handled by the {@link WorkHandler}s. + * @param workHandlers to distribute the work load across. + */ + public WorkerPool(final EventFactory eventFactory, + final ExceptionHandler exceptionHandler, + final WorkHandler... workHandlers) + { + ringBuffer = RingBuffer.createMultiProducer(eventFactory, 1024, new BlockingWaitStrategy()); + final SequenceBarrier barrier = ringBuffer.newBarrier(); + final int numWorkers = workHandlers.length; + workProcessors = new WorkProcessor[numWorkers]; + + for (int i = 0; i < numWorkers; i++) + { + workProcessors[i] = new WorkProcessor(ringBuffer, + barrier, + workHandlers[i], + exceptionHandler, + workSequence); + } + + ringBuffer.addGatingSequences(getWorkerSequences()); + } + + /** + * Get an array of {@link Sequence}s representing the progress of the workers. + * + * @return an array of {@link Sequence}s representing the progress of the workers. + */ + public Sequence[] getWorkerSequences() + { + final Sequence[] sequences = new Sequence[workProcessors.length]; + for (int i = 0, size = workProcessors.length; i < size; i++) + { + sequences[i] = workProcessors[i].getSequence(); + } + + return sequences; + } + + /** + * Start the worker pool processing events in sequence. + * + * @param executor providing threads for running the workers. + * @return the {@link RingBuffer} used for the work queue. + * @throws IllegalStateException if the pool has already been started and not halted yet + */ + public RingBuffer start(final Executor executor) + { + if (!started.compareAndSet(false, true)) + { + throw new IllegalStateException("WorkerPool has already been started and cannot be restarted until halted."); + } + + final long cursor = ringBuffer.getCursor(); + workSequence.set(cursor); + + for (WorkProcessor processor : workProcessors) + { + processor.getSequence().set(cursor); + executor.execute(processor); + } + + return ringBuffer; + } + + /** + * Wait for the {@link RingBuffer} to drain of published events then halt the workers. + */ + public void drainAndHalt() + { + Sequence[] workerSequences = getWorkerSequences(); + while (ringBuffer.getCursor() > Util.getMinimumSequence(workerSequences)) + { + Thread.yield(); + } + + for (WorkProcessor processor : workProcessors) + { + processor.halt(); + } + + started.set(false); + } + + /** + * Halt all workers immediately at the end of their current cycle. + */ + public void halt() + { + for (WorkProcessor processor : workProcessors) + { + processor.halt(); + } + + started.set(false); + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/YieldingWaitStrategy.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/YieldingWaitStrategy.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/YieldingWaitStrategy.java (working copy) @@ -0,0 +1,65 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor; + + +/** + * Yielding strategy that uses a Thread.yield() for {@link org.apache.logging.log4j.async.com.lmax.disruptor.EventProcessor}s waiting on a barrier + * after an initially spinning. + * + * This strategy is a good compromise between performance and CPU resource without incurring significant latency spikes. + */ +public final class YieldingWaitStrategy implements WaitStrategy +{ + private static final int SPIN_TRIES = 100; + + @Override + public long waitFor(final long sequence, Sequence cursor, final Sequence dependentSequence, final SequenceBarrier barrier) + throws AlertException, InterruptedException + { + long availableSequence; + int counter = SPIN_TRIES; + + while ((availableSequence = dependentSequence.get()) < sequence) + { + counter = applyWaitMethod(barrier, counter); + } + + return availableSequence; + } + + @Override + public void signalAllWhenBlocking() + { + } + + private int applyWaitMethod(final SequenceBarrier barrier, int counter) + throws AlertException + { + barrier.checkAlert(); + + if (0 == counter) + { + Thread.yield(); + } + else + { + --counter; + } + + return counter; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/package.html =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/package.html (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/package.html (working copy) @@ -0,0 +1,174 @@ + + + + Disruptor + + +

+ The Disruptor is a concurrent programming framework for exchanging and coordinating work as a continuous series of events. + It can be used as an alternative to wiring processing stages together via queues. The Disruptor design has the + characteristics of generating significantly less garbage than queues and separates the concurrency concerns so + non-locking algorithms can be employed resulting in greater scalability and performance. +

+

+ It works on the principle of having a number of stages that are each single threaded with local state and memory. + No global memory exists and all communication is achieved by passing messages/state via managed ring buffers. +

+

+ Almost any graph or pipeline structure can be composed via one or more Disruptor patterns. +

+ +

+ UniCast a series of items between 1 publisher and 1 EventProcessor. +

+                                          track to prevent wrap
+                                          +------------------+
+                                          |                  |
+                                          |                  v
++----+    +-----+            +----+    +====+    +====+   +-----+
+| P1 |--->| EP1 |            | P1 |--->| RB |<---| SB |   | EP1 |
++----+    +-----+            +----+    +====+    +====+   +-----+
+                                  claim      get    ^        |
+                                                    |        |
+                                                    +--------+
+                                                      waitFor
+        
+

+

+ Sequence a series of messages from multiple publishers +

+                                         track to prevent wrap
+                                         +--------------------+
+                                         |                    |
+                                         |                    v
++----+                       +----+    +====+    +====+    +-----+
+| P1 |-------+               | P1 |--->| RB |<---| SB |    | EP1 |
++----+       |               +----+    +====+    +====+    +-----+
+             v                           ^   get    ^         |
++----+    +-----+            +----+      |          |         |
+| P2 |--->| EP1 |            | P2 |------+          +---------+
++----+    +-----+            +----+      |            waitFor
+             ^                           |
++----+       |               +----+      |
+| P3 |-------+               | P3 |------+
++----+                       +----+
+        
+

+

+ Pipeline a series of messages +

+                          +----+    +-----+    +-----+    +-----+
+                          | P1 |--->| EP1 |--->| EP2 |--->| EP3 |
+                          +----+    +-----+    +-----+    +-----+
+
+
+
+                          track to prevent wrap
+             +----------------------------------------------------------------+
+             |                                                                |
+             |                                                                v
++----+    +====+    +=====+    +-----+    +=====+    +-----+    +=====+    +-----+
+| P1 |--->| RB |    | SB1 |<---| EP1 |<---| SB2 |<---| EP2 |<---| SB3 |<---| EP3 |
++----+    +====+    +=====+    +-----+    +=====+    +-----+    +=====+    +-----+
+     claim   ^  get    |   waitFor           |   waitFor           |  waitFor
+             |         |                     |                     |
+             +---------+---------------------+---------------------+
+        
+

+

+ Multicast a series of messages to multiple EventProcessors +

+          +-----+                                        track to prevent wrap
+   +----->| EP1 |                        +--------------------+----------+----------+
+   |      +-----+                        |                    |          |          |
+   |                                     |                    v          v          v
++----+    +-----+            +----+    +====+    +====+    +-----+    +-----+    +-----+
+| P1 |--->| EP2 |            | P1 |--->| RB |<---| SB |    | EP1 |    | EP2 |    | EP3 |
++----+    +-----+            +----+    +====+    +====+    +-----+    +-----+    +-----+
+   |                              claim      get    ^         |          |          |
+   |      +-----+                                   |         |          |          |
+   +----->| EP3 |                                   +---------+----------+----------+
+          +-----+                                                 waitFor
+        
+

+

+ Replicate a message then fold back the results +

+          +-----+                               track to prevent wrap
+   +----->| EP1 |-----+                   +-------------------------------+
+   |      +-----+     |                   |                               |
+   |                  v                   |                               v
++----+             +-----+   +----+    +====+               +=====+    +-----+
+| P1 |             | EP3 |   | P1 |--->| RB |<--------------| SB2 |<---| EP3 |
++----+             +-----+   +----+    +====+               +=====+    +-----+
+   |                  ^           claim   ^  get               |   waitFor
+   |      +-----+     |                   |                    |
+   +----->| EP2 |-----+                +=====+    +-----+      |
+          +-----+                      | SB1 |<---| EP1 |<-----+
+                                       +=====+    +-----+      |
+                                          ^                    |
+                                          |       +-----+      |
+                                          +-------| EP2 |<-----+
+                                         waitFor  +-----+
+        
+

+

Code Example

+
+    // Event holder for data to be exchanged
+    public final class ValueEvent
+    {
+        private long value;
+
+        public long getValue()
+        {
+            return value;
+        }
+
+        public void setValue(final long value)
+        {
+            this.value = value;
+        }
+
+        public final static EventFactory<ValueEvent> EVENT_FACTORY = new EventFactory<ValueEvent>()
+        {
+            public ValueEvent newInstance()
+            {
+                return new ValueEvent();
+            }
+        };
+    }
+
+    // Callback handler which can be implemented by EventProcessors
+    final EventHandler<ValueEvent> eventHandler = new EventHandler<ValueEvent>()
+    {
+        public void onEvent(final ValueEvent event, final long sequence, final boolean endOfBatch)
+            throws Exception
+        {
+            // process a new event as it becomes available.
+        }
+    };
+
+    RingBuffer<ValueEvent> ringBuffer =
+        new RingBuffer<ValueEvent>(ValueEvent.EVENT_FACTORY,
+                                   new SingleThreadedClaimStrategy(BUFFER_SIZE),
+                                   new SleepingWaitStrategy());
+
+    SequenceBarrier<ValueEvent> sequenceBarrier = ringBuffer.newBarrier();
+    BatchEventProcessor<ValueEvent> batchProcessor = new BatchEventProcessor<ValueEvent>(sequenceBarrier, eventHandler);
+    ringBuffer.setGatingSequences(batchProcessor.getSequence());
+
+    // Each processor runs on a separate thread
+    EXECUTOR.submit(batchProcessor);
+
+    // Publishers claim events in sequence
+    long sequence = ringBuffer.next();
+    ValueEvent event = ringBuffer.get(sequence);
+
+    event.setValue(1234);
+
+    // publish the event so it is available to EventProcessors
+    ringBuffer.publish(sequence);
+    
+ + \ No newline at end of file Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/collections/Histogram.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/collections/Histogram.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/collections/Histogram.java (working copy) @@ -0,0 +1,390 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor.collections; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Arrays; + +/** + * Histogram for tracking the frequency of observations of values below interval upper bounds.

+ * + * This class is useful for recording timings across a large number of observations + * when high performance is required.

+ * + * The interval bounds are used to define the ranges of the histogram buckets. If provided bounds + * are [10,20,30,40,50] then there will be five buckets, accessible by index 0-4. Any value + * 0-10 will fall into the first interval bar, values 11-20 will fall into the + * second bar, and so on. + */ +public final class Histogram +{ + // tracks the upper intervals of each of the buckets/bars + private final long[] upperBounds; + // tracks the count of the corresponding bucket + private final long[] counts; + // minimum value so far observed + private long minValue = Long.MAX_VALUE; + // maximum value so far observed + private long maxValue = 0L; + + /** + * Create a new Histogram with a provided list of interval bounds. + * + * @param upperBounds of the intervals. Bounds must be provided in order least to greatest, and + * lowest bound must be greater than or equal to 1. + * @throws IllegalArgumentException if any of the upper bounds are less than or equal to zero + * @throws IllegalArgumentException if the bounds are not in order, least to greatest + */ + public Histogram(final long[] upperBounds) + { + validateBounds(upperBounds); + + this.upperBounds = Arrays.copyOf(upperBounds, upperBounds.length); + this.counts = new long[upperBounds.length]; + } + + /** + * Validates the input bounds; used by constructor only. + */ + private void validateBounds(final long[] upperBounds) + { + long lastBound = -1L; + if (upperBounds.length <= 0) { + throw new IllegalArgumentException("Must provide at least one interval"); + } + for (final long bound : upperBounds) + { + if (bound <= 0L) + { + throw new IllegalArgumentException("Bounds must be positive values"); + } + + if (bound <= lastBound) + { + throw new IllegalArgumentException("bound " + bound + " is not greater than " + lastBound); + } + + lastBound = bound; + } + } + + /** + * Size of the list of interval bars (ie: count of interval bars). + * + * @return size of the interval bar list. + */ + public int getSize() + { + return upperBounds.length; + } + + /** + * Get the upper bound of an interval for an index. + * + * @param index of the upper bound. + * @return the interval upper bound for the index. + */ + public long getUpperBoundAt(final int index) + { + return upperBounds[index]; + } + + /** + * Get the count of observations at a given index. + * + * @param index of the observations counter. + * @return the count of observations at a given index. + */ + public long getCountAt(final int index) + { + return counts[index]; + } + + /** + * Add an observation to the histogram and increment the counter for the interval it matches. + * + * @param value for the observation to be added. + * @return return true if in the range of intervals and successfully added observation; otherwise false. + */ + public boolean addObservation(final long value) + { + int low = 0; + int high = upperBounds.length - 1; + + // do a classic binary search to find the high value + while (low < high) + { + int mid = low + ((high - low) >> 1); + if (upperBounds[mid] < value) + { + low = mid + 1; + } + else + { + high = mid; + } + } + + // if the binary search found an eligible bucket, increment + if (value <= upperBounds[high]) + { + counts[high]++; + trackRange(value); + + return true; + } + + // otherwise value was not found + return false; + } + + /** + * Track minimum and maximum observations + * + * @see getMin + * @see getMax + */ + private void trackRange(final long value) + { + if (value < minValue) + { + minValue = value; + } + + if (value > maxValue) + { + maxValue = value; + } + } + + /** + * Add observations from another Histogram into this one.

+ * + * Histograms must have the same intervals. + * + * @param histogram from which to add the observation counts. + * @throws IllegalArgumentException if interval count or values do not match exactly + */ + public void addObservations(final Histogram histogram) + { + // validate the intervals + if (upperBounds.length != histogram.upperBounds.length) + { + throw new IllegalArgumentException("Histograms must have matching intervals"); + } + + for (int i = 0, size = upperBounds.length; i < size; i++) + { + if (upperBounds[i] != histogram.upperBounds[i]) + { + throw new IllegalArgumentException("Histograms must have matching intervals"); + } + } + + // increment all of the internal counts + for (int i = 0, size = counts.length; i < size; i++) + { + counts[i] += histogram.counts[i]; + } + + // refresh the minimum and maximum observation ranges + trackRange(histogram.minValue); + trackRange(histogram.maxValue); + } + + /** + * Clear the list of interval counters + */ + public void clear() + { + maxValue = 0L; + minValue = Long.MAX_VALUE; + + for (int i = 0, size = counts.length; i < size; i++) + { + counts[i] = 0L; + } + } + + /** + * Count total number of recorded observations. + * + * @return the total number of recorded observations. + */ + public long getCount() + { + long count = 0L; + + for (int i = 0, size = counts.length; i < size; i++) + { + count += counts[i]; + } + + return count; + } + + /** + * Get the minimum observed value. + * + * @return the minimum value observed. + */ + public long getMin() + { + return minValue; + } + + /** + * Get the maximum observed value. + * + * @return the maximum of the observed values; + */ + public long getMax() + { + return maxValue; + } + + /** + * Calculate the mean of all recorded observations.

+ * + * The mean is calculated by summing the mid points of each interval multiplied by the count + * for that interval, then dividing by the total count of observations. The max and min are + * considered for adjusting the top and bottom bin when calculating the mid point, this + * minimises skew if the observed values are very far away from the possible histogram values. + * + * @return the mean of all recorded observations. + */ + public BigDecimal getMean() + { + // early exit to avoid divide by zero later + if (0L == getCount()) + { + return BigDecimal.ZERO; + } + + // precalculate the initial lower bound; needed in the loop + long lowerBound = counts[0] > 0L ? minValue : 0L; + // use BigDecimal to avoid precision errors + BigDecimal total = BigDecimal.ZERO; + + // midpoint is calculated as the average between the lower and upper bound + // (after taking into account the min & max values seen) + // then, simply multiply midpoint by the count of values at the interval (intervalTotal) + // and add to running total (total) + for (int i = 0, size = upperBounds.length; i < size; i++) + { + if (0L != counts[i]) + { + long upperBound = Math.min(upperBounds[i], maxValue); + long midPoint = lowerBound + ((upperBound - lowerBound) / 2L); + + BigDecimal intervalTotal = new BigDecimal(midPoint).multiply(new BigDecimal(counts[i])); + total = total.add(intervalTotal); + } + + // and recalculate the lower bound for the next time around the loop + lowerBound = Math.max(upperBounds[i] + 1L, minValue); + } + + return total.divide(new BigDecimal(getCount()), 2, RoundingMode.HALF_UP); + } + + /** + * Calculate the upper bound within which 99% of observations fall. + * + * @return the upper bound for 99% of observations. + */ + public long getTwoNinesUpperBound() + { + return getUpperBoundForFactor(0.99d); + } + + /** + * Calculate the upper bound within which 99.99% of observations fall. + * + * @return the upper bound for 99.99% of observations. + */ + public long getFourNinesUpperBound() + { + return getUpperBoundForFactor(0.9999d); + } + + /** + * Get the interval upper bound for a given factor of the observation population.

+ * + * Note this does not get the actual percentile measurement, it only gets the bucket + * + * @param factor representing the size of the population. + * @return the interval upper bound. + * @throws IllegalArgumentException if factor < 0.0 or factor > 1.0 + */ + public long getUpperBoundForFactor(final double factor) + { + if (0.0d >= factor || factor >= 1.0d) + { + throw new IllegalArgumentException("factor must be >= 0.0 and <= 1.0"); + } + + final long totalCount = getCount(); + final long tailTotal = totalCount - Math.round(totalCount * factor); + long tailCount = 0L; + + // reverse search the intervals ('tailCount' from end) + for (int i = counts.length - 1; i >= 0; i--) + { + if (0L != counts[i]) + { + tailCount += counts[i]; + if (tailCount >= tailTotal) + { + return upperBounds[i]; + } + } + } + + return 0L; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append("Histogram{"); + + sb.append("min=").append(getMin()).append(", "); + sb.append("max=").append(getMax()).append(", "); + sb.append("mean=").append(getMean()).append(", "); + sb.append("99%=").append(getTwoNinesUpperBound()).append(", "); + sb.append("99.99%=").append(getFourNinesUpperBound()).append(", "); + + sb.append('['); + for (int i = 0, size = counts.length; i < size; i++) + { + sb.append(upperBounds[i]).append('=').append(counts[i]).append(", "); + } + + if (counts.length > 0) + { + sb.setLength(sb.length() - 2); + } + sb.append(']'); + + sb.append('}'); + + return sb.toString(); + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/ConsumerInfo.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/ConsumerInfo.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/ConsumerInfo.java (working copy) @@ -0,0 +1,22 @@ +package org.apache.logging.log4j.async.com.lmax.disruptor.dsl; + + +import java.util.concurrent.Executor; + +import org.apache.logging.log4j.async.com.lmax.disruptor.Sequence; +import org.apache.logging.log4j.async.com.lmax.disruptor.SequenceBarrier; + +interface ConsumerInfo +{ + Sequence[] getSequences(); + + SequenceBarrier getBarrier(); + + boolean isEndOfChain(); + + void start(Executor executor); + + void halt(); + + void markAsUsedInBarrier(); +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/ConsumerRepository.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/ConsumerRepository.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/ConsumerRepository.java (working copy) @@ -0,0 +1,120 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor.dsl; + + +import java.util.*; + +import org.apache.logging.log4j.async.com.lmax.disruptor.*; + +/** + * Provides a repository mechanism to associate {@link EventHandler}s with {@link EventProcessor}s + * + * @param T the type of the {@link EventHandler} + */ +class ConsumerRepository implements Iterable +{ + private final Map, EventProcessorInfo> eventProcessorInfoByEventHandler = new IdentityHashMap, EventProcessorInfo>(); + private final Map eventProcessorInfoBySequence = new IdentityHashMap(); + private final Collection consumerInfos = new ArrayList(); + + public void add(final EventProcessor eventprocessor, + final EventHandler handler, + final SequenceBarrier barrier) + { + final EventProcessorInfo consumerInfo = new EventProcessorInfo(eventprocessor, handler, barrier); + eventProcessorInfoByEventHandler.put(handler, consumerInfo); + eventProcessorInfoBySequence.put(eventprocessor.getSequence(), consumerInfo); + consumerInfos.add(consumerInfo); + } + + public void add(final EventProcessor processor) + { + final EventProcessorInfo consumerInfo = new EventProcessorInfo(processor, null, null); + eventProcessorInfoBySequence.put(processor.getSequence(), consumerInfo); + consumerInfos.add(consumerInfo); + } + + public void add(final WorkerPool workerPool, final SequenceBarrier sequenceBarrier) + { + final WorkerPoolInfo workerPoolInfo = new WorkerPoolInfo(workerPool, sequenceBarrier); + consumerInfos.add(workerPoolInfo); + for (Sequence sequence : workerPool.getWorkerSequences()) + { + eventProcessorInfoBySequence.put(sequence, workerPoolInfo); + } + } + + public Sequence[] getLastSequenceInChain() + { + List lastSequence = new ArrayList(); + for (ConsumerInfo consumerInfo : consumerInfos) + { + if (consumerInfo.isEndOfChain()) + { + final Sequence[] sequences = consumerInfo.getSequences(); + Collections.addAll(lastSequence, sequences); + } + } + + return lastSequence.toArray(new Sequence[lastSequence.size()]); + } + + public EventProcessor getEventProcessorFor(final EventHandler handler) + { + final EventProcessorInfo eventprocessorInfo = getEventProcessorInfo(handler); + if (eventprocessorInfo == null) + { + throw new IllegalArgumentException("The event handler " + handler + " is not processing events."); + } + + return eventprocessorInfo.getEventProcessor(); + } + + public Sequence getSequenceFor(final EventHandler handler) + { + return getEventProcessorFor(handler).getSequence(); + } + + public void unMarkEventProcessorsAsEndOfChain(final Sequence... barrierEventProcessors) + { + for (Sequence barrierEventProcessor : barrierEventProcessors) + { + getEventProcessorInfo(barrierEventProcessor).markAsUsedInBarrier(); + } + } + + public Iterator iterator() + { + return consumerInfos.iterator(); + } + + public SequenceBarrier getBarrierFor(final EventHandler handler) + { + final ConsumerInfo consumerInfo = getEventProcessorInfo(handler); + return consumerInfo != null ? consumerInfo.getBarrier() : null; + } + + private EventProcessorInfo getEventProcessorInfo(final EventHandler handler) + { + return eventProcessorInfoByEventHandler.get(handler); + } + + private ConsumerInfo getEventProcessorInfo(final Sequence barrierEventProcessor) + { + return eventProcessorInfoBySequence.get(barrierEventProcessor); + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/Disruptor.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/Disruptor.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/Disruptor.java (working copy) @@ -0,0 +1,388 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor.dsl; + + +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.logging.log4j.async.com.lmax.disruptor.*; +import org.apache.logging.log4j.async.com.lmax.disruptor.util.Util; + +/** + * A DSL-style API for setting up the disruptor pattern around a ring buffer (aka the Builder pattern). + * + *

A simple example of setting up the disruptor with two event handlers that must process events in order:

+ * + *
 Disruptor disruptor = new Disruptor(MyEvent.FACTORY, 32, Executors.newCachedThreadPool());
+ * EventHandler handler1 = new EventHandler() { ... };
+ * EventHandler handler2 = new EventHandler() { ... };
+ * disruptor.handleEventsWith(handler1);
+ * disruptor.after(handler1).handleEventsWith(handler2);
+ *
+ * RingBuffer ringBuffer = disruptor.start();
+ * + * @param the type of event used. + */ +public class Disruptor +{ + private final RingBuffer ringBuffer; + private final Executor executor; + private final ConsumerRepository consumerRepository = new ConsumerRepository(); + private final AtomicBoolean started = new AtomicBoolean(false); + private ExceptionHandler exceptionHandler; + + /** + * Create a new Disruptor. + * + * @param eventFactory the factory to create events in the ring buffer. + * @param ringBufferSize the size of the ring buffer. + * @param executor an {@link Executor} to execute event processors. + */ + public Disruptor(final EventFactory eventFactory, final int ringBufferSize, final Executor executor) + { + this(RingBuffer.createMultiProducer(eventFactory, ringBufferSize), executor); + } + + /** + * Create a new Disruptor. + * + * @param eventFactory the factory to create events in the ring buffer. + * @param executor an {@link Executor} to execute event processors. + * @param producerType the claim strategy to use for the ring buffer. + * @param waitStrategy the wait strategy to use for the ring buffer. + */ + public Disruptor(final EventFactory eventFactory, + final int ringBufferSize, + final Executor executor, + final ProducerType producerType, + final WaitStrategy waitStrategy) + { + this(RingBuffer.create(producerType, eventFactory, ringBufferSize, waitStrategy), + executor); + } + + /** + * Private constructor helper + */ + private Disruptor(final RingBuffer ringBuffer, final Executor executor) + { + this.ringBuffer = ringBuffer; + this.executor = executor; + } + + /** + * Set up event handlers to handle events from the ring buffer. These handlers will process events + * as soon as they become available, in parallel. + *

+ *

This method can be used as the start of a chain. For example if the handler A must + * process events before handler B:

+ *

+ *

dw.handleEventsWith(A).then(B);
+ * + * @param handlers the event handlers that will process events. + * @return a {@link EventHandlerGroup} that can be used to chain dependencies. + */ + @SuppressWarnings("varargs") + public EventHandlerGroup handleEventsWith(final EventHandler... handlers) + { + return createEventProcessors(new Sequence[0], handlers); + } + + /** + * Set up custom event processors to handle events from the ring buffer. The Disruptor will + * automatically start this processors when {@link #start()} is called. + * + * @param processors the event processors that will process events. + * @return a {@link EventHandlerGroup} that can be used to chain dependencies. + */ + public EventHandlerGroup handleEventsWith(final EventProcessor... processors) + { + for (EventProcessor processor : processors) + { + consumerRepository.add(processor); + } + return new EventHandlerGroup(this, consumerRepository, Util.getSequencesFor(processors)); + } + + + /** + * Set up a {@link WorkerPool} to distribute an event to one of a pool of work handler threads. + * Each event will only be processed by one of the work handlers. + * The Disruptor will automatically start this processors when {@link #start()} is called. + * + * @param workHandlers the work handlers that will process events. + * @return a {@link EventHandlerGroup} that can be used to chain dependencies. + */ + @SuppressWarnings("varargs") + public EventHandlerGroup handleEventsWithWorkerPool(final WorkHandler... workHandlers) + { + return createWorkerPool(new Sequence[0], workHandlers); + } + + /** + * Specify an exception handler to be used for any future event handlers.

+ * + * Note that only event handlers set up after calling this method will use the exception handler. + * + * @param exceptionHandler the exception handler to use for any future {@link EventProcessor}. + */ + public void handleExceptionsWith(final ExceptionHandler exceptionHandler) + { + this.exceptionHandler = exceptionHandler; + } + + /** + * Override the default exception handler for a specific handler. + *

disruptorWizard.handleExceptionsIn(eventHandler).with(exceptionHandler);
+ * + * @param eventHandler the event handler to set a different exception handler for. + * @return an ExceptionHandlerSetting dsl object - intended to be used by chaining the with method call. + */ + public ExceptionHandlerSetting handleExceptionsFor(final EventHandler eventHandler) + { + return new ExceptionHandlerSetting(eventHandler, consumerRepository); + } + + /** + * Create a group of event handlers to be used as a dependency. + * For example if the handler A must process events before handler B: + *

+ *

dw.after(A).handleEventsWith(B);
+ * + * @param handlers the event handlers, previously set up with {@link #handleEventsWith(org.apache.logging.log4j.async.com.lmax.disruptor.EventHandler[])}, + * that will form the barrier for subsequent handlers or processors. + * @return an {@link EventHandlerGroup} that can be used to setup a dependency barrier over the specified event handlers. + */ + @SuppressWarnings("varargs") + public EventHandlerGroup after(final EventHandler... handlers) + { + Sequence[] sequences = new Sequence[handlers.length]; + for (int i = 0, handlersLength = handlers.length; i < handlersLength; i++) + { + sequences[i] = consumerRepository.getSequenceFor(handlers[i]); + } + + return new EventHandlerGroup(this, consumerRepository, sequences); + } + + /** + * Create a group of event processors to be used as a dependency. + * + * @param processors the event processors, previously set up with {@link #handleEventsWith(org.apache.logging.log4j.async.com.lmax.disruptor.EventProcessor...)}, + * that will form the barrier for subsequent handlers or processors. + * @return an {@link EventHandlerGroup} that can be used to setup a {@link SequenceBarrier} over the specified event processors. + * @see #after(org.apache.logging.log4j.async.com.lmax.disruptor.EventHandler[]) + */ + public EventHandlerGroup after(final EventProcessor... processors) + { + for (EventProcessor processor : processors) + { + consumerRepository.add(processor); + } + + return new EventHandlerGroup(this, consumerRepository, Util.getSequencesFor(processors)); + } + + /** + * Publish an event to the ring buffer. + * + * @param eventTranslator the translator that will load data into the event. + */ + public void publishEvent(final EventTranslator eventTranslator) + { + ringBuffer.publishEvent(eventTranslator); + } + + /** + * Starts the event processors and returns the fully configured ring buffer.

+ * + * The ring buffer is set up to prevent overwriting any entry that is yet to + * be processed by the slowest event processor.

+ * + * This method must only be called once after all event processors have been added. + * + * @return the configured ring buffer. + */ + public RingBuffer start() + { + Sequence[] gatingSequences = consumerRepository.getLastSequenceInChain(); + ringBuffer.addGatingSequences(gatingSequences); + + checkOnlyStartedOnce(); + for (ConsumerInfo consumerInfo : consumerRepository) + { + consumerInfo.start(executor); + } + + return ringBuffer; + } + + /** + * Calls {@link org.apache.logging.log4j.async.com.lmax.disruptor.EventProcessor#halt()} on all of the event processors created via this disruptor. + */ + public void halt() + { + for (ConsumerInfo consumerInfo : consumerRepository) + { + consumerInfo.halt(); + } + } + + /** + * Waits until all events currently in the disruptor have been processed by all event processors + * and then halts the processors. It is critical that publishing to the ring buffer has stopped + * before calling this method, otherwise it may never return. + * + *

This method will not shutdown the executor, nor will it await the final termination of the + * processor threads.

+ */ + public void shutdown() + { + while (hasBacklog()) + { + // Busy spin + } + halt(); + } + + /** + * The {@link RingBuffer} used by this Disruptor. This is useful for creating custom + * event processors if the behaviour of {@link BatchEventProcessor} is not suitable. + * + * @return the ring buffer used by this Disruptor. + */ + public RingBuffer getRingBuffer() + { + return ringBuffer; + } + + /** + * Get the value of the cursor indicating the published sequence. + * + * @return value of the cursor for events that have been published. + */ + public long getCursor() + { + return ringBuffer.getCursor(); + } + + /** + * The capacity of the data structure to hold entries. + * + * @return the size of the RingBuffer. + * @see org.apache.logging.log4j.async.com.lmax.disruptor.Sequencer#getBufferSize() + */ + public long getBufferSize() + { + return ringBuffer.getBufferSize(); + } + + /** + * Get the event for a given sequence in the RingBuffer. + * + * @param sequence for the event. + * @return event for the sequence. + * @see RingBuffer#getPublished(long) + */ + public T get(final long sequence) + { + return ringBuffer.getPublished(sequence); + } + + /** + * Get the {@link SequenceBarrier} used by a specific handler. Note that the {@link SequenceBarrier} + * may be shared by multiple event handlers. + * + * @param handler the handler to get the barrier for. + * @return the SequenceBarrier used by handler. + */ + public SequenceBarrier getBarrierFor(final EventHandler handler) + { + return consumerRepository.getBarrierFor(handler); + } + + /** + * Confirms if all messages have been consumed by all event processors + */ + private boolean hasBacklog() + { + final long cursor = ringBuffer.getCursor(); + for (Sequence consumer : consumerRepository.getLastSequenceInChain()) + { + if (cursor > consumer.get()) + { + return true; + } + } + return false; + } + + EventHandlerGroup createEventProcessors(final Sequence[] barrierSequences, + final EventHandler[] eventHandlers) + { + checkNotStarted(); + + final Sequence[] processorSequences = new Sequence[eventHandlers.length]; + final SequenceBarrier barrier = ringBuffer.newBarrier(barrierSequences); + + for (int i = 0, eventHandlersLength = eventHandlers.length; i < eventHandlersLength; i++) + { + final EventHandler eventHandler = eventHandlers[i]; + + final BatchEventProcessor batchEventProcessor = new BatchEventProcessor(ringBuffer, barrier, eventHandler); + + if (exceptionHandler != null) + { + batchEventProcessor.setExceptionHandler(exceptionHandler); + } + + consumerRepository.add(batchEventProcessor, eventHandler, barrier); + processorSequences[i] = batchEventProcessor.getSequence(); + } + + if (processorSequences.length > 0) + { + consumerRepository.unMarkEventProcessorsAsEndOfChain(barrierSequences); + } + + return new EventHandlerGroup(this, consumerRepository, processorSequences); + } + + EventHandlerGroup createWorkerPool(final Sequence[] barrierSequences, final WorkHandler[] workHandlers) + { + final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(barrierSequences); + final WorkerPool workerPool = new WorkerPool(ringBuffer, sequenceBarrier, exceptionHandler, workHandlers); + consumerRepository.add(workerPool, sequenceBarrier); + return new EventHandlerGroup(this, consumerRepository, workerPool.getWorkerSequences()); + } + + private void checkNotStarted() + { + if (started.get()) + { + throw new IllegalStateException("All event handlers must be added before calling starts."); + } + } + + private void checkOnlyStartedOnce() + { + if (!started.compareAndSet(false, true)) + { + throw new IllegalStateException("Disruptor.start() must only be called once."); + } + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/EventHandlerGroup.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/EventHandlerGroup.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/EventHandlerGroup.java (working copy) @@ -0,0 +1,159 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor.dsl; + +import org.apache.logging.log4j.async.com.lmax.disruptor.EventHandler; +import org.apache.logging.log4j.async.com.lmax.disruptor.EventProcessor; +import org.apache.logging.log4j.async.com.lmax.disruptor.Sequence; +import org.apache.logging.log4j.async.com.lmax.disruptor.SequenceBarrier; +import org.apache.logging.log4j.async.com.lmax.disruptor.WorkHandler; + +/** + * A group of {@link EventProcessor}s used as part of the {@link Disruptor}. + * + * @param the type of entry used by the event processors. + */ +public class EventHandlerGroup +{ + private final Disruptor disruptor; + private final ConsumerRepository consumerRepository; + private final Sequence[] sequences; + + EventHandlerGroup(final Disruptor disruptor, + final ConsumerRepository consumerRepository, + final Sequence[] sequences) + { + this.disruptor = disruptor; + this.consumerRepository = consumerRepository; + this.sequences = sequences; + } + + /** + * Create a new event handler group that combines the consumers in this group with otherHandlerGroup. + * + * @param otherHandlerGroup the event handler group to combine. + * @return a new EventHandlerGroup combining the existing and new consumers into a single dependency group. + */ + public EventHandlerGroup and(final EventHandlerGroup otherHandlerGroup) + { + final Sequence[] combinedSequences = new Sequence[this.sequences.length + otherHandlerGroup.sequences.length]; + System.arraycopy(this.sequences, 0, combinedSequences, 0, this.sequences.length); + System.arraycopy(otherHandlerGroup.sequences, 0, combinedSequences, this.sequences.length, otherHandlerGroup.sequences.length); + return new EventHandlerGroup(disruptor, consumerRepository, combinedSequences); + } + + /** + * Create a new event handler group that combines the handlers in this group with processors. + * + * @param processors the processors to combine. + * @return a new EventHandlerGroup combining the existing and new processors into a single dependency group. + */ + public EventHandlerGroup and(final EventProcessor... processors) + { + Sequence[] combinedSequences = new Sequence[sequences.length + processors.length]; + + for (int i = 0; i < processors.length; i++) + { + consumerRepository.add(processors[i]); + combinedSequences[i] = processors[i].getSequence(); + } + System.arraycopy(sequences, 0, combinedSequences, processors.length, sequences.length); + + return new EventHandlerGroup(disruptor, consumerRepository, combinedSequences); + } + + /** + * Set up batch handlers to consume events from the ring buffer. These handlers will only process events + * after every {@link EventProcessor} in this group has processed the event. + * + *

This method is generally used as part of a chain. For example if the handler A must + * process events before handler B:

+ * + *
dw.handleEventsWith(A).then(B);
+ * + * @param handlers the batch handlers that will process events. + * @return a {@link EventHandlerGroup} that can be used to set up a event processor barrier over the created event processors. + */ + public EventHandlerGroup then(final EventHandler... handlers) + { + return handleEventsWith(handlers); + } + + /** + * Set up a worker pool to handle events from the ring buffer. The worker pool will only process events + * after every {@link EventProcessor} in this group has processed the event. Each event will be processed + * by one of the work handler instances. + * + *

This method is generally used as part of a chain. For example if the handler A must + * process events before the worker pool with handlers B, C:

+ * + *
dw.handleEventsWith(A).thenHandleEventsWithWorkerPool(B, C);
+ * + * @param handlers the work handlers that will process events. Each work handler instance will provide an extra thread in the worker pool. + * @return a {@link EventHandlerGroup} that can be used to set up a event processor barrier over the created event processors. + */ + public EventHandlerGroup thenHandleEventsWithWorkerPool(final WorkHandler... handlers) + { + return handleEventsWithWorkerPool(handlers); + } + + /** + * Set up batch handlers to handle events from the ring buffer. These handlers will only process events + * after every {@link EventProcessor} in this group has processed the event. + * + *

This method is generally used as part of a chain. For example if the handler A must + * process events before handler B:

+ * + *
dw.after(A).handleEventsWith(B);
+ * + * @param handlers the batch handlers that will process events. + * @return a {@link EventHandlerGroup} that can be used to set up a event processor barrier over the created event processors. + */ + public EventHandlerGroup handleEventsWith(final EventHandler... handlers) + { + return disruptor.createEventProcessors(sequences, handlers); + } + + /** + * Set up a worker pool to handle events from the ring buffer. The worker pool will only process events + * after every {@link EventProcessor} in this group has processed the event. Each event will be processed + * by one of the work handler instances. + * + *

This method is generally used as part of a chain. For example if the handler A must + * process events before the worker pool with handlers B, C:

+ * + *
dw.after(A).handleEventsWithWorkerPool(B, C);
+ * + * @param handlers the work handlers that will process events. Each work handler instance will provide an extra thread in the worker pool. + * @return a {@link EventHandlerGroup} that can be used to set up a event processor barrier over the created event processors. + */ + public EventHandlerGroup handleEventsWithWorkerPool(final WorkHandler... handlers) + { + return disruptor.createWorkerPool(sequences, handlers); + } + + /** + * Create a dependency barrier for the processors in this group. + * This allows custom event processors to have dependencies on + * {@link org.apache.logging.log4j.async.com.lmax.disruptor.BatchEventProcessor}s created by the disruptor. + * + * @return a {@link SequenceBarrier} including all the processors in this group. + */ + public SequenceBarrier asSequenceBarrier() + { + return disruptor.getRingBuffer().newBarrier(sequences); + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/EventProcessorInfo.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/EventProcessorInfo.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/EventProcessorInfo.java (working copy) @@ -0,0 +1,95 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor.dsl; + + +import java.util.concurrent.Executor; + +import org.apache.logging.log4j.async.com.lmax.disruptor.EventHandler; +import org.apache.logging.log4j.async.com.lmax.disruptor.EventProcessor; +import org.apache.logging.log4j.async.com.lmax.disruptor.Sequence; +import org.apache.logging.log4j.async.com.lmax.disruptor.SequenceBarrier; + +/** + * Wrapper class to tie together a particular event processing stage

+ * + * Tracks the event processor instance, the event handler instance, and sequence barrier which the stage is attached to + * + * @param T the type of the configured {@link EventHandler} + */ +class EventProcessorInfo implements ConsumerInfo +{ + private final EventProcessor eventprocessor; + private final EventHandler handler; + private final SequenceBarrier barrier; + private boolean endOfChain = true; + + EventProcessorInfo(final EventProcessor eventprocessor, final EventHandler handler, final SequenceBarrier barrier) + { + this.eventprocessor = eventprocessor; + this.handler = handler; + this.barrier = barrier; + } + + public EventProcessor getEventProcessor() + { + return eventprocessor; + } + + @Override + public Sequence[] getSequences() + { + return new Sequence[] { eventprocessor.getSequence() }; + } + + public EventHandler getHandler() + { + return handler; + } + + @Override + public SequenceBarrier getBarrier() + { + return barrier; + } + + @Override + public boolean isEndOfChain() + { + return endOfChain; + } + + @Override + public void start(final Executor executor) + { + executor.execute(eventprocessor); + } + + @Override + public void halt() + { + eventprocessor.halt(); + } + + /** + * + */ + @Override + public void markAsUsedInBarrier() + { + endOfChain = false; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/ExceptionHandlerSetting.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/ExceptionHandlerSetting.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/ExceptionHandlerSetting.java (working copy) @@ -0,0 +1,51 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor.dsl; + +import org.apache.logging.log4j.async.com.lmax.disruptor.BatchEventProcessor; +import org.apache.logging.log4j.async.com.lmax.disruptor.EventHandler; +import org.apache.logging.log4j.async.com.lmax.disruptor.ExceptionHandler; + +/** + * A support class used as part of setting an exception handler for a specific event handler. + * For example: + *

disruptorWizard.handleExceptionsIn(eventHandler).with(exceptionHandler);
+ * + * @param the type of event being handled. + */ +public class ExceptionHandlerSetting +{ + private final EventHandler eventHandler; + private final ConsumerRepository consumerRepository; + + ExceptionHandlerSetting(final EventHandler eventHandler, + final ConsumerRepository consumerRepository) + { + this.eventHandler = eventHandler; + this.consumerRepository = consumerRepository; + } + + /** + * Specify the {@link ExceptionHandler} to use with the event handler. + * + * @param exceptionHandler the exception handler to use. + */ + public void with(ExceptionHandler exceptionHandler) + { + ((BatchEventProcessor) consumerRepository.getEventProcessorFor(eventHandler)).setExceptionHandler(exceptionHandler); + consumerRepository.getBarrierFor(eventHandler).alert(); + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/ProducerType.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/ProducerType.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/ProducerType.java (working copy) @@ -0,0 +1,28 @@ +/* + * Copyright 2012 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor.dsl; + +/** + * Defines producer types to support creation of RingBuffer with correct sequencer and publisher.

+ */ +public enum ProducerType +{ + /** Create a RingBuffer with a single event publisher to the RingBuffer */ + SINGLE, + + /** Create a RingBuffer supporting multiple event publishers to the one RingBuffer */ + MULTI +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/WorkerPoolInfo.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/WorkerPoolInfo.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/WorkerPoolInfo.java (working copy) @@ -0,0 +1,55 @@ +package org.apache.logging.log4j.async.com.lmax.disruptor.dsl; + + +import java.util.concurrent.Executor; + +import org.apache.logging.log4j.async.com.lmax.disruptor.*; + +class WorkerPoolInfo implements ConsumerInfo +{ + private final WorkerPool workerPool; + private final SequenceBarrier sequenceBarrier; + private boolean endOfChain = true; + + public WorkerPoolInfo(final WorkerPool workerPool, final SequenceBarrier sequenceBarrier) + { + this.workerPool = workerPool; + this.sequenceBarrier = sequenceBarrier; + } + + @Override + public Sequence[] getSequences() + { + return workerPool.getWorkerSequences(); + } + + @Override + public SequenceBarrier getBarrier() + { + return sequenceBarrier; + } + + @Override + public boolean isEndOfChain() + { + return endOfChain; + } + + @Override + public void start(final Executor executor) + { + workerPool.start(executor); + } + + @Override + public void halt() + { + workerPool.halt(); + } + + @Override + public void markAsUsedInBarrier() + { + endOfChain = false; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/util/DaemonThreadFactory.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/util/DaemonThreadFactory.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/util/DaemonThreadFactory.java (working copy) @@ -0,0 +1,34 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor.util; + +import java.util.concurrent.ThreadFactory; + +/** + * Access to a ThreadFactory instance. All threads are created with setDaemon(true). + */ +public enum DaemonThreadFactory implements ThreadFactory +{ + INSTANCE; + + @Override + public Thread newThread(final Runnable r) + { + Thread t = new Thread(r); + t.setDaemon(true); + return t; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/util/MutableLong.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/util/MutableLong.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/util/MutableLong.java (working copy) @@ -0,0 +1,61 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor.util; + +/** + * Holder class for a long value. + */ +public class MutableLong +{ + private long value = 0L; + + /** + * Default constructor + */ + public MutableLong() + { + } + + /** + * Construct the holder with initial value. + * + * @param initialValue to be initially set. + */ + public MutableLong(final long initialValue) + { + this.value = initialValue; + } + + /** + * Get the long value. + * + * @return the long value. + */ + public long get() + { + return value; + } + + /** + * Set the long value. + * + * @param value to set. + */ + public void set(final long value) + { + this.value = value; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/util/PaddedLong.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/util/PaddedLong.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/util/PaddedLong.java (working copy) @@ -0,0 +1,46 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor.util; + +/** + * Cache line padded long variable to be used when false sharing maybe an issue. + */ +public final class PaddedLong extends MutableLong +{ + public volatile long p1, p2, p3, p4, p5, p6 = 7L; + + /** + * Default constructor + */ + public PaddedLong() + { + } + + /** + * Construct with an initial value. + * + * @param initialValue for construction + */ + public PaddedLong(final long initialValue) + { + super(initialValue); + } + + public long sumPaddingToPreventOptimisation() + { + return p1 + p2 + p3 + p4 + p5 + p6; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/util/Util.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/util/Util.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/util/Util.java (working copy) @@ -0,0 +1,163 @@ +/* + * Copyright 2011 LMAX Ltd. + * + * Licensed 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.async.com.lmax.disruptor.util; + +import java.lang.reflect.Field; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; + +import org.apache.logging.log4j.async.com.lmax.disruptor.EventProcessor; +import org.apache.logging.log4j.async.com.lmax.disruptor.Sequence; + +import sun.misc.Unsafe; + + +/** + * Set of common functions used by the Disruptor + */ +public final class Util +{ + /** + * Calculate the next power of 2, greater than or equal to x.

+ * From Hacker's Delight, Chapter 3, Harry S. Warren Jr. + * + * @param x Value to round up + * @return The next power of 2 from x inclusive + */ + public static int ceilingNextPowerOfTwo(final int x) + { + return 1 << (32 - Integer.numberOfLeadingZeros(x - 1)); + } + + /** + * Get the minimum sequence from an array of {@link org.apache.logging.log4j.async.com.lmax.disruptor.Sequence}s. + * + * @param sequences to compare. + * @return the minimum sequence found or Long.MAX_VALUE if the array is empty. + */ + public static long getMinimumSequence(final Sequence[] sequences) + { + return getMinimumSequence(sequences, Long.MAX_VALUE); + } + + /** + * Get the minimum sequence from an array of {@link org.apache.logging.log4j.async.com.lmax.disruptor.Sequence}s. + * + * @param sequences to compare. + * @param minimum an initial default minimum. If the array is empty this value will be + * returned. + * @return the minimum sequence found or Long.MAX_VALUE if the array is empty. + */ + public static long getMinimumSequence(final Sequence[] sequences, long minimum) + { + for (int i = 0, n = sequences.length; i < n; i++) + { + long value = sequences[i].get(); + minimum = Math.min(minimum, value); + } + + return minimum; + } + + /** + * Get an array of {@link Sequence}s for the passed {@link EventProcessor}s + * + * @param processors for which to get the sequences + * @return the array of {@link Sequence}s + */ + public static Sequence[] getSequencesFor(final EventProcessor... processors) + { + Sequence[] sequences = new Sequence[processors.length]; + for (int i = 0; i < sequences.length; i++) + { + sequences[i] = processors[i].getSequence(); + } + + return sequences; + } + + private static final Unsafe THE_UNSAFE; + static + { + try + { + final PrivilegedExceptionAction action = new PrivilegedExceptionAction() + { + public Unsafe run() throws Exception + { + Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + return (Unsafe) theUnsafe.get(null); + } + }; + + THE_UNSAFE = AccessController.doPrivileged(action); + } + catch (Exception e) + { + throw new RuntimeException("Unable to load unsafe", e); + } + } + + /** + * Get a handle on the Unsafe instance, used for accessing low-level concurrency + * and memory constructs. + * @return The Unsafe + */ + public static Unsafe getUnsafe() + { + return THE_UNSAFE; + } + + /** + * Gets the address value for the memory that backs a direct byte buffer. + * @param buffer + * @return The system address for the buffers + */ + public static long getAddressFromDirectByteBuffer(ByteBuffer buffer) + { + try + { + Field addressField = Buffer.class.getDeclaredField("address"); + addressField.setAccessible(true); + return addressField.getLong(buffer); + } + catch (Exception e) + { + throw new RuntimeException("Unable to address field from ByteBuffer", e); + } + } + + + /** + * Calculate the log base 2 of the supplied integer, essentially reports the location + * of the highest bit. + * + * @param i Value to calculate log2 for. + * @return The log2 value + */ + public static int log2(int i) + { + int r = 0; + while ((i >>= 1) != 0) + { + ++r; + } + return r; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/config/AsyncLoggerConfig.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/config/AsyncLoggerConfig.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/config/AsyncLoggerConfig.java (working copy) @@ -0,0 +1,342 @@ +/* + * 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.async.config; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.async.com.lmax.disruptor.BlockingWaitStrategy; +import org.apache.logging.log4j.async.com.lmax.disruptor.EventFactory; +import org.apache.logging.log4j.async.com.lmax.disruptor.EventHandler; +import org.apache.logging.log4j.async.com.lmax.disruptor.EventTranslator; +import org.apache.logging.log4j.async.com.lmax.disruptor.ExceptionHandler; +import org.apache.logging.log4j.async.com.lmax.disruptor.RingBuffer; +import org.apache.logging.log4j.async.com.lmax.disruptor.SleepingWaitStrategy; +import org.apache.logging.log4j.async.com.lmax.disruptor.WaitStrategy; +import org.apache.logging.log4j.async.com.lmax.disruptor.YieldingWaitStrategy; +import org.apache.logging.log4j.async.com.lmax.disruptor.dsl.Disruptor; +import org.apache.logging.log4j.async.com.lmax.disruptor.dsl.ProducerType; +import org.apache.logging.log4j.async.com.lmax.disruptor.util.Util; +import org.apache.logging.log4j.core.Filter; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.LoggerContext.Status; +import org.apache.logging.log4j.core.config.AppenderRef; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.config.Property; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginAttr; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * Asynchronous Logger object that is created via configuration. + */ +@Plugin(name = "asyncLogger", type = "Core", printObject = true) +public class AsyncLoggerConfig extends LoggerConfig { + + private static final Logger LOGGER = StatusLogger.getLogger(); + private static volatile Disruptor disruptor; + private static ExecutorService executor = Executors + .newSingleThreadExecutor(); + + private ThreadLocal currentLogEvent = new ThreadLocal(); + + /** + * RingBuffer events contain all information necessary to perform the work + * in a separate thread. + */ + private static class RingBufferLog4jEvent { + private AsyncLoggerConfig loggerConfig; + private LogEvent event; + } + + /** + * Factory used to populate the RingBuffer with events. These event objects + * are then re-used during the life of the RingBuffer. + */ + private static final EventFactory FACTORY = new EventFactory() { + // @Override + public RingBufferLog4jEvent newInstance() { + return new RingBufferLog4jEvent(); + } + }; + + /** + * Object responsible for passing on data to a specific RingBuffer event. + */ + private final EventTranslator translator = new EventTranslator() { + // @Override + public void translateTo(RingBufferLog4jEvent event, long sequence) { + event.event = currentLogEvent.get(); + event.loggerConfig = AsyncLoggerConfig.this; + } + }; + + /** + * EventHandler performs the work in a separate thread. + */ + private static class RingBufferLog4jEventHandler implements + EventHandler { + // @Override + public void onEvent(RingBufferLog4jEvent event, long sequence, + boolean endOfBatch) throws Exception { + event.loggerConfig.asyncCallAppenders(event.event, endOfBatch); + } + } + + /** + * Default constructor. + */ + public AsyncLoggerConfig() { + super(); + } + + /** + * Constructor that sets the name, level and additive values. + * + * @param name The Logger name. + * @param level The Level. + * @param additive true if the Logger is additive, false otherwise. + */ + public AsyncLoggerConfig(final String name, final Level level, + final boolean additive) { + super(name, level, additive); + } + + protected AsyncLoggerConfig(final String name, + final List appenders, final Filter filter, + final Level level, final boolean additive, + final Property[] properties, final Configuration config, + boolean isLocationRequired) { + super(name, appenders, filter, level, additive, properties, config, + isLocationRequired); + } + + /** + * Passes on the event to a separate thread that will call + * {@link #asyncCallAppenders(LogEvent)}. + */ + // @Override + protected void callAppenders(LogEvent event) { + // populate lazily initialized fields + event.getSource(); + event.getThreadName(); + + // pass on the event to a separate thread + currentLogEvent.set(event); + disruptor.publishEvent(translator); + } + + /** Called by RingBufferLog4jEventHandler. */ + private void asyncCallAppenders(LogEvent event, boolean endOfBatch) { + event.setEndOfBatch(endOfBatch); + super.callAppenders(event); + } + + @Override + public void startFilter() { + if (disruptor == null) { + int ringBufferSize = calculateRingBufferSize(); + WaitStrategy waitStrategy = createWaitStrategy(); + disruptor = new Disruptor(FACTORY, + ringBufferSize, executor, ProducerType.MULTI, waitStrategy); + EventHandler[] handlers = new RingBufferLog4jEventHandler[] { new RingBufferLog4jEventHandler() }; + disruptor.handleExceptionsWith(getExceptionHandler()); + disruptor.handleEventsWith(handlers); + + LOGGER.debug( + "Starting AsyncLoggerConfig disruptor with ringbuffer size {}...", + disruptor.getRingBuffer().getBufferSize()); + disruptor.start(); + } + super.startFilter(); + } + + private WaitStrategy createWaitStrategy() { + String strategy = System.getProperty("AsyncLoggerConfig.WaitStrategy"); + LOGGER.debug("property AsyncLoggerConfig.WaitStrategy={}", strategy); + if ("Sleep".equals(strategy)) { + LOGGER.debug("disruptor event handler uses SleepingWaitStrategy"); + return new SleepingWaitStrategy(); + } else if ("Yield".equals(strategy)) { + LOGGER.debug("disruptor event handler uses YieldingWaitStrategy"); + return new YieldingWaitStrategy(); + } else if ("Block".equals(strategy)) { + LOGGER.debug("disruptor event handler uses BlockingWaitStrategy"); + return new BlockingWaitStrategy(); + } + LOGGER.debug("disruptor event handler uses SleepingWaitStrategy"); + return new SleepingWaitStrategy(); + } + + private static int calculateRingBufferSize() { + String userPreferredRBSize = System.getProperty( + "AsyncLoggerConfig.RingBufferSize", "256000"); + int ringBufferSize = 256000; // default + try { + int size = Integer.parseInt(userPreferredRBSize); + if (size < 128) { + size = 128; + LOGGER.warn( + "Invalid RingBufferSize {}, using minimum size 128.", + userPreferredRBSize); + } + ringBufferSize = size; + } catch (Exception ex) { + LOGGER.warn("Invalid RingBufferSize {}, using default size.", + userPreferredRBSize); + } + return Util.ceilingNextPowerOfTwo(ringBufferSize); + } + + private static ExceptionHandler getExceptionHandler() { + String cls = System.getProperty("AsyncLoggerConfig.ExceptionHandler"); + if (cls == null) { + return null; + } + try { + @SuppressWarnings("unchecked") + Class klass = (Class) Class + .forName(cls); + return klass.newInstance(); + } catch (Exception ignored) { + return null; + } + } + + @Override + public void stopFilter() { + // only stop disruptor if shutting down logging subsystem + if (LogManager.getContext() instanceof LoggerContext) { + if (((LoggerContext) LogManager.getContext()).getStatus() != Status.STOPPING) { + return; + } + } + Disruptor temp = disruptor; + + // Must guarantee that publishing to the RingBuffer has stopped + // before we call disruptor.shutdown() + disruptor = null; // client code fails with NPE if log after stop = OK + temp.shutdown(); + + // wait up to 10 seconds for the ringbuffer to drain + RingBuffer ringBuffer = temp.getRingBuffer(); + for (int i = 0; i < 20; i++) { + if (ringBuffer.hasAvailableCapacity(ringBuffer.getBufferSize())) { + break; + } + try { + Thread.sleep(500); // give ringbuffer some time to drain... + } catch (InterruptedException e) { + } + } + executor.shutdown(); // finally, kill the processor thread + super.stopFilter(); + } + + /** + * Factory method to create a LoggerConfig. + * + * @param additivity True if additive, false otherwise. + * @param levelName The Level to be associated with the Logger. + * @param loggerName The name of the Logger. + * @param refs An array of Appender names. + * @param properties Properties to pass to the Logger. + * @param config The Configuration. + * @param filter A Filter. + * @return A new LoggerConfig. + */ + @PluginFactory + public static LoggerConfig createLogger( + @PluginAttr("additivity") final String additivity, + @PluginAttr("level") final String levelName, + @PluginAttr("name") final String loggerName, + @PluginAttr("needsLocation") final String needsLocation, + @PluginElement("appender-ref") final AppenderRef[] refs, + @PluginElement("properties") final Property[] properties, + @PluginConfiguration final Configuration config, + @PluginElement("filters") final Filter filter) { + if (loggerName == null) { + LOGGER.error("Loggers cannot be configured without a name"); + return null; + } + + final List appenderRefs = Arrays.asList(refs); + Level level; + try { + level = Level.toLevel(levelName, Level.ERROR); + } catch (final Exception ex) { + LOGGER.error( + "Invalid Log level specified: {}. Defaulting to Error", + levelName); + level = Level.ERROR; + } + final String name = loggerName.equals("root") ? "" : loggerName; + final boolean additive = additivity == null ? true : Boolean + .parseBoolean(additivity); + final boolean isLocationRequired = needsLocation == null ? true + : Boolean.parseBoolean(needsLocation); + + return new AsyncLoggerConfig(name, appenderRefs, filter, level, + additive, properties, config, isLocationRequired); + } + + /** + * An asynchronous root Logger. + */ + @Plugin(name = "asyncRoot", type = "Core", printObject = true) + public static class RootLogger extends LoggerConfig { + + @PluginFactory + public static LoggerConfig createLogger( + @PluginAttr("additivity") final String additivity, + @PluginAttr("level") final String levelName, + @PluginAttr("needsLocation") final String needsLocation, + @PluginElement("appender-ref") final AppenderRef[] refs, + @PluginElement("properties") final Property[] properties, + @PluginConfiguration final Configuration config, + @PluginElement("filters") final Filter filter) { + final List appenderRefs = Arrays.asList(refs); + Level level; + try { + level = Level.toLevel(levelName, Level.ERROR); + } catch (final Exception ex) { + LOGGER.error( + "Invalid Log level specified: {}. Defaulting to Error", + levelName); + level = Level.ERROR; + } + final boolean additive = additivity == null ? true : Boolean + .parseBoolean(additivity); + final boolean isLocationRequired = needsLocation == null ? true + : Boolean.parseBoolean(needsLocation); + + return new AsyncLoggerConfig(LogManager.ROOT_LOGGER_NAME, + appenderRefs, filter, level, additive, properties, config, + isLocationRequired); + } + } +} Index: log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerContextSelectorTest.java =================================================================== --- log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerContextSelectorTest.java (revision 0) +++ log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerContextSelectorTest.java (working copy) @@ -0,0 +1,37 @@ +package org.apache.logging.log4j.async; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.apache.logging.log4j.core.LoggerContext; +import org.junit.Test; + +public class AsyncLoggerContextSelectorTest { + + @Test + public void testContextReturnsAsyncLoggerContext() { + AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(); + LoggerContext context = selector.getContext(null, null, false); + + assertTrue(context instanceof AsyncLoggerContext); + } + + @Test + public void testContext2ReturnsAsyncLoggerContext() { + AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(); + LoggerContext context = selector.getContext(null, null, false, null); + + assertTrue(context instanceof AsyncLoggerContext); + } + + @Test + public void testLoggerContextsReturnsAsyncLoggerContext() { + AsyncLoggerContextSelector selector = new AsyncLoggerContextSelector(); + List list = selector.getLoggerContexts(); + + assertEquals(1, list.size()); + assertTrue(list.get(0) instanceof AsyncLoggerContext); + } + +} Index: log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerContextTest.java =================================================================== --- log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerContextTest.java (revision 0) +++ log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerContextTest.java (working copy) @@ -0,0 +1,21 @@ +package org.apache.logging.log4j.async; + +import static org.junit.Assert.*; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LifeCycle; +import org.apache.logging.log4j.core.LoggerContext; +import org.junit.Test; + +public class AsyncLoggerContextTest { + + @Test + public void testNewInstanceReturnsAsyncLogger() { + Logger logger = new AsyncLoggerContext("a").newInstance( + new LoggerContext("a"), "a", null); + assertTrue(logger instanceof AsyncLogger); + + ((LifeCycle) LogManager.getContext()).stop(); // stop async thread + } +} Index: log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerLocationTest.java =================================================================== --- log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerLocationTest.java (revision 0) +++ log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerLocationTest.java (working copy) @@ -0,0 +1,54 @@ +package org.apache.logging.log4j.async; + +import static org.junit.Assert.*; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LifeCycle; +import org.apache.logging.log4j.core.config.XMLConfigurationFactory; +import org.apache.logging.log4j.core.helpers.Constants; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class AsyncLoggerLocationTest { + + @BeforeClass + public static void beforeClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, + AsyncLoggerContextSelector.class.getName()); + System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY, + "AsyncLoggerLocationTest.xml"); + } + + @AfterClass + public static void afterClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, ""); + } + + @Test + public void testAsyncLogWritesToLog() throws Exception { + File f = new File("AsyncLoggerLocationTest.log"); + // System.out.println(f.getAbsolutePath()); + f.delete(); + Logger log = LogManager.getLogger("com.foo.Bar"); + String msg = "Async logger msg with location"; + log.info(msg); + ((LifeCycle) LogManager.getContext()).stop(); // stop async thread + + BufferedReader reader = new BufferedReader(new FileReader(f)); + String line1 = reader.readLine(); + reader.close(); + f.delete(); + assertNotNull("line1", line1); + assertTrue("line1 correct", line1.contains(msg)); + + String location = "testAsyncLogWritesToLog"; + assertTrue("has location", line1.contains(location)); + } + +} Index: log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerTest.java =================================================================== --- log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerTest.java (revision 0) +++ log4j-async/src/test/java/org/apache/logging/log4j/async/AsyncLoggerTest.java (working copy) @@ -0,0 +1,54 @@ +package org.apache.logging.log4j.async; + +import static org.junit.Assert.*; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LifeCycle; +import org.apache.logging.log4j.core.config.XMLConfigurationFactory; +import org.apache.logging.log4j.core.helpers.Constants; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class AsyncLoggerTest { + + @BeforeClass + public static void beforeClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, + AsyncLoggerContextSelector.class.getName()); + System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY, + "AsyncLoggerTest.xml"); + } + + @AfterClass + public static void afterClass() { + System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, ""); + } + + @Test + public void testAsyncLogWritesToLog() throws Exception { + File f = new File("AsyncLoggerTest.log"); + // System.out.println(f.getAbsolutePath()); + f.delete(); + Logger log = LogManager.getLogger("com.foo.Bar"); + String msg = "Async logger msg"; + log.info(msg); + ((LifeCycle) LogManager.getContext()).stop(); // stop async thread + + BufferedReader reader = new BufferedReader(new FileReader(f)); + String line1 = reader.readLine(); + reader.close(); + f.delete(); + assertNotNull("line1", line1); + assertTrue("line1 correct", line1.contains(msg)); + + String location = "testAsyncLogWritesToLog"; + assertTrue("no location", !line1.contains(location)); + } + +} Index: log4j-async/src/test/java/org/apache/logging/log4j/async/CachedClockTest.java =================================================================== --- log4j-async/src/test/java/org/apache/logging/log4j/async/CachedClockTest.java (revision 0) +++ log4j-async/src/test/java/org/apache/logging/log4j/async/CachedClockTest.java (working copy) @@ -0,0 +1,30 @@ +package org.apache.logging.log4j.async; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class CachedClockTest { + + @Test + public void testLessThan17Millis() { + long millis1 = CachedClock.instance().currentTimeMillis(); + long sysMillis = System.currentTimeMillis(); + + long diff = sysMillis - millis1; + + assertTrue("diff too large: " + diff, diff <= 16); + } + + @Test + public void testAfterWaitStillLessThan17Millis() throws Exception { + Thread.sleep(100); + long millis1 = CachedClock.instance().currentTimeMillis(); + long sysMillis = System.currentTimeMillis(); + + long diff = sysMillis - millis1; + + assertTrue("diff too large: " + diff, diff <= 16); + } + +} Index: log4j-async/src/test/java/org/apache/logging/log4j/async/ClockFactoryTest.java =================================================================== --- log4j-async/src/test/java/org/apache/logging/log4j/async/ClockFactoryTest.java (revision 0) +++ log4j-async/src/test/java/org/apache/logging/log4j/async/ClockFactoryTest.java (working copy) @@ -0,0 +1,64 @@ +package org.apache.logging.log4j.async; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class ClockFactoryTest { + + @Test + public void testDefaultIsSystemClock() { + System.clearProperty(ClockFactory.PROPERTY_NAME); + assertEquals(SystemClock.class, ClockFactory.getClock().getClass()); + } + + @Test + public void testSpecifySystemClockShort() { + System.setProperty(ClockFactory.PROPERTY_NAME, "SystemClock"); + assertEquals(SystemClock.class, ClockFactory.getClock().getClass()); + } + + @Test + public void testSpecifySystemClockLong() { + System.setProperty(ClockFactory.PROPERTY_NAME, SystemClock.class.getName()); + assertEquals(SystemClock.class, ClockFactory.getClock().getClass()); + } + + @Test + public void testSpecifyCachedClockShort() { + System.setProperty(ClockFactory.PROPERTY_NAME, "CachedClock"); + assertEquals(CachedClock.class, ClockFactory.getClock().getClass()); + } + + @Test + public void testSpecifyCachedClockLong() { + System.setProperty(ClockFactory.PROPERTY_NAME, CachedClock.class.getName()); + assertEquals(CachedClock.class, ClockFactory.getClock().getClass()); + } + + @Test + public void testSpecifyCoarseCachedClockShort() { + System.setProperty(ClockFactory.PROPERTY_NAME, "CoarseCachedClock"); + assertEquals(CoarseCachedClock.class, ClockFactory.getClock().getClass()); + } + + @Test + public void testSpecifyCoarseCachedClockLong() { + System.setProperty(ClockFactory.PROPERTY_NAME, CoarseCachedClock.class.getName()); + assertEquals(CoarseCachedClock.class, ClockFactory.getClock().getClass()); + } + + static class MyClock implements Clock { + @Override + public long currentTimeMillis() { + return 42; + } + } + + @Test + public void testCustomClock() { + System.setProperty(ClockFactory.PROPERTY_NAME, MyClock.class.getName()); + assertEquals(MyClock.class, ClockFactory.getClock().getClass()); + } + +} Index: log4j-async/src/test/java/org/apache/logging/log4j/async/SystemClockTest.java =================================================================== --- log4j-async/src/test/java/org/apache/logging/log4j/async/SystemClockTest.java (revision 0) +++ log4j-async/src/test/java/org/apache/logging/log4j/async/SystemClockTest.java (working copy) @@ -0,0 +1,30 @@ +package org.apache.logging.log4j.async; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class SystemClockTest { + + @Test + public void testLessThan2Millis() { + long millis1 = new SystemClock().currentTimeMillis(); + long sysMillis = System.currentTimeMillis(); + + long diff = sysMillis - millis1; + + assertTrue("diff too large: " + diff, diff <= 1); + } + + @Test + public void testAfterWaitStillLessThan2Millis() throws Exception { + Thread.sleep(100); + long millis1 = new SystemClock().currentTimeMillis(); + long sysMillis = System.currentTimeMillis(); + + long diff = sysMillis - millis1; + + assertTrue("diff too large: " + diff, diff <= 1); + } + +} Index: log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastFileAppenderLocationTest.java =================================================================== --- log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastFileAppenderLocationTest.java (revision 0) +++ log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastFileAppenderLocationTest.java (working copy) @@ -0,0 +1,44 @@ +package org.apache.logging.log4j.async.appender; + +import static org.junit.Assert.*; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LifeCycle; +import org.apache.logging.log4j.core.config.XMLConfigurationFactory; +import org.junit.BeforeClass; +import org.junit.Test; + +public class FastFileAppenderLocationTest { + + @BeforeClass + public static void beforeClass() { + System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY, + "FastFileAppenderLocationTest.xml"); + } + + @Test + public void testLocationIncluded() throws Exception { + File f = new File("FastFileAppenderLocationTest.log"); + // System.out.println(f.getAbsolutePath()); + f.delete(); + Logger log = LogManager.getLogger("com.foo.Bar"); + String msg = "Message with location, flushed with immediate flush=false"; + log.info(msg); + ((LifeCycle) LogManager.getContext()).stop(); // stop async thread + + BufferedReader reader = new BufferedReader(new FileReader(f)); + String line1 = reader.readLine(); + reader.close(); + f.delete(); + assertNotNull("line1", line1); + assertTrue("line1 correct", line1.contains(msg)); + + String location = "testLocationIncluded"; + assertTrue("has location", line1.contains(location)); + } +} Index: log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastFileAppenderTest.java =================================================================== --- log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastFileAppenderTest.java (revision 0) +++ log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastFileAppenderTest.java (working copy) @@ -0,0 +1,44 @@ +package org.apache.logging.log4j.async.appender; + +import static org.junit.Assert.*; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LifeCycle; +import org.apache.logging.log4j.core.config.XMLConfigurationFactory; +import org.junit.BeforeClass; +import org.junit.Test; + +public class FastFileAppenderTest { + + @BeforeClass + public static void beforeClass() { + System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY, + "FastFileAppenderTest.xml"); + } + + @Test + public void testFlushAtEndOfBatch() throws Exception { + File f = new File("FastFileAppenderTest.log"); + // System.out.println(f.getAbsolutePath()); + f.delete(); + Logger log = LogManager.getLogger("com.foo.Bar"); + String msg = "Message flushed with immediate flush=false"; + log.info(msg); + ((LifeCycle) LogManager.getContext()).stop(); // stop async thread + + BufferedReader reader = new BufferedReader(new FileReader(f)); + String line1 = reader.readLine(); + reader.close(); + f.delete(); + assertNotNull("line1", line1); + assertTrue("line1 correct", line1.contains(msg)); + + String location = "testFlushAtEndOfBatch"; + assertTrue("no location", !line1.contains(location)); + } +} Index: log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastRollingFileAppenderLocationTest.java =================================================================== --- log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastRollingFileAppenderLocationTest.java (revision 0) +++ log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastRollingFileAppenderLocationTest.java (working copy) @@ -0,0 +1,44 @@ +package org.apache.logging.log4j.async.appender; + +import static org.junit.Assert.*; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LifeCycle; +import org.apache.logging.log4j.core.config.XMLConfigurationFactory; +import org.junit.BeforeClass; +import org.junit.Test; + +public class FastRollingFileAppenderLocationTest { + + @BeforeClass + public static void beforeClass() { + System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY, + "FastRollingFileAppenderLocationTest.xml"); + } + + @Test + public void testLocationIncluded() throws Exception { + File f = new File("FastRollingFileAppenderLocationTest.log"); + // System.out.println(f.getAbsolutePath()); + f.delete(); + Logger log = LogManager.getLogger("com.foo.Bar"); + String msg = "Message with location, flushed with immediate flush=false"; + log.info(msg); + ((LifeCycle) LogManager.getContext()).stop(); // stop async thread + + BufferedReader reader = new BufferedReader(new FileReader(f)); + String line1 = reader.readLine(); + reader.close(); + f.delete(); + assertNotNull("line1", line1); + assertTrue("line1 correct", line1.contains(msg)); + + String location = "testLocationIncluded"; + assertTrue("has location", line1.contains(location)); + } +} Index: log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastRollingFileAppenderTest.java =================================================================== --- log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastRollingFileAppenderTest.java (revision 0) +++ log4j-async/src/test/java/org/apache/logging/log4j/async/appender/FastRollingFileAppenderTest.java (working copy) @@ -0,0 +1,44 @@ +package org.apache.logging.log4j.async.appender; + +import static org.junit.Assert.*; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LifeCycle; +import org.apache.logging.log4j.core.config.XMLConfigurationFactory; +import org.junit.BeforeClass; +import org.junit.Test; + +public class FastRollingFileAppenderTest { + + @BeforeClass + public static void beforeClass() { + System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY, + "FastRollingFileAppenderTest.xml"); + } + + @Test + public void testFlushAtEndOfBatch() throws Exception { + File f = new File("FastRollingFileAppenderTest.log"); + // System.out.println(f.getAbsolutePath()); + f.delete(); + Logger log = LogManager.getLogger("com.foo.Bar"); + String msg = "Message flushed with immediate flush=false"; + log.info(msg); + ((LifeCycle) LogManager.getContext()).stop(); // stop async thread + + BufferedReader reader = new BufferedReader(new FileReader(f)); + String line1 = reader.readLine(); + reader.close(); + f.delete(); + assertNotNull("line1", line1); + assertTrue("line1 correct", line1.contains(msg)); + + String location = "testFlushAtEndOfBatch"; + assertTrue("no location", !line1.contains(location)); + } +} Index: log4j-async/src/test/java/org/apache/logging/log4j/async/config/AsyncLoggerConfigTest.java =================================================================== --- log4j-async/src/test/java/org/apache/logging/log4j/async/config/AsyncLoggerConfigTest.java (revision 0) +++ log4j-async/src/test/java/org/apache/logging/log4j/async/config/AsyncLoggerConfigTest.java (working copy) @@ -0,0 +1,49 @@ +package org.apache.logging.log4j.async.config; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LifeCycle; +import org.apache.logging.log4j.core.config.XMLConfigurationFactory; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class AsyncLoggerConfigTest { + + @BeforeClass + public static void beforeClass() { + System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY, + "AsyncLoggerConfigTest.xml"); + } + + @Test + public void testAdditivity() throws Exception { + File f = new File("AsyncLoggerConfigTest.log"); + // System.out.println(f.getAbsolutePath()); + f.delete(); + Logger log = LogManager.getLogger("com.foo.Bar"); + String msg = "Additive logging: 2 for the price of 1!"; + log.info(msg); + ((LifeCycle) LogManager.getContext()).stop(); // stop async thread + + BufferedReader reader = new BufferedReader(new FileReader(f)); + String line1 = reader.readLine(); + String line2 = reader.readLine(); + reader.close(); + f.delete(); + assertNotNull("line1", line1); + assertNotNull("line2", line2); + assertTrue("line1 correct", line1.contains(msg)); + assertTrue("line2 correct", line2.contains(msg)); + + String location = "testAdditivity"; + assertTrue("location", + line1.contains(location) || line2.contains(location)); + } + +} Index: log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/MTPerfTest.java =================================================================== --- log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/MTPerfTest.java (revision 0) +++ log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/MTPerfTest.java (working copy) @@ -0,0 +1,97 @@ +package org.apache.logging.log4j.async.perftest; + +import java.io.File; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.async.com.lmax.disruptor.collections.Histogram; +import org.apache.logging.log4j.core.LifeCycle; + +public class MTPerfTest { + + public static void main(String[] args) throws Exception { + String name = args[0]; + String resultFile = args.length > 1 ? args[1] : null; + int tcount = args.length > 2 ? Integer.parseInt(args[2]) : 3; + for (String arg : args) { + if ("-verbose".equalsIgnoreCase(arg)) { + PerfTest.verbose = true; + } + if ("-throughput".equalsIgnoreCase(arg)) { + PerfTest.throughput = true; + } + } + PerfTest.printf("Starting multi-thread (%d) test %s...%n", tcount, name); + runTestAndPrintResult(name, tcount, resultFile); + ((LifeCycle) LogManager.getContext()).stop(); + System.exit(0); + } + + public static void runTestAndPrintResult(final String name, + final int threadCount, String resultFile) throws Exception { + + ThreadContext.put("aKey", "mdcVal"); + PerfTest.println("Warming up the JVM..."); + long t1 = System.nanoTime(); + + // warmup + final Histogram warmupHist = PerfTest.createHistogram(); + Runnable run1 = new Runnable() { + public void run() { + for (int i = 0; i < 10; i++) { + PerfTest.runTest(200000, null, warmupHist); + } + } + }; + Thread thread1 = new Thread(run1); + Thread thread2 = new Thread(run1); + thread1.start(); + thread2.start(); + thread1.join(); + thread2.join(); + + PerfTest.printf("Warmup complete in %.1f seconds%n", + (System.nanoTime() - t1) / (1000.0 * 1000.0 * 1000.0)); + PerfTest.println("Waiting 10 seconds for buffers to drain warmup data..."); + Thread.sleep(10000); + new File("perftest.log").delete(); + + PerfTest.println("Starting the main test..."); + PerfTest.throughput = false; + multiThreadedTestRun(name, threadCount, resultFile); + + Thread.sleep(1000); + PerfTest.throughput = true; + multiThreadedTestRun(name, threadCount, resultFile); + } + + private static void multiThreadedTestRun(final String name, + final int threadCount, String resultFile) throws Exception { + + final Histogram[] histograms = new Histogram[threadCount]; + for (int i = 0; i < histograms.length; i++) { + histograms[i] = PerfTest.createHistogram(); + } + final int LINES = 256 * 1024; + + Thread[] threads = new Thread[threadCount]; + for (int i = 0; i < threads.length; i++) { + final Histogram histogram = histograms[i]; + threads[i] = new Thread() { + public void run() { + PerfTest.runTest(LINES / threadCount, "end", histogram); + } + }; + } + for (Thread thread : threads) { + thread.start(); + } + for (Thread thread : threads) { + thread.join(); + } + + for (Histogram histogram : histograms) { + PerfTest.reportResult(resultFile, name, histogram); + } + } +} \ No newline at end of file Index: log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/PerfTest.java =================================================================== --- log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/PerfTest.java (revision 0) +++ log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/PerfTest.java (working copy) @@ -0,0 +1,188 @@ +package org.apache.logging.log4j.async.perftest; + +import java.io.FileWriter; +import java.io.IOException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.async.com.lmax.disruptor.collections.Histogram; +import org.apache.logging.log4j.core.LifeCycle; + +public class PerfTest { + + private static final String LINE100 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!\"#$%&'()-=^~|\\@`[]{};:+*,.<>/?_123456"; + private static final String LINE500 = LINE100 + LINE100 + LINE100 + LINE100 + + LINE100; + + static boolean verbose = false; + static boolean throughput; + + // determine how long it takes to call System.nanoTime() (on average) + static long calcNanoTimeCost() { + final long iterations = 10000000; + long start = System.nanoTime(); + long finish = start; + + for (int i = 0; i < iterations; i++) { + finish = System.nanoTime(); + } + + if (finish <= start) { + throw new IllegalStateException(); + } + + finish = System.nanoTime(); + return (finish - start) / iterations; + } + + static Histogram createHistogram() { + long[] intervals = new long[31]; + long intervalUpperBound = 1L; + for (int i = 0, size = intervals.length - 1; i < size; i++) { + intervalUpperBound *= 2; + intervals[i] = intervalUpperBound; + } + + intervals[intervals.length - 1] = Long.MAX_VALUE; + return new Histogram(intervals); + } + + public static void main(String[] args) throws Exception { + String name = args[0]; + String resultFile = args.length > 1 ? args[1] : null; + for (String arg : args) { + if ("-verbose".equalsIgnoreCase(arg)) { + verbose = true; + } + if ("-throughput".equalsIgnoreCase(arg)) { + throughput = true; + } + } + printf("Starting test %s...%n", name); + runTestAndPrintResult(name, resultFile); + ((LifeCycle) LogManager.getContext()).stop(); + System.exit(0); + } + + public static void runTestAndPrintResult(final String name, + String resultFile) throws Exception { + Histogram warmupHist = createHistogram(); + + ThreadContext.put("aKey", "mdcVal"); + println("Warming up the JVM..."); + long t1 = System.nanoTime(); + // warmup + for (int i = 0; i < 10; i++) { + PerfTest.runTest(200000, null, warmupHist); + } + + printf("Warmup complete in %.1f seconds%n", (System.nanoTime() - t1) + / (1000.0 * 1000.0 * 1000.0)); + println("Waiting 10 seconds for buffers to drain warmup data..."); + Thread.sleep(10000); + + println("Starting the main test..."); + // test + throughput = false; + runSingleThreadedTest(name, resultFile); + + Thread.sleep(1000); + + throughput = true; + runSingleThreadedTest(name, resultFile); + } + + private static int runSingleThreadedTest(String name, String resultFile) + throws IOException { + Histogram latency = createHistogram(); + final int LINES = 50000; + PerfTest.runTest(LINES, "end", latency); + reportResult(resultFile, name, latency); + return LINES; + } + + static void reportResult(String file, String name, Histogram histogram) + throws IOException { + String result = createSamplingReport(name, histogram); + println(result); + + if (file != null) { + FileWriter writer = new FileWriter(file, true); + writer.write(result); + writer.write(System.getProperty("line.separator")); + writer.close(); + } + } + + static void printf(String msg, Object... objects) { + if (verbose) { + System.out.printf(msg, objects); + } + } + + static void println(String msg) { + if (verbose) { + System.out.println(msg); + } + } + + static String createSamplingReport(String name, Histogram histogram) { + Histogram data = histogram; + if (throughput) { + return data.getMax() + " operations/second"; + } + String result = String.format( + "avg=%.0f 99%%=%d 99.99%%=%d sampleCount=%d", data.getMean(), // + data.getTwoNinesUpperBound(), // + data.getFourNinesUpperBound(), // + data.getCount() // + ); + return result; + } + + public static void runTest(int lines, String finalMessage, + Histogram histogram) { + if (throughput) { + runThroughputTest(lines, histogram); + } else { + runLatencyTest(histogram); + } + if (finalMessage != null) { + Logger logger = LogManager.getLogger(PerfTest.class); + logger.info(finalMessage); + } + } + + private static void runThroughputTest(int lines, Histogram histogram) { + long s1 = System.nanoTime(); + Logger logger = LogManager.getLogger(PerfTest.class); + for (int j = 0; j < lines; j++) { + logger.info(LINE500); + } + long s2 = System.nanoTime(); + long opsPerSec = (1000L * 1000L * 1000L * lines) / (s2 - s1); + histogram.addObservation(opsPerSec); + } + + private static void runLatencyTest(Histogram histogram) { + long nanoTimeCost = calcNanoTimeCost(); + Logger logger = LogManager.getLogger(PerfTest.class); + final int SAMPLES = 1000; + for (int i = 0; i < SAMPLES; i++) { + long s1 = System.nanoTime(); + logger.info(LINE500); + long s2 = System.nanoTime(); + long value = s2 - s1 - nanoTimeCost; + if (value > 0) { + histogram.addObservation(value); + } + // wait 1 microsec + final long PAUSE_NANOS = 1000; + long pauseStart = System.nanoTime(); + while (PAUSE_NANOS > (System.nanoTime() - pauseStart)) { + // busy spin + } + } + } +} Index: log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/PerfTestDriver.java =================================================================== --- log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/PerfTestDriver.java (revision 0) +++ log4j-async/src/test/java/org/apache/logging/log4j/async/perftest/PerfTestDriver.java (working copy) @@ -0,0 +1,357 @@ +package org.apache.logging.log4j.async.perftest; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.CharBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.logging.log4j.async.ClockFactory; + +public class PerfTestDriver { + static enum WaitStrategy { + Sleep, Yield, Block + }; + + static class Setup implements Comparable { + private Class _class; + private String _log4jConfig; + private String _name; + private String[] _systemProperties; + private int _threadCount; + private File _temp; + public Stats _stats; + private WaitStrategy _wait; + + public Setup(Class klass, String name, String log4jConfig, + int threadCount, WaitStrategy wait, String... systemProperties) + throws IOException { + _class = klass; + _name = name; + _log4jConfig = log4jConfig; + _threadCount = threadCount; + _systemProperties = systemProperties; + _wait = wait; + _temp = File.createTempFile("log4jperformance", ".txt"); + } + + List processArguments(String java) { + List args = new ArrayList(); + args.add(java); + args.add("-server"); + args.add("-Xms1g"); + args.add("-Xmx1g"); + + // args.add("-XX:+UseParallelOldGC"); + // args.add("-Xloggc:gc.log"); + // args.add("-XX:+PrintGCTimeStamps"); + // args.add("-XX:+PrintGCDetails"); + // args.add("-XX:+PrintGCDateStamps"); + // args.add("-XX:+PrintGCApplicationStoppedTime"); + // args.add("-XX:+PrintGCApplicationConcurrentTime"); + // args.add("-XX:+PrintSafepointStatistics"); + + args.add("-Dlog4j.configurationFile=" + _log4jConfig); + int ringBufferSize = getUserSpecifiedRingBufferSize(); + if (ringBufferSize >= 128) { + args.add("-DAsyncLoggerConfig.RingBufferSize=" + ringBufferSize); + args.add("-DAsyncLogger.RingBufferSize=" + ringBufferSize); + } + args.add("-DAsyncLoggerConfig.WaitStrategy=" + _wait); + args.add("-DAsyncLogger.WaitStrategy=" + _wait); + if (_systemProperties != null) { + for (String property : _systemProperties) { + args.add(property); + } + } + args.add("-cp"); + args.add(System.getProperty("java.class.path")); + args.add(_class.getName()); + args.add(_name); + args.add(_temp.getAbsolutePath()); + args.add(String.valueOf(_threadCount)); + return args; + } + + private int getUserSpecifiedRingBufferSize() { + try { + return Integer.parseInt(System.getProperty("RingBufferSize", + "-1")); + } catch (Exception ignored) { + return -1; + } + } + + ProcessBuilder latencyTest(String java) { + return new ProcessBuilder(processArguments(java)); + } + + ProcessBuilder throughputTest(String java) { + List args = processArguments(java); + args.add("-throughput"); + return new ProcessBuilder(args); + } + + @Override + public int compareTo(Setup other) { + // largest ops/sec first + return (int) Math.signum(other._stats._averageOpsPerSec + - _stats._averageOpsPerSec); + } + + public String description() { + String detail = _class.getSimpleName(); + if (PerfTest.class == _class) { + detail = "single thread"; + } else if (MTPerfTest.class == _class) { + detail = _threadCount + " threads"; + } + return _name + " (" + detail + ")"; + } + } + + static class Stats { + int _count; + long _average; + long _pct99; + long _pct99_99; + double _latencyRowCount; + int _throughputRowCount; + private long _averageOpsPerSec; + + // example line: avg=828 99%=1118 99.99%=5028 Count=3125 + public Stats(String raw, int repeat) { + String[] lines = raw.split("[\\r\\n]+"); + long totalOps = 0; + for (String line : lines) { + if (line.startsWith("avg")) { + _latencyRowCount++; + String[] parts = line.split(" "); + int i = 0; + _average += Long.parseLong(parts[i++].split("=")[1]); + _pct99 += Long.parseLong(parts[i++].split("=")[1]); + _pct99_99 += Long.parseLong(parts[i++].split("=")[1]); + _count += Integer.parseInt(parts[i++].split("=")[1]); + } else { + _throughputRowCount++; + String number = line.substring(0, line.indexOf(' ')); + long opsPerSec = Long.parseLong(number); + totalOps += opsPerSec; + } + } + _averageOpsPerSec = totalOps / (int) _throughputRowCount; + } + + public String toString() { + String fmt = "throughput: %,d ops/sec. latency(ns): avg=%.1f 99%% < %.1f 99.99%% < %.1f (%d samples)"; + return String.format(fmt, _averageOpsPerSec, // + _average / _latencyRowCount, // mean latency + _pct99 / _latencyRowCount, // 99% observations less than + _pct99_99 / _latencyRowCount,// 99.99% observs less than + _count); + } + } + + // single-threaded performance test + private static Setup s(String config, String name, + String... systemProperties) throws IOException { + WaitStrategy wait = WaitStrategy.valueOf(System.getProperty( + "WaitStrategy", "Block")); + return new Setup(PerfTest.class, name, config, 1, wait, + systemProperties); + } + + // single-threaded performance test + private static Setup m(String config, String name, int threadCount, + String... systemProperties) throws IOException { + WaitStrategy wait = WaitStrategy.valueOf(System.getProperty( + "WaitStrategy", "Block")); + return new Setup(MTPerfTest.class, name, config, threadCount, wait, + systemProperties); + } + + public static void main(String[] args) throws Exception { + final String ALL_ASYNC = "-DLog4jContextSelector=org.apache.logging.log4j.async.AsyncLoggerContextSelector"; + final String CACHEDCLOCK = "-D" + ClockFactory.PROPERTY_NAME + + "=CachedClock"; + + long start = System.nanoTime(); + Setup[] tests = new Setup[] { // + // 2 threads + // m("perf5AsyncApndNoLoc.xml", "Async Appender no location", + // 2), // + m("perf6AsyncApndLoc.xml", "Async Appender with location", 2), // + // m("perf3PlainNoLoc.xml", "All async no loc CachedClock", 2, + // ALL_ASYNC, CACHEDCLOCK), // + // m("perf3PlainNoLoc.xml", "All sync no location", 2), // + // m("perf3PlainNoLoc.xml", "All async no loc SysClock", 2, + // ALL_ASYNC), // + // m("perf7MixedNoLoc.xml", "Mixed async no location", 2), // + m("perf8MixedLoc.xml", "Mixed async with location", 2), // + m("perf4PlainLocation.xml", "All async with location SysClock", + 2, ALL_ASYNC), // + m("perf4PlainLocation.xml", + "All async with location CachedClock", 2, ALL_ASYNC, + CACHEDCLOCK), // + m("perf4PlainLocation.xml", "All sync with location", 2), // + // m("perf1syncFile.xml", "FileAppender", 2), // + // m("perf1syncFastFile.xml", "FastFileAppender", 2), // + // m("perf2syncRollFile.xml", "RollFileAppender", 2), // + // m("perf2syncRollFastFile.xml", "RollFastFileAppender", 2), // + + // 4 threads + // m("perf5AsyncApndNoLoc.xml", "Async Appender no location", + // 4), // + m("perf6AsyncApndLoc.xml", "Async Appender with location", 4), // + // m("perf3PlainNoLoc.xml", "All async no loc CachedClock", 4, + // ALL_ASYNC, CACHEDCLOCK), // + // m("perf3PlainNoLoc.xml", "All sync no location", 4), // + // m("perf3PlainNoLoc.xml", "All async no loc SysClock", 4, + // ALL_ASYNC), // + // m("perf7MixedNoLoc.xml", "Mixed async no location", 4), // + m("perf8MixedLoc.xml", "Mixed async with location", 4), // + m("perf4PlainLocation.xml", "All async with location SysClock", + 4, ALL_ASYNC), // + m("perf4PlainLocation.xml", + "All async with location CachedClock", 4, ALL_ASYNC, + CACHEDCLOCK), // + m("perf4PlainLocation.xml", "All sync with location", 4), // + // m("perf1syncFile.xml", "FileAppender", 4), // + // m("perf1syncFastFile.xml", "FastFileAppender", 4), // + // m("perf2syncRollFile.xml", "RollFileAppender", 4), // + // m("perf2syncRollFastFile.xml", "RollFastFileAppender", 4), // + + // 8 threads + // m("perf5AsyncApndNoLoc.xml", "Async Appender no location", + // 8), // + m("perf6AsyncApndLoc.xml", "Async Appender with location", 8), // + // m("perf3PlainNoLoc.xml", "All async no loc CachedClock", 8, + // ALL_ASYNC, CACHEDCLOCK), // + // m("perf3PlainNoLoc.xml", "All sync no location", 8), // + // m("perf3PlainNoLoc.xml", "All async no loc SysClock", 8, + // ALL_ASYNC), // + // m("perf7MixedNoLoc.xml", "Mixed async no location", 8), // + m("perf8MixedLoc.xml", "Mixed async with location", 8), // + m("perf4PlainLocation.xml", "All async with location SysClock", + 8, ALL_ASYNC), // + m("perf4PlainLocation.xml", + "All async with location CachedClock", 8, ALL_ASYNC, + CACHEDCLOCK), // + m("perf4PlainLocation.xml", "All sync with location", 8), // + // m("perf1syncFile.xml", "FileAppender", 8), // + // m("perf1syncFastFile.xml", "FastFileAppender", 8), // + // m("perf2syncRollFile.xml", "RollFileAppender", 8), // + // m("perf2syncRollFastFile.xml", "RollFastFileAppender", 8), // + + // s("perf3PlainNoLoc.xml", "All async no loc CachedClock", + // ALL_ASYNC, CACHEDCLOCK), // + // s("perf3PlainNoLoc.xml", "All sync no location"), // + // s("perf3PlainNoLoc.xml", "All async no loc SysClock", + // ALL_ASYNC), // + // s("perf5AsyncApndNoLoc.xml", "Async Appender no location"), + // // + s("perf6AsyncApndLoc.xml", "Async Appender with location"), // + // s("perf7MixedNoLoc.xml", "Mixed async no location"), // + s("perf8MixedLoc.xml", "Mixed async with location"), // + s("perf4PlainLocation.xml", "All async with location SysClock", + ALL_ASYNC), // + s("perf4PlainLocation.xml", + "All async with location CachedClock", ALL_ASYNC, + CACHEDCLOCK), // + s("perf4PlainLocation.xml", "All sync with location"), // + // s("perf1syncFile.xml", "FileAppender"), // + // s("perf1syncFastFile.xml", "FastFileAppender"), // + // s("perf2syncRollFile.xml", "RollFileAppender"), // + // s("perf2syncRollFastFile.xml", "RollFastFileAppender"), // + }; + + String java = args.length > 0 ? args[0] : "java"; + int repeat = args.length > 1 ? Integer.parseInt(args[1]) : 5; + int x = 0; + for (Setup config : tests) { + System.out.print(config.description()); + ProcessBuilder pb = config.latencyTest(java); + pb.redirectErrorStream(true); // merge System.out and System.err + long t1 = System.nanoTime(); + runPerfTest(repeat, x++, config, pb); + System.out.printf(" took %.1f seconds%n", (System.nanoTime() - t1) + / (1000.0 * 1000.0 * 1000.0)); + + FileReader reader = new FileReader(config._temp); + CharBuffer buffer = CharBuffer.allocate(4 * 1024); + reader.read(buffer); + reader.close(); + config._temp.delete(); + buffer.flip(); + + String raw = buffer.toString(); + System.out.print(raw); + Stats stats = new Stats(raw, repeat); + System.out.println(stats); + System.out.println("-----"); + config._stats = stats; + } + new File("perftest.log").delete(); + System.out + .printf("Done. Total duration: %.1f minutes%n", + (System.nanoTime() - start) + / (60.0 * 1000.0 * 1000.0 * 1000.0)); + + printRanking(tests); + } + + private static void printRanking(Setup[] tests) { + System.out.println(); + System.out.println("Ranking:"); + Arrays.sort(tests); + for (int i = 0; i < tests.length; i++) { + Setup setup = tests[i]; + System.out.println((i + 1) + ". " + setup.description() + ": " + + setup._stats); + } + } + + private static void runPerfTest(int repeat, int setupIndex, Setup config, + ProcessBuilder pb) throws IOException, InterruptedException { + for (int i = 0; i < repeat; i++) { + System.out.print(" (" + (i + 1) + "/" + repeat + ")..."); + final Process process = pb.start(); + + final boolean[] stop = { false }; + printProcessOutput(process, stop); + process.waitFor(); + stop[0] = true; + + File gc = new File("gc" + setupIndex + "_" + i + + config._log4jConfig + ".log"); + if (gc.exists()) { + gc.delete(); + } + new File("gc.log").renameTo(gc); + } + } + + private static Thread printProcessOutput(final Process process, + final boolean[] stop) { + + Thread t = new Thread("OutputWriter") { + public void run() { + BufferedReader in = new BufferedReader(new InputStreamReader( + process.getInputStream())); + try { + String line = null; + while (!stop[0] && (line = in.readLine()) != null) { + System.out.println(line); + } + } catch (Exception ignored) { + } + } + }; + t.start(); + return t; + } +} Index: log4j-async/src/test/resources/AsyncLoggerConfigTest.xml =================================================================== --- log4j-async/src/test/resources/AsyncLoggerConfigTest.xml (revision 0) +++ log4j-async/src/test/resources/AsyncLoggerConfigTest.xml (working copy) @@ -0,0 +1,19 @@ + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %location %ex%n + + + + + + + + + + + + + \ No newline at end of file Index: log4j-async/src/test/resources/AsyncLoggerLocationTest.xml =================================================================== --- log4j-async/src/test/resources/AsyncLoggerLocationTest.xml (revision 0) +++ log4j-async/src/test/resources/AsyncLoggerLocationTest.xml (working copy) @@ -0,0 +1,17 @@ + + + + + + %d %p %c{1.} [%t] %X{aKey} %location %m %ex%n + + + + + + + + + + \ No newline at end of file Index: log4j-async/src/test/resources/AsyncLoggerTest.xml =================================================================== --- log4j-async/src/test/resources/AsyncLoggerTest.xml (revision 0) +++ log4j-async/src/test/resources/AsyncLoggerTest.xml (working copy) @@ -0,0 +1,17 @@ + + + + + + %d %p %c{1.} [%t] %X{aKey} %location %m %ex%n + + + + + + + + + + \ No newline at end of file Index: log4j-async/src/test/resources/FastFileAppenderLocationTest.xml =================================================================== --- log4j-async/src/test/resources/FastFileAppenderLocationTest.xml (revision 0) +++ log4j-async/src/test/resources/FastFileAppenderLocationTest.xml (working copy) @@ -0,0 +1,19 @@ + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %location %ex%n + + + + + + + + + + + \ No newline at end of file Index: log4j-async/src/test/resources/FastFileAppenderTest.xml =================================================================== --- log4j-async/src/test/resources/FastFileAppenderTest.xml (revision 0) +++ log4j-async/src/test/resources/FastFileAppenderTest.xml (working copy) @@ -0,0 +1,19 @@ + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %location %ex%n + + + + + + + + + + + \ No newline at end of file Index: log4j-async/src/test/resources/FastRollingFileAppenderLocationTest.xml =================================================================== --- log4j-async/src/test/resources/FastRollingFileAppenderLocationTest.xml (revision 0) +++ log4j-async/src/test/resources/FastRollingFileAppenderLocationTest.xml (working copy) @@ -0,0 +1,21 @@ + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %location %ex%n + + + + + + + + + + + + + \ No newline at end of file Index: log4j-async/src/test/resources/FastRollingFileAppenderTest.xml =================================================================== --- log4j-async/src/test/resources/FastRollingFileAppenderTest.xml (revision 0) +++ log4j-async/src/test/resources/FastRollingFileAppenderTest.xml (working copy) @@ -0,0 +1,21 @@ + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %location %ex%n + + + + + + + + + + + + + \ No newline at end of file Index: log4j-async/src/test/resources/perf1syncFastFile.xml =================================================================== --- log4j-async/src/test/resources/perf1syncFastFile.xml (revision 0) +++ log4j-async/src/test/resources/perf1syncFastFile.xml (working copy) @@ -0,0 +1,15 @@ + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %ex%n + + + + + + + + + Index: log4j-async/src/test/resources/perf1syncFile.xml =================================================================== --- log4j-async/src/test/resources/perf1syncFile.xml (revision 0) +++ log4j-async/src/test/resources/perf1syncFile.xml (working copy) @@ -0,0 +1,15 @@ + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %ex%n + + + + + + + + + Index: log4j-async/src/test/resources/perf2syncRollFastFile.xml =================================================================== --- log4j-async/src/test/resources/perf2syncRollFastFile.xml (revision 0) +++ log4j-async/src/test/resources/perf2syncRollFastFile.xml (working copy) @@ -0,0 +1,20 @@ + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %ex%n + + + + + + + + + + + + Index: log4j-async/src/test/resources/perf2syncRollFile.xml =================================================================== --- log4j-async/src/test/resources/perf2syncRollFile.xml (revision 0) +++ log4j-async/src/test/resources/perf2syncRollFile.xml (working copy) @@ -0,0 +1,20 @@ + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %ex%n + + + + + + + + + + + + Index: log4j-async/src/test/resources/perf3PlainNoLoc.xml =================================================================== --- log4j-async/src/test/resources/perf3PlainNoLoc.xml (revision 0) +++ log4j-async/src/test/resources/perf3PlainNoLoc.xml (working copy) @@ -0,0 +1,15 @@ + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %ex%n + + + + + + + + + Index: log4j-async/src/test/resources/perf4PlainLocation.xml =================================================================== --- log4j-async/src/test/resources/perf4PlainLocation.xml (revision 0) +++ log4j-async/src/test/resources/perf4PlainLocation.xml (working copy) @@ -0,0 +1,15 @@ + + + + + + %d %p %c{1.} %C %location %line [%t] %X{aKey} %m %ex%n + + + + + + + + + Index: log4j-async/src/test/resources/perf5AsyncApndNoLoc.xml =================================================================== --- log4j-async/src/test/resources/perf5AsyncApndNoLoc.xml (revision 0) +++ log4j-async/src/test/resources/perf5AsyncApndNoLoc.xml (working copy) @@ -0,0 +1,18 @@ + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %ex%n + + + + + + + + + + + + Index: log4j-async/src/test/resources/perf6AsyncApndLoc.xml =================================================================== --- log4j-async/src/test/resources/perf6AsyncApndLoc.xml (revision 0) +++ log4j-async/src/test/resources/perf6AsyncApndLoc.xml (working copy) @@ -0,0 +1,18 @@ + + + + + + %d %p %c{1.} %C %location %line [%t] %X{aKey} %m %ex%n + + + + + + + + + + + + Index: log4j-async/src/test/resources/perf7MixedNoLoc.xml =================================================================== --- log4j-async/src/test/resources/perf7MixedNoLoc.xml (revision 0) +++ log4j-async/src/test/resources/perf7MixedNoLoc.xml (working copy) @@ -0,0 +1,15 @@ + + + + + + %d %p %c{1.} [%t] %X{aKey} %m %ex%n + + + + + + + + + Index: log4j-async/src/test/resources/perf8MixedLoc.xml =================================================================== --- log4j-async/src/test/resources/perf8MixedLoc.xml (revision 0) +++ log4j-async/src/test/resources/perf8MixedLoc.xml (working copy) @@ -0,0 +1,15 @@ + + + + + + %d %p %c{1.} %C %location %line [%t] %X{aKey} %m %ex%n + + + + + + + + + \ No newline at end of file Index: src/site/site.xml =================================================================== --- src/site/site.xml (revision 1458654) +++ src/site/site.xml (working copy) @@ -101,6 +101,17 @@ + + + + + + + + + + + Index: src/site/xdoc/manual/async.xml =================================================================== --- src/site/xdoc/manual/async.xml (revision 0) +++ src/site/xdoc/manual/async.xml (working copy) @@ -0,0 +1,1463 @@ + + + + + Log4j2 Asynchronous Logging + Remko Popma + + +

+

+ Asynchronous logging can improve your application's + performance by executing the I/O operations + in a separate thread. + Log4j2 makes a number of improvements in this area. +

+
    +
  • + Asynchronous Loggers + are a new addition to Log4j2. + Their aim is to return from the call + to Logger.log to the application as + soon as possible. You can choose + between making all Loggers asynchronous + or using a mixture of synchronous + and asynchronous Loggers. Making all + Loggers asynchronous will give + the best performance, while mixing + gives you more flexibility. +
  • +
  • + Asynchronous Appenders + already existed in Log4j 1.x, but have + been enhanced in two ways. First, with the new + "needsLocation=true|false" attribute on + the logger configuration, they can avoid + taking stack snapshots if not required. This alone + gives a 4 - 23 times performance improvement. + Second, they will now flush to disk + at the end of a batch (when the queue is empty). + This produces the same result as configuring + "immediateFlush=true", that is, all + received log events are always available on disk, + but is more efficient because it does not need to + touch the disk on each and every log event. +
  • +
  • + Fast File Appenders + are an alternative to Buffered File + Appenders. Under the hood, these + new appenders use a ByteBuffer + RandomAccessFile instead + of a BufferedOutputStream. In our testing + this was about 10-30% faster. + These appenders can also be used + with synchronous loggers + and will give the same performance benefits. +
  • +
+
+ +

+ Although asynchronous logging can give significant performance + benefits, there are situations where you may want to + choose synchronous logging. + This section describes some of the trade-offs of + asynchronous logging. +

+

+ Benefits +

+
    +
  • Higher throughput. + With an asynchronous logger + (and "needsLocation=false"), + your application can log messages at 7 - 20 times + the rate of a synchronous logger. +
  • +
  • + Lower logging latency: the time your application thread(s) + spend in the logging library, that is, how long it + takes for a call to Logger.log to return, + is 4 - 10 times shorter on average. +
  • +
  • Prevent or dampen latency spikes during bursts of events. + If the queue size is configured large enough to handle + spikes, asynchronous logging will help prevent your + application from falling behind (as much) during + sudden bursts of activity. +
  • +
+ Drawbacks +
    +
  • Location configuration required. If one of the layouts is + configured with a location-related attribute like %line, + %location, %class or %method, Log4j will take a snapshot of the + stack, and walk the stack trace to find the location information. + This is an expensive operation, and synchronous loggers wait as + long as possible before they take this stack snapshot. If no + location is required, the snapshot will never be taken. However, + asynchronous loggers need to make this decision before passing the + log message to another thread; the location information will be + lost after that point. +
    + The current version of Log4j2 cannot automatically + detect if any filter or layout needs location + information, so you need to configure this manually. + In your logger configuration you can specify + needsLocation="false" (or "true") to + control whether location information should be passed + to the I/O thread. +
    + For legacy reasons, if you don't specify needsLocation, + the default value is "needsLocation=true". +
    +
  • +
  • Error handling. If a problem happens during the logging + process and an exception is thrown, it is less easy for an + asynchronous logger or appender to signal this problem to the + application. This can partly be alleviated by configuring an + ExceptionHandler, but this may + still not cover all cases. For this + reason, if logging is part of your business logic, + for example if + you are using Log4j as an audit logging framework, + we would + recommend to synchronously log those audit messages. + (Note that you + can still combine them + and use asynchronous logging for debug/trace + logging in addition to synchronous + logging for the audit trail.) +
  • +
+ + + +

+ This is simplest to configure and gives the best performance. + To make all loggers asynchronous, add the log4j-async.jar to + the classpath + and set the system property Log4jContextSelector + to + org.apache.logging.log4j.async.AsyncLoggerContextSelector. +

+

+ + Important: + + do not forget to set "needsLocation=false" + in the configuration of all loggers, including the root logger, + unless one of your layouts or custom filters actually needs + location information. +

+

+ A configuration that does not require location might look like: +


+
+  
+    
+      
+        %d %p %c{1.} [%t] %m %ex%n
+      
+    
+  
+  
+    
+      
+    
+  
+]]>
+

+

+ When + AsyncLoggerContextSelector is used to make + all loggers asynchronous, make sure to use normal + <root> and + <logger> elements + in the configuration. The + AsyncLoggerContextSelector + will ensure that all loggers are asynchronous, using a mechanism + that is different from what happens when you configure + <asyncRoot> or + <asyncLogger>. + The latter elements are intended for mixing async with sync + loggers. If you use both mechanisms together you will end + up with two + background threads, where your application passes the log + message to + thread A, which passes the message to thread B, + which then finally + logs the message to disk. This works, but + there will be an unnecessary + step in the middle. +

+ +

+ There are a few system properties + you can use to control + aspects of the asynchronous logging subsystem. + Some of these can be used to + tune logging performance. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
System PropertyDefault ValueDescription
AsyncLogger.ExceptionHandler + null + + Fully qualified name of a class that implements the + org.apache.logging.log4j.async.com.lmax.disruptor.ExceptionHandler + + interface. The class needs to have a public + zero-argument + constructor. If specified, this class + will be notified when an + exception occurs while + logging the messages. +
AsyncLogger.RingBufferSize256 * 1024 + + Size (number of slots) in the RingBuffer used by the + asynchronous logging subsystem. + Make this value large enough to + deal with bursts + of activity. The minimum size is 128. + The + RingBuffer will + be pre-allocated at first use and will never grow + or shrink during the life of the system. +
AsyncLogger.WaitStrategy + Sleep + + Valid values: Block, Sleep, Yield. +
+ Block + is a strategy that uses a lock and + condition + variable for the I/O thread waiting for log events. + Block can be used when + throughput and low-latency + are not as important as CPU resource. + Recommended for resource constrained/virtualised environments. +
+ Sleep + is a strategy that initially spins, then + uses a Thread.yield(), and + eventually parks for the + minimum number of nanos the OS and JVM will allow + while the I/O thread is waiting for log events. + Sleep is a good compromise between performance + and CPU resource. + This strategy has very low impact on the + application thread, in exchange for some additional + latency for actually getting the message logged. +
+ Yield + is a strategy that uses a Thread.yield() for + waiting for log events after an initially spinning. + Yield is a good compromise between performance + and CPU resource, but may use more CPU than Sleep + in order to get the message logged to disk sooner. +
AsyncLogger.Clock + SystemClock + + Implementation of the + org.apache.logging.log4j.async.Clock + interface that is used for timestamping the log + events when all loggers are asynchronous. +
+ By default, System.currentTimeMillis + is called on every log event. +
+ CachedClock + is an optimization where + time stamps are generated from a clock that + updates its internal time in a background thread once + every millisecond, or every 1024 log events, + whichever comes first. This + reduces logging latency a little, at the cost of + some precision in the logged time stamps. + Unless you are logging many + events, you may see "jumps" + of 10-16 milliseconds between log time stamps. +
+ You can also specify a fully qualified class name + of a custom class that implements the + Clock + interface. +
System Properties to configure all + asynchronous loggers +
+ + + +

+ Synchronous and Asynchronous loggers can be combined in + configuration. This gives you more flexibility at the cost + of a + slight loss in performance (compared to making + all loggers asynchronous). Use the + <asyncRoot> + or + <asyncLogger> + configuration elements to specify the loggers that need to + be + asynchronous. The same configuration file can also + contain + <root> + and + <logger> + elements for the synchronous loggers. +

+

+ There is no need to set the system property + "Log4jContextSelector" to any value. +

+

+ + Important: + + remember to configure all loggers, including + the root logger, with the correct needsLocation="true|false" value. +

+

+ A configuration that mixes asynchronous loggers might look like: +


+
+  
+    
+      
+        %d %p %class{1.} [%t] %location %m %ex%n
+      
+    
+  
+  
+    
+      
+    
+    
+      
+    
+  
+]]>
+

+

+ There are a few system properties + you can use to control + aspects of the asynchronous logging subsystem. + Some of these can be used to + tune logging performance, +

+ + + + + + + + + + + + + + + + + + + + + + +
System PropertyDefault ValueDescription
AsyncLoggerConfig.ExceptionHandler + null + + Fully qualified name of a class that implements the + org.apache.logging.log4j.async.com.lmax.disruptor.ExceptionHandler + interface. The class needs to have a public + zero-argument + constructor. If specified, this class + will be notified when an + exception occurs while + logging the messages. +
AsyncLoggerConfig.RingBufferSize256 * 1024 + + Size (number of slots) in the RingBuffer used by the + asynchronous logging subsystem. + Make this value large enough to + deal with bursts + of activity. The minimum size is 128. + The RingBuffer will + be pre-allocated at first use and will never grow + or shrink during the life of the system. +
AsyncLoggerConfig.WaitStrategy + Sleep + + Valid values: Block, Sleep, Yield. +
+ Block + is a strategy that uses a lock and + condition + variable for the I/O thread waiting for log events. + Block can be used when + throughput and low-latency + are not as important as CPU resource. + Recommended for resource constrained/virtualised environments. +
+ Sleep + is a strategy that initially spins, then + uses a Thread.yield(), and + eventually parks for the + minimum number of nanos the OS and JVM will allow + while the I/O thread is waiting for log events. + Sleep is a good compromise between performance + and CPU resource. + This strategy has very low impact on the + application thread, in exchange for some additional + latency for actually getting the message logged. +
+ Yield + is a strategy that uses a Thread.yield() for + waiting for log events after an initially spinning. + Yield is a good compromise between performance + and CPU resource, but may use more CPU than Sleep + in order to get the message logged to disk sooner. +
System Properties to configure mixed + asynchronous and normal loggers +
+ + + +

+ The FastFileAppender is similar to the standard + FileAppender + except it is always buffered (this cannot be switched off) + and internally it uses a + ByteBuffer + RandomAccessFile + instead of a + BufferedOutputStream. + We saw a 10-30% performance improvement compared to + FileAppender with "bufferedIO=true" in our + measurements. + Similar to the FileAppender, + FastFileAppender uses a FastFileManager to actually perform the + file I/O. While FastFileAppender + from different Configurations + cannot be shared, the FastFileManagers can be if the Manager is + accessible. For example, two webapps in a + servlet container can have + their own configuration and safely + write to the same file if Log4j + is in a ClassLoader that is common to + both of them. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Parameter NameTypeDescription
appendbooleanWhen true - the default, records will be appended to the end + of the file. When set to false, + the file will be cleared before + new records are written. +
fileNameStringThe name of the file to write to. If the file, or any of its + parent directories, do not exist, + they will be created. +
filtersFilterA Filter to determine if the event should be handled by this + Appender. More than one Filter + may be used by using a CompositeFilter. +
immediateFlushbooleanWhen set to true, each write will be followed by a flush. + This will guarantee the data is written + to disk but could impact performance. + This option is only necessary when using this + appender with synchronous loggers. Asynchronous loggers will + automatically flush at the end of a batch, which also guarantees + the data is written to disk but is more efficient. +
layoutLayoutThe Layout to use to format the LogEvent
nameStringThe name of the Appender.
suppressExceptionsbooleanThe default is true, causing exceptions to be internally + logged and then ignored. When set to + false exceptions will be + percolated to the caller. +
FastFileAppender Parameters
+

+ Here is a sample FastFile configuration: + +


+
+  
+    
+      
+        %d %p %c{1.} [%t] %m%n
+      
+    
+  
+  
+    
+      
+    
+  
+]]>
+

+ + + +

+ The FastRollingFileAppender is similar to the standard + RollingFileAppender + except it is always buffered (this cannot be switched off) + and + internally it uses a + ByteBuffer + RandomAccessFile + instead of a + BufferedOutputStream. + We saw a 10-30% performance improvement compared to + RollingFileAppender with "bufferedIO=true" + in our + measurements. + The FastRollingFileAppender writes + to the File named in the + fileName parameter + and rolls the file over according the + TriggeringPolicy + and the RolloverPolicy. + + Similar to the RollingFileAppender, + FastRollingFileAppender uses a FastRollingFileManager + to actually perform the + file I/O and perform the rollover. While FastRollingFileAppender + from different Configurations cannot be + shared, the FastRollingFileManagers can be + if the Manager is accessible. + For example, two webapps in a servlet + container can have their own configuration and safely write to the + same file if Log4j is in a ClassLoader that is common to both of them. +

+

+ A FastRollingFileAppender requires a + TriggeringPolicy + and a + RolloverStrategy. + The triggering policy determines if a rollover should + be performed + while the RolloverStrategy defines how the rollover + should be done. + If no RolloverStrategy + is configured, FastRollingFileAppender will + use the + DefaultRolloverStrategy. +

+

+ File locking is not supported by the FastRollingFileAppender. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Parameter NameTypeDescription
appendbooleanWhen true - the default, records will be appended to the end + of the file. When set to false, + the file will be cleared before + new records are written. +
filterFilterA Filter to determine if the event should be handled by this + Appender. More than one Filter + may be used by using a + CompositeFilter. +
fileNameStringThe name of the file to write to. If the file, or any of its + parent directories, do not exist, + they will be created. +
filePatternString + The pattern of the file name of the archived log file. The format + of the pattern should is + dependent on the RolloverPolicy that is + used. The DefaultRolloverPolicy + will accept both + a date/time + pattern compatible with + + SimpleDateFormat + + and/or a %i which represents an integer counter. The pattern + also supports interpolation at + runtime so any of the Lookups (such + as the + DateLookup + can + be included in the pattern. +
immediateFlushbooleanWhen set to true, each write will be followed by a flush. This + will guarantee the data is written + to disk but could impact + performance. + This option is only necessary when using this + appender with + synchronous loggers. Asynchronous loggers will + automatically + flush at the end of a batch, which also guarantees + the data + is written to disk but is more efficient. +
layoutLayoutThe Layout to use to format the LogEvent
nameStringThe name of the Appender.
policyTriggeringPolicyThe policy to use to determine if a rollover should occur. +
strategyRolloverStrategyThe strategy to use to determine the name and location of the + archive file. +
suppressExceptionsbooleanThe default is true, causing exceptions to be internally + logged and then ignored. When set to + false exceptions will be + percolated to the caller. +
FastRollingFileAppender Parameters
+ +

Triggering Policies

+

+ See + RollingFileAppender Triggering Policies. +

+ +

Rollover Strategies

+

+ See + RollingFileAppender Rollover Strategies. +

+ +

+ Below is a sample configuration that uses a FastRollingFileAppender + with both the time and size based + triggering policies, will create + up to 7 archives on the same day (1-7) that + are stored in a + directory + based on the current year and month, and will compress + each + archive using gzip: + +


+
+  
+    
+      
+        %d %p %c{1.} [%t] %m%n
+      
+      
+        
+        
+      
+    
+  
+  
+    
+      
+    
+  
+]]>
+

+

+ This second example shows a rollover strategy that will keep up to + 20 files before removing them. +


+
+  
+    
+      
+        %d %p %c{1.} [%t] %m%n
+      
+      
+        
+        
+      
+      
+    
+  
+  
+    
+      
+    
+  
+]]>
+

+

+ Below is a sample configuration that uses a FastRollingFileAppender + with both the time and size based + triggering policies, will create + up to 7 archives on the same day (1-7) that + are stored in a + directory + based on the current year and month, and will compress + each + archive using gzip and will roll every 6 hours when the hour is + divisible + by 6: + +


+
+  
+    
+      
+        %d %p %c{1.} [%t] %m%n
+      
+      
+        
+        
+      
+    
+  
+  
+    
+      
+    
+  
+]]>
+

+ + + +

+ This section shows the results of our performance testing. + The + methodology used was the same for all tests: +

+
    +
  • First, warm up the JVM by logging 200,000 log messages of 500 + characters.
  • +
  • Repeat the warm-up 10 times, then wait 10 seconds for the I/O thread to catch up and buffers to drain.
  • +
  • Latency test: at less than saturation, measure how + long a call to Logger.log takes. + Pause for 10 microsecond between measurements. + Repeat this 5 million times.
  • +
  • Throughput test: measure how long it takes to log 256 * + 1024 / threadCount messages of 500 characters.
  • +
+

Throughput of Logging Without Location (needsLocation="false")

+

On Windows 7 (64bit) with JDK1.7.0_11, 2-core Intel i5-3317u CPU + @1.70Ghz with hyperthreading switched on (4 virtual cores):

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Logger1 thread2 threads4 threads8 threads
All Async1,464,517943,197561,161444,504
Mixed Async897,858584,239391,814524,226
Async Appender507,097504,168254,453161,342
Synchronous187,285123,86255,71827,613
Throughput in log messages/second +
+

+

On Solaris 10 (64bit) with JDK1.7.0_06, 4-core Xeon X5570 CPU + @2.93Ghz with hyperthreading switched on (8 virtual cores):

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Logger1 thread2 threads4 threads8 threads
All Async1,568,623948,653629,951651,340
Mixed Async1,371,511567,829407,676408,071
Async Appender1,106,130452,140213,777110,036
Synchronous162,660122,12659,78430,612
Throughput in log messages/second +
+

+ In the above two environments, with the + default settings (using the SystemClock and the SleepingWaitStrategy), + asynchronous logging + processes 7 - 21 times the number of log events in the same time + as synchronous logging. These numbers may also give you an + idea of the performance trade-off when choosing between + All Async and the more flexible + Mixed Async logging. + +

+ +

Throughput of Logging With Location (needsLocation="true")

+

On Solaris 10 (64bit) with JDK1.7.0_06, 4-core Xeon X5570 CPU + @2.93Ghz with hyperthreading switched on (8 virtual cores):

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Logger1 thread2 threads4 threads8 threads
All Async75,86288,77580,24068,077
Mixed Async61,99366,16455,73552,843
Async Appender47,03352,42650,88236,905
Synchronous31,05433,17529,79123,628
Throughput in log messages/second +
+

As expected, logging location information + has a large performance impact. Asynchronous loggers are 4 - 20 + times slower, while synchronous loggers are 1.3 - 5 times slower. + However, if you do need location information, + asynchronous logging will still be faster than synchronous logging.

+ + +

FileAppender vs. FastFileAppender

+

On Windows 7 (64bit) with JDK1.7.0_11, 2-core Intel i5-3317u CPU + @1.70Ghz with hyperthreading switched on (4 virtual cores):

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Appender1 thread2 threads4 threads8 threads
FastFileAppender250,438169,939109,07458,845
FileAppender186,695118,58757,01228,846
RollingFastFileAppender104,25439,34516,9719,465
RollingFileAppender182,518114,69055,14728,153
Throughput in operations/second +
+

On Solaris 10 (64bit) with JDK1.7.0_06, 4-core Xeon X5570 CPU + @2.93GHz with hyperthreading switched on (8 virtual cores):

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Appender1 thread2 threads4 threads8 threads
FastFileAppender240,760128,71366,55530,544
FileAppender172,517106,58755,88525,675
RollingFastFileAppender78,71531,42617,8818,727
RollingFileAppender186,42297,73755,76625,097
Throughput in operations/second +
+ + +
+ +

While the default settings should give good results out + of the box, you may want to change settings to improve + your logging throughput and/or latency. + We suggest the following steps for tuning your logging performance:

+
    +
  1. Create a base line by running the performance tests in your environment.
  2. +
  3. Change a parameter (e.g. WaitStrategy or RingBufferSize) and + run the tests again. Check performance results.
  4. +
  5. Repeat (2) until you find a parameter combination that gives acceptable performance.
  6. +
+

Use the following command to run the performance tests:

+
+ java -cp log4j-async-2.0-tests.jar:log4j-async-2.0.jar:log4j-api-2.0.jar:log4j-core-2.0.jar \
+ [-DWaitStrategy=Sleep] [-DRingBufferSize=262144] org.apache.logging.log4j.async.perftest.PerfTestDriver [path-to-java] [repeats]
+

+ [WaitStrategy] is an optional system property with valid values "Block", "Sleep", or "Yield". + Details are documented under "System Properties to configure ... loggers". +
+ [RingBufferSize] is an optional system property with an integer value of at least 128. + Details are documented under "System Properties to configure ... loggers". +
+ [path-to-java] is an optional parameter that is the full + path to the "java" executable. Specify this if just running + "java" in the current directory does not specify the version of + java that you want to test with. +
+ [repeats] is an optional parameter that specifies how often + each test is repeated. The default is 5. +
+ +

For reference, below are some of the numbers we used to + determine the default settings. (Solaris 10 (64bit), 2.93GHz Xeon X5570 with JDK1.7.0_06):

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LoggerWaitStrategy1 thread2 threads4 threads8 threads
All Async System ClockBlock1,717,261727,075263,760150,533
Sleep1,568,623948,653629,951651,340
Yield1,618,103884,314628,008675,879
All Async Cached ClockBlock2,771,734642,899331,003172,877
Sleep2,393,9011,211,425770,416632,361
Yield2,331,7631,132,529684,109671,957
Mixed AsyncBlock1,347,853443,652251,433136,152
Sleep1,371,511567,829407,676408,071
Yield1,360,267675,570389,609391,969
Throughput in log messages/second
+
+
+ +

+ Asynchronous Loggers are implemented using the + LMAX Disruptor + inter-thread messaging library. From the LMAX web site: +

+
+

... using queues to pass + data between stages of the system was + introducing latency, so we + focused on optimising this area. + The Disruptor is the result of our research and + testing. We found that cache misses at the + CPU-level, and locks requiring kernel + arbitration are both extremely costly, so we created a + framework which has "mechanical sympathy" for + the hardware it's running on, and that's lock-free. +

+
+ +

+ LMAX Disruptor internal performance comparisons with + java.util.concurrent.ArrayBlockingQueue + can be found + + here. +

+ +
+ + \ No newline at end of file