View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.server.session;
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertTrue;
23  
24  import java.io.IOException;
25  import java.io.PrintWriter;
26  import java.util.Random;
27  import java.util.concurrent.CyclicBarrier;
28  import java.util.concurrent.ExecutorService;
29  import java.util.concurrent.Executors;
30  import java.util.concurrent.TimeUnit;
31  
32  import javax.servlet.ServletException;
33  import javax.servlet.http.HttpServlet;
34  import javax.servlet.http.HttpServletRequest;
35  import javax.servlet.http.HttpServletResponse;
36  import javax.servlet.http.HttpSession;
37  
38  import org.eclipse.jetty.client.HttpClient;
39  import org.eclipse.jetty.client.api.ContentResponse;
40  import org.eclipse.jetty.client.api.Request;
41  import org.junit.Test;
42  
43  
44  /**
45   * AbstractScatterGunLoadTest
46   * 
47   * This is an unrealistic test. It takes a scatter-gun approach to smearing a
48   * single session across 2 different nodes at once. 
49   * 
50   * In the real world, we must have a load balancer that uses sticky sessions
51   * to keep the session pinned to a particular node.
52   */
53  public abstract class AbstractScatterGunLoadTest
54  {
55      protected boolean _stress = Boolean.getBoolean( "STRESS" );
56  
57      public abstract AbstractTestServer createServer(int port);
58  
59      @Test
60      public void testLightLoad()
61          throws Exception
62      {
63          if ( _stress )
64          {
65              String contextPath = "";
66              String servletMapping = "/server";
67              AbstractTestServer server1 = createServer( 0 );
68              server1.addContext( contextPath ).addServlet( TestServlet.class, servletMapping );
69  
70              try
71              {
72                  server1.start();
73                  int port1 = server1.getPort();
74                  AbstractTestServer server2 = createServer( 0 );
75                  server2.addContext( contextPath ).addServlet( TestServlet.class, servletMapping );
76  
77                  try
78                  {
79                      server2.start();
80                      int port2=server2.getPort();
81                      HttpClient client = new HttpClient();
82                      client.start();
83                      try
84                      {
85                          String[] urls = new String[2];
86                          urls[0] = "http://localhost:" + port1 + contextPath + servletMapping;
87                          urls[1] = "http://localhost:" + port2 + contextPath + servletMapping;
88  
89                          //create session via first server
90                          ContentResponse response1 = client.GET(urls[0] + "?action=init");
91                          assertEquals(HttpServletResponse.SC_OK,response1.getStatus());
92                          String sessionCookie = response1.getHeaders().get( "Set-Cookie" );
93                          assertTrue(sessionCookie != null);
94                          // Mangle the cookie, replacing Path with $Path, etc.
95                          sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
96  
97                          //simulate 50 clients making 100 requests each
98                          ExecutorService executor = Executors.newCachedThreadPool();
99                          int clientsCount = 50;
100                         CyclicBarrier barrier = new CyclicBarrier( clientsCount + 1 );
101                         int requestsCount = 100;
102                         Worker[] workers = new Worker[clientsCount];
103                         for ( int i = 0; i < clientsCount; ++i )
104                         {
105                             workers[i] = new Worker( barrier, requestsCount, sessionCookie, urls );
106                             workers[i].start();
107                             executor.execute( workers[i] );
108                         }
109                         // Wait for all workers to be ready
110                         barrier.await();
111                         long start = System.nanoTime();
112 
113                         // Wait for all workers to be done
114                         barrier.await();
115                         long end = System.nanoTime();
116                         long elapsed = TimeUnit.NANOSECONDS.toMillis( end - start );
117                         System.out.println( "elapsed ms: " + elapsed );
118 
119                         for ( Worker worker : workers )
120                             worker.stop();
121                         executor.shutdownNow();
122 
123                         // Perform one request to get the result
124                         Request request = client.newRequest( urls[0] + "?action=result" );
125                         request.header("Cookie", sessionCookie);
126                         ContentResponse response2 = request.send();
127                         assertEquals(HttpServletResponse.SC_OK,response2.getStatus());
128                         String response = response2.getContentAsString();
129                         System.out.println( "get = " + response );
130                         assertEquals(response.trim(), String.valueOf( clientsCount * requestsCount ) );
131                     }
132                     finally
133                     {
134                         client.stop();
135                     }
136                 }
137                 finally
138                 {
139                     server2.stop();
140                 }
141             }
142             finally
143             {
144                 server1.stop();
145             }
146         }
147     }
148 
149     public static class Worker
150         implements Runnable
151     {
152         private final HttpClient client;
153 
154         private final CyclicBarrier barrier;
155 
156         private final int requestsCount;
157 
158         private final String sessionCookie;
159 
160         private final String[] urls;
161 
162 
163         public Worker( CyclicBarrier barrier, int requestsCount, String sessionCookie, String[] urls )
164         {
165             this.client = new HttpClient();
166             this.barrier = barrier;
167             this.requestsCount = requestsCount;
168             this.sessionCookie = sessionCookie;
169             this.urls = urls;
170         }
171 
172         public void start()
173             throws Exception
174         {
175             client.start();
176         }
177 
178         public void stop()
179             throws Exception
180         {
181             client.stop();
182         }
183 
184         public void run()
185         {
186             try
187             {
188                 // Wait for all workers to be ready
189                 barrier.await();
190 
191                 Random random = new Random( System.nanoTime() );
192 
193                 for ( int i = 0; i < requestsCount; ++i )
194                 {
195                     int urlIndex = random.nextInt( urls.length );
196                     Request request = client.newRequest(urls[urlIndex] + "?action=increment");
197                     request.header("Cookie", sessionCookie);
198                     ContentResponse response = request.send();
199                     assertEquals(HttpServletResponse.SC_OK,response.getStatus());
200                 }
201 
202                 // Wait for all workers to be done
203                 barrier.await();
204             }
205             catch ( Exception x )
206             {
207                 throw new RuntimeException( x );
208             }
209         }
210     }
211 
212     public static class TestServlet
213         extends HttpServlet
214     {
215         @Override
216         protected void doGet( HttpServletRequest request, HttpServletResponse response )
217             throws ServletException, IOException
218         {
219             String action = request.getParameter( "action" );
220             if ( "init".equals( action ) )
221             {
222                 HttpSession session = request.getSession( true );
223                 session.setAttribute( "value", 0 );
224             }
225             else if ( "increment".equals( action ) )
226             {
227                 // Without synchronization
228                 HttpSession session = request.getSession( false );
229                 int value = (Integer) session.getAttribute( "value" );
230                 session.setAttribute( "value", value + 1 );
231             }
232             else if ( "result".equals( action ) )
233             {
234                 HttpSession session = request.getSession( false );
235                 int value = (Integer) session.getAttribute( "value" );
236                 PrintWriter writer = response.getWriter();
237                 writer.println( value );
238                 writer.flush();
239             }
240         }
241     }
242 }