Index: pom.xml =================================================================== --- pom.xml (revision 1452646) +++ 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 @@ -544,6 +544,7 @@ flume-ng web samples + log4j-async Index: api/src/main/java/org/apache/logging/log4j/LogManager.java =================================================================== --- api/src/main/java/org/apache/logging/log4j/LogManager.java (revision 1452646) +++ api/src/main/java/org/apache/logging/log4j/LogManager.java (working copy) @@ -23,7 +23,7 @@ import java.util.SortedMap; import java.util.TreeMap; -import com.sun.xml.internal.xsom.impl.UnionSimpleTypeImpl; +//import com.sun.xml.internal.xsom.impl.UnionSimpleTypeImpl; import org.apache.logging.log4j.message.MessageFactory; import org.apache.logging.log4j.message.StringFormatterMessageFactory; import org.apache.logging.log4j.simple.SimpleLoggerContextFactory; Index: core/src/main/java/org/apache/logging/log4j/core/LogEvent.java =================================================================== --- core/src/main/java/org/apache/logging/log4j/core/LogEvent.java (revision 1452646) +++ 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 1452646) +++ 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 1452646) +++ 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 1452646) +++ 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 1452660) +++ 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 1452646) +++ 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 1452646) +++ 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/.classpath =================================================================== --- log4j-async/.classpath (revision 0) +++ log4j-async/.classpath (working copy) @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Index: log4j-async/.project =================================================================== --- log4j-async/.project (revision 0) +++ log4j-async/.project (working copy) @@ -0,0 +1,23 @@ + + + log4j-async + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + Index: log4j-async/pom.xml =================================================================== --- log4j-async/pom.xml (revision 0) +++ log4j-async/pom.xml (working copy) @@ -0,0 +1,222 @@ + + + + 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.codehaus.mojo + findbugs-maven-plugin + 2.3.2 + + Normal + Default + findbugs-exclude-filter.xml + + + + 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,213 @@ +/* + * 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.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.dsl.Disruptor; +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 = Boolean.getBoolean("useSystemClock") ? new SystemClock() + : CachedClock.instance(); + private static ExecutorService executor = Executors + .newSingleThreadExecutor(); + private ThreadLocal threadlocalInfo = new ThreadLocal(); + + static { + int ringBufferSize = calculateRingBufferSize(); + + disruptor = new Disruptor( + RingBufferLogEvent.FACTORY, ringBufferSize, executor); + 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 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/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/AbstractMultithreadedClaimStrategy.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/AbstractMultithreadedClaimStrategy.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/AbstractMultithreadedClaimStrategy.java (working copy) @@ -0,0 +1,123 @@ +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.MutableLong; + +public abstract class AbstractMultithreadedClaimStrategy implements ClaimStrategy +{ + private final int bufferSize; + private final Sequence claimSequence = new Sequence(Sequencer.INITIAL_CURSOR_VALUE); + private final ThreadLocal minGatingSequenceThreadLocal = new ThreadLocal() + { + @Override + protected MutableLong initialValue() + { + return new MutableLong(Sequencer.INITIAL_CURSOR_VALUE); + } + }; + + public AbstractMultithreadedClaimStrategy(int bufferSize) + { + this.bufferSize = bufferSize; + } + +// @Override + public int getBufferSize() + { + return bufferSize; + } + +// @Override + public long getSequence() + { + return claimSequence.get(); + } + +// @Override + public boolean hasAvailableCapacity(final int availableCapacity, final Sequence[] dependentSequences) + { + return hasAvailableCapacity(claimSequence.get(), availableCapacity, dependentSequences); + } + +// @Override + public long incrementAndGet(final Sequence[] dependentSequences) + { + final long nextSequence = claimSequence.incrementAndGet(); + waitForFreeSlotAt(nextSequence, dependentSequences, minGatingSequenceThreadLocal.get()); + + return nextSequence; + } + +// @Override + public long checkAndIncrement(int availableCapacity, int delta, Sequence[] gatingSequences) throws InsufficientCapacityException + { + for (;;) + { + long sequence = claimSequence.get(); + if (hasAvailableCapacity(sequence, availableCapacity, gatingSequences)) + { + long nextSequence = sequence + delta; + if (claimSequence.compareAndSet(sequence, nextSequence)) + { + return nextSequence; + } + } + else + { + throw InsufficientCapacityException.INSTANCE; + } + } + } + +// @Override + public long incrementAndGet(final int delta, final Sequence[] dependentSequences) + { + final long nextSequence = claimSequence.addAndGet(delta); + waitForFreeSlotAt(nextSequence, dependentSequences, minGatingSequenceThreadLocal.get()); + + return nextSequence; + } + +// @Override + public void setSequence(final long sequence, final Sequence[] dependentSequences) + { + claimSequence.set(sequence); + waitForFreeSlotAt(sequence, dependentSequences, minGatingSequenceThreadLocal.get()); + } + + private void waitForFreeSlotAt(final long sequence, final Sequence[] dependentSequences, final MutableLong minGatingSequence) + { + final long wrapPoint = sequence - bufferSize; + if (wrapPoint > minGatingSequence.get()) + { + long minSequence; + while (wrapPoint > (minSequence = getMinimumSequence(dependentSequences))) + { + LockSupport.parkNanos(1L); + } + + minGatingSequence.set(minSequence); + } + } + + private boolean hasAvailableCapacity(long sequence, final int availableCapacity, final Sequence[] dependentSequences) + { + final long wrapPoint = (sequence + availableCapacity) - bufferSize; + final MutableLong minGatingSequence = minGatingSequenceThreadLocal.get(); + if (wrapPoint > minGatingSequence.get()) + { + long minSequence = getMinimumSequence(dependentSequences); + minGatingSequence.set(minSequence); + + if (wrapPoint > minSequence) + { + return false; + } + } + + return true; + } +} \ No newline at end of file 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,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; + + +/** + * 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,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; + + +/** + * 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/BatchDescriptor.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/BatchDescriptor.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/BatchDescriptor.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; + + +/** + * Used to record the batch of sequences claimed via a {@link Sequencer}. + */ +public final class BatchDescriptor +{ + private final int size; + private long end = Sequencer.INITIAL_CURSOR_VALUE; + + /** + * Create a holder for tracking a batch of claimed sequences in a {@link Sequencer} + * @param size of the batch to claim. + */ + BatchDescriptor(final int size) + { + this.size = size; + } + + /** + * Get the end sequence of a batch. + * + * @return the end sequence in a batch + */ + public long getEnd() + { + return end; + } + + /** + * Set the end of the batch sequence. To be used by the {@link Sequencer}. + * + * @param end sequence in the batch. + */ + void setEnd(final long end) + { + this.end = end; + } + + /** + * Get the size of the batch. + * + * @return the size of the batch. + */ + public int getSize() + { + return size; + } + + /** + * Get the starting sequence for a batch. + * + * @return the starting sequence of a batch. + */ + public long getStart() + { + return end - (size - 1L); + } +} 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,168 @@ +/* + * 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 a {@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); + + /** + * 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); + } + } + +// @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(). + */ +// @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); + while (nextSequence <= availableSequence) + { + event = ringBuffer.get(nextSequence); + eventHandler.onEvent(event, nextSequence, nextSequence == availableSequence); + nextSequence++; + } + + sequence.set(nextSequence - 1L); + } + 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 notifyStart() + { + if (eventHandler instanceof LifecycleAware) + { + try + { + ((LifecycleAware)eventHandler).onStart(); + } + catch (final Throwable ex) + { + exceptionHandler.handleOnStartException(ex); + } + } + } + + 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,119 @@ +/* + * 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.TimeUnit; +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(final long sequence, final Sequence cursor, final Sequence[] dependents, final SequenceBarrier barrier) + throws AlertException, InterruptedException + { + long availableSequence; + if ((availableSequence = cursor.get()) < sequence) + { + lock.lock(); + try + { + while ((availableSequence = cursor.get()) < sequence) + { + barrier.checkAlert(); + processorNotifyCondition.await(); + } + } + finally + { + lock.unlock(); + } + } + + if (0 != dependents.length) + { + while ((availableSequence = getMinimumSequence(dependents)) < sequence) + { + barrier.checkAlert(); + } + } + + return availableSequence; + } + +// @Override + public long waitFor(final long sequence, final Sequence cursor, final Sequence[] dependents, final SequenceBarrier barrier, + final long timeout, final TimeUnit sourceUnit) + throws AlertException, InterruptedException + { + long availableSequence; + if ((availableSequence = cursor.get()) < sequence) + { + lock.lock(); + try + { + while ((availableSequence = cursor.get()) < sequence) + { + barrier.checkAlert(); + + if (!processorNotifyCondition.await(timeout, sourceUnit)) + { + break; + } + } + } + finally + { + lock.unlock(); + } + } + + if (0 != dependents.length) + { + while ((availableSequence = getMinimumSequence(dependents)) < 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,97 @@ +/* + * 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.TimeUnit; + +/** + * 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, final Sequence cursor, final Sequence[] dependents, final SequenceBarrier barrier) + throws AlertException, InterruptedException + { + long availableSequence; + + if (0 == dependents.length) + { + while ((availableSequence = cursor.get()) < sequence) + { + barrier.checkAlert(); + } + } + else + { + while ((availableSequence = getMinimumSequence(dependents)) < sequence) + { + barrier.checkAlert(); + } + } + + return availableSequence; + } + +// @Override + public long waitFor(final long sequence, final Sequence cursor, final Sequence[] dependents, final SequenceBarrier barrier, + final long timeout, final TimeUnit sourceUnit) + throws AlertException, InterruptedException + { + final long timeoutMs = sourceUnit.toMillis(timeout); + final long startTime = System.currentTimeMillis(); + long availableSequence; + + if (0 == dependents.length) + { + while ((availableSequence = cursor.get()) < sequence) + { + barrier.checkAlert(); + + final long elapsedTime = System.currentTimeMillis() - startTime; + if (elapsedTime > timeoutMs) + { + break; + } + } + } + else + { + while ((availableSequence = getMinimumSequence(dependents)) < sequence) + { + barrier.checkAlert(); + + final long elapsedTime = System.currentTimeMillis() - startTime; + if (elapsedTime > timeoutMs) + { + break; + } + } + } + + return availableSequence; + } + +// @Override + public void signalAllWhenBlocking() + { + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/ClaimStrategy.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/ClaimStrategy.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/ClaimStrategy.java (working copy) @@ -0,0 +1,96 @@ +/* + * 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 contract for claiming the sequence of events in the {@link Sequencer} by event publishers. + */ +public interface ClaimStrategy +{ + /** + * Get the size of the data structure used to buffer events. + * + * @return size of the underlying buffer. + */ + int getBufferSize(); + + /** + * Get the current claimed sequence. + * + * @return the current claimed sequence. + */ + long getSequence(); + + /** + * Is there available capacity in the buffer for the requested sequence. + * + * @param availableCapacity remaining in the buffer. + * @param dependentSequences to be checked for range. + * @return true if the buffer has capacity for the requested sequence. + */ + boolean hasAvailableCapacity(final int availableCapacity, final Sequence[] dependentSequences); + + /** + * Claim the next sequence in the {@link Sequencer}. + * The caller should be held up until the claimed sequence is available by tracking the dependentSequences. + * + * @param dependentSequences to be checked for range. + * @return the index to be used for the publishing. + */ + long incrementAndGet(final Sequence[] dependentSequences); + + /** + * Increment sequence by a delta and get the result. + * The caller should be held up until the claimed sequence batch is available by tracking the dependentSequences. + * + * @param delta to increment by. + * @param dependentSequences to be checked for range. + * @return the result after incrementing. + */ + long incrementAndGet(final int delta, final Sequence[] dependentSequences); + + /** + * Set the current sequence value for claiming an event in the {@link Sequencer} + * The caller should be held up until the claimed sequence is available by tracking the dependentSequences. + * + * @param dependentSequences to be checked for range. + * @param sequence to be set as the current value. + */ + void setSequence(final long sequence, final Sequence[] dependentSequences); + + /** + * Serialise publishers in sequence and set cursor to latest available sequence. + * + * @param sequence sequence to be applied + * @param cursor to serialise against. + * @param batchSize of the sequence. + */ + void serialisePublishing(final long sequence, final Sequence cursor, final int batchSize); + + /** + * Atomically checks the available capacity of the ring buffer and claims the next sequence. Will + * throw InsufficientCapacityException if the capacity not available. + * + * @param availableCapacity the capacity that should be available before claiming the next slot + * @param delta the number of slots to claim + * @param gatingSequences the set of sequences to check to ensure capacity is available + * @return the slot after incrementing + * @throws InsufficientCapacityException thrown if capacity is not available + */ + long checkAndIncrement(int availableCapacity, int delta, Sequence[] gatingSequences) + throws InsufficientCapacityException; +} 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,27 @@ +/* + * 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 +{ + 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,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; + + +/** + * 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 propigated 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 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/EventPublisher.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventPublisher.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/EventPublisher.java (working copy) @@ -0,0 +1,87 @@ +/* + * 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; + + + +/** + * Utility class for simplifying publication to the ring buffer. + */ +public class EventPublisher +{ + private final RingBuffer ringBuffer; + + /** + * Construct from the ring buffer to be published to. + * @param ringBuffer into which events will be published. + */ + public EventPublisher(final RingBuffer ringBuffer) + { + this.ringBuffer = ringBuffer; + } + + /** + * Publishes an event to the ring buffer. It handles + * claiming the next sequence, getting the current (uninitialized) + * event from the ring buffer and publishing the claimed sequence + * after translation. + * + * @param translator The user specified translation for the event + */ + public void publishEvent(final EventTranslator translator) + { + final long sequence = ringBuffer.next(); + translateAndPublish(translator, sequence); + } + + /** + * Attempts to publish an event to the ring buffer. It handles + * claiming the next sequence, getting the current (uninitialized) + * 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 = ringBuffer.tryNext(capacity); + translateAndPublish(translator, sequence); + return true; + } + catch (InsufficientCapacityException e) + { + return false; + } + } + + private void translateAndPublish(final EventTranslator translator, final long sequence) + { + try + { + translator.translateTo(ringBuffer.get(sequence), sequence); + } + finally + { + ringBuffer.publish(sequence); + } + } +} 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,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; + + +/** + * 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. + */ +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/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,49 @@ +/* + * 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 suspend further processing by the {@link BatchEventProcessor} + * then is 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. + */ + 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/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,19 @@ +package org.apache.logging.log4j.async.com.lmax.disruptor; + + +@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,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; + + +/** + * Implement this interface 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. + */ + void onShutdown(); +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/MultiThreadedClaimStrategy.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/MultiThreadedClaimStrategy.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/MultiThreadedClaimStrategy.java (working copy) @@ -0,0 +1,105 @@ +/* + * 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.AtomicLongArray; + + +/** + * Strategy to be used when there are multiple publisher threads claiming sequences. + * + * This strategy is reasonably forgiving when the multiple publisher threads are highly contended or working in an + * environment where there is insufficient CPUs to handle multiple publisher threads. It requires 2 CAS operations + * for a single publisher, compared to the {@link MultiThreadedLowContentionClaimStrategy} strategy which needs only a single + * CAS and a lazySet per publication. + */ +public final class MultiThreadedClaimStrategy extends AbstractMultithreadedClaimStrategy + implements ClaimStrategy +{ + private static final int RETRIES = 1000; + + private final AtomicLongArray pendingPublication; + private final int pendingMask; + + /** + * Construct a new multi-threaded publisher {@link ClaimStrategy} for a given buffer size. + * + * @param bufferSize for the underlying data structure. + * @param pendingBufferSize number of item that can be pending for serialisation + */ + public MultiThreadedClaimStrategy(final int bufferSize, final int pendingBufferSize) + { + super(bufferSize); + + if (Integer.bitCount(pendingBufferSize) != 1) + { + throw new IllegalArgumentException("pendingBufferSize must be a power of 2, was: " + pendingBufferSize); + } + + this.pendingPublication = new AtomicLongArray(pendingBufferSize); + this.pendingMask = pendingBufferSize - 1; + } + + /** + * Construct a new multi-threaded publisher {@link ClaimStrategy} for a given buffer size. + * + * @param bufferSize for the underlying data structure. + */ + public MultiThreadedClaimStrategy(final int bufferSize) + { + this(bufferSize, 1024); + } + +// @Override + public void serialisePublishing(final long sequence, final Sequence cursor, final int batchSize) + { + int counter = RETRIES; + while (sequence - cursor.get() > pendingPublication.length()) + { + if (--counter == 0) + { + Thread.yield(); + counter = RETRIES; + } + } + + long expectedSequence = sequence - batchSize; + for (long pendingSequence = expectedSequence + 1; pendingSequence < sequence; pendingSequence++) + { + pendingPublication.lazySet((int) pendingSequence & pendingMask, pendingSequence); + } + pendingPublication.set((int) sequence & pendingMask, sequence); + + long cursorSequence = cursor.get(); + if (cursorSequence >= sequence) + { + return; + } + + expectedSequence = Math.max(expectedSequence, cursorSequence); + long nextSequence = expectedSequence + 1; + while (cursor.compareAndSet(expectedSequence, nextSequence)) + { + expectedSequence = nextSequence; + nextSequence++; + if (pendingPublication.get((int) nextSequence & pendingMask) != nextSequence) + { + break; + } + } + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/MultiThreadedLowContentionClaimStrategy.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/MultiThreadedLowContentionClaimStrategy.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/MultiThreadedLowContentionClaimStrategy.java (working copy) @@ -0,0 +1,50 @@ +/* + * 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 to be used when there are multiple publisher threads claiming sequences. + * + * This strategy requires sufficient cores to allow multiple publishers to be concurrently claiming sequences and those + * thread a contented relatively infrequently. + */ +public final class MultiThreadedLowContentionClaimStrategy + extends AbstractMultithreadedClaimStrategy +{ + /** + * Construct a new multi-threaded publisher {@link ClaimStrategy} for a given buffer size. + * + * @param bufferSize for the underlying data structure. + */ + public MultiThreadedLowContentionClaimStrategy(final int bufferSize) + { + super(bufferSize); + } + +// @Override + public void serialisePublishing(final long sequence, final Sequence cursor, final int batchSize) + { + final long expectedSequence = sequence - batchSize; + while (expectedSequence != cursor.get()) + { + // busy spin + } + + cursor.set(sequence); + } +} 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,69 @@ +/* + * 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 Sequencer}. + * 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 Sequencer}. + * + * @param sequencer to track. + */ + public NoOpEventProcessor(final Sequencer sequencer) + { + sequence = new SequencerFollowingSequence(sequencer); + } + +// @Override + public Sequence getSequence() + { + return sequence; + } + +// @Override + public void halt() + { + } + +// @Override + public void run() + { + } + + private static final class SequencerFollowingSequence extends Sequence + { + private final Sequencer sequencer; + + private SequencerFollowingSequence(final Sequencer 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/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,90 @@ +/* + * 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; + +/** + * {@link SequenceBarrier} handed out for gating {@link EventProcessor}s on a cursor sequence and optional dependent {@link EventProcessor}(s) + */ +final class ProcessingSequenceBarrier implements SequenceBarrier +{ + private final WaitStrategy waitStrategy; + private final Sequence cursorSequence; + private final Sequence[] dependentSequences; + private volatile boolean alerted = false; + + public ProcessingSequenceBarrier(final WaitStrategy waitStrategy, + final Sequence cursorSequence, + final Sequence[] dependentSequences) + { + this.waitStrategy = waitStrategy; + this.cursorSequence = cursorSequence; + this.dependentSequences = dependentSequences; + } + +// @Override + public long waitFor(final long sequence) + throws AlertException, InterruptedException + { + checkAlert(); + + return waitStrategy.waitFor(sequence, cursorSequence, dependentSequences, this); + } + +// @Override + public long waitFor(final long sequence, final long timeout, final TimeUnit units) + throws AlertException, InterruptedException + { + checkAlert(); + + return waitStrategy.waitFor(sequence, cursorSequence, dependentSequences, this, timeout, units); + } + +// @Override + public long getCursor() + { + return cursorSequence.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/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,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; + + +/** + * 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 extends Sequencer +{ + private final int indexMask; + private final Object[] entries; + + /** + * Construct a RingBuffer with the full option set. + * + * @param eventFactory to newInstance entries for filling the RingBuffer + * @param claimStrategy threading strategy for publisher claiming entries in the ring. + * @param waitStrategy waiting strategy employed by processorsToTrack waiting on entries becoming available. + * + * @throws IllegalArgumentException if bufferSize is not a power of 2 + */ + public RingBuffer(final EventFactory eventFactory, + final ClaimStrategy claimStrategy, + final WaitStrategy waitStrategy) + { + super(claimStrategy, waitStrategy); + + if (Integer.bitCount(claimStrategy.getBufferSize()) != 1) + { + throw new IllegalArgumentException("bufferSize must be a power of 2"); + } + + indexMask = claimStrategy.getBufferSize() - 1; + entries = new Object[claimStrategy.getBufferSize()]; + + fill(eventFactory); + } + + /** + * Construct a RingBuffer with default strategies of: + * {@link MultiThreadedClaimStrategy} and {@link BlockingWaitStrategy} + * + * @param eventFactory to newInstance entries for filling the RingBuffer + * @param bufferSize of the RingBuffer that will be rounded up to the next power of 2 + */ + public RingBuffer(final EventFactory eventFactory, final int bufferSize) + { + this(eventFactory, + new MultiThreadedClaimStrategy(bufferSize), + new BlockingWaitStrategy()); + } + + /** + * Get the event for a given sequence in the RingBuffer. + * + * @param sequence for the event + * @return event for the sequence + */ + @SuppressWarnings("unchecked") + public T get(final long sequence) + { + return (T)entries[(int)sequence & indexMask]; + } + + private void fill(final EventFactory eventFactory) + { + for (int i = 0; i < entries.length; i++) + { + entries[i] = eventFactory.newInstance(); + } + } +} 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,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 org.apache.logging.log4j.async.com.lmax.disruptor.util.Util; + +import sun.misc.Unsafe; + +public class Sequence +{ + 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]; + + public Sequence() + { + setOrdered(-1); + } + + public Sequence(final long initialValue) + { + setOrdered(initialValue); + } + + public long get() + { + return unsafe.getLongVolatile(paddedValue, valueOffset); + } + + public void set(final long value) + { + unsafe.putOrderedLong(paddedValue, valueOffset, value); + } + + private void setOrdered(final long value) + { + unsafe.putOrderedLong(paddedValue, valueOffset, value); + } + + public boolean compareAndSet(final long expectedValue, final long newValue) + { + return unsafe.compareAndSwapLong(paddedValue, valueOffset, expectedValue, newValue); + } + + public String toString() + { + return Long.toString(get()); + } + + public long incrementAndGet() + { + return addAndGet(1L); + } + + public long addAndGet(final long increment) + { + long currentValue; + long newValue; + + do + { + currentValue = get(); + newValue = currentValue + increment; + } + while (!compareAndSet(currentValue, newValue)); + + return newValue; + } +} + 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,78 @@ +/* + * 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; + +/** + * 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; + + /** + * Wait for the given sequence to be available for consumption with a time out. + * + * @param sequence to wait for + * @param timeout value + * @param units for the timeout value + * @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, long timeout, TimeUnit units) throws AlertException, InterruptedException; + + /** + * Delegate a call to the {@link Sequencer#getCursor()} + * + * @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,147 @@ +/* + * 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.AtomicReference; + +import org.apache.logging.log4j.async.com.lmax.disruptor.util.Util; + +/** + * {@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 final AtomicReference sequencesRef; + + /** + * Default Constructor + */ + public SequenceGroup() + { + super(-1); + sequencesRef = new AtomicReference(new Sequence[0]); + } + + /** + * Get the minimum sequence value for the group. + * + * @return the minimum sequence value for the group. + */ + @Override + public long get() + { + Sequence[] sequences = sequencesRef.get(); + return sequences.length != 0 ? Util.getMinimumSequence(sequences) : RingBuffer.INITIAL_CURSOR_VALUE; + } + + /** + * 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 = sequencesRef.get(); + for (int i = 0, size = sequences.length; i < size; i++) + { + sequences[i].set(value); + } + } + + /** + * Add a {@link Sequence} into this aggregate. + * + * @param sequence to be added to the aggregate. + */ + public void add(final Sequence sequence) + { + Sequence[] oldSequences; + Sequence[] newSequences; + do + { + oldSequences = sequencesRef.get(); + final int oldSize = oldSequences.length; + newSequences = new Sequence[oldSize + 1]; + System.arraycopy(oldSequences, 0, newSequences, 0, oldSize); + newSequences[oldSize] = sequence; + } + while (!sequencesRef.compareAndSet(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) + { + boolean found; + int numToRemove; + Sequence[] oldSequences; + Sequence[] newSequences; + + do + { + found = false; + oldSequences = sequencesRef.get(); + + numToRemove = 0; + for (Sequence oldSequence : oldSequences) + { + if (oldSequence == sequence) + { + numToRemove++; + } + } + + 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 (!sequencesRef.compareAndSet(oldSequences, newSequences)); + + return numToRemove != 0; + } + + /** + * Get the size of the group. + * + * @return the size of the group. + */ + public int size() + { + return sequencesRef.get().length; + } +} 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,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; + + +/** + * 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 are writing to an IO device. + *

+ * @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,236 @@ +/* + * 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 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 + */ +public class Sequencer +{ + /** Set to -1 as sequence starting point */ + public static final long INITIAL_CURSOR_VALUE = -1L; + + private final Sequence cursor = new Sequence(Sequencer.INITIAL_CURSOR_VALUE); + private Sequence[] gatingSequences; + + private final ClaimStrategy claimStrategy; + private final WaitStrategy waitStrategy; + + /** + * Construct a Sequencer with the selected strategies. + * + * @param claimStrategy for those claiming sequences. + * @param waitStrategy for those waiting on sequences. + */ + public Sequencer(final ClaimStrategy claimStrategy, final WaitStrategy waitStrategy) + { + this.claimStrategy = claimStrategy; + this.waitStrategy = waitStrategy; + } + + /** + * Set the sequences that will gate publishers to prevent the buffer wrapping. + * + * This method must be called prior to claiming sequences otherwise + * a NullPointerException will be thrown. + * + * @param sequences to be to be gated on. + */ + public void setGatingSequences(final Sequence... sequences) + { + this.gatingSequences = sequences; + } + + /** + * Create a {@link SequenceBarrier} that gates on the the cursor and a list of {@link Sequence}s + * + * @param sequencesToTrack this barrier will track + * @return the barrier gated as required + */ + public SequenceBarrier newBarrier(final Sequence... sequencesToTrack) + { + return new ProcessingSequenceBarrier(waitStrategy, cursor, sequencesToTrack); + } + + /** + * Create a new {@link BatchDescriptor} that is the minimum of the requested size + * and the buffer size. + * + * @param size for the batch + * @return the new {@link BatchDescriptor} + */ + public BatchDescriptor newBatchDescriptor(final int size) + { + return new BatchDescriptor(Math.min(size, claimStrategy.getBufferSize())); + } + + /** + * The capacity of the data structure to hold entries. + * + * @return the size of the RingBuffer. + */ + public int getBufferSize() + { + return claimStrategy.getBufferSize(); + } + + /** + * 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 cursor.get(); + } + + /** + * 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 availableCapacity in the buffer + * @return true if the buffer has the capacity to allocate the next sequence otherwise false. + */ + public boolean hasAvailableCapacity(final int availableCapacity) + { + return claimStrategy.hasAvailableCapacity(availableCapacity, gatingSequences); + } + + /** + * Claim the next event in sequence for publishing. + * + * @return the claimed sequence value + */ + public long next() + { + if (null == gatingSequences) + { + throw new NullPointerException("gatingSequences must be set before claiming sequences"); + } + + return claimStrategy.incrementAndGet(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 requiredCapacity as slots in the data structure + * @return the claimed sequence value + * @throws InsufficientCapacityException when the requiredCapacity is not available + */ + public long tryNext(int requiredCapacity) throws InsufficientCapacityException + { + if (null == gatingSequences) + { + throw new NullPointerException("gatingSequences must be set before claiming sequences"); + } + + if (requiredCapacity < 1) + { + throw new IllegalArgumentException("Required capacity must be greater than 0"); + } + + return claimStrategy.checkAndIncrement(requiredCapacity, 1, gatingSequences); + } + + /** + * Claim the next batch of sequence numbers for publishing. + * + * @param batchDescriptor to be updated for the batch range. + * @return the updated batchDescriptor. + */ + public BatchDescriptor next(final BatchDescriptor batchDescriptor) + { + if (null == gatingSequences) + { + throw new NullPointerException("gatingSequences must be set before claiming sequences"); + } + + final long sequence = claimStrategy.incrementAndGet(batchDescriptor.getSize(), gatingSequences); + batchDescriptor.setEnd(sequence); + return batchDescriptor; + } + + /** + * Claim a specific sequence when only one publisher is involved. + * + * @param sequence to be claimed. + * @return sequence just claimed. + */ + public long claim(final long sequence) + { + if (null == gatingSequences) + { + throw new NullPointerException("gatingSequences must be set before claiming sequences"); + } + + claimStrategy.setSequence(sequence, gatingSequences); + + return sequence; + } + + /** + * Publish an event and make it visible to {@link EventProcessor}s + * + * @param sequence to be published + */ + public void publish(final long sequence) + { + publish(sequence, 1); + } + + /** + * Publish the batch of events in sequence. + * + * @param batchDescriptor to be published. + */ + public void publish(final BatchDescriptor batchDescriptor) + { + publish(batchDescriptor.getEnd(), batchDescriptor.getSize()); + } + + /** + * Force the publication of a cursor sequence. + * + * Only use this method when forcing a sequence and you are sure only one publisher exists. + * This will cause the cursor to advance to this sequence. + * + * @param sequence which is to be forced for publication. + */ + public void forcePublish(final long sequence) + { + cursor.set(sequence); + waitStrategy.signalAllWhenBlocking(); + } + + private void publish(final long sequence, final int batchSize) + { + claimStrategy.serialisePublishing(sequence, cursor, batchSize); + waitStrategy.signalAllWhenBlocking(); + } + + public long remainingCapacity() + { + long consumed = Util.getMinimumSequence(gatingSequences); + long produced = cursor.get(); + return getBufferSize() - (produced - consumed); + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SingleThreadedClaimStrategy.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SingleThreadedClaimStrategy.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/SingleThreadedClaimStrategy.java (working copy) @@ -0,0 +1,135 @@ +/* + * 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.PaddedLong; + +/** + * Optimised strategy can be used when there is a single publisher thread claiming sequences. + * + * This strategy must not be used when multiple threads are used for publishing concurrently on the same {@link Sequencer} + */ +public final class SingleThreadedClaimStrategy + implements ClaimStrategy +{ + private final int bufferSize; + private final PaddedLong minGatingSequence = new PaddedLong(Sequencer.INITIAL_CURSOR_VALUE); + private final PaddedLong claimSequence = new PaddedLong(Sequencer.INITIAL_CURSOR_VALUE); + + /** + * Construct a new single threaded publisher {@link ClaimStrategy} for a given buffer size. + * + * @param bufferSize for the underlying data structure. + */ + public SingleThreadedClaimStrategy(final int bufferSize) + { + this.bufferSize = bufferSize; + } + +// @Override + public int getBufferSize() + { + return bufferSize; + } + +// @Override + public long getSequence() + { + return claimSequence.get(); + } + +// @Override + public boolean hasAvailableCapacity(final int availableCapacity, final Sequence[] dependentSequences) + { + final long wrapPoint = (claimSequence.get() + availableCapacity) - bufferSize; + if (wrapPoint > minGatingSequence.get()) + { + long minSequence = getMinimumSequence(dependentSequences); + minGatingSequence.set(minSequence); + + if (wrapPoint > minSequence) + { + return false; + } + } + + return true; + } + +// @Override + public long incrementAndGet(final Sequence[] dependentSequences) + { + long nextSequence = claimSequence.get() + 1L; + claimSequence.set(nextSequence); + waitForFreeSlotAt(nextSequence, dependentSequences); + + return nextSequence; + } + +// @Override + public long incrementAndGet(final int delta, final Sequence[] dependentSequences) + { + long nextSequence = claimSequence.get() + delta; + claimSequence.set(nextSequence); + waitForFreeSlotAt(nextSequence, dependentSequences); + + return nextSequence; + } + +// @Override + public void setSequence(final long sequence, final Sequence[] dependentSequences) + { + claimSequence.set(sequence); + waitForFreeSlotAt(sequence, dependentSequences); + } + +// @Override + public void serialisePublishing(final long sequence, final Sequence cursor, final int batchSize) + { + cursor.set(sequence); + } + +// @Override + public long checkAndIncrement(int availableCapacity, int delta, Sequence[] dependentSequences) + throws InsufficientCapacityException + { + if (!hasAvailableCapacity(availableCapacity, dependentSequences)) + { + throw InsufficientCapacityException.INSTANCE; + } + + return incrementAndGet(delta, dependentSequences); + } + + private void waitForFreeSlotAt(final long sequence, final Sequence[] dependentSequences) + { + final long wrapPoint = sequence - bufferSize; + if (wrapPoint > minGatingSequence.get()) + { + long minSequence; + while (wrapPoint > (minSequence = getMinimumSequence(dependentSequences))) + { + LockSupport.parkNanos(1L); + } + + minGatingSequence.set(minSequence); + } + } +} 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,124 @@ +/* + * 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.TimeUnit; +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, final Sequence cursor, final Sequence[] dependents, final SequenceBarrier barrier) + throws AlertException, InterruptedException + { + long availableSequence; + int counter = RETRIES; + + if (0 == dependents.length) + { + while ((availableSequence = cursor.get()) < sequence) + { + counter = applyWaitMethod(barrier, counter); + } + } + else + { + while ((availableSequence = getMinimumSequence(dependents)) < sequence) + { + counter = applyWaitMethod(barrier, counter); + } + } + + return availableSequence; + } + +// @Override + public long waitFor(final long sequence, final Sequence cursor, final Sequence[] dependents, final SequenceBarrier barrier, + final long timeout, final TimeUnit sourceUnit) + throws AlertException, InterruptedException + { + final long timeoutMs = sourceUnit.toMillis(timeout); + final long startTime = System.currentTimeMillis(); + long availableSequence; + int counter = RETRIES; + + if (0 == dependents.length) + { + while ((availableSequence = cursor.get()) < sequence) + { + counter = applyWaitMethod(barrier, counter); + + final long elapsedTime = System.currentTimeMillis() - startTime; + if (elapsedTime > timeoutMs) + { + break; + } + } + } + else + { + while ((availableSequence = getMinimumSequence(dependents)) < sequence) + { + counter = applyWaitMethod(barrier, counter); + + final long elapsedTime = System.currentTimeMillis() - startTime; + if (elapsedTime > timeoutMs) + { + break; + } + } + } + + 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/TimeoutException.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/TimeoutException.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/TimeoutException.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; + + +/** + * Used to signal that an operation has timed out and been aborted. + *

+ * It does not fill in a stack trace for performance reasons. + */ +@SuppressWarnings("serial") +public class TimeoutException extends Exception +{ + /** Pre-allocated exception to avoid garbage generation */ + public static final TimeoutException INSTANCE = new TimeoutException(); + + /** + * Private constructor so only a single instance exists. + */ + private TimeoutException() + { + } + + /** + * 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/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,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; + +import java.util.concurrent.TimeUnit; + +/** + * Strategy employed for making {@link EventProcessor}s wait on a cursor {@link Sequence}. + */ +public interface WaitStrategy +{ + /** + * Wait for the given sequence to be available + * + * @param sequence to be waited on. + * @param cursor on which to wait. + * @param dependents further back the chain that must advance first + * @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[] dependents, SequenceBarrier barrier) + throws AlertException, InterruptedException; + + /** + * Wait for the given sequence to be available with a timeout specified. + * + * @param sequence to be waited on. + * @param cursor on which to wait. + * @param dependents further back the chain that must advance first + * @param barrier the processor is waiting on. + * @param timeout value to abort after. + * @param sourceUnit of the timeout value. + * @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. + * @deprecated Use a separate timeout event pushed into the Disruptor from different thread. + */ + @Deprecated + long waitFor(long sequence, Sequence cursor, Sequence[] dependents, SequenceBarrier barrier, long timeout, TimeUnit sourceUnit) + throws AlertException, InterruptedException; + + /** + * Signal those {@link EventProcessor}s waiting 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. + */ +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,156 @@ +/* + * 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; + +/** + * {@link WorkProcessor} for ensuring each sequence is handled by only a single processor, effectively consuming the sequence. + * + * No other {@link WorkProcessor}s in the {@link WorkerPool} will consume the same sequence. + * + * @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(). + */ +// @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 (processedSequence) + { + processedSequence = false; + nextSequence = workSequence.incrementAndGet(); + sequence.set(nextSequence - 1L); + } + + sequenceBarrier.waitFor(nextSequence); + event = ringBuffer.get(nextSequence); + workHandler.onEvent(event); + + processedSequence = true; + } + catch (final AlertException ex) + { + if (!running.get()) + { + break; + } + } + catch (final Throwable ex) + { + 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,173 @@ +/* + * 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; + +/** + * A pool of {@link WorkProcessor}s that will consume sequences so jobs can be farmed out across a pool of workers + * which are implemented the {@link WorkHandler} interface. + * + * @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; + 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#setGatingSequences(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#setGatingSequences(Sequence...)} to be called before the work pool is started. + * + * @param eventFactory for filling the {@link RingBuffer} + * @param claimStrategy for the {@link RingBuffer} + * @param waitStrategy for 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 ClaimStrategy claimStrategy, + final WaitStrategy waitStrategy, + final ExceptionHandler exceptionHandler, + final WorkHandler... workHandlers) + { + ringBuffer = new RingBuffer(eventFactory, claimStrategy, waitStrategy); + 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.setGatingSequences(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 is 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 then 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,118 @@ +/* + * 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.TimeUnit; + +/** + * 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, final Sequence cursor, final Sequence[] dependents, final SequenceBarrier barrier) + throws AlertException, InterruptedException + { + long availableSequence; + int counter = SPIN_TRIES; + + if (0 == dependents.length) + { + while ((availableSequence = cursor.get()) < sequence) + { + counter = applyWaitMethod(barrier, counter); + } + } + else + { + while ((availableSequence = getMinimumSequence(dependents)) < sequence) + { + counter = applyWaitMethod(barrier, counter); + } + } + + return availableSequence; + } + +// @Override + public long waitFor(final long sequence, final Sequence cursor, final Sequence[] dependents, final SequenceBarrier barrier, + final long timeout, final TimeUnit sourceUnit) + throws AlertException, InterruptedException + { + final long timeoutMs = sourceUnit.toMillis(timeout); + final long startTime = System.currentTimeMillis(); + long availableSequence; + int counter = SPIN_TRIES; + + if (0 == dependents.length) + { + while ((availableSequence = cursor.get()) < sequence) + { + counter = applyWaitMethod(barrier, counter); + + final long elapsedTime = System.currentTimeMillis() - startTime; + if (elapsedTime > timeoutMs) + { + break; + } + } + } + else + { + while ((availableSequence = getMinimumSequence(dependents)) < sequence) + { + counter = applyWaitMethod(barrier, counter); + + final long elapsedTime = System.currentTimeMillis() - startTime; + if (elapsedTime > timeoutMs) + { + break; + } + } + } + + 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,345 @@ +/* + * 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 in nanoseconds across a large number of observations + * when high performance is required. + */ +public final class Histogram +{ + private final long[] upperBounds; + private final long[] counts; + private long minValue = Long.MAX_VALUE; + private long maxValue = 0L; + + /** + * Create a new Histogram with a provided list of interval bounds. + * + * @param upperBounds of the intervals. + */ + public Histogram(final long[] upperBounds) + { + validateBounds(upperBounds); + + this.upperBounds = Arrays.copyOf(upperBounds, upperBounds.length); + this.counts = new long[upperBounds.length]; + } + + private void validateBounds(final long[] upperBounds) + { + long lastBound = -1L; + 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. + * + * @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 otherwise false. + */ + public boolean addObservation(final long value) + { + int low = 0; + int high = upperBounds.length - 1; + + while (low < high) + { + int mid = low + ((high - low) >> 1); + if (upperBounds[mid] < value) + { + low = mid + 1; + } + else + { + high = mid; + } + } + + if (value <= upperBounds[high]) + { + counts[high]++; + trackRange(value); + + return true; + } + + return false; + } + + 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. + */ + public void addObservations(final Histogram histogram) + { + 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"); + } + } + + for (int i = 0, size = counts.length; i < size; i++) + { + counts[i] += histogram.counts[i]; + } + + 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 the 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. + * + * @return the mean of all recorded observations. + */ + public BigDecimal getMean() + { + if (0L == getCount()) + { + return BigDecimal.ZERO; + } + + long lowerBound = counts[0] > 0L ? minValue : 0L; + BigDecimal total = BigDecimal.ZERO; + + 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); + } + + 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. + * + * @param factor representing the size of the population. + * @return the interval upper bound. + */ + 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; + + 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(); + } +} \ No newline at end of file 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,331 @@ +/* + * 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.BatchEventProcessor; +import org.apache.logging.log4j.async.com.lmax.disruptor.ClaimStrategy; +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.EventProcessor; +import org.apache.logging.log4j.async.com.lmax.disruptor.EventPublisher; +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.SequenceBarrier; +import org.apache.logging.log4j.async.com.lmax.disruptor.WaitStrategy; +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. + * + *

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 EventProcessorRepository eventProcessorRepository = new EventProcessorRepository(); + private final AtomicBoolean started = new AtomicBoolean(false); + private final EventPublisher eventPublisher; + 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(new RingBuffer(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 claimStrategy 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 Executor executor, + final ClaimStrategy claimStrategy, + final WaitStrategy waitStrategy) + { + this(new RingBuffer(eventFactory, claimStrategy, waitStrategy), executor); + } + + private Disruptor(final RingBuffer ringBuffer, final Executor executor) + { + this.ringBuffer = ringBuffer; + this.executor = executor; + eventPublisher = new EventPublisher(ringBuffer); + } + + /** + * 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 EventProcessor[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) + { + eventProcessorRepository.add(processor); + } + return new EventHandlerGroup(this, eventProcessorRepository, processors); + } + + /** + * 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, eventProcessorRepository); + } + + /** + * 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) + { + EventProcessor[] selectedEventProcessors = new EventProcessor[handlers.length]; + for (int i = 0, handlersLength = handlers.length; i < handlersLength; i++) + { + selectedEventProcessors[i] = eventProcessorRepository.getEventProcessorFor(handlers[i]); + } + + return new EventHandlerGroup(this, eventProcessorRepository, selectedEventProcessors); + } + + /** + * 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 hte specified event processors. + * @see #after(org.apache.logging.log4j.async.com.lmax.disruptor.EventHandler[]) + */ + public EventHandlerGroup after(final EventProcessor... processors) + { + for (EventProcessor processor : processors) + { + eventProcessorRepository.add(processor); + } + + return new EventHandlerGroup(this, eventProcessorRepository, 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) + { + eventPublisher.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() + { + EventProcessor[] gatingProcessors = eventProcessorRepository.getLastEventProcessorsInChain(); + ringBuffer.setGatingSequences(Util.getSequencesFor(gatingProcessors)); + + checkOnlyStartedOnce(); + for (EventProcessorInfo eventProcessorInfo : eventProcessorRepository) + { + executor.execute(eventProcessorInfo.getEventProcessor()); + } + + 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 (EventProcessorInfo eventprocessorInfo : eventProcessorRepository) + { + eventprocessorInfo.getEventProcessor().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 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 {@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 eventProcessorRepository.getBarrierFor(handler); + } + + private boolean hasBacklog() + { + final long cursor = ringBuffer.getCursor(); + for (EventProcessor consumer : eventProcessorRepository.getLastEventProcessorsInChain()) + { + if (cursor != consumer.getSequence().get()) + { + return true; + } + } + return false; + } + + EventHandlerGroup createEventProcessors(final EventProcessor[] barrierEventProcessors, + final EventHandler[] eventHandlers) + { + checkNotStarted(); + + final EventProcessor[] createdEventProcessors = new EventProcessor[eventHandlers.length]; + final SequenceBarrier barrier = ringBuffer.newBarrier(Util.getSequencesFor(barrierEventProcessors)); + + 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); + } + + eventProcessorRepository.add(batchEventProcessor, eventHandler, barrier); + createdEventProcessors[i] = batchEventProcessor; + } + + if (createdEventProcessors.length > 0) + { + eventProcessorRepository.unMarkEventProcessorsAsEndOfChain(barrierEventProcessors); + } + + return new EventHandlerGroup(this, eventProcessorRepository, createdEventProcessors); + } + + 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,128 @@ +/* + * 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.SequenceBarrier; +import org.apache.logging.log4j.async.com.lmax.disruptor.util.Util; + +/** + * 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 EventProcessorRepository eventProcessorRepository; + private final EventProcessor[] eventProcessors; + + EventHandlerGroup(final Disruptor disruptor, + final EventProcessorRepository eventProcessorRepository, + final EventProcessor[] eventProcessors) + { + this.disruptor = disruptor; + this.eventProcessorRepository = eventProcessorRepository; + this.eventProcessors = eventProcessors; + } + + /** + * Create a new event handler group that combines the handlers in this group with + * handlers. + * + * @param handlers the handlers to combine. + * @return a new EventHandlerGroup combining the existing and new handlers into a + * single dependency group. + */ + public EventHandlerGroup and(final EventHandler... handlers) + { + EventProcessor[] combinedProcessors = new EventProcessor[eventProcessors.length + handlers.length]; + for (int i = 0; i < handlers.length; i++) + { + combinedProcessors[i] = eventProcessorRepository.getEventProcessorFor(handlers[i]); + } + System.arraycopy(eventProcessors, 0, combinedProcessors, handlers.length, eventProcessors.length); + + return new EventHandlerGroup(disruptor, eventProcessorRepository, combinedProcessors); + } + + /** + * 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) + { + EventProcessor[] combinedProcessors = new EventProcessor[eventProcessors.length + processors.length]; + + for (EventProcessor processor : processors) + { + eventProcessorRepository.add(processor); + } + System.arraycopy(processors, 0, combinedProcessors, 0, processors.length); + System.arraycopy(eventProcessors, 0, combinedProcessors, processors.length, eventProcessors.length); + + return new EventHandlerGroup(disruptor, eventProcessorRepository, combinedProcessors); + } + + /** + * 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 batch handlers to handleEventException 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(eventProcessors, 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(Util.getSequencesFor(eventProcessors)); + } +} 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,60 @@ +/* + * 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.SequenceBarrier; + +class EventProcessorInfo +{ + 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; + } + + public EventHandler getHandler() + { + return handler; + } + + public SequenceBarrier getBarrier() + { + return barrier; + } + + public boolean isEndOfChain() + { + return endOfChain; + } + + public void markAsUsedInBarrier() + { + endOfChain = false; + } +} Index: log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/EventProcessorRepository.java =================================================================== --- log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/EventProcessorRepository.java (revision 0) +++ log4j-async/src/main/java/org/apache/logging/log4j/async/com/lmax/disruptor/dsl/EventProcessorRepository.java (working copy) @@ -0,0 +1,101 @@ +/* + * 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.ArrayList; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +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.SequenceBarrier; + +class EventProcessorRepository implements Iterable> +{ + private final Map, EventProcessorInfo> eventProcessorInfoByHandler = new IdentityHashMap, EventProcessorInfo>(); + private final Map> eventProcessorInfoByEventProcessor = new IdentityHashMap>(); + + public void add(final EventProcessor eventprocessor, + final EventHandler handler, + final SequenceBarrier barrier) + { + final EventProcessorInfo eventProcessorInfo = new EventProcessorInfo(eventprocessor, handler, barrier); + eventProcessorInfoByHandler.put(handler, eventProcessorInfo); + eventProcessorInfoByEventProcessor.put(eventprocessor, eventProcessorInfo); + } + + public void add(final EventProcessor processor) + { + final EventProcessorInfo eventProcessorInfo = new EventProcessorInfo(processor, null, null); + eventProcessorInfoByEventProcessor.put(processor, eventProcessorInfo); + } + + public EventProcessor[] getLastEventProcessorsInChain() + { + List lastEventProcessors = new ArrayList(); + for (EventProcessorInfo eventProcessorInfo : eventProcessorInfoByEventProcessor.values()) + { + if (eventProcessorInfo.isEndOfChain()) + { + lastEventProcessors.add(eventProcessorInfo.getEventProcessor()); + } + } + + return lastEventProcessors.toArray(new EventProcessor[lastEventProcessors.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 void unMarkEventProcessorsAsEndOfChain(final EventProcessor... barrierEventProcessors) + { + for (EventProcessor barrierEventProcessor : barrierEventProcessors) + { + getEventProcessorInfo(barrierEventProcessor).markAsUsedInBarrier(); + } + } + + public Iterator> iterator() + { + return eventProcessorInfoByEventProcessor.values().iterator(); + } + + public SequenceBarrier getBarrierFor(final EventHandler handler) + { + final EventProcessorInfo eventProcessorInfo = getEventProcessorInfo(handler); + return eventProcessorInfo != null ? eventProcessorInfo.getBarrier() : null; + } + + private EventProcessorInfo getEventProcessorInfo(final EventHandler handler) + { + return eventProcessorInfoByHandler.get(handler); + } + + private EventProcessorInfo getEventProcessorInfo(final EventProcessor barrierEventProcessor) + { + return eventProcessorInfoByEventProcessor.get(barrierEventProcessor); + } +} 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 EventProcessorRepository eventProcessorRepository; + + ExceptionHandlerSetting(final EventHandler eventHandler, + final EventProcessorRepository eventProcessorRepository) + { + this.eventHandler = eventHandler; + this.eventProcessorRepository = eventProcessorRepository; + } + + /** + * Specify the {@link ExceptionHandler} to use with the event handler. + * + * @param exceptionHandler the exception handler to use. + */ + public void with(ExceptionHandler exceptionHandler) + { + ((BatchEventProcessor)eventProcessorRepository.getEventProcessorFor(eventHandler)).setExceptionHandler(exceptionHandler); + eventProcessorRepository.getBarrierFor(eventHandler).alert(); + } +} 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,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.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,153 @@ +/* + * 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) + { + long minimum = Long.MAX_VALUE; + + for (Sequence sequence : sequences) + { + long value = sequence.get(); + minimum = minimum < value ? 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; + } + + @SuppressWarnings("restriction") + 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,320 @@ +/* + * 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.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.dsl.Disruptor; +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(); + + disruptor = new Disruptor(FACTORY, + ringBufferSize, executor); + 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 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/AsyncLoggerConfigTest.xml =================================================================== --- log4j-async/src/test/java/AsyncLoggerConfigTest.xml (revision 0) +++ log4j-async/src/test/java/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/java/AsyncLoggerLocationTest.xml =================================================================== --- log4j-async/src/test/java/AsyncLoggerLocationTest.xml (revision 0) +++ log4j-async/src/test/java/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/java/AsyncLoggerTest.xml =================================================================== --- log4j-async/src/test/java/AsyncLoggerTest.xml (revision 0) +++ log4j-async/src/test/java/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/java/FastFileAppenderLocationTest.xml =================================================================== --- log4j-async/src/test/java/FastFileAppenderLocationTest.xml (revision 0) +++ log4j-async/src/test/java/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/java/FastFileAppenderTest.xml =================================================================== --- log4j-async/src/test/java/FastFileAppenderTest.xml (revision 0) +++ log4j-async/src/test/java/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/java/FastRollingFileAppenderLocationTest.xml =================================================================== --- log4j-async/src/test/java/FastRollingFileAppenderLocationTest.xml (revision 0) +++ log4j-async/src/test/java/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/java/FastRollingFileAppenderTest.xml =================================================================== --- log4j-async/src/test/java/FastRollingFileAppenderTest.xml (revision 0) +++ log4j-async/src/test/java/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/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/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)); + } + +}