8164147: Improve streaming socket output

Reviewed-by: chegar, igerasim
This commit is contained in:
Mark Sheppard 2016-09-13 11:59:56 +01:00
parent 6e132741b6
commit 3463ee94d8
4 changed files with 90 additions and 80 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1995, 2016, 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,11 +155,12 @@ class SocketInputStream extends FileInputStream
} }
// bounds check // bounds check
if (length <= 0 || off < 0 || off + length > b.length) { if (length <= 0 || off < 0 || length > b.length - off) {
if (length == 0) { if (length == 0) {
return 0; return 0;
} }
throw new ArrayIndexOutOfBoundsException(); throw new ArrayIndexOutOfBoundsException("length == " + length
+ " off == " + off + " buffer length == " + b.length);
} }
boolean gotReset = false; boolean gotReset = false;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1995, 2016, 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
@ -97,11 +97,13 @@ class SocketOutputStream extends FileOutputStream
*/ */
private void socketWrite(byte b[], int off, int len) throws IOException { private void socketWrite(byte b[], int off, int len) throws IOException {
if (len <= 0 || off < 0 || off + len > b.length) {
if (len <= 0 || off < 0 || len > b.length - off) {
if (len == 0) { if (len == 0) {
return; return;
} }
throw new ArrayIndexOutOfBoundsException(); throw new ArrayIndexOutOfBoundsException("len == " + len
+ " off == " + off + " buffer length == " + b.length);
} }
FileDescriptor fd = impl.acquireFD(); FileDescriptor fd = impl.acquireFD();

View File

@ -98,27 +98,31 @@ Java_java_net_SocketOutputStream_socketWrite0(JNIEnv *env, jobject this,
int llen = chunkLen; int llen = chunkLen;
(*env)->GetByteArrayRegion(env, data, off, chunkLen, (jbyte *)bufP); (*env)->GetByteArrayRegion(env, data, off, chunkLen, (jbyte *)bufP);
while(llen > 0) { if ((*env)->ExceptionCheck(env)) {
int n = NET_Send(fd, bufP + loff, llen, 0); break;
if (n > 0) { } else {
llen -= n; while(llen > 0) {
loff += n; int n = NET_Send(fd, bufP + loff, llen, 0);
continue; if (n > 0) {
llen -= n;
loff += n;
continue;
}
if (errno == ECONNRESET) {
JNU_ThrowByName(env, "sun/net/ConnectionResetException",
"Connection reset");
} else {
JNU_ThrowByNameWithMessageAndLastError
(env, "java/net/SocketException", "Write failed");
}
if (bufP != BUF) {
free(bufP);
}
return;
} }
if (errno == ECONNRESET) { len -= chunkLen;
JNU_ThrowByName(env, "sun/net/ConnectionResetException", off += chunkLen;
"Connection reset");
} else {
JNU_ThrowByNameWithMessageAndLastError
(env, "java/net/SocketException", "Write failed");
}
if (bufP != BUF) {
free(bufP);
}
return;
} }
len -= chunkLen;
off += chunkLen;
} }
if (bufP != BUF) { if (bufP != BUF) {

View File

@ -92,66 +92,69 @@ Java_java_net_SocketOutputStream_socketWrite0(JNIEnv *env, jobject this,
int retry = 0; int retry = 0;
(*env)->GetByteArrayRegion(env, data, off, chunkLen, (jbyte *)bufP); (*env)->GetByteArrayRegion(env, data, off, chunkLen, (jbyte *)bufP);
if ((*env)->ExceptionCheck(env)) {
while(llen > 0) { break;
int n = send(fd, bufP + loff, llen, 0); } else {
if (n > 0) { while(llen > 0) {
llen -= n; int n = send(fd, bufP + loff, llen, 0);
loff += n; if (n > 0) {
continue; llen -= n;
} loff += n;
/*
* Due to a bug in Windows Sockets (observed on NT and Windows
* 2000) it may be necessary to retry the send. The issue is that
* on blocking sockets send/WSASend is supposed to block if there
* is insufficient buffer space available. If there are a large
* number of threads blocked on write due to congestion then it's
* possile to hit the NT/2000 bug whereby send returns WSAENOBUFS.
* The workaround we use is to retry the send. If we have a
* large buffer to send (>2k) then we retry with a maximum of
* 2k buffer. If we hit the issue with <=2k buffer then we backoff
* for 1 second and retry again. We repeat this up to a reasonable
* limit before bailing out and throwing an exception. In load
* conditions we've observed that the send will succeed after 2-3
* attempts but this depends on network buffers associated with
* other sockets draining.
*/
if (WSAGetLastError() == WSAENOBUFS) {
if (llen > MAX_BUFFER_LEN) {
buflen = MAX_BUFFER_LEN;
chunkLen = MAX_BUFFER_LEN;
llen = MAX_BUFFER_LEN;
continue; continue;
} }
if (retry >= 30) {
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
"No buffer space available - exhausted attempts to queue buffer");
if (bufP != BUF) {
free(bufP);
}
return;
}
Sleep(1000);
retry++;
continue;
}
/* /*
* Send failed - can be caused by close or write error. * Due to a bug in Windows Sockets (observed on NT and Windows
*/ * 2000) it may be necessary to retry the send. The issue is that
if (WSAGetLastError() == WSAENOTSOCK) { * on blocking sockets send/WSASend is supposed to block if there
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); * is insufficient buffer space available. If there are a large
} else { * number of threads blocked on write due to congestion then it's
NET_ThrowCurrent(env, "socket write error"); * possile to hit the NT/2000 bug whereby send returns WSAENOBUFS.
* The workaround we use is to retry the send. If we have a
* large buffer to send (>2k) then we retry with a maximum of
* 2k buffer. If we hit the issue with <=2k buffer then we backoff
* for 1 second and retry again. We repeat this up to a reasonable
* limit before bailing out and throwing an exception. In load
* conditions we've observed that the send will succeed after 2-3
* attempts but this depends on network buffers associated with
* other sockets draining.
*/
if (WSAGetLastError() == WSAENOBUFS) {
if (llen > MAX_BUFFER_LEN) {
buflen = MAX_BUFFER_LEN;
chunkLen = MAX_BUFFER_LEN;
llen = MAX_BUFFER_LEN;
continue;
}
if (retry >= 30) {
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException",
"No buffer space available - exhausted attempts to queue buffer");
if (bufP != BUF) {
free(bufP);
}
return;
}
Sleep(1000);
retry++;
continue;
}
/*
* Send failed - can be caused by close or write error.
*/
if (WSAGetLastError() == WSAENOTSOCK) {
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed");
} else {
NET_ThrowCurrent(env, "socket write error");
}
if (bufP != BUF) {
free(bufP);
}
return;
} }
if (bufP != BUF) { len -= chunkLen;
free(bufP); off += chunkLen;
}
return;
} }
len -= chunkLen;
off += chunkLen;
} }
if (bufP != BUF) { if (bufP != BUF) {