Author: carnold Date: Wed Mar 15 21:33:43 2006 New Revision: 386266 URL: http://svn.apache.org/viewcvs?rev=386266&view=rev Log: Bug 38982: Non-blocking option for AsyncAppender
Added: logging/log4j/trunk/src/java/org/apache/log4j/Dispatcher.java Modified: logging/log4j/trunk/src/java/org/apache/log4j/AsyncAppender.java logging/log4j/trunk/src/java/org/apache/log4j/helpers/BoundedFIFO.java logging/log4j/trunk/tests/src/java/org/apache/log4j/AsyncAppenderTestCase.java Modified: logging/log4j/trunk/src/java/org/apache/log4j/AsyncAppender.java URL: http://svn.apache.org/viewcvs/logging/log4j/trunk/src/java/org/apache/log4j/AsyncAppender.java?rev=386266&r1=386265&r2=386266&view=diff ============================================================================== --- logging/log4j/trunk/src/java/org/apache/log4j/AsyncAppender.java (original) +++ logging/log4j/trunk/src/java/org/apache/log4j/AsyncAppender.java Wed Mar 15 21:33:43 2006 @@ -1,12 +1,12 @@ /* - * Copyright 1999,2004 The Apache Software Foundation. - * + * Copyright 1999,2006 The Apache Software Foundation. + * * 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. @@ -14,41 +14,43 @@ * limitations under the License. */ - // Contibutors: Aaron Greenhouse <[EMAIL PROTECTED]> // Thomas Tuft Muller <[EMAIL PROTECTED]> package org.apache.log4j; import org.apache.log4j.helpers.AppenderAttachableImpl; -import org.apache.log4j.helpers.BoundedFIFO; import org.apache.log4j.spi.AppenderAttachable; import org.apache.log4j.spi.LoggingEvent; +import java.text.MessageFormat; + +import java.util.ArrayList; import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; /** - * The AsyncAppender lets users log events asynchronously. It uses a bounded - * buffer to store logging events. - * - * <p> + * The AsyncAppender lets users log events asynchronously. + * <p/> + * <p/> * The AsyncAppender will collect the events sent to it and then dispatch them * to all the appenders that are attached to it. You can attach multiple * appenders to an AsyncAppender. * </p> - * - * <p> - * The AsyncAppender uses a separate thread to serve the events in its bounded - * buffer. + * <p/> + * <p/> + * The AsyncAppender uses a separate thread to serve the events in its buffer. * </p> - - * * <p> + * <p/> * <b>Important note:</b> The <code>AsyncAppender</code> can only be script * configured using the [EMAIL PROTECTED] org.apache.log4j.joran.JoranConfigurator}. * </p> * * @author Ceki Gülcü - * + * @author Curt Arnold * @since 0.9.1 */ public class AsyncAppender extends AppenderSkeleton @@ -58,67 +60,161 @@ */ public static final int DEFAULT_BUFFER_SIZE = 128; - //static Category cat = Category.getInstance(AsyncAppender.class.getName()); - private BoundedFIFO bf = new BoundedFIFO(DEFAULT_BUFFER_SIZE); + /** + * Event buffer, also used as monitor to protect itself and + * discardMap from simulatenous modifications. + */ + private final List buffer = new ArrayList(); + + /** + * Map of DiscardSummary objects keyed by logger name. + */ + private final Map discardMap = new HashMap(); + + /** + * Buffer size. + */ + private int bufferSize = DEFAULT_BUFFER_SIZE; + + /** Nested appenders. */ AppenderAttachableImpl aai; - private Dispatcher dispatcher; + + /** + * Nested appenders. + */ + private final AppenderAttachableImpl appenders; + + /** + * Dispatcher. + */ + private final Thread dispatcher; + + /** + * Should location info be included in dispatched messages. + */ private boolean locationInfo = false; - private boolean interruptedWarningMessage = false; + /** + * Does appender block when buffer is full. + */ + private boolean blocking = true; + + /** + * Create new instance. + */ public AsyncAppender() { super(true); - // Note: The dispatcher code assumes that the aai is set once and - // for all. - aai = new AppenderAttachableImpl(); - dispatcher = new Dispatcher(bf, this); + appenders = new AppenderAttachableImpl(); + + // + // only set for compatibility + aai = appenders; + + dispatcher = + new Thread(new Dispatcher(this, buffer, discardMap, appenders)); + + // It is the user's responsibility to close appenders before + // exiting. + dispatcher.setDaemon(true); + + // set the dispatcher priority to lowest possible value + // dispatcher.setPriority(Thread.MIN_PRIORITY); + dispatcher.setName("Dispatcher-" + dispatcher.getName()); dispatcher.start(); } - - public void addAppender(Appender newAppender) { - synchronized (aai) { - aai.addAppender(newAppender); + + /** + * Add appender. + * + * @param newAppender appender to add, may not be null. + */ + public void addAppender(final Appender newAppender) { + synchronized (appenders) { + appenders.addAppender(newAppender); } } - public void append(LoggingEvent event) { + /** + * [EMAIL PROTECTED] + */ + public void append(final LoggingEvent event) { // // if dispatcher thread has died then // append subsequent events synchronously // See bug 23021 - if (!dispatcher.isAlive()) { - synchronized(aai) { - aai.appendLoopOnAppenders(event); - } - return; + if ((dispatcher == null) || !dispatcher.isAlive() || (bufferSize <= 0)) { + synchronized (appenders) { + appenders.appendLoopOnAppenders(event); + } + + return; } + // extract all the thread dependent information now as later it will // be too late. event.prepareForDeferredProcessing(); + if (locationInfo) { event.getLocationInformation(); } - synchronized (bf) { - while (bf.isFull()) { - try { - //LogLog.debug("Waiting for free space in buffer, "+bf.length()); - bf.wait(); - } catch (InterruptedException e) { - if (!interruptedWarningMessage) { - interruptedWarningMessage = true; - getLogger().warn("AsyncAppender interrupted.", e); - } else { - getLogger().warn("AsyncAppender interrupted again."); + synchronized (buffer) { + while (true) { + int previousSize = buffer.size(); + + if (previousSize < bufferSize) { + buffer.add(event); + + // + // if buffer had been empty + // signal all threads waiting on buffer + // to check their conditions. + // + if (previousSize == 0) { + buffer.notifyAll(); + } + + break; + } + + // + // Following code is only reachable if buffer is full + // + // + // if blocking and thread is not already interrupted + // wait for a buffer notification + boolean discard = true; + + if (blocking && !Thread.interrupted()) { + try { + buffer.wait(); + discard = false; + } catch (InterruptedException e) { + // + // reset interrupt status so + // calling code can see interrupt on + // their next wait or sleep. + Thread.currentThread().interrupt(); } } - } - //cat.debug("About to put new event in buffer."); - bf.put(event); + // + // if blocking is false or thread has been interrupted + // add event to discard map. + // + if (discard) { + String loggerName = event.getLoggerName(); + DiscardSummary summary = (DiscardSummary) discardMap.get(loggerName); + + if (summary == null) { + summary = new DiscardSummary(event); + discardMap.put(loggerName, summary); + } else { + summary.add(event); + } - if (bf.wasEmpty()) { - //cat.debug("Notifying dispatcher to process events."); - bf.notify(); + break; + } } } } @@ -128,81 +224,118 @@ * thread which will process all pending events before exiting. */ public void close() { - synchronized (this) { - // avoid multiple close, otherwise one gets NullPointerException - if (closed) { - return; - } - + /** + * Set closed flag and notify all threads to check their conditions. + * Should result in dispatcher terminating. + */ + synchronized (buffer) { closed = true; + buffer.notifyAll(); } - // The following cannot be synchronized on "this" because the - // dispatcher synchronizes with "this" in its while loop. If we - // did synchronize we would systematically get deadlocks when - // close was called. - dispatcher.close(); - try { dispatcher.join(); } catch (InterruptedException e) { + Thread.currentThread().interrupt(); getLogger().error( "Got an InterruptedException while waiting for the " + "dispatcher to finish.", e); } - dispatcher = null; - bf = null; + // + // close all attached appenders. + // + synchronized (appenders) { + Enumeration iter = appenders.getAllAppenders(); + + if (iter != null) { + while (iter.hasMoreElements()) { + Object next = iter.nextElement(); + + if (next instanceof Appender) { + ((Appender) next).close(); + } + } + } + } } + /** + * Get iterator over attached appenders. + * @return iterator or null if no attached appenders. + */ public Enumeration getAllAppenders() { - synchronized (aai) { - return aai.getAllAppenders(); + synchronized (appenders) { + return appenders.getAllAppenders(); } } - public Appender getAppender(String name) { - synchronized (aai) { - return aai.getAppender(name); + /** + * Get appender by name. + * + * @param name name, may not be null. + * @return matching appender or null. + */ + public Appender getAppender(final String name) { + synchronized (appenders) { + return appenders.getAppender(name); } } /** - * Returns the current value of the <b>LocationInfo</b> option. + * Gets whether the location of the logging request call + * should be captured. + * + * @return the current value of the <b>LocationInfo</b> option. */ public boolean getLocationInfo() { return locationInfo; } /** - * Is the appender passed as parameter attached to this category? + * Determines if specified appender is attached. + * @param appender appender. + * @return true if attached. */ - public boolean isAttached(Appender appender) { - return aai.isAttached(appender); + public boolean isAttached(final Appender appender) { + synchronized (appenders) { + return appenders.isAttached(appender); + } } /** - * @deprecated Will be removed with no replacement. + * [EMAIL PROTECTED] */ public boolean requiresLayout() { return false; } + /** + * Removes and closes all attached appenders. + */ public void removeAllAppenders() { - synchronized (aai) { - aai.removeAllAppenders(); + synchronized (appenders) { + appenders.removeAllAppenders(); } } - public void removeAppender(Appender appender) { - synchronized (aai) { - aai.removeAppender(appender); + /** + * Removes an appender. + * @param appender appender to remove. + */ + public void removeAppender(final Appender appender) { + synchronized (appenders) { + appenders.removeAppender(appender); } } - public void removeAppender(String name) { - synchronized (aai) { - aai.removeAppender(name); + /** + * Remove appender by name. + * @param name name. + */ + public void removeAppender(final String name) { + synchronized (appenders) { + appenders.removeAppender(name); } } @@ -212,131 +345,241 @@ * information related to the event. As a result, the event that will be * ultimately logged will likely to contain the wrong location information * (if present in the log format). - * - * <p> + * <p/> + * <p/> * Location information extraction is comparatively very slow and should be * avoided unless performance is not a concern. * </p> + * @param flag true if location information should be extracted. */ - public void setLocationInfo(boolean flag) { + public void setLocationInfo(final boolean flag) { locationInfo = flag; } /** - * The <b>BufferSize</b> option takes a non-negative integer value. This - * integer value determines the maximum size of the bounded buffer. - * Increasing the size of the buffer is always safe. However, if an - * existing buffer holds unwritten elements, then <em>decreasing the buffer - * size will result in event loss.</em> Nevertheless, while script - * configuring the AsyncAppender, it is safe to set a buffer size smaller - * than the [EMAIL PROTECTED] #DEFAULT_BUFFER_SIZE default buffer size} because - * configurators guarantee that an appender cannot be used before being - * completely configured. + * Sets the number of messages allowed in the event buffer + * before the calling thread is blocked (if blocking is true) + * or until messages are summarized and discarded. Changing + * the size will not affect messages already in the buffer. + * + * @param size buffer size, must be positive. */ - public void setBufferSize(int size) { - bf.resize(size); + public void setBufferSize(final int size) { + // + // log4j 1.2 would throw exception if size was negative + // and deadlock if size was zero. + // + if (size < 0) { + throw new java.lang.NegativeArraySizeException("size"); + } + + synchronized (buffer) { + // + // don't let size be zero. + // + bufferSize = (size < 1) ? 1 : size; + buffer.notifyAll(); + } } /** - * Returns the current value of the <b>BufferSize</b> option. + * Gets the current buffer size. + * @return the current value of the <b>BufferSize</b> option. */ public int getBufferSize() { - // Bugzilla 23912 - synchronized(bf) { - return bf.getMaxSize(); - } + return bufferSize; } -} + /** + * Sets whether appender should wait if there is no + * space available in the event buffer or immediately return. + * + * @param value true if appender should wait until available space in buffer. + */ + public void setBlocking(final boolean value) { + synchronized (buffer) { + blocking = value; + buffer.notifyAll(); + } + } -// ------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------ -// ---------------------------------------------------------------------------- -class Dispatcher extends Thread { - private BoundedFIFO bf; - private AppenderAttachableImpl aai; - private boolean interrupted = false; - AsyncAppender container; - - Dispatcher(BoundedFIFO bf, AsyncAppender container) { - this.bf = bf; - this.container = container; - this.aai = container.aai; + /** + * Gets whether appender should block calling thread when buffer is full. + * If false, messages will be counted by logger and a summary + * message appended after the contents of the buffer have been appended. + * + * @return true if calling thread will be blocked when buffer is full. + */ + public boolean getBlocking() { + return blocking; + } - // It is the user's responsibility to close appenders before - // exiting. - this.setDaemon(true); + /** + * Summary of discarded logging events for a logger. + */ + private static final class DiscardSummary { + /** + * First event of the highest severity. + */ + private LoggingEvent maxEvent; + + /** + * Total count of messages discarded. + */ + private int count; + + /** + * Create new instance. + * + * @param event event, may not be null. + */ + public DiscardSummary(final LoggingEvent event) { + maxEvent = event; + count = 1; + } + + /** + * Add discarded event to summary. + * + * @param event event, may not be null. + */ + public void add(final LoggingEvent event) { + if (event.getLevel().toInt() > maxEvent.getLevel().toInt()) { + maxEvent = event; + } - // set the dispatcher priority to lowest possible value - this.setPriority(Thread.MIN_PRIORITY); - this.setName("Dispatcher-" + getName()); + count++; + } - // set the dispatcher priority to MIN_PRIORITY plus or minus 2 - // depending on the direction of MIN to MAX_PRIORITY. - //+ (Thread.MAX_PRIORITY > Thread.MIN_PRIORITY ? 1 : -1)*2); - } - - void close() { - synchronized (bf) { - interrupted = true; - - // We have a waiting dispacther if and only if bf.length is - // zero. In that case, we need to give it a death kiss. - if (bf.length() == 0) { - bf.notify(); - } + /** + * Create event with summary information. + * + * @return new event. + */ + public LoggingEvent createEvent() { + String msg = + MessageFormat.format( + "Discarded {0} messages due to full event buffer including: {1}", + new Object[] { new Integer(count), maxEvent.getMessage() }); + + return new LoggingEvent( + null, maxEvent.getLogger(), maxEvent.getLevel(), msg, null); } } /** - * The dispatching strategy is to wait until there are events in the buffer - * to process. After having processed an event, we release the monitor - * (variable bf) so that new events can be placed in the buffer, instead of - * keeping the monitor and processing the remaining events in the buffer. - * - * <p> - * Other approaches might yield better results. - * </p> + * Event dispatcher. */ - public void run() { - //Category cat = Category.getInstance(Dispatcher.class.getName()); - LoggingEvent event; - - while (true) { - synchronized (bf) { - if (bf.length() == 0) { - // Exit loop if interrupted but only if the the buffer is empty. - if (interrupted) { - //cat.info("Exiting."); - break; + private static class Dispatcher implements Runnable { + /** + * Parent AsyncAppender. + */ + private final AsyncAppender parent; + + /** + * Event buffer. + */ + private final List buffer; + + /** + * Map of DiscardSummary keyed by logger name. + */ + private final Map discardMap; + + /** + * Wrapped appenders. + */ + private final AppenderAttachableImpl appenders; + + /** + * Create new instance of dispatcher. + * + * @param parent parent AsyncAppender, may not be null. + * @param buffer event buffer, may not be null. + * @param discardMap discard map, may not be null. + * @param appenders appenders, may not be null. + */ + public Dispatcher( + final AsyncAppender parent, final List buffer, final Map discardMap, + final AppenderAttachableImpl appenders) { + + this.parent = parent; + this.buffer = buffer; + this.appenders = appenders; + this.discardMap = discardMap; + } + + /** + * [EMAIL PROTECTED] + */ + public void run() { + boolean isActive = true; + + // + // if interrupted (unlikely), end thread + // + try { + // + // loop until the AsyncAppender is closed. + // + while (isActive) { + LoggingEvent[] events = null; + + // + // extract pending events while synchronized + // on buffer + // + synchronized (buffer) { + int bufferSize = buffer.size(); + isActive = !parent.isClosed(); + + while ((bufferSize == 0) && isActive) { + buffer.wait(); + bufferSize = buffer.size(); + isActive = !parent.isClosed(); + } + + if (bufferSize > 0) { + events = new LoggingEvent[bufferSize + discardMap.size()]; + buffer.toArray(events); + + // + // add events due to buffer overflow + // + int index = bufferSize; + + for ( + Iterator iter = discardMap.values().iterator(); + iter.hasNext();) { + events[index++] = ((DiscardSummary) iter.next()).createEvent(); + } + + // + // clear buffer and discard map + // + buffer.clear(); + discardMap.clear(); + + // + // allow blocked appends to continue + buffer.notifyAll(); + } } - try { - //LogLog.debug("Waiting for new event to dispatch."); - bf.wait(); - } catch (InterruptedException e) { - break; + // + // process events after lock on buffer is released. + // + if (events != null) { + for (int i = 0; i < events.length; i++) { + synchronized (appenders) { + appenders.appendLoopOnAppenders(events[i]); + } + } } } - - event = bf.get(); - - if (bf.wasFull()) { - //LogLog.debug("Notifying AsyncAppender about freed space."); - bf.notify(); - } - } - // synchronized - - synchronized (container.aai) { - if ((aai != null) && (event != null)) { - aai.appendLoopOnAppenders(event); - } + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); } } - // while - - // close and remove all appenders - aai.removeAllAppenders(); } } Added: logging/log4j/trunk/src/java/org/apache/log4j/Dispatcher.java URL: http://svn.apache.org/viewcvs/logging/log4j/trunk/src/java/org/apache/log4j/Dispatcher.java?rev=386266&view=auto ============================================================================== --- logging/log4j/trunk/src/java/org/apache/log4j/Dispatcher.java (added) +++ logging/log4j/trunk/src/java/org/apache/log4j/Dispatcher.java Wed Mar 15 21:33:43 2006 @@ -0,0 +1,124 @@ +/* + * Copyright 1999,2005 The Apache Software Foundation. + * + * 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.log4j; + +import org.apache.log4j.helpers.AppenderAttachableImpl; +import org.apache.log4j.spi.LoggingEvent; + + +/** + * Obsolete AsyncAppender dispatcher provided for compatibility only. + * + * @deprecated Since 1.3. + */ +class Dispatcher extends Thread { + /** + * @deprecated + */ + private org.apache.log4j.helpers.BoundedFIFO bf; + private AppenderAttachableImpl aai; + private boolean interrupted = false; + AsyncAppender container; + + /** + * + * @param bf + * @param container + * @deprecated + */ + Dispatcher(org.apache.log4j.helpers.BoundedFIFO bf, AsyncAppender container) { + this.bf = bf; + this.container = container; + this.aai = container.aai; + + // It is the user's responsibility to close appenders before + // exiting. + this.setDaemon(true); + + // set the dispatcher priority to lowest possible value + this.setPriority(Thread.MIN_PRIORITY); + this.setName("Dispatcher-" + getName()); + + // set the dispatcher priority to MIN_PRIORITY plus or minus 2 + // depending on the direction of MIN to MAX_PRIORITY. + //+ (Thread.MAX_PRIORITY > Thread.MIN_PRIORITY ? 1 : -1)*2); + } + + void close() { + synchronized (bf) { + interrupted = true; + + // We have a waiting dispacther if and only if bf.length is + // zero. In that case, we need to give it a death kiss. + if (bf.length() == 0) { + bf.notify(); + } + } + } + + /** + * The dispatching strategy is to wait until there are events in the buffer + * to process. After having processed an event, we release the monitor + * (variable bf) so that new events can be placed in the buffer, instead of + * keeping the monitor and processing the remaining events in the buffer. + * + * <p> + * Other approaches might yield better results. + * </p> + */ + public void run() { + //Category cat = Category.getInstance(Dispatcher.class.getName()); + LoggingEvent event; + + while (true) { + synchronized (bf) { + if (bf.length() == 0) { + // Exit loop if interrupted but only if the the buffer is empty. + if (interrupted) { + //cat.info("Exiting."); + break; + } + + try { + //LogLog.debug("Waiting for new event to dispatch."); + bf.wait(); + } catch (InterruptedException e) { + break; + } + } + + event = bf.get(); + + if (bf.wasFull()) { + //LogLog.debug("Notifying AsyncAppender about freed space."); + bf.notify(); + } + } + + // synchronized + synchronized (container.aai) { + if ((aai != null) && (event != null)) { + aai.appendLoopOnAppenders(event); + } + } + } + + // while + // close and remove all appenders + aai.removeAllAppenders(); + } +} Modified: logging/log4j/trunk/src/java/org/apache/log4j/helpers/BoundedFIFO.java URL: http://svn.apache.org/viewcvs/logging/log4j/trunk/src/java/org/apache/log4j/helpers/BoundedFIFO.java?rev=386266&r1=386265&r2=386266&view=diff ============================================================================== --- logging/log4j/trunk/src/java/org/apache/log4j/helpers/BoundedFIFO.java (original) +++ logging/log4j/trunk/src/java/org/apache/log4j/helpers/BoundedFIFO.java Wed Mar 15 21:33:43 2006 @@ -23,10 +23,14 @@ /** <code>BoundedFIFO</code> serves as the bounded first-in-first-out - buffer heavily used by the [EMAIL PROTECTED] org.apache.log4j.AsyncAppender}. + buffer previously used by the [EMAIL PROTECTED] org.apache.log4j.AsyncAppender}. @author Ceki Gülcü - @since version 0.9.1 */ + @since version 0.9.1 + + @deprecated Since 1.3. + + */ public class BoundedFIFO { LoggingEvent[] buf; Modified: logging/log4j/trunk/tests/src/java/org/apache/log4j/AsyncAppenderTestCase.java URL: http://svn.apache.org/viewcvs/logging/log4j/trunk/tests/src/java/org/apache/log4j/AsyncAppenderTestCase.java?rev=386266&r1=386265&r2=386266&view=diff ============================================================================== --- logging/log4j/trunk/tests/src/java/org/apache/log4j/AsyncAppenderTestCase.java (original) +++ logging/log4j/trunk/tests/src/java/org/apache/log4j/AsyncAppenderTestCase.java Wed Mar 15 21:33:43 2006 @@ -27,6 +27,8 @@ /** * Tests for AsyncAppender. * + * @author Curt Arnold + * */ public final class AsyncAppenderTestCase extends TestCase { /** @@ -100,7 +102,7 @@ root.debug("m2"); Vector v = vectorAppender.getVector(); - assertEquals(v.size(), 1); + assertEquals(1, v.size()); assertTrue(vectorAppender.isClosed()); } @@ -120,7 +122,7 @@ // NullPointerException should kill dispatching thread // before sleep returns. root.info("Message"); - Thread.sleep(200); + Thread.sleep(100); try { // @@ -146,7 +148,7 @@ // final int threadCount = 10; Thread[] threads = new Thread[threadCount]; - final int repetitions = 100; + final int repetitions = 20; Greeter greeter = new Greeter(root, repetitions); for (int i = 0; i < threads.length; i++) { @@ -188,19 +190,14 @@ Thread greeter = new Thread(new Greeter(root, 100)); synchronized (blockableAppender.getMonitor()) { + // Start greeter greeter.start(); + + // Give it enough time to fill buffer Thread.sleep(100); // - // Undesirable behavior: Interrupts are swallowed by - // AsycnAppender which could interfere with expected - // response to interrupts if the client code called wait or - // sleep. - // - greeter.interrupt(); - Thread.sleep(10); - greeter.interrupt(); - Thread.sleep(10); + // Interrupt should stop greeter after next logging event greeter.interrupt(); } @@ -208,7 +205,12 @@ asyncAppender.close(); Vector events = blockableAppender.getVector(); - assertEquals(100, events.size()); + + // + // 1 popped off of buffer by dispatcher before being blocked + // 5 in buffer before it filled up + // 1 before Thread.sleep in greeter + assertEquals(7, events.size()); } /** @@ -257,13 +259,16 @@ assertNotNull(dispatcher); dispatcher.interrupt(); Thread.sleep(50); + root.info("Hello, World"); // - // Undesirable action: interrupting the dispatch thread - // removes all appenders. - // + // interrupting the dispatch thread should + // degrade to synchronous dispatching of logging requests Enumeration iter = asyncAppender.getAllAppenders(); - assertTrue((iter == null) || !iter.hasMoreElements()); + assertTrue(iter.hasMoreElements()); + assertSame(blockableAppender, iter.nextElement()); + assertFalse(iter.hasMoreElements()); + assertEquals(2, blockableAppender.getVector().size()); } /** @@ -281,16 +286,16 @@ public void testSetBufferSizeZero() { VectorAppender vectorAppender = createDelayedAppender(); asyncAppender = createAsyncAppender(vectorAppender, 0); - assertEquals(0, asyncAppender.getBufferSize()); + assertEquals(1, asyncAppender.getBufferSize()); // // any logging request will deadlock. - //root.debug("m1"); - //root.debug("m2"); + root.debug("m1"); + root.debug("m2"); asyncAppender.close(); Vector v = vectorAppender.getVector(); - assertEquals(v.size(), 0); + assertEquals(2, v.size()); } /** @@ -408,6 +413,40 @@ assertFalse(asyncAppender.getAllAppenders().hasMoreElements()); } + /** + * Tests discarding of messages when buffer is full. + */ + public void testDiscard() { + BlockableVectorAppender blockableAppender = new BlockableVectorAppender(); + asyncAppender = createAsyncAppender(blockableAppender, 5); + assertTrue(asyncAppender.getBlocking()); + asyncAppender.setBlocking(false); + assertFalse(asyncAppender.getBlocking()); + Greeter greeter = new Greeter(root, 100); + synchronized(blockableAppender.getMonitor()) { + greeter.run(); + root.error("That's all folks."); + } + asyncAppender.close(); + Vector events = blockableAppender.getVector(); + // + // 1 event pulled from buffer by dispatcher before blocking + // 5 events in buffer + // 1 summary event + // + assertEquals(7, events.size()); + // + // last message should start with "Discarded" + LoggingEvent event = (LoggingEvent) events.get(events.size() - 1); + assertEquals("Discarded", event.getMessage().toString().substring(0, 9)); + assertSame(Level.ERROR, event.getLevel()); + for (int i = 0; i < events.size() - 1; i++) { + event = (LoggingEvent) events.get(i); + assertEquals("Hello, World", event.getMessage().toString()); + } + } + + /** * Appender that throws a NullPointerException on calls to append. * Used to test behavior of AsyncAppender when dispatching to @@ -474,10 +513,13 @@ * [EMAIL PROTECTED] */ public void run() { - synchronized (this) { - for (int i = 0; (i < repetitions) && !Thread.interrupted(); i++) { + try { + for (int i = 0; i < repetitions; i++) { logger.info("Hello, World"); + Thread.sleep(1); } + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); } } } --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]