// ======================================================================== // Copyright (c) 2004-2009 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.mortbay.jetty; import java.io.IOException; import java.io.InputStream; import java.net.Inet4Address; import java.net.InetAddress; import java.net.Socket; import java.util.Random; import java.util.Timer; import java.util.TimerTask; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.mortbay.jetty.handler.AbstractHandler; import org.mortbay.jetty.handler.HandlerWrapper; import org.mortbay.jetty.nio.SelectChannelConnector; import org.mortbay.log.Log; import org.mortbay.thread.QueuedThreadPool; import org.mortbay.util.IO; import org.mortbay.util.StringUtil; import org.mortbay.util.ajax.Continuation; import org.mortbay.util.ajax.ContinuationSupport; import junit.framework.TestCase; public class AsyncStressTest extends TestCase { protected Server _server = new Server(); protected SuspendHandler _handler = new SuspendHandler(); protected SelectChannelConnector _connector; protected InetAddress _addr; protected int _port; protected Random _random = new Random(); protected int[] _loops; protected QueuedThreadPool _threads=new QueuedThreadPool(); protected boolean _stress; protected void setUp() throws Exception { _stress= Boolean.getBoolean("STRESS"); _threads.setMaxThreads(50); if (_stress) _threads.setMaxThreads(200); _server.setThreadPool(_threads); _connector = new SelectChannelConnector(); _server.setConnectors(new Connector[]{ _connector }); _server.setHandler(_handler); _server.start(); _port=_connector.getLocalPort(); _addr=Inet4Address.getLocalHost(); } protected void tearDown() throws Exception { _server.stop(); } final static String[][] __tests = { {"/path","NORMAL"}, {"/path?sleep=<TIMEOUT>","SLEPT"}, {"/path?suspend=<TIMEOUT>","TIMEOUT"}, {"/path?suspend=1000&resume=<TIMEOUT>","RESUMED"}, {"/path?suspend=1000&complete=<TIMEOUT>","COMPLETED"}, }; public void doPaths(String name) throws Exception { for (int i=0;i<__tests.length;i++) { int timeout = _random.nextInt(200)+1; String uri=StringUtil.replace(__tests[i][0],"<TIMEOUT>",Integer.toString(timeout)); long start=System.currentTimeMillis(); Socket socket = new Socket(_addr,_port); socket.setSoTimeout(30000); String request = "GET "+uri+" HTTP/1.0\r\n\r\n"; socket.getOutputStream().write(request.getBytes()); socket.getOutputStream().flush(); String response = IO.toString(socket.getInputStream()); socket.close(); long end=System.currentTimeMillis(); response=response.substring(response.indexOf("\r\n\r\n")+4); String test=name+"-"+i+" "+uri+" "+__tests[i][1]; assertEquals(test,__tests[i][1],response); if (!response.equals("NORMAL")) { long duration=end-start; assertTrue(test+" "+duration,duration+50>=timeout); } } } public void doLoops(int thread, String name, int loops) throws Exception { try { for (int i=0;i<loops;i++) { _loops[thread]=i; doPaths(name+"-"+i); Thread.sleep(_random.nextInt(100)); } _loops[thread]=loops; } catch(Exception e) { _loops[thread]=-_loops[thread]; throw e; } } public void doThreads(int threads,final int loops) throws Throwable { final Throwable[] throwable=new Throwable[threads]; final Thread[] thread=new Thread[threads]; for (int i=0;i<threads;i++) { final int id=i; final String name = "T"+i; thread[i]=new Thread() { public void run() { try { doLoops(id,name,loops); } catch(Throwable th) { th.printStackTrace(); throwable[id]=th; } finally { } } }; } _loops=new int[threads]; for (int i=0;i<threads;i++) thread[i].start(); while(true) { Thread.sleep(1000L); int finished=0; int errors=0; int min=loops; int max=0; int total=0; for (int i=0;i<threads;i++) { int l=_loops[i]; if (l<0) { errors++; total-=l; } else { if (l<min) min=l; if (l>max) max=l; total+=l; if (l==loops) finished++; } } Log.info("min/ave/max/target="+min+"/"+(total/threads)+"/"+max+"/"+loops+" errors/finished/loops="+errors+"/"+finished+"/"+threads+" idle/threads="+_threads.getIdleThreads()+"/"+_threads.getThreads()); if ((finished+errors)==threads) break; } for (int i=0;i<threads;i++) thread[i].join(); for (int i=0;i<threads;i++) if (throwable[i]!=null) throw throwable[i]; } public void testAsync() throws Throwable { if (_stress) { System.err.println("STRESS! "); doThreads(40,100); } else doThreads(20,20); Thread.sleep(1000); } private static class SuspendHandler extends AbstractHandler { private Timer _timer; public SuspendHandler() { _timer=new Timer(); } public void handle(String target, HttpServletRequest request, final HttpServletResponse response, int dispatch) throws IOException, ServletException { final Request base_request = (request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest(); int read_before=0; long sleep_for=-1; long suspend_for=-1; long resume_after=-1; long complete_after=-1; if (request.getParameter("read")!=null) read_before=Integer.parseInt(request.getParameter("read")); if (request.getParameter("sleep")!=null) sleep_for=Integer.parseInt(request.getParameter("sleep")); if (request.getParameter("suspend")!=null) suspend_for=Integer.parseInt(request.getParameter("suspend")); if (request.getParameter("resume")!=null) resume_after=Integer.parseInt(request.getParameter("resume")); if (request.getParameter("complete")!=null) complete_after=Integer.parseInt(request.getParameter("complete")); final Continuation continuation = ContinuationSupport.getContinuation(request,this); if (!(continuation.isPending() || continuation.isResumed())) { if (read_before>0) { byte[] buf=new byte[read_before]; request.getInputStream().read(buf); } else if (read_before<0) { InputStream in = request.getInputStream(); int b=in.read(); while(b!=-1) b=in.read(); } if (suspend_for>=0) { try { if (suspend_for>0) continuation.suspend(suspend_for); else continuation.suspend(100L); } finally { if (complete_after>0) { TimerTask complete = new TimerTask() { public void run() { try { response.setStatus(200); response.getOutputStream().print("COMPLETED"); response.flushBuffer(); continuation.resume(); } catch(Exception e) { e.printStackTrace(); } } }; synchronized (_timer) { _timer.schedule(complete,complete_after); } } else if (complete_after==0) { response.setStatus(200); response.getOutputStream().print("COMPLETED"); response.flushBuffer(); continuation.resume(); } if (resume_after>0) { TimerTask resume = new TimerTask() { public void run() { continuation.resume(); } }; synchronized (_timer) { _timer.schedule(resume,resume_after); } } else if (resume_after==0) { continuation.resume(); } } } else if (sleep_for>=0) { try { Thread.sleep(sleep_for); } catch (InterruptedException e) { e.printStackTrace(); } response.setStatus(200); response.getOutputStream().print("SLEPT"); base_request.setHandled(true); return; } else { response.setStatus(200); response.getOutputStream().print("NORMAL"); base_request.setHandled(true); return; } } else if (response.isCommitted()) { base_request.setHandled(true); } else if (continuation.isResumed()) { response.setStatus(200); response.getOutputStream().print("RESUMED"); base_request.setHandled(true); } else { response.setStatus(200); response.getOutputStream().print("TIMEOUT"); base_request.setHandled(true); } } } }