Issue #1732 Stop accepting new connections

This commit is contained in:
Greg Wilkins 2017-08-12 10:26:17 +10:00
parent 829fa4fe9b
commit 5197ce4f54
7 changed files with 456 additions and 75 deletions

View File

@ -281,10 +281,6 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
if (task != null)
return task;
}
else if (key.isAcceptable())
{
processAccept(key);
}
else
{
throw new IllegalStateException("key=" + key + ", att=" + attachment + ", iOps=" + key.interestOps() + ", rOps=" + key.readyOps());
@ -386,27 +382,6 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
}
}
private void processAccept(SelectionKey key)
{
SelectableChannel server = key.channel();
SelectableChannel channel = null;
try
{
while(true)
{
channel = _selectorManager.doAccept(server);
if (channel==null)
break;
_selectorManager.accepted(channel);
}
}
catch (Throwable x)
{
closeNoExceptions(channel);
LOG.warn("Accept failed for channel " + channel, x);
}
}
private void closeNoExceptions(Closeable closeable)
{
try
@ -530,9 +505,10 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
}
}
class Acceptor extends NonBlockingAction
class Acceptor extends NonBlockingAction implements Selectable, Closeable
{
private final SelectableChannel _channel;
private SelectionKey _key;
public Acceptor(SelectableChannel channel)
{
@ -544,9 +520,14 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
{
try
{
SelectionKey key = _channel.register(_selector, SelectionKey.OP_ACCEPT, "Acceptor");
if (_key==null)
{
_key = _channel.register(_selector, SelectionKey.OP_ACCEPT, this);
}
if (LOG.isDebugEnabled())
LOG.debug("{} acceptor={}", this, key);
LOG.debug("{} acceptor={}", this, _key);
}
catch (Throwable x)
{
@ -554,6 +535,44 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
LOG.warn(x);
}
}
@Override
public Runnable onSelected()
{
SelectableChannel server = _key.channel();
SelectableChannel channel = null;
try
{
while(true)
{
channel = _selectorManager.doAccept(server);
if (channel==null)
break;
_selectorManager.accepted(channel);
}
}
catch (Throwable x)
{
closeNoExceptions(channel);
LOG.warn("Accept failed for channel " + channel, x);
}
return null;
}
@Override
public void updateKey()
{
}
@Override
public void close() throws IOException
{
SelectionKey key = _key;
_key = null;
if (key!=null && key.isValid())
key.cancel();
}
}
class Accept extends NonBlockingAction implements Closeable

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.io;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
@ -267,11 +268,14 @@ public abstract class SelectorManager extends ContainerLifeCycle implements Dump
* overridden by a derivation of this class to handle the accepted channel
*
* @param server the server channel to register
* @return A Closable that allows the acceptor to be cancelled
*/
public void acceptor(SelectableChannel server)
public Closeable acceptor(SelectableChannel server)
{
final ManagedSelector selector = chooseSelector(null);
selector.submit(selector.new Acceptor(server));
ManagedSelector.Acceptor acceptor = selector.new Acceptor(server);
selector.submit(acceptor);
return acceptor;
}
/**
@ -435,4 +439,5 @@ public abstract class SelectorManager extends ContainerLifeCycle implements Dump
*/
public abstract Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment) throws IOException;
}

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.server;
import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedByInterruptException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -34,6 +35,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import org.eclipse.jetty.io.ArrayByteBufferPool;
import org.eclipse.jetty.io.ByteBufferPool;
@ -48,6 +50,7 @@ import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.Locker;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.Scheduler;
@ -137,8 +140,10 @@ import org.eclipse.jetty.util.thread.Scheduler;
public abstract class AbstractConnector extends ContainerLifeCycle implements Connector, Dumpable
{
protected final Logger LOG = Log.getLogger(AbstractConnector.class);
// Order is important on server side, so we use a LinkedHashMap
private final Map<String, ConnectionFactory> _factories = new LinkedHashMap<>();
private final Locker _locker = new Locker();
private final Condition _setAccepting = _locker.newCondition();
private final Map<String, ConnectionFactory> _factories = new LinkedHashMap<>(); // Order is important on server side, so we use a LinkedHashMap
private final Server _server;
private final Executor _executor;
private final Scheduler _scheduler;
@ -146,12 +151,13 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
private final Thread[] _acceptors;
private final Set<EndPoint> _endpoints = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final Set<EndPoint> _immutableEndPoints = Collections.unmodifiableSet(_endpoints);
private volatile CountDownLatch _stopping;
private CountDownLatch _stopping;
private long _idleTimeout = 30000;
private String _defaultProtocol;
private ConnectionFactory _defaultConnectionFactory;
private String _name;
private int _acceptorPriorityDelta=-2;
private boolean _accepting = true;
/**
@ -222,7 +228,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
{
return _idleTimeout;
}
/**
* <p>Sets the maximum Idle time for a connection, which roughly translates to the {@link Socket#setSoTimeout(int)}
* call, although with NIO implementations other mechanisms may be used to implement the timeout.</p>
@ -283,7 +289,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
protected void interruptAcceptors()
{
synchronized (this)
try (Locker.Lock lock = _locker.lockIfNotHeld())
{
for (Thread thread : _acceptors)
{
@ -327,7 +333,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
public void join(long timeout) throws InterruptedException
{
synchronized (this)
try (Locker.Lock lock = _locker.lock())
{
for (Thread thread : _acceptors)
if (thread != null)
@ -338,19 +344,31 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
protected abstract void accept(int acceptorID) throws IOException, InterruptedException;
/* ------------------------------------------------------------ */
/**
* @return Is the connector accepting new connections
*/
protected boolean isAccepting()
public boolean isAccepting()
{
return isRunning();
try (Locker.Lock lock = _locker.lock())
{
return _accepting;
}
}
public void setAccepting(boolean accepting)
{
try (Locker.Lock lock = _locker.lock())
{
_accepting=accepting;
_setAccepting.signalAll();
}
}
@Override
public ConnectionFactory getConnectionFactory(String protocol)
{
synchronized (_factories)
try (Locker.Lock lock = _locker.lock())
{
return _factories.get(StringUtil.asciiToLowerCase(protocol));
}
@ -359,7 +377,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
@Override
public <T> T getConnectionFactory(Class<T> factoryType)
{
synchronized (_factories)
try (Locker.Lock lock = _locker.lock())
{
for (ConnectionFactory f : _factories.values())
if (factoryType.isAssignableFrom(f.getClass()))
@ -370,7 +388,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
public void addConnectionFactory(ConnectionFactory factory)
{
synchronized (_factories)
try (Locker.Lock lock = _locker.lockIfNotHeld())
{
Set<ConnectionFactory> to_remove = new HashSet<>();
for (String key:factory.getProtocols())
@ -409,7 +427,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
public void addFirstConnectionFactory(ConnectionFactory factory)
{
synchronized (_factories)
try (Locker.Lock lock = _locker.lock())
{
List<ConnectionFactory> existings = new ArrayList<>(_factories.values());
_factories.clear();
@ -422,7 +440,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
public void addIfAbsentConnectionFactory(ConnectionFactory factory)
{
synchronized (_factories)
try (Locker.Lock lock = _locker.lock())
{
String key=StringUtil.asciiToLowerCase(factory.getProtocol());
if (_factories.containsKey(key))
@ -444,7 +462,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
public ConnectionFactory removeConnectionFactory(String protocol)
{
synchronized (_factories)
try (Locker.Lock lock = _locker.lock())
{
ConnectionFactory factory= _factories.remove(StringUtil.asciiToLowerCase(protocol));
removeBean(factory);
@ -455,7 +473,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
@Override
public Collection<ConnectionFactory> getConnectionFactories()
{
synchronized (_factories)
try (Locker.Lock lock = _locker.lock())
{
return _factories.values();
}
@ -463,7 +481,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
public void setConnectionFactories(Collection<ConnectionFactory> factories)
{
synchronized (_factories)
try (Locker.Lock lock = _locker.lock())
{
List<ConnectionFactory> existing = new ArrayList<>(_factories.values());
for (ConnectionFactory factory: existing)
@ -538,14 +556,23 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
return getConnectionFactory(_defaultProtocol);
}
protected boolean handleAcceptFailure(Throwable previous, Throwable current)
protected boolean handleAcceptFailure(Throwable ex)
{
if (isAccepting())
if (isRunning())
{
if (previous == null)
LOG.warn(current);
else
LOG.debug(current);
if (ex instanceof InterruptedException)
{
LOG.debug(ex);
return true;
}
if (ex instanceof ClosedByInterruptException)
{
LOG.debug(ex);
return false;
}
LOG.warn(ex);
try
{
// Arbitrary sleep to avoid spin looping.
@ -556,12 +583,13 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
}
catch (Throwable x)
{
return false;
LOG.ignore(x);
}
return false;
}
else
{
LOG.ignore(current);
LOG.ignore(ex);
return false;
}
}
@ -595,19 +623,28 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
try
{
Throwable exception = null;
while (isAccepting())
while (isRunning())
{
try (Locker.Lock lock = _locker.lock())
{
if (!_accepting && isRunning())
{
_setAccepting.await();
continue;
}
}
catch (InterruptedException e)
{
continue;
}
try
{
accept(_id);
exception = null;
}
catch (Throwable x)
{
if (handleAcceptFailure(exception, x))
exception = x;
else
if (!handleAcceptFailure(x))
break;
}
}
@ -636,12 +673,9 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
return String.format("acceptor-%d@%x", _id, hashCode());
return name;
}
}
// protected void connectionOpened(Connection connection)
// {
// _stats.connectionOpened();

View File

@ -96,8 +96,6 @@ public abstract class AbstractNetworkConnector extends AbstractConnector impleme
@Override
public void close()
{
// Interrupting is often sufficient to close the channel
interruptAcceptors();
}
@ -107,11 +105,13 @@ public abstract class AbstractNetworkConnector extends AbstractConnector impleme
close();
return super.shutdown();
}
@Override
protected boolean isAccepting()
protected boolean handleAcceptFailure(Throwable ex)
{
return super.isAccepting() && isOpen();
if (isOpen())
return super.handleAcceptFailure(ex);
LOG.ignore(ex);
return false;
}
@Override

View File

@ -18,6 +18,7 @@
package org.eclipse.jetty.server;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
@ -31,6 +32,7 @@ import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.ChannelEndPoint;
@ -79,6 +81,7 @@ import org.eclipse.jetty.util.thread.Scheduler;
public class ServerConnector extends AbstractNetworkConnector
{
private final SelectorManager _manager;
private final AtomicReference<Closeable> _acceptor = new AtomicReference<>();
private volatile ServerSocketChannel _acceptChannel;
private volatile boolean _inheritChannel = false;
private volatile int _localPort = -1;
@ -237,7 +240,7 @@ public class ServerConnector extends AbstractNetworkConnector
if (getAcceptors()==0)
{
_acceptChannel.configureBlocking(false);
_manager.acceptor(_acceptChannel);
_acceptor.set(_manager.acceptor(_acceptChannel));
}
}
@ -344,14 +347,14 @@ public class ServerConnector extends AbstractNetworkConnector
@Override
public void close()
{
super.close();
ServerSocketChannel serverChannel = _acceptChannel;
_acceptChannel = null;
if (serverChannel != null)
{
removeBean(serverChannel);
// If the interrupt did not close it, we should close it
if (serverChannel.isOpen())
{
try
@ -364,7 +367,6 @@ public class ServerConnector extends AbstractNetworkConnector
}
}
}
// super.close();
_localPort = -2;
}
@ -375,6 +377,7 @@ public class ServerConnector extends AbstractNetworkConnector
if (serverChannel != null && serverChannel.isOpen())
{
SocketChannel channel = serverChannel.accept();
System.err.println("Accepted "+channel);
accepted(channel);
}
}
@ -483,6 +486,38 @@ public class ServerConnector extends AbstractNetworkConnector
_reuseAddress = reuseAddress;
}
@Override
public void setAccepting(boolean accepting)
{
super.setAccepting(accepting);
if (getAcceptors()>0)
return;
try
{
if (accepting)
{
if (_acceptor.get()==null)
{
Closeable acceptor = _manager.acceptor(_acceptChannel);
if (!_acceptor.compareAndSet(null,acceptor))
acceptor.close();
}
}
else
{
Closeable acceptor = _acceptor.get();
if (acceptor!=null && _acceptor.compareAndSet(acceptor,null))
acceptor.close();
}
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
protected class ServerConnectorManager extends SelectorManager
{
public ServerConnectorManager(Executor executor, Scheduler scheduler, int selectors)

View File

@ -0,0 +1,281 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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 static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.net.Socket;
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.server.LocalConnector.LocalEndPoint;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.util.BufferUtil;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AdvancedRunner.class)
public class NotAcceptingTest
{
@Test
public void testServerConnectorBlockingAccept() throws Exception
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server,1,1);
connector.setPort(0);
connector.setIdleTimeout(500);
connector.setAcceptQueueSize(10);
server.addConnector(connector);
TestHandler handler = new TestHandler();
server.setHandler(handler);
server.start();
try(Socket client0 = new Socket("localhost",connector.getLocalPort());)
{
HttpTester.Input in0 = HttpTester.from(client0.getInputStream());
client0.getOutputStream().write("GET /one HTTP/1.1\r\nHost:localhost\r\n\r\n".getBytes());
String uri = handler.exchange.exchange("data");
assertThat(uri,is("/one"));
HttpTester.Response response = HttpTester.parseResponse(in0);
assertThat(response.getStatus(),is(200));
assertThat(response.getContent(),is("data"));
connector.setAccepting(false);
// 0th connection still working
client0.getOutputStream().write("GET /two HTTP/1.1\r\nHost:localhost\r\n\r\n".getBytes());
uri = handler.exchange.exchange("more data");
assertThat(uri,is("/two"));
response = HttpTester.parseResponse(in0);
assertThat(response.getStatus(),is(200));
assertThat(response.getContent(),is("more data"));
try(Socket client1 = new Socket("localhost",connector.getLocalPort());)
{
// can't stop next connection being accepted
HttpTester.Input in1 = HttpTester.from(client1.getInputStream());
client1.getOutputStream().write("GET /three HTTP/1.1\r\nHost:localhost\r\n\r\n".getBytes());
uri = handler.exchange.exchange("new connection");
assertThat(uri,is("/three"));
response = HttpTester.parseResponse(in1);
assertThat(response.getStatus(),is(200));
assertThat(response.getContent(),is("new connection"));
try(Socket client2 = new Socket("localhost",connector.getLocalPort());)
{
HttpTester.Input in2 = HttpTester.from(client2.getInputStream());
client2.getOutputStream().write("GET /four HTTP/1.1\r\nHost:localhost\r\n\r\n".getBytes());
try
{
uri = handler.exchange.exchange("delayed connection",500,TimeUnit.MILLISECONDS);
Assert.fail(uri);
}
catch(TimeoutException e)
{
// Can we accept the original?
connector.setAccepting(true);
uri = handler.exchange.exchange("delayed connection");
assertThat(uri,is("/four"));
response = HttpTester.parseResponse(in2);
assertThat(response.getStatus(),is(200));
assertThat(response.getContent(),is("delayed connection"));
}
}
}
}
}
@Test
public void testLocalConnector() throws Exception
{
Server server = new Server();
LocalConnector connector = new LocalConnector(server);
connector.setIdleTimeout(500);
server.addConnector(connector);
TestHandler handler = new TestHandler();
server.setHandler(handler);
server.start();
try(LocalEndPoint client0 = connector.connect())
{
client0.addInputAndExecute(BufferUtil.toBuffer("GET /one HTTP/1.1\r\nHost:localhost\r\n\r\n"));
String uri = handler.exchange.exchange("data");
assertThat(uri,is("/one"));
HttpTester.Response response = HttpTester.parseResponse(client0.getResponse());
assertThat(response.getStatus(),is(200));
assertThat(response.getContent(),is("data"));
connector.setAccepting(false);
// 0th connection still working
client0.addInputAndExecute(BufferUtil.toBuffer("GET /two HTTP/1.1\r\nHost:localhost\r\n\r\n"));
uri = handler.exchange.exchange("more data");
assertThat(uri,is("/two"));
response = HttpTester.parseResponse(client0.getResponse());
assertThat(response.getStatus(),is(200));
assertThat(response.getContent(),is("more data"));
try(LocalEndPoint client1 = connector.connect())
{
// can't stop next connection being accepted
client1.addInputAndExecute(BufferUtil.toBuffer("GET /three HTTP/1.1\r\nHost:localhost\r\n\r\n"));
uri = handler.exchange.exchange("new connection");
assertThat(uri,is("/three"));
response = HttpTester.parseResponse(client1.getResponse());
assertThat(response.getStatus(),is(200));
assertThat(response.getContent(),is("new connection"));
try(LocalEndPoint client2 = connector.connect())
{
client2.addInputAndExecute(BufferUtil.toBuffer("GET /four HTTP/1.1\r\nHost:localhost\r\n\r\n"));
try
{
uri = handler.exchange.exchange("delayed connection",500,TimeUnit.MILLISECONDS);
Assert.fail(uri);
}
catch(TimeoutException e)
{
// Can we accept the original?
connector.setAccepting(true);
uri = handler.exchange.exchange("delayed connection");
assertThat(uri,is("/four"));
response = HttpTester.parseResponse(client2.getResponse());
assertThat(response.getStatus(),is(200));
assertThat(response.getContent(),is("delayed connection"));
}
}
}
}
}
@Test
public void testServerConnectorAsyncAccept() throws Exception
{
Server server = new Server();
ServerConnector connector = new ServerConnector(server,0,1);
connector.setPort(0);
connector.setIdleTimeout(500);
connector.setAcceptQueueSize(10);
server.addConnector(connector);
TestHandler handler = new TestHandler();
server.setHandler(handler);
server.start();
try(Socket client0 = new Socket("localhost",connector.getLocalPort());)
{
HttpTester.Input in0 = HttpTester.from(client0.getInputStream());
client0.getOutputStream().write("GET /one HTTP/1.1\r\nHost:localhost\r\n\r\n".getBytes());
String uri = handler.exchange.exchange("data");
assertThat(uri,is("/one"));
HttpTester.Response response = HttpTester.parseResponse(in0);
assertThat(response.getStatus(),is(200));
assertThat(response.getContent(),is("data"));
connector.setAccepting(false);
// 0th connection still working
client0.getOutputStream().write("GET /two HTTP/1.1\r\nHost:localhost\r\n\r\n".getBytes());
uri = handler.exchange.exchange("more data");
assertThat(uri,is("/two"));
response = HttpTester.parseResponse(in0);
assertThat(response.getStatus(),is(200));
assertThat(response.getContent(),is("more data"));
try(Socket client1 = new Socket("localhost",connector.getLocalPort());)
{
HttpTester.Input in1 = HttpTester.from(client1.getInputStream());
client1.getOutputStream().write("GET /three HTTP/1.1\r\nHost:localhost\r\n\r\n".getBytes());
try
{
uri = handler.exchange.exchange("delayed connection",500,TimeUnit.MILLISECONDS);
Assert.fail(uri);
}
catch(TimeoutException e)
{
// Can we accept the original?
connector.setAccepting(true);
uri = handler.exchange.exchange("delayed connection");
assertThat(uri,is("/three"));
response = HttpTester.parseResponse(in1);
assertThat(response.getStatus(),is(200));
assertThat(response.getContent(),is("delayed connection"));
}
}
}
}
public static class TestHandler extends AbstractHandler
{
final Exchanger<String> exchange = new Exchanger<>();
transient int handled;
public TestHandler()
{
}
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
try
{
String content = exchange.exchange(baseRequest.getRequestURI());
baseRequest.setHandled(true);
handled++;
response.setContentType("text/html;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().print(content);
}
catch (InterruptedException e)
{
throw new ServletException(e);
}
}
public int getHandled()
{
return handled;
}
}
}

View File

@ -53,12 +53,19 @@ public class ServerConnectorTimeoutTest extends ConnectorTimeoutTest
connector.setIdleTimeout(MAX_IDLE_TIME);
startServer(connector);
}
@Test(timeout=60000)
public void testStartStopStart() throws Exception
{
_server.stop();
_server.start();
}
@Test(timeout=60000)
public void testIdleTimeoutAfterSuspend() throws Exception
{
SuspendHandler _handler = new SuspendHandler();
_server.stop();
SuspendHandler _handler = new SuspendHandler();
SessionHandler session = new SessionHandler();
session.setHandler(_handler);
_server.setHandler(session);