[tomcat] branch 9.0.x updated: Refactor to reduce pinning in HTTP/2 code when using virtual threads

2023-07-27 Thread markt
This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/9.0.x by this push:
 new dc8f0d1b3c Refactor to reduce pinning in HTTP/2 code when using 
virtual threads
dc8f0d1b3c is described below

commit dc8f0d1b3c3d50caf84ee60b3a487928216ea432
Author: Mark Thomas 
AuthorDate: Thu Jul 27 15:37:59 2023 +0100

Refactor to reduce pinning in HTTP/2 code when using virtual threads
---
 java/org/apache/coyote/http2/AbstractStream.java   |  84 ---
 .../apache/coyote/http2/Http2UpgradeHandler.java   | 241 +++--
 java/org/apache/coyote/http2/RecycledStream.java   |   1 -
 java/org/apache/coyote/http2/Stream.java   |  84 +++
 .../coyote/http2/WindowAllocationManager.java  |  27 ++-
 5 files changed, 250 insertions(+), 187 deletions(-)

diff --git a/java/org/apache/coyote/http2/AbstractStream.java 
b/java/org/apache/coyote/http2/AbstractStream.java
index f332b8c593..d6fb8d8280 100644
--- a/java/org/apache/coyote/http2/AbstractStream.java
+++ b/java/org/apache/coyote/http2/AbstractStream.java
@@ -16,6 +16,10 @@
  */
 package org.apache.coyote.http2;
 
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.res.StringManager;
@@ -33,6 +37,8 @@ abstract class AbstractStream {
 private final String idAsString;
 
 private long windowSize = 
ConnectionSettingsBase.DEFAULT_INITIAL_WINDOW_SIZE;
+protected final Lock windowAllocationLock = new ReentrantLock();
+protected final Condition windowAllocationAvailable = 
windowAllocationLock.newCondition();
 
 private volatile int connectionAllocationRequested = 0;
 private volatile int connectionAllocationMade = 0;
@@ -59,13 +65,23 @@ abstract class AbstractStream {
 }
 
 
-final synchronized void setWindowSize(long windowSize) {
-this.windowSize = windowSize;
+final void setWindowSize(long windowSize) {
+windowAllocationLock.lock();
+try {
+this.windowSize = windowSize;
+} finally {
+windowAllocationLock.unlock();
+}
 }
 
 
-final synchronized long getWindowSize() {
-return windowSize;
+final long getWindowSize() {
+windowAllocationLock.lock();
+try {
+return windowSize;
+} finally {
+windowAllocationLock.unlock();
+}
 }
 
 
@@ -76,37 +92,47 @@ abstract class AbstractStream {
  *
  * @throws Http2Exception If the window size is now higher than the 
maximum allowed
  */
-synchronized void incrementWindowSize(int increment) throws Http2Exception 
{
-// No need for overflow protection here.
-// Increment can't be more than Integer.MAX_VALUE and once windowSize
-// goes beyond 2^31-1 an error is triggered.
-windowSize += increment;
-
-if (log.isDebugEnabled()) {
-log.debug(sm.getString("abstractStream.windowSizeInc", 
getConnectionId(), getIdAsString(),
-Integer.toString(increment), Long.toString(windowSize)));
-}
+void incrementWindowSize(int increment) throws Http2Exception {
+windowAllocationLock.lock();
+try {
+// No need for overflow protection here.
+// Increment can't be more than Integer.MAX_VALUE and once 
windowSize
+// goes beyond 2^31-1 an error is triggered.
+windowSize += increment;
+
+if (log.isDebugEnabled()) {
+log.debug(sm.getString("abstractStream.windowSizeInc", 
getConnectionId(), getIdAsString(),
+Integer.toString(increment), 
Long.toString(windowSize)));
+}
 
-if (windowSize > ConnectionSettingsBase.MAX_WINDOW_SIZE) {
-String msg = sm.getString("abstractStream.windowSizeTooBig", 
getConnectionId(), identifier,
-Integer.toString(increment), Long.toString(windowSize));
-if (identifier.intValue() == 0) {
-throw new ConnectionException(msg, 
Http2Error.FLOW_CONTROL_ERROR);
-} else {
-throw new StreamException(msg, Http2Error.FLOW_CONTROL_ERROR, 
identifier.intValue());
+if (windowSize > ConnectionSettingsBase.MAX_WINDOW_SIZE) {
+String msg = sm.getString("abstractStream.windowSizeTooBig", 
getConnectionId(), identifier,
+Integer.toString(increment), 
Long.toString(windowSize));
+if (identifier.intValue() == 0) {
+throw new ConnectionException(msg, 
Http2Error.FLOW_CONTROL_ERROR);
+} else {
+throw new StreamException(msg, 
Http2Error.FLOW_CONTROL_ERROR, identi

[tomcat] branch 9.0.x updated: Refactor to reduce pinning in HTTP/2 code when using virtual threads

2023-07-26 Thread markt
This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/9.0.x by this push:
 new e74bda808a Refactor to reduce pinning in HTTP/2 code when using 
virtual threads
e74bda808a is described below

commit e74bda808aa043b09790135f53d7de692cc3e1c3
Author: Mark Thomas 
AuthorDate: Wed Jul 26 14:38:37 2023 +0100

Refactor to reduce pinning in HTTP/2 code when using virtual threads
---
 java/org/apache/coyote/http2/Stream.java | 90 +---
 1 file changed, 60 insertions(+), 30 deletions(-)

diff --git a/java/org/apache/coyote/http2/Stream.java 
b/java/org/apache/coyote/http2/Stream.java
index bce6c33559..915fda02ac 100644
--- a/java/org/apache/coyote/http2/Stream.java
+++ b/java/org/apache/coyote/http2/Stream.java
@@ -1153,6 +1153,7 @@ class Stream extends AbstractNonZeroStream implements 
HeaderEmitter {
 
 class StandardStreamInputBuffer extends StreamInputBuffer {
 
+private final Lock readStateLock = new ReentrantLock();
 /*
  * Two buffers are required to avoid various multi-threading issues. 
These issues arise from the fact that the
  * Stream (or the Request/Response) used by the application is 
processed in one thread but the connection is
@@ -1263,7 +1264,8 @@ class Stream extends AbstractNonZeroStream implements 
HeaderEmitter {
 final boolean isReadyForRead() {
 ensureBuffersExist();
 
-synchronized (this) {
+readStateLock.lock();
+try {
 if (available() > 0) {
 return true;
 }
@@ -1273,21 +1275,33 @@ class Stream extends AbstractNonZeroStream implements 
HeaderEmitter {
 }
 
 return false;
+} finally {
+readStateLock.unlock();
 }
 }
 
 @Override
-final synchronized boolean isRequestBodyFullyRead() {
-return (inBuffer == null || inBuffer.position() == 0) && 
isInputFinished();
+final boolean isRequestBodyFullyRead() {
+readStateLock.lock();
+try {
+return (inBuffer == null || inBuffer.position() == 0) && 
isInputFinished();
+} finally {
+readStateLock.unlock();
+}
 }
 
 
 @Override
-public final synchronized int available() {
-if (inBuffer == null) {
-return 0;
+public final int available() {
+readStateLock.lock();
+try {
+if (inBuffer == null) {
+return 0;
+}
+return inBuffer.position();
+} finally {
+readStateLock.unlock();
 }
-return inBuffer.position();
 }
 
 
@@ -1295,26 +1309,31 @@ class Stream extends AbstractNonZeroStream implements 
HeaderEmitter {
  * Called after placing some data in the inBuffer.
  */
 @Override
-final synchronized void onDataAvailable() throws IOException {
-if (closed) {
-swallowUnread();
-} else if (readInterest) {
-if (log.isDebugEnabled()) {
-log.debug(sm.getString("stream.inputBuffer.dispatch"));
-}
-readInterest = false;
-coyoteRequest.action(ActionCode.DISPATCH_READ, null);
-// Always need to dispatch since this thread is processing
-// the incoming connection and streams are processed on their
-// own.
-coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
-} else {
-if (log.isDebugEnabled()) {
-log.debug(sm.getString("stream.inputBuffer.signal"));
-}
-synchronized (inBuffer) {
-inBuffer.notifyAll();
+final void onDataAvailable() throws IOException {
+readStateLock.lock();
+try {
+if (closed) {
+swallowUnread();
+} else if (readInterest) {
+if (log.isDebugEnabled()) {
+log.debug(sm.getString("stream.inputBuffer.dispatch"));
+}
+readInterest = false;
+coyoteRequest.action(ActionCode.DISPATCH_READ, null);
+// Always need to dispatch since this thread is processing
+// the incoming connection and streams are processed on 
their
+// own.
+coyoteRequest.action(ActionCode.DISPATCH_EXECUTE, null);
+} else {
+if (log.isDebugEnabled()) {
+log.debug(sm.getString("stream.inputBuffer.signal"));
+   

[tomcat] branch 9.0.x updated: Refactor to reduce pinning in HTTP/2 code when using virtual threads

2023-07-26 Thread markt
This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/9.0.x by this push:
 new 50f627546f Refactor to reduce pinning in HTTP/2 code when using 
virtual threads
50f627546f is described below

commit 50f627546f085ae64aff4c3e4465d4cd797a685c
Author: Mark Thomas 
AuthorDate: Wed Jul 26 13:23:39 2023 +0100

Refactor to reduce pinning in HTTP/2 code when using virtual threads
---
 .../apache/coyote/http2/Http2AsyncUpgradeHandler.java| 16 
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java 
b/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java
index 1b86d0da05..d9f69464f6 100644
--- a/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java
+++ b/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java
@@ -26,6 +26,8 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 import javax.servlet.http.WebConnection;
 
@@ -44,9 +46,9 @@ public class Http2AsyncUpgradeHandler extends 
Http2UpgradeHandler {
 // Ensures headers are generated and then written for one thread at a time.
 // Because of the compression used, headers need to be written to the
 // network in the same order they are generated.
-private final Object headerWriteLock = new Object();
+private final Lock headerWriteLock = new ReentrantLock();
 // Ensures thread triggers the stream reset is the first to send a RST 
frame
-private final Object sendResetLock = new Object();
+private final Lock sendResetLock = new ReentrantLock();
 private final AtomicReference error = new AtomicReference<>();
 private final AtomicReference applicationIOE = new 
AtomicReference<>();
 
@@ -148,7 +150,8 @@ public class Http2AsyncUpgradeHandler extends 
Http2UpgradeHandler {
 // may see out of order RST frames which may hard to follow if
 // the client is unaware the RST frames may be received out of
 // order.
-synchronized (sendResetLock) {
+sendResetLock.lock();
+try {
 if (state != null) {
 boolean active = state.isActive();
 state.sendReset();
@@ -159,6 +162,8 @@ public class Http2AsyncUpgradeHandler extends 
Http2UpgradeHandler {
 
 socketWrapper.write(BlockingMode.SEMI_BLOCK, 
protocol.getWriteTimeout(), TimeUnit.MILLISECONDS, null,
 SocketWrapperBase.COMPLETE_WRITE, errorCompletion, 
ByteBuffer.wrap(rstFrame));
+} finally {
+sendResetLock.unlock();
 }
 handleAsyncException();
 }
@@ -191,7 +196,8 @@ public class Http2AsyncUpgradeHandler extends 
Http2UpgradeHandler {
 @Override
 void writeHeaders(Stream stream, int pushedStreamId, MimeHeaders 
mimeHeaders, boolean endOfStream, int payloadSize)
 throws IOException {
-synchronized (headerWriteLock) {
+headerWriteLock.lock();
+try {
 AsyncHeaderFrameBuffers headerFrameBuffers = 
(AsyncHeaderFrameBuffers) doWriteHeaders(stream,
 pushedStreamId, mimeHeaders, endOfStream, payloadSize);
 if (headerFrameBuffers != null) {
@@ -200,6 +206,8 @@ public class Http2AsyncUpgradeHandler extends 
Http2UpgradeHandler {
 headerFrameBuffers.bufs.toArray(BYTEBUFFER_ARRAY));
 handleAsyncException();
 }
+} finally {
+headerWriteLock.unlock();
 }
 if (endOfStream) {
 sentEndOfStream(stream);


-
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org



[tomcat] branch 9.0.x updated: Refactor to reduce pinning in HTTP/2 code when using virtual threads

2023-07-26 Thread markt
This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/9.0.x by this push:
 new acf7bc0d20 Refactor to reduce pinning in HTTP/2 code when using 
virtual threads
acf7bc0d20 is described below

commit acf7bc0d20954f809cf2fb7a13afb6372d82f1f4
Author: Mark Thomas 
AuthorDate: Wed Jul 26 12:36:15 2023 +0100

Refactor to reduce pinning in HTTP/2 code when using virtual threads
---
 java/org/apache/coyote/http2/Stream.java | 244 +--
 1 file changed, 136 insertions(+), 108 deletions(-)

diff --git a/java/org/apache/coyote/http2/Stream.java 
b/java/org/apache/coyote/http2/Stream.java
index 4a770b54e4..bce6c33559 100644
--- a/java/org/apache/coyote/http2/Stream.java
+++ b/java/org/apache/coyote/http2/Stream.java
@@ -28,6 +28,8 @@ import java.util.HashSet;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.Supplier;
 
 import org.apache.coyote.ActionCode;
@@ -911,6 +913,7 @@ class Stream extends AbstractNonZeroStream implements 
HeaderEmitter {
 
 class StreamOutputBuffer implements HttpOutputBuffer, WriteBuffer.Sink {
 
+private final Lock writeLock = new ReentrantLock();
 private final ByteBuffer buffer = ByteBuffer.allocate(8 * 1024);
 private final WriteBuffer writeBuffer = new WriteBuffer(32 * 1024);
 // Flag that indicates that data was left over on a previous
@@ -929,125 +932,145 @@ class Stream extends AbstractNonZeroStream implements 
HeaderEmitter {
  */
 
 @Override
-public final synchronized int doWrite(ByteBuffer chunk) throws 
IOException {
-if (closed) {
-throw new IOException(sm.getString("stream.closed", 
getConnectionId(), getIdAsString()));
-}
-// chunk is always fully written
-int result = chunk.remaining();
-if (writeBuffer.isEmpty()) {
-int chunkLimit = chunk.limit();
-while (chunk.remaining() > 0) {
-int thisTime = Math.min(buffer.remaining(), 
chunk.remaining());
-chunk.limit(chunk.position() + thisTime);
-buffer.put(chunk);
-chunk.limit(chunkLimit);
-if (chunk.remaining() > 0 && !buffer.hasRemaining()) {
-// Only flush if we have more data to write and the 
buffer
-// is full
-if (flush(true, coyoteResponse.getWriteListener() == 
null)) {
-writeBuffer.add(chunk);
-dataLeft = true;
-break;
+public final int doWrite(ByteBuffer chunk) throws IOException {
+writeLock.lock();
+try {
+if (closed) {
+throw new IOException(sm.getString("stream.closed", 
getConnectionId(), getIdAsString()));
+}
+// chunk is always fully written
+int result = chunk.remaining();
+if (writeBuffer.isEmpty()) {
+int chunkLimit = chunk.limit();
+while (chunk.remaining() > 0) {
+int thisTime = Math.min(buffer.remaining(), 
chunk.remaining());
+chunk.limit(chunk.position() + thisTime);
+buffer.put(chunk);
+chunk.limit(chunkLimit);
+if (chunk.remaining() > 0 && !buffer.hasRemaining()) {
+// Only flush if we have more data to write and 
the buffer
+// is full
+if (flush(true, coyoteResponse.getWriteListener() 
== null)) {
+writeBuffer.add(chunk);
+dataLeft = true;
+break;
+}
 }
 }
+} else {
+writeBuffer.add(chunk);
 }
-} else {
-writeBuffer.add(chunk);
+written += result;
+return result;
+} finally {
+writeLock.unlock();
 }
-written += result;
-return result;
 }
 
-final synchronized boolean flush(boolean block) throws IOException {
-/*
- * Need to ensure that there is exactly one call to flush even 
when there is no data to write. Too few calls
- * (i.e. zero) and the end of stream message is not sent for a 
completed asynchronous write. Too many calls
- * and the end of stream message is sent too soon and tra

[tomcat] branch 9.0.x updated: Refactor to reduce pinning in HTTP/2 code when using virtual threads

2023-07-26 Thread markt
This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/9.0.x by this push:
 new 56ba62e2fc Refactor to reduce pinning in HTTP/2 code when using 
virtual threads
56ba62e2fc is described below

commit 56ba62e2fc60c550a8b90a54452f78683a682ea0
Author: Mark Thomas 
AuthorDate: Wed Jul 26 10:34:38 2023 +0100

Refactor to reduce pinning in HTTP/2 code when using virtual threads
---
 java/org/apache/coyote/http2/StreamProcessor.java | 10 --
 webapps/docs/changelog.xml|  4 
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/java/org/apache/coyote/http2/StreamProcessor.java 
b/java/org/apache/coyote/http2/StreamProcessor.java
index 4b06ff9b71..8d8af36852 100644
--- a/java/org/apache/coyote/http2/StreamProcessor.java
+++ b/java/org/apache/coyote/http2/StreamProcessor.java
@@ -22,6 +22,8 @@ import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 import javax.servlet.http.HttpServletResponse;
 
@@ -55,6 +57,7 @@ class StreamProcessor extends AbstractProcessor {
 
 private static final Set H2_PSEUDO_HEADERS_REQUEST = new 
HashSet<>();
 
+private final Lock processLock = new ReentrantLock();
 private final Http2UpgradeHandler handler;
 private final Stream stream;
 private SendfileData sendfileData = null;
@@ -77,8 +80,9 @@ class StreamProcessor extends AbstractProcessor {
 
 final void process(SocketEvent event) {
 try {
-// FIXME: the regular processor syncs on socketWrapper, but here 
this deadlocks
-synchronized (this) {
+// Note: The regular processor uses the socketWrapper lock, but 
using that here triggers a deadlock
+processLock.lock();
+try {
 // HTTP/2 equivalent of AbstractConnectionHandler#process() 
without the
 // socket <-> processor mapping
 SocketState state = SocketState.CLOSED;
@@ -134,6 +138,8 @@ class StreamProcessor extends AbstractProcessor {
 recycle();
 }
 }
+} finally {
+processLock.unlock();
 }
 } finally {
 handler.executeQueuedStream();
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 3d7f7256a8..f8b8f3f95e 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -143,6 +143,10 @@
 certificateKeystoreFile attribute of an
 SSLHostConfigCertificate instance. (markt)
   
+  
+Refactor HTTP/2 implementation to reduce pinning when using virtual
+threads. (markt)
+  
 
   
   


-
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org