Index: .classpath
===================================================================
--- .classpath	(revision 0)
+++ .classpath	(revision 0)
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java"/>
+	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
+	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
+	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
Index: src/main/java/org/perf4j/log4j/AsyncCoalescingStatisticsAppender.java
===================================================================
--- src/main/java/org/perf4j/log4j/AsyncCoalescingStatisticsAppender.java	(revision 56)
+++ src/main/java/org/perf4j/log4j/AsyncCoalescingStatisticsAppender.java	(working copy)
@@ -34,6 +34,7 @@
 import java.util.NoSuchElementException;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
 
 /**
  * This log4j Appender groups StopWatch log messages together to form GroupedTimingStatistics. At a scheduled interval
@@ -361,18 +362,26 @@
         /**
          * State variable keeps track of whether we've already determined that the loggedMessages queue has been closed.
          */
-        private boolean done;
+        private boolean done;        
+        /**
+         * State variable keeps track of whether we've finished waiting for a timeslice. If true, hasNext will return true and next will return null.
+         */
+        private boolean timeSliceOver;
 
         public boolean hasNext() {
             if (nextStopWatch == null) {
                 nextStopWatch = getNext(); //then try to get it
             }
-            return nextStopWatch != null;
+            return timeSliceOver || nextStopWatch != null;
         }
 
         public StopWatch next() {
-            if (nextStopWatch == null) {
-                nextStopWatch = getNext(); //then try to get it, and barf if there is no more
+        	if(timeSliceOver) {
+        		timeSliceOver = false;
+            	return null;
+        	}
+        	else if (nextStopWatch == null) {
+                nextStopWatch = getNext(); //then try to get it, and barf if there is no more                
                 if (nextStopWatch == null) {
                     throw new NoSuchElementException();
                 }
@@ -401,7 +410,14 @@
                     if (drainedMessages.isEmpty()) {
                         //then wait for a message to show up
                         try {
-                            drainedMessages.add(loggedMessages.take());
+                        	String message = loggedMessages.poll(timeSlice, TimeUnit.MILLISECONDS);
+                        	if(message == null) {
+                        		// no new messages, but want to indicate to check the timeslice
+                        		timeSliceOver = true;
+                        		return null;
+                        	} else {
+                        		drainedMessages.add(message);
+                        	}                            
                         } catch (InterruptedException ie) {
                             //someone interrupted us, we're done
                             done = true;
Index: src/main/java/org/perf4j/helpers/GroupingStatisticsIterator.java
===================================================================
--- src/main/java/org/perf4j/helpers/GroupingStatisticsIterator.java	(revision 56)
+++ src/main/java/org/perf4j/helpers/GroupingStatisticsIterator.java	(working copy)
@@ -66,9 +66,10 @@
     /**
      * Creates a GroupingStatisticsIterator that groups StopWatch instances pulled from the specified
      * stopWatchIterator into GroupedTimingStatistics. A timeslice of 30 seconds is used and rollup statistics are not
-     * created.
+     * created. 
      *
-     * @param stopWatchIterator The StopWatch Iterator that provides the StopWatch instances.
+     * @param stopWatchIterator The StopWatch Iterator that provides the StopWatch instances. If stopWatchIterator returns
+     * 							a null value, will check to see if a timeslice is over and return GroupedTimingStatistics if necessary.
      */
     public GroupingStatisticsIterator(Iterator<StopWatch> stopWatchIterator) {
         this(stopWatchIterator, 30000L, false);
@@ -78,7 +79,9 @@
      * Creates a GroupingStatisticsIterator that groups StopWatch instances pulled from the specified
      * stopWatchIterator into GroupedTimingStatistics.
      *
-     * @param stopWatchIterator      The StopWatch Iterator that provides the StopWatch instances.
+     * @param stopWatchIterator      The StopWatch Iterator that provides the StopWatch instances. If stopWatchIterator
+     * 								 returns a null value, will check to see if a timeslice is over and return
+     * 								 GroupedTimingStatistics if necessary.
      * @param timeSlice              The length of each time slice, in milliseconds.
      * @param createRollupStatistics Whether or not entries for "rollup" tags should be created
      */
@@ -143,13 +146,16 @@
     private GroupedTimingStatistics getNext() {
         while (stopWatchIterator.hasNext()) {
             StopWatch stopWatch = stopWatchIterator.next();
-
+            
+            // if stopwatch is null, then the timeslice might be over (use current time)
+            long startTime = stopWatch == null ? System.currentTimeMillis() : stopWatch.getStartTime();
+            
             //the first time we pull a stop watch we need to set the first end time
             if (nextTimeSliceEndTime == 0L) {
-                nextTimeSliceEndTime = ((stopWatch.getStartTime() / timeSlice) * timeSlice) + timeSlice;
+                nextTimeSliceEndTime = ((startTime / timeSlice) * timeSlice) + timeSlice;
             }
 
-            if (stopWatch.getStartTime() >= nextTimeSliceEndTime) {
+            if (startTime >= nextTimeSliceEndTime) {
                 //then we're over a new time boundary, so update the current timing statistics and return it.
                 currentGroupedTimingStatistics.setStartTime(nextTimeSliceEndTime - timeSlice);
                 currentGroupedTimingStatistics.setStopTime(nextTimeSliceEndTime);
@@ -158,9 +164,11 @@
                 //set the state for the next slice
                 currentGroupedTimingStatistics = new GroupedTimingStatistics();
                 currentGroupedTimingStatistics.setCreateRollupStatistics(createRollupStatistics);
-                currentGroupedTimingStatistics.addStopWatch(stopWatch);
-                nextTimeSliceEndTime = ((stopWatch.getStartTime() / timeSlice) * timeSlice) + timeSlice;
-
+                if(stopWatch != null) {
+                	// only add if we got a new stopwatch, not if timeslice just expired
+                	currentGroupedTimingStatistics.addStopWatch(stopWatch);
+                }                
+                nextTimeSliceEndTime = ((startTime / timeSlice) * timeSlice) + timeSlice;
                 return retVal;
             } else {
                 currentGroupedTimingStatistics.addStopWatch(stopWatch);
Index: src/test/java/org/perf4j/log4j/AppenderTest.java
===================================================================
--- src/test/java/org/perf4j/log4j/AppenderTest.java	(revision 56)
+++ src/test/java/org/perf4j/log4j/AppenderTest.java	(working copy)
@@ -57,8 +57,44 @@
         //simple verification ensures that the total number of logged messages is correct.
         //tagName  avg           min     max     std dev       count, which is group 1
         String regex = "tag\\d\\s*\\d+\\.\\d\\s*\\d+\\s*\\d+\\s*\\d+\\.\\d\\s*(\\d+)";
+        Pattern statLinePattern = Pattern.compile(regex);        
+        Scanner scanner = new Scanner(new File("target/statisticsLog.log"));
+
+        int totalCount = 0;
+        while (scanner.findWithinHorizon(statLinePattern, 0) != null) {
+            totalCount += Integer.parseInt(scanner.match().group(1));
+        }
+        assertEquals(testThreads.length * TestLoggingThread.STOP_WATCH_COUNT, totalCount);
+    }
+    
+    // http://jira.codehaus.org/browse/PERFFORJ-21
+    public void testAppendersTimesliceOver() throws Exception {
+    	// need to do immediateflush on the fileappender since close will not be called
+        DOMConfigurator.configure(getClass().getResource("log4j-timeslicebug.xml"));
+
+        AsyncCoalescingStatisticsAppender appender =
+                (AsyncCoalescingStatisticsAppender) Logger.getLogger(StopWatch.DEFAULT_LOGGER_NAME)
+                        .getAppender("coalescingStatistics");
+
+        //log from a bunch of threads
+        TestLoggingThread[] testThreads = new TestLoggingThread[10];
+        for (int i = 0; i < testThreads.length; i++) {
+            testThreads[i] = new TestLoggingThread();
+            testThreads[i].start();
+        }
+
+        for (TestLoggingThread testThread : testThreads) {
+            testThread.join();
+        }
+        
+        // we should see all the logging after waiting this long
+        Thread.sleep(2*appender.getTimeSlice());
+
+        //simple verification ensures that the total number of logged messages is correct.
+        //tagName  avg           min     max     std dev       count, which is group 1
+        String regex = "tag\\d+\\s*\\d+\\.\\d\\s*\\d+\\s*\\d+\\s*\\d+\\.\\d\\s*(\\d+)";
         Pattern statLinePattern = Pattern.compile(regex);
-        Scanner scanner = new Scanner(new File("target/statisticsLog.log"));
+        Scanner scanner = new Scanner(new File("target/statisticsLog-timeslicebug.log"));
 
         int totalCount = 0;
         while (scanner.findWithinHorizon(statLinePattern, 0) != null) {
Index: src/test/resources/org/perf4j/log4j/log4j-timeslicebug.xml
===================================================================
--- src/test/resources/org/perf4j/log4j/log4j-timeslicebug.xml	(revision 0)
+++ src/test/resources/org/perf4j/log4j/log4j-timeslicebug.xml	(revision 0)
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+  <appender name="coalescingStatistics" class="org.perf4j.log4j.AsyncCoalescingStatisticsAppender">
+	<!--
+		the larger this is the more likely to simulate that
+		http://jira.codehaus.org/browse/PERFFORJ-21 is fixed
+	-->
+    <param name="TimeSlice" value="10000"/>
+    <appender-ref ref="statistics"/>
+  </appender>
+
+  <appender name="statistics" class="org.apache.log4j.FileAppender">
+  	<param name="ImmediateFlush" value="true"/> 
+    <param name="File" value="./target/statisticsLog-timeslicebug.log"/>
+    <param name="Append" value="false"/>
+    <layout class="org.apache.log4j.PatternLayout">
+      <param name="ConversionPattern" value="%m%n"/>
+    </layout> 
+  </appender>
+
+  <logger name="org.perf4j.TimingLogger">
+    <level value="info"/>
+    <appender-ref ref="coalescingStatistics"/>
+  </logger>
+</log4j:configuration>
\ No newline at end of file
Index: .settings/org.eclipse.jdt.core.prefs
===================================================================
--- .settings/org.eclipse.jdt.core.prefs	(revision 0)
+++ .settings/org.eclipse.jdt.core.prefs	(revision 0)
@@ -0,0 +1,5 @@
+#Mon Mar 16 16:18:41 CDT 2009
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.source=1.5
Index: .settings/org.maven.ide.eclipse.prefs
===================================================================
--- .settings/org.maven.ide.eclipse.prefs	(revision 0)
+++ .settings/org.maven.ide.eclipse.prefs	(revision 0)
@@ -0,0 +1,9 @@
+#Mon Mar 16 16:18:27 CDT 2009
+activeProfiles=
+eclipse.preferences.version=1
+fullBuildGoals=process-test-resources
+includeModules=false
+resolveWorkspaceProjects=true
+resourceFilterGoals=process-resources resources\:testResources
+skipCompilerPlugin=true
+version=1
Index: .project
===================================================================
--- .project	(revision 0)
+++ .project	(revision 0)
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>perf4j-0.9.9</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.maven.ide.eclipse.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.maven.ide.eclipse.maven2Nature</nature>
+	</natures>
+</projectDescription>

