diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java index 44e2518d9aa..40293b210b2 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java @@ -355,9 +355,10 @@ public class Server extends HandlerWrapper implements Attributes futures.add(connector.shutdown()); // Then tell the contexts that we are shutting down - Handler[] contexts = getChildHandlersByClass(Graceful.class); - for (Handler context : contexts) - futures.add(((Graceful)context).shutdown()); + + Handler[] gracefuls = getChildHandlersByClass(Graceful.class); + for (Handler graceful : gracefuls) + futures.add(((Graceful)graceful).shutdown()); // Shall we gracefully wait for zero connections? long stopTimeout = getStopTimeout(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java index 26a0d8dcf22..3fbbddd7107 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java @@ -19,8 +19,11 @@ package org.eclipse.jetty.server.handler; import java.io.IOException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; @@ -32,14 +35,16 @@ import org.eclipse.jetty.server.AsyncContextEvent; import org.eclipse.jetty.server.HttpChannelState; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedOperation; +import org.eclipse.jetty.util.component.Graceful; import org.eclipse.jetty.util.statistic.CounterStatistic; import org.eclipse.jetty.util.statistic.SampleStatistic; @ManagedObject("Request Statistics Gathering") -public class StatisticsHandler extends HandlerWrapper +public class StatisticsHandler extends HandlerWrapper implements Graceful { private final AtomicLong _statsStartedAt = new AtomicLong(); @@ -59,6 +64,8 @@ public class StatisticsHandler extends HandlerWrapper private final AtomicInteger _responses5xx = new AtomicInteger(); private final AtomicLong _responsesTotalBytes = new AtomicLong(); + private final AtomicReference _shutdown=new AtomicReference<>(); + private final AsyncListener _onCompletion = new AsyncListener() { @Override @@ -86,14 +93,21 @@ public class StatisticsHandler extends HandlerWrapper Request request = state.getBaseRequest(); final long elapsed = System.currentTimeMillis()-request.getTimeStamp(); - _requestStats.decrement(); + long d=_requestStats.decrement(); _requestTimeStats.set(elapsed); updateResponse(request); _asyncWaitStats.decrement(); + + // If we have no more dispatches, should we signal shutdown? + if (d==0) + { + FutureCallback shutdown = _shutdown.get(); + if (shutdown!=null) + shutdown.succeeded(); + } } - }; /** @@ -162,9 +176,18 @@ public class StatisticsHandler extends HandlerWrapper } else if (state.isInitial()) { - _requestStats.decrement(); + long d=_requestStats.decrement(); _requestTimeStats.set(dispatched); updateResponse(request); + + // If we have no more dispatches, should we signal shutdown? + FutureCallback shutdown = _shutdown.get(); + if (shutdown!=null) + { + httpResponse.flushBuffer(); + if (d==0) + shutdown.succeeded(); + } } // else onCompletion will handle it. } @@ -205,9 +228,20 @@ public class StatisticsHandler extends HandlerWrapper @Override protected void doStart() throws Exception { + _shutdown.set(null); super.doStart(); statsReset(); } + + + @Override + protected void doStop() throws Exception + { + super.doStop(); + FutureCallback shutdown = _shutdown.get(); + if (shutdown!=null && !shutdown.isDone()) + shutdown.failed(new TimeoutException()); + } /** * @return the number of requests handled by this handler @@ -523,4 +557,15 @@ public class StatisticsHandler extends HandlerWrapper return sb.toString(); } + + @Override + public Future shutdown() + { + FutureCallback shutdown=new FutureCallback(false); + _shutdown.compareAndSet(null,shutdown); + shutdown=_shutdown.get(); + if (_dispatchedStats.getCurrent()==0) + shutdown.succeeded(); + return shutdown; + } } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java new file mode 100644 index 00000000000..0c11502ff68 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java @@ -0,0 +1,105 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server; + +import java.io.IOException; +import java.net.Socket; +import java.util.concurrent.TimeUnit; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.handler.StatisticsHandler; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.StringUtil; +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class GracefulStopTest +{ + private Server server; + + @Before + public void setup() throws Exception + { + server = new Server(0); + StatisticsHandler stats = new StatisticsHandler(); + TestHandler test=new TestHandler(); + server.setHandler(stats); + stats.setHandler(test); + server.setStopTimeout(10 * 1000); + + server.start(); + } + + @Test + public void testGraceful() throws Exception + { + new Thread() + { + @Override + public void run() + { + try + { + TimeUnit.SECONDS.sleep(1); + server.stop(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }.start(); + + try(Socket socket = new Socket("localhost",server.getBean(NetworkConnector.class).getLocalPort());) + { + socket.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes(StringUtil.__ISO_8859_1_CHARSET)); + String out = IO.toString(socket.getInputStream()); + Assert.assertThat(out,Matchers.containsString("200 OK")); + } + } + + private static class TestHandler extends AbstractHandler + { + @Override + public void handle(final String s, final Request request, final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) + throws IOException, ServletException + { + try + { + TimeUnit.SECONDS.sleep(2); + } + catch (InterruptedException e) + { + } + + httpServletResponse.getWriter().write("OK"); + httpServletResponse.setStatus(200); + request.setHandled(true); + } + } + +} \ No newline at end of file diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/CounterStatistic.java b/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/CounterStatistic.java index 67699911c9b..51a82ea3bb4 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/CounterStatistic.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/CounterStatistic.java @@ -55,37 +55,31 @@ public class CounterStatistic /** * @param delta the amount to add to the count */ - public void add(final long delta) + public long add(final long delta) { long value=_curr.addAndGet(delta); if (delta > 0) + { _total.addAndGet(delta); - Atomics.updateMax(_max,value); - } - - /* ------------------------------------------------------------ */ - /** - * @param delta the amount to subtract the count by. - */ - public void subtract(final long delta) - { - add(-delta); + Atomics.updateMax(_max,value); + } + return value; } /* ------------------------------------------------------------ */ /** */ - public void increment() + public long increment() { - add(1); + return add(1); } /* ------------------------------------------------------------ */ /** */ - public void decrement() + public long decrement() { - add(-1); + return add(-1); } /* ------------------------------------------------------------ */