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:
parent
3c5625032a
commit
57a8287da7
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -256,6 +256,8 @@ public class HttpGeneratorClientTest
|
|||
hb.completeHeader(fields, Generator.LAST);
|
||||
}
|
||||
hb.complete();
|
||||
while(!hb.isComplete())
|
||||
hb.flushBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -25,4 +25,10 @@ public class SelectChannelServerTest extends HttpServerTestBase
|
|||
{
|
||||
startServer(new SelectChannelConnector());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testBigBlocks() throws Exception
|
||||
{
|
||||
super.testBigBlocks();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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++)
|
||||
|
|
Loading…
Reference in New Issue