330210 Improve performance of writing large bytes arrays

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/trunk@2507 7e9141cc-0065-0410-87d8-b60c137991c4
This commit is contained in:
Greg Wilkins 2010-11-15 11:57:53 +00:00
parent 3c5625032a
commit 57a8287da7
12 changed files with 256 additions and 79 deletions

View File

@ -1,6 +1,7 @@
jetty-7.2.2-SNAPSHOT
+ 330188 Reject web-fragment.xml with same <name> as another already loaded one
+ 330210 Improve performance of writing large bytes arrays
+ 330208 Support new wording on servlet-mapping and filter-mapping merging from servlet3.0a
+ 330188 Reject web-fragment.xml with same <name> as another already loaded one
jetty-7.2.1.v20101111 11 November 2010
+ 324679 Fixed dedection of write before static content

View File

@ -31,7 +31,6 @@ public class MonitoredDirAppProviderStartupTest
@BeforeClass
public static void setupEnvironment() throws Exception
{
Log.getLog().setDebugEnabled(true);
jetty = new XmlConfiguredJetty();
jetty.addConfiguration("jetty.xml");
jetty.addConfiguration("jetty-deploymgr-contexts.xml");

View File

@ -69,9 +69,9 @@ public abstract class HttpBuffers extends AbstractLifeCycle
public HttpBuffers()
{
super();
_requestBuffers.setBufferSize(8*1024);
_requestBuffers.setBufferSize(12*1024);
_requestBuffers.setHeaderSize(6*1024);
_responseBuffers.setBufferSize(12*1024);
_responseBuffers.setBufferSize(32*1024);
_responseBuffers.setHeaderSize(6*1024);
}

View File

@ -184,10 +184,8 @@ public class HttpGenerator extends AbstractGenerator
content.clear();
_content=null;
}
else if (_endp != null && _buffer == null && content.length() > 0 && _last)
else if (_endp != null && (_buffer==null || _buffer.length()==0) && _content.length() > 0 && (_last || isCommitted() && _content.length()>1024))
{
// TODO - use bypass in more cases.
// Make _content a direct buffer
_bypass = true;
}
else if (!_bufferChunked)
@ -851,7 +849,8 @@ public class HttpGenerator extends AbstractGenerator
len = _endp.flush(_header);
break;
case 3:
throw new IllegalStateException(); // should never happen!
len = _endp.flush(_buffer, _content, null);
break;
case 2:
len = _endp.flush(_buffer);
break;
@ -882,13 +881,13 @@ public class HttpGenerator extends AbstractGenerator
{
_buffer.put(_content);
_content.clear();
_content = null;
_content=null;
}
}
}
// Are we completely finished for now?
if (!_needCRLF && !_needEOC && (_content == null || _content.length() == 0))
if (!_needCRLF && !_needEOC && (_content==null || _content.length()==0))
{
if (_state == STATE_FLUSHING)
_state = STATE_END;
@ -899,7 +898,7 @@ public class HttpGenerator extends AbstractGenerator
// Try to prepare more to write.
prepareBuffers();
}
}
}
if (len > 0)
total+=len;
@ -920,57 +919,80 @@ public class HttpGenerator extends AbstractGenerator
if (!_bufferChunked)
{
// Refill buffer if possible
if (_content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0)
if (!_bypass && _content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0)
{
int len = _buffer.put(_content);
_content.skip(len);
if (_content.length() == 0)
_content = null;
}
// Chunk buffer if need be
if (_contentLength == HttpTokens.CHUNKED_CONTENT)
{
int size = _buffer == null ? 0 : _buffer.length();
if (size > 0)
if ((_buffer==null||_buffer.length()==0) && _content!=null)
{
// Prepare a chunk!
// this is a bypass write
int size = _content.length();
_bufferChunked = true;
// Did we leave space at the start of the buffer.
//noinspection ConstantConditions
if (_buffer.getIndex() == CHUNK_SPACE)
// if we need CRLF add this to header
if (_needCRLF)
{
// Oh yes, goodie! let's use it then!
_buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
_buffer.setGetIndex(_buffer.getIndex() - 2);
BufferUtil.prependHexInt(_buffer, size);
if (_header.length() > 0) throw new IllegalStateException("EOC");
_header.put(HttpTokens.CRLF);
_needCRLF = false;
}
// Add the chunk size to the header
BufferUtil.putHexInt(_header, size);
_header.put(HttpTokens.CRLF);
// Need a CRLF after the content
_needCRLF=true;
}
else if (_buffer!=null)
{
int size = _buffer.length();
if (size > 0)
{
// Prepare a chunk!
_bufferChunked = true;
if (_needCRLF)
// Did we leave space at the start of the buffer.
//noinspection ConstantConditions
if (_buffer.getIndex() == CHUNK_SPACE)
{
// Oh yes, goodie! let's use it then!
_buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
_buffer.setGetIndex(_buffer.getIndex() - 2);
_needCRLF = false;
}
}
else
{
// No space so lets use the header buffer.
if (_needCRLF)
{
if (_header.length() > 0) throw new IllegalStateException("EOC");
_header.put(HttpTokens.CRLF);
_needCRLF = false;
}
BufferUtil.putHexInt(_header, size);
_header.put(HttpTokens.CRLF);
}
BufferUtil.prependHexInt(_buffer, size);
// Add end chunk trailer.
if (_buffer.space() >= 2)
_buffer.put(HttpTokens.CRLF);
else
_needCRLF = true;
if (_needCRLF)
{
_buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
_buffer.setGetIndex(_buffer.getIndex() - 2);
_needCRLF = false;
}
}
else
{
// No space so lets use the header buffer.
if (_needCRLF)
{
if (_header.length() > 0) throw new IllegalStateException("EOC");
_header.put(HttpTokens.CRLF);
_needCRLF = false;
}
BufferUtil.putHexInt(_header, size);
_header.put(HttpTokens.CRLF);
}
// Add end chunk trailer.
if (_buffer.space() >= 2)
_buffer.put(HttpTokens.CRLF);
else
_needCRLF = true;
}
}
// If we need EOC and everything written

View File

@ -256,6 +256,8 @@ public class HttpGeneratorClientTest
hb.completeHeader(fields, Generator.LAST);
}
hb.complete();
while(!hb.isComplete())
hb.flushBuffer();
}
@Override

View File

@ -109,7 +109,7 @@ public class HttpGeneratorTest
catch(IOException e)
{
if (tr[r].body!=null)
throw e;
throw new Exception(t,e);
continue;
}
@ -190,6 +190,9 @@ public class HttpGeneratorTest
hb.completeHeader(fields, Generator.LAST);
}
hb.complete();
while(!hb.isComplete())
hb.flushBuffer();
}
@Override

View File

@ -46,7 +46,7 @@ import org.eclipse.jetty.util.thread.Timeout.Task;
public abstract class SelectorManager extends AbstractLifeCycle
{
// TODO Tune these by approx system speed.
private static final int __JVMBUG_THRESHHOLD=Integer.getInteger("org.eclipse.jetty.io.nio.JVMBUG_THRESHHOLD",512).intValue();
private static final int __JVMBUG_THRESHHOLD=Integer.getInteger("org.eclipse.jetty.io.nio.JVMBUG_THRESHHOLD",0).intValue();
private static final int __MONITOR_PERIOD=Integer.getInteger("org.eclipse.jetty.io.nio.MONITOR_PERIOD",1000).intValue();
private static final int __MAX_SELECTS=Integer.getInteger("org.eclipse.jetty.io.nio.MAX_SELECTS",25000).intValue();
private static final int __BUSY_PAUSE=Integer.getInteger("org.eclipse.jetty.io.nio.BUSY_PAUSE",50).intValue();
@ -482,7 +482,9 @@ public abstract class SelectorManager extends AbstractLifeCycle
_selects++;
now = System.currentTimeMillis();
_timeout.setNow(now);
checkJvmBugs(before, now, wait, selected);
if (__JVMBUG_THRESHHOLD>0)
checkJvmBugs(before, now, wait, selected);
}
}

View File

@ -38,7 +38,6 @@ public class HttpOutput extends ServletOutputStream
{
protected final AbstractGenerator _generator;
protected final long _maxIdleTime;
protected final ByteArrayBuffer _buf = new ByteArrayBuffer(AbstractGenerator.NO_BYTES);
private boolean _closed;
// These are held here for reuse by Writer
@ -94,9 +93,7 @@ public class HttpOutput extends ServletOutputStream
@Override
public void write(byte[] b, int off, int len) throws IOException
{
_buf.wrap(b, off, len);
write(_buf);
_buf.wrap(AbstractGenerator.NO_BYTES);
write(new ByteArrayBuffer(b,off,len));
}
/* ------------------------------------------------------------ */
@ -106,9 +103,7 @@ public class HttpOutput extends ServletOutputStream
@Override
public void write(byte[] b) throws IOException
{
_buf.wrap(b);
write(_buf);
_buf.wrap(AbstractGenerator.NO_BYTES);
write(new ByteArrayBuffer(b));
}
/* ------------------------------------------------------------ */
@ -167,18 +162,20 @@ public class HttpOutput extends ServletOutputStream
_generator.addContent(buffer, Generator.MORE);
// Have to flush and complete headers?
if (_generator.isBufferFull())
flush();
if (_generator.isAllContentWritten())
{
flush();
close();
}
}
else if (_generator.isBufferFull())
flush();
// Block until our buffer is free
while (buffer.length() > 0 && _generator.isOpen())
{
_generator.blockForOutput(_maxIdleTime);
}
}
/* ------------------------------------------------------------ */

View File

@ -29,6 +29,7 @@ import org.eclipse.jetty.http.HttpHeaders;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.log.StdErrLog;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@ -148,31 +149,39 @@ public class HttpConnectionTest
@Test
public void testBad() throws Exception
{
String response=connector.getResponses("GET & HTTP/1.1\n"+
"Host: localhost\n"+
"\015\012");
checkContains(response,0,"HTTP/1.1 400");
try
{
((StdErrLog)Log.getLog()).setHideStacks(true);
response=connector.getResponses("GET http://localhost:WRONG/ HTTP/1.1\n"+
"Host: localhost\n"+
"\015\012");
checkContains(response,0,"HTTP/1.1 400");
String response=connector.getResponses("GET & HTTP/1.1\n"+
"Host: localhost\n"+
"\015\012");
checkContains(response,0,"HTTP/1.1 400");
response=connector.getResponses("GET /foo/bar%1 HTTP/1.1\n"+
"Host: localhost\n"+
"\015\012");
checkContains(response,0,"HTTP/1.1 400");
response=connector.getResponses("GET http://localhost:WRONG/ HTTP/1.1\n"+
"Host: localhost\n"+
"\015\012");
checkContains(response,0,"HTTP/1.1 400");
response=connector.getResponses("GET /foo/bar%c0%00 HTTP/1.1\n"+
"Host: localhost\n"+
"\015\012");
checkContains(response,0,"pathInfo=/foo/bar?");
response=connector.getResponses("GET /bad/encoding%1 HTTP/1.1\n"+
"Host: localhost\n"+
"\015\012");
checkContains(response,0,"HTTP/1.1 400");
response=connector.getResponses("GET /foo/bar%c1 HTTP/1.1\n"+
"Host: localhost\n"+
"\015\012");
checkContains(response,0,"HTTP/1.1 400");
response=connector.getResponses("GET /foo/bar%c0%00 HTTP/1.1\n"+
"Host: localhost\n"+
"\015\012");
checkContains(response,0,"pathInfo=/foo/bar?");
response=connector.getResponses("GET /bad/utf8%c1 HTTP/1.1\n"+
"Host: localhost\n"+
"\015\012");
checkContains(response,0,"HTTP/1.1 400");
}
finally
{
((StdErrLog)Log.getLog()).setHideStacks(false);
}
}
@Test

View File

@ -32,11 +32,15 @@ import java.util.Arrays;
import java.util.Random;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import junit.framework.Assert;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.TypeUtil;
import org.junit.Test;
/**
@ -212,7 +216,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
// Read the response
String response=readResponse(client);
// Check the response
assertEquals("response "+i,RESPONSE2,response);
}
@ -435,6 +439,137 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
client.close();
}
}
@Test
public void testBigBlocks() throws Exception
{
configureServer(new BigBlockHandler());
Socket client=newSocket(HOST,_connector.getLocalPort());
client.setSoTimeout(10000);
try
{
OutputStream os=client.getOutputStream();
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
os.write((
"GET / HTTP/1.1\r\n"+
"host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
"\r\n"+
"GET / HTTP/1.1\r\n"+
"host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
"connection: close\r\n"+
"\r\n"
).getBytes());
os.flush();
// read the chunked response header
boolean chunked=false;
boolean closed=false;
while(true)
{
String line=in.readLine();
if (line==null || line.length()==0)
break;
chunked|="Transfer-Encoding: chunked".equals(line);
closed|="Connection: close".equals(line);
}
Assert.assertTrue(chunked);
Assert.assertFalse(closed);
// Read the chunks
int max=Integer.MIN_VALUE;
while(true)
{
String chunk=in.readLine();
String line=in.readLine();
if (line.length()==0)
break;
int len=line.length();
Assert.assertEquals(Integer.valueOf(chunk,16).intValue(),len);
if (max<len)
max=len;
}
// Check that a direct content buffer was used as a chunk
Assert.assertEquals(1024*1024,max);
// read and check the times are < 900ms
String[] times=in.readLine().split(",");
for (String t:times)
Assert.assertTrue(Integer.valueOf(t).intValue()<900);
// read the EOF chunk
String end=in.readLine();
Assert.assertEquals("0",end);
end=in.readLine();
Assert.assertEquals(0,end.length());
// read the non-chunked response header
chunked=false;
closed=false;
while(true)
{
String line=in.readLine();
if (line==null || line.length()==0)
break;
chunked|="Transfer-Encoding: chunked".equals(line);
closed|="Connection: close".equals(line);
}
Assert.assertFalse(chunked);
Assert.assertTrue(closed);
String bigline = in.readLine();
Assert.assertEquals(10*1024*1024,bigline.length());
// read and check the times are < 900ms
times=in.readLine().split(",");
for (String t:times)
Assert.assertTrue(Integer.valueOf(t).intValue()<900);
// check close
Assert.assertTrue(in.readLine()==null);
}
finally
{
client.close();
}
}
protected static class BigBlockHandler extends AbstractHandler
{
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
byte[] buf = new byte[1024*1024];
for (int i=0;i<buf.length;i++)
buf[i]=(byte)("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_".charAt(i%63));
baseRequest.setHandled(true);
response.setStatus(200);
response.setContentType("text/plain");
ServletOutputStream out=response.getOutputStream();
long[] times=new long[10];
for (int i=0;i<times.length;i++)
{
long start=System.currentTimeMillis();
out.write(buf);
long end=System.currentTimeMillis();
times[i]=end-start;
}
out.println();
for (long t : times)
{
out.print(t);
out.print(",");
}
out.close();
}
}
@Test
public void testPipeline() throws Exception
@ -792,4 +927,5 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
Thread.sleep(PAUSE);
}
}

View File

@ -25,4 +25,10 @@ public class SelectChannelServerTest extends HttpServerTestBase
{
startServer(new SelectChannelConnector());
}
@Override
public void testBigBlocks() throws Exception
{
super.testBigBlocks();
}
}

View File

@ -123,7 +123,7 @@ public class QueuedThreadPoolTest
Thread.sleep(1100);
int threads=tp.getThreads();
assertTrue(threads<10);
Thread.sleep(1100);
Thread.sleep(2000);
assertTrue(tp.getThreads()<threads);
for (int i=9;i<jobs.length;i++)