7157903: JSSE client sockets are very slow

Reviewed-by: xuelei
This commit is contained in:
Bradford Wetmore 2012-04-11 17:12:35 -07:00
parent 5017ee9fdb
commit 8716e4744b
4 changed files with 131 additions and 18 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -91,9 +91,20 @@ class AppOutputStream extends OutputStream {
// however they like; if we buffered here, they couldn't. // however they like; if we buffered here, they couldn't.
try { try {
do { do {
boolean holdRecord = false;
int howmuch; int howmuch;
if (isFirstRecordOfThePayload && c.needToSplitPayload()) { if (isFirstRecordOfThePayload && c.needToSplitPayload()) {
howmuch = Math.min(0x01, r.availableDataBytes()); howmuch = Math.min(0x01, r.availableDataBytes());
/*
* Nagle's algorithm (TCP_NODELAY) was coming into
* play here when writing short (split) packets.
* Signal to the OutputRecord code to internally
* buffer this small packet until the next outbound
* packet (of any type) is written.
*/
if ((len != 1) && (howmuch == 1)) {
holdRecord = true;
}
} else { } else {
howmuch = Math.min(len, r.availableDataBytes()); howmuch = Math.min(len, r.availableDataBytes());
} }
@ -108,7 +119,7 @@ class AppOutputStream extends OutputStream {
off += howmuch; off += howmuch;
len -= howmuch; len -= howmuch;
} }
c.writeRecord(r); c.writeRecord(r, holdRecord);
c.checkWrite(); c.checkWrite();
} while (len > 0); } while (len > 0);
} catch (Exception e) { } catch (Exception e) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -155,8 +155,9 @@ final class EngineOutputRecord extends OutputRecord {
* data to be generated/output before the exception is ever * data to be generated/output before the exception is ever
* generated. * generated.
*/ */
void writeBuffer(OutputStream s, byte [] buf, int off, int len) @Override
throws IOException { void writeBuffer(OutputStream s, byte [] buf, int off, int len,
int debugOffset) throws IOException {
/* /*
* Copy data out of buffer, it's ready to go. * Copy data out of buffer, it's ready to go.
*/ */
@ -196,7 +197,8 @@ final class EngineOutputRecord extends OutputRecord {
// compress(); // eventually // compress(); // eventually
addMAC(writeMAC); addMAC(writeMAC);
encrypt(writeCipher); encrypt(writeCipher);
write((OutputStream)null); // send down for processing write((OutputStream)null, false, // send down for processing
(ByteArrayOutputStream)null);
} }
return; return;
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -28,6 +28,7 @@ package sun.security.ssl;
import java.io.*; import java.io.*;
import java.nio.*; import java.nio.*;
import java.util.Arrays;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import sun.misc.HexDumpEncoder; import sun.misc.HexDumpEncoder;
@ -226,6 +227,24 @@ class OutputRecord extends ByteArrayOutputStream implements Record {
return maxDataSize - dataSize; return maxDataSize - dataSize;
} }
/*
* Increases the capacity if necessary to ensure that it can hold
* at least the number of elements specified by the minimum
* capacity argument.
*
* Note that the increased capacity is only can be used for held
* record buffer. Please DO NOT update the availableDataBytes()
* according to the expended buffer capacity.
*
* @see availableDataBytes()
*/
private void ensureCapacity(int minCapacity) {
// overflow-conscious code
if (minCapacity > buf.length) {
buf = Arrays.copyOf(buf, minCapacity);
}
}
/* /*
* Return the type of SSL record that's buffered here. * Return the type of SSL record that's buffered here.
*/ */
@ -243,7 +262,9 @@ class OutputRecord extends ByteArrayOutputStream implements Record {
* that synchronization be done elsewhere. Also, this does its work * that synchronization be done elsewhere. Also, this does its work
* in a single low level write, for efficiency. * in a single low level write, for efficiency.
*/ */
void write(OutputStream s) throws IOException { void write(OutputStream s, boolean holdRecord,
ByteArrayOutputStream heldRecordBuffer) throws IOException {
/* /*
* Don't emit content-free records. (Even change cipher spec * Don't emit content-free records. (Even change cipher spec
* messages have a byte of data!) * messages have a byte of data!)
@ -300,7 +321,49 @@ class OutputRecord extends ByteArrayOutputStream implements Record {
} }
firstMessage = false; firstMessage = false;
writeBuffer(s, buf, 0, count); /*
* The upper levels may want us to delay sending this packet so
* multiple TLS Records can be sent in one (or more) TCP packets.
* If so, add this packet to the heldRecordBuffer.
*
* NOTE: all writes have been synchronized by upper levels.
*/
int debugOffset = 0;
if (holdRecord) {
/*
* If holdRecord is true, we must have a heldRecordBuffer.
*
* Don't worry about the override of writeBuffer(), because
* when holdRecord is true, the implementation in this class
* will be used.
*/
writeBuffer(heldRecordBuffer, buf, 0, count, debugOffset);
} else {
// It's time to send, do we have buffered data?
// May or may not have a heldRecordBuffer.
if (heldRecordBuffer != null && heldRecordBuffer.size() > 0) {
int heldLen = heldRecordBuffer.size();
// Ensure the capacity of this buffer.
ensureCapacity(count + heldLen);
// Slide everything in the buffer to the right.
System.arraycopy(buf, 0, buf, heldLen, count);
// Prepend the held record to the buffer.
System.arraycopy(
heldRecordBuffer.toByteArray(), 0, buf, 0, heldLen);
count += heldLen;
// Clear the held buffer.
heldRecordBuffer.reset();
// The held buffer has been dumped, set the debug dump offset.
debugOffset = heldLen;
}
writeBuffer(s, buf, 0, count, debugOffset);
}
reset(); reset();
} }
@ -309,15 +372,17 @@ class OutputRecord extends ByteArrayOutputStream implements Record {
* we'll override this method and let it take the appropriate * we'll override this method and let it take the appropriate
* action. * action.
*/ */
void writeBuffer(OutputStream s, byte [] buf, int off, int len) void writeBuffer(OutputStream s, byte [] buf, int off, int len,
throws IOException { int debugOffset) throws IOException {
s.write(buf, off, len); s.write(buf, off, len);
s.flush(); s.flush();
// Output only the record from the specified debug offset.
if (debug != null && Debug.isOn("packet")) { if (debug != null && Debug.isOn("packet")) {
try { try {
HexDumpEncoder hd = new HexDumpEncoder(); HexDumpEncoder hd = new HexDumpEncoder();
ByteBuffer bb = ByteBuffer.wrap(buf, off, len); ByteBuffer bb = ByteBuffer.wrap(
buf, off + debugOffset, len - debugOffset);
System.out.println("[Raw write]: length = " + System.out.println("[Raw write]: length = " +
bb.remaining()); bb.remaining());

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -374,6 +374,12 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl {
*/ */
private boolean isFirstAppOutputRecord = true; private boolean isFirstAppOutputRecord = true;
/*
* If AppOutputStream needs to delay writes of small packets, we
* will use this to store the data until we actually do the write.
*/
private ByteArrayOutputStream heldRecordBuffer = null;
// //
// CONSTRUCTORS AND INITIALIZATION CODE // CONSTRUCTORS AND INITIALIZATION CODE
// //
@ -653,6 +659,17 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl {
// READING AND WRITING RECORDS // READING AND WRITING RECORDS
// //
/*
* AppOutputStream calls may need to buffer multiple outbound
* application packets.
*
* All other writeRecord() calls will not buffer, so do not hold
* these records.
*/
void writeRecord(OutputRecord r) throws IOException {
writeRecord(r, false);
}
/* /*
* Record Output. Application data can't be sent until the first * Record Output. Application data can't be sent until the first
* handshake establishes a session. * handshake establishes a session.
@ -660,7 +677,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl {
* NOTE: we let empty records be written as a hook to force some * NOTE: we let empty records be written as a hook to force some
* TCP-level activity, notably handshaking, to occur. * TCP-level activity, notably handshaking, to occur.
*/ */
void writeRecord(OutputRecord r) throws IOException { void writeRecord(OutputRecord r, boolean holdRecord) throws IOException {
/* /*
* The loop is in case of HANDSHAKE --> ERROR transitions, etc * The loop is in case of HANDSHAKE --> ERROR transitions, etc
*/ */
@ -731,7 +748,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl {
try { try {
if (writeLock.tryLock(getSoLinger(), TimeUnit.SECONDS)) { if (writeLock.tryLock(getSoLinger(), TimeUnit.SECONDS)) {
try { try {
writeRecordInternal(r); writeRecordInternal(r, holdRecord);
} finally { } finally {
writeLock.unlock(); writeLock.unlock();
} }
@ -779,7 +796,7 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl {
} else { } else {
writeLock.lock(); writeLock.lock();
try { try {
writeRecordInternal(r); writeRecordInternal(r, holdRecord);
} finally { } finally {
writeLock.unlock(); writeLock.unlock();
} }
@ -787,11 +804,29 @@ final public class SSLSocketImpl extends BaseSSLSocketImpl {
} }
} }
private void writeRecordInternal(OutputRecord r) throws IOException { private void writeRecordInternal(OutputRecord r,
boolean holdRecord) throws IOException {
// r.compress(c); // r.compress(c);
r.addMAC(writeMAC); r.addMAC(writeMAC);
r.encrypt(writeCipher); r.encrypt(writeCipher);
r.write(sockOutput);
if (holdRecord) {
// If we were requested to delay the record due to possibility
// of Nagle's being active when finally got to writing, and
// it's actually not, we don't really need to delay it.
if (getTcpNoDelay()) {
holdRecord = false;
} else {
// We need to hold the record, so let's provide
// a per-socket place to do it.
if (heldRecordBuffer == null) {
// Likely only need 37 bytes.
heldRecordBuffer = new ByteArrayOutputStream(40);
}
}
}
r.write(sockOutput, holdRecord, heldRecordBuffer);
/* /*
* Check the sequence number state * Check the sequence number state