Improved failsafe close handling for half closed endpoints
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java
index bfcb1aa..8fa2cc8 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java
@@ -44,6 +44,7 @@
             return AbstractEndPoint.this.needsFill();
         }
     };
+    
     private final WriteFlusher _writeFlusher = new WriteFlusher(this)
     {
         @Override
@@ -142,9 +143,22 @@
     @Override
     protected void onIdleExpired(TimeoutException timeout)
     {
-        // Note: Rely on fillInterest to notify onReadTimeout to close connection.
-        _fillInterest.onFail(timeout);
-        _writeFlusher.onFail(timeout);
+        boolean output_shutdown=isOutputShutdown();
+        boolean input_shutdown=isInputShutdown();
+        boolean fillFailed = _fillInterest.onFail(timeout);
+        boolean writeFailed = _writeFlusher.onFail(timeout);
+        
+        // If the endpoint is half closed and there was no onFail handling, the close here
+        // This handles the situation where the connection has completed its close handling 
+        // and the endpoint is half closed, but the other party does not complete the close.
+        // This perhaps should not check for half closed, however the servlet spec case allows
+        // for a dispatched servlet or suspended request to extend beyond the connections idle 
+        // time.  So if this test would always close an idle endpoint that is not handled, then 
+        // we would need a mode to ignore timeouts for some HTTP states
+        if (isOpen() && (output_shutdown || input_shutdown) && !(fillFailed || writeFailed))
+            close();
+        else 
+            LOG.debug("Ignored idle endpoint {}",this);
     }
 
     @Override
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java b/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java
index 0f3c2e5..b2c3f68 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java
@@ -93,12 +93,17 @@
     
     /* ------------------------------------------------------------ */
     /** Call to signal a failure to a registered interest
+     * @return true if the cause was passed to a {@link Callback} instance
      */
-    public void onFail(Throwable cause)
+    public boolean onFail(Throwable cause)
     {
         Callback callback=_interested.get();
         if (callback!=null && _interested.compareAndSet(callback,null))
+        {
             callback.failed(cause);
+            return true;
+        }
+        return false;
     }
     
     /* ------------------------------------------------------------ */
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java
index 326ef3f..dd44e53 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java
@@ -253,10 +253,14 @@
             return _buffers;
         }
 
-        protected void fail(Throwable cause)
+        protected boolean fail(Throwable cause)
         {
             if (_callback!=null)
+            {
                 _callback.failed(cause);
+                return true;
+            }
+            return false;
         }
 
         protected void complete()
@@ -430,7 +434,12 @@
         }
     }
 
-    public void onFail(Throwable cause)
+    /* ------------------------------------------------------------ */
+    /** Notify the flusher of a failure
+     * @param cause The cause of the failure
+     * @return true if the flusher passed the failure to a {@link Callback} instance
+     */
+    public boolean onFail(Throwable cause)
     {
         // Keep trying to handle the failure until we get to IDLE or FAILED state
         while(true)
@@ -442,7 +451,7 @@
                 case FAILED:
                     if (DEBUG)
                         LOG.debug("ignored: {} {}", this, cause);
-                    return;
+                    return false;
 
                 case PENDING:
                     if (DEBUG)
@@ -450,10 +459,7 @@
 
                     PendingState pending = (PendingState)current;
                     if (updateState(pending,__IDLE))
-                    {
-                        pending.fail(cause);
-                        return;
-                    }
+                        return pending.fail(cause);
                     break;
 
                 default:
@@ -461,7 +467,7 @@
                         LOG.debug("failed: {} {}", this, cause);
 
                     if (updateState(current,new FailedState(cause)))
-                        return;
+                        return false;
                     break;
             }
         }
diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java
index d23be98..f6cefa1 100644
--- a/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java
+++ b/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java
@@ -19,8 +19,6 @@
 package org.eclipse.jetty.io;
 
 import static org.hamcrest.Matchers.*;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.greaterThan;
 import static org.hamcrest.Matchers.instanceOf;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -37,8 +35,6 @@
 import org.eclipse.jetty.toolchain.test.annotation.Slow;
 import org.eclipse.jetty.util.BufferUtil;
 import org.eclipse.jetty.util.FutureCallback;
-import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
 import org.eclipse.jetty.util.thread.Scheduler;
 import org.eclipse.jetty.util.thread.TimerScheduler;
 import org.junit.After;
@@ -132,6 +128,7 @@
 
         assertEquals(true,endp.flush(BufferUtil.EMPTY_BUFFER,BufferUtil.toBuffer(" and"),BufferUtil.toBuffer(" more")));
         assertEquals("some output some more and more",endp.getOutputString());
+        endp.close();
     }
 
     @Test
@@ -150,6 +147,7 @@
 
         assertEquals(true,endp.flush(data));
         assertEquals("data.",BufferUtil.toString(endp.takeOutput()));
+        endp.close();
     }
 
 
@@ -237,6 +235,7 @@
         assertTrue(fcb.isDone());
         assertEquals(null, fcb.get());
         assertEquals(" more.", endp.getOutputString());
+        endp.close();
     }
     
     /**