Skip to content

Commit b3bc453

Browse files
committed
less garbage generation with Huffman decoding in http2
1 parent 0f78bef commit b3bc453

File tree

8 files changed

+178
-77
lines changed

8 files changed

+178
-77
lines changed

src/main/java/robaho/net/httpserver/OpenAddressMap.java

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,17 @@
33
import java.util.Arrays;
44
import java.util.function.BiConsumer;
55

6-
public class OpenAddressMap {
6+
public class OpenAddressMap<K,V> {
77

8-
private static class Entry {
8+
private static class Entry<K,V> {
9+
K key;
10+
V value;
911

10-
String key;
11-
Object value;
12-
13-
Entry(String key, Object value) {
12+
Entry(K key, V value) {
1413
this.key = key;
1514
this.value = value;
1615
}
1716
}
18-
1917
private int capacity;
2018
private int mask;
2119
private int size;
@@ -37,7 +35,7 @@ public OpenAddressMap(int capacity) {
3735
this.entries = new Entry[capacity];
3836
}
3937

40-
public Object put(String key, Object value) {
38+
public V put(K key, V value) {
4139
if(used>=capacity/2) {
4240
resize();
4341
}
@@ -53,7 +51,7 @@ public Object put(String key, Object value) {
5351
if (value == null) {
5452
size--;
5553
}
56-
return oldValue;
54+
return (V)oldValue;
5755
} else if (entry.value == null) {
5856
sentinel = index;
5957
}
@@ -72,7 +70,7 @@ public Object put(String key, Object value) {
7270

7371
private void resize() {
7472
OpenAddressMap newMap = new OpenAddressMap(capacity << 1);
75-
for (Entry entry : entries) {
73+
for (var entry : entries) {
7674
if (entry != null) {
7775
newMap.put(entry.key, entry.value);
7876
}
@@ -84,13 +82,13 @@ private void resize() {
8482
this.used = newMap.used;
8583
}
8684

87-
public Object get(String key) {
85+
public V get(K key) {
8886
int index = key.hashCode() & mask;
8987
int start = index;
9088
Entry entry;
9189
while ((entry = entries[index]) != null) {
9290
if (entry.key.equals(key)) {
93-
return entry.value;
91+
return (V)entry.value;
9492
}
9593
index = (index + 1) & mask;
9694
if(index==start) {
@@ -110,10 +108,10 @@ public void clear() {
110108
used=0;
111109
}
112110

113-
public void forEach(BiConsumer<String,Object> action) {
111+
public void forEach(BiConsumer<K,V> action) {
114112
for (Entry entry : entries) {
115113
if (entry != null && entry.value != null) {
116-
action.accept(entry.key,entry.value);
114+
action.accept((K)entry.key,(V)entry.value);
117115
}
118116
}
119117
}

src/main/java/robaho/net/httpserver/OptimizedHeaders.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import com.sun.net.httpserver.Headers;
1414

1515
public class OptimizedHeaders extends Headers {
16-
private final OpenAddressMap map;
16+
private final OpenAddressMap<String,Object> map;
1717
public OptimizedHeaders() {
1818
super();
1919
map = new OpenAddressMap(16);
@@ -83,8 +83,6 @@ private String normalize(String key) {
8383
}
8484
if(i==len) return key;
8585

86-
System.out.println("normalizing key: " + key);
87-
8886
char[] buffer = key.toCharArray();
8987
for(;i<len;i++) {
9088
char c = buffer[i];

src/main/java/robaho/net/httpserver/ServerImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ public void run() {
512512
http2.handle();
513513
} catch (HTTP2Exception ex) {
514514
logger.log(Level.WARNING, "ServerImpl http2 protocol exception "+http2, ex.getMessage());
515-
} catch (EOFException ex) {
515+
} catch (EOFException | SocketException ex) {
516516
logger.log(Level.DEBUG, "end of stream "+http2);
517517
} catch (Exception ex) {
518518
logger.log(Level.WARNING, "ServerImpl unexpected exception handling http2 connection "+http2, ex);

src/main/java/robaho/net/httpserver/http2/HTTP2Connection.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.util.EnumSet;
1212
import java.util.List;
1313
import java.util.concurrent.Executor;
14+
import java.util.concurrent.atomic.AtomicBoolean;
1415
import java.util.concurrent.atomic.AtomicInteger;
1516
import java.util.concurrent.atomic.AtomicLong;
1617
import java.util.concurrent.locks.Lock;
@@ -72,6 +73,7 @@ public class HTTP2Connection {
7273
private int highNumberStreams = 0;
7374

7475
private final Lock lock = new ReentrantLock();
76+
private AtomicBoolean closed = new AtomicBoolean(false);
7577

7678
/**
7779
* Constructor to instantiate HTTP2Connection object
@@ -108,8 +110,10 @@ public String toString() {
108110
}
109111

110112
public void close() {
111-
for (HTTP2Stream stream : http2Streams.values()) {
112-
stream.close();
113+
if(closed.compareAndSet(false,true)) {
114+
for (HTTP2Stream stream : http2Streams.values()) {
115+
stream.close();
116+
}
113117
}
114118
}
115119

@@ -160,7 +164,7 @@ public boolean hasProperPreface() throws IOException {
160164
}
161165

162166
public boolean isClosed() {
163-
return httpConnection.isClosed();
167+
return closed.get();
164168
}
165169

166170
public void handle() throws Exception {

src/main/java/robaho/net/httpserver/http2/HTTP2Stream.java

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import com.sun.net.httpserver.Headers;
1919

20+
import robaho.net.httpserver.NoSyncBufferedInputStream;
2021
import robaho.net.httpserver.NoSyncBufferedOutputStream;
2122
import robaho.net.httpserver.OptimizedHeaders;
2223
import robaho.net.httpserver.http2.hpack.HPackContext;
@@ -106,7 +107,7 @@ public void close() {
106107
} catch (IOException e) {
107108
if(!connection.isClosed()) {
108109
connection.close();
109-
logger.log(connection.httpConnection.requestCount.get()>0 ? Level.WARNING : Level.DEBUG,"IOException closing http2 stream",e);
110+
logger.log(connection.httpConnection.requestCount.get()>0 ? Level.WARNING : Level.DEBUG, "IOException closing http2 stream",e);
110111
}
111112
} finally {
112113
}
@@ -124,15 +125,14 @@ public void processFrame(BaseFrame frame) throws HTTP2Exception, IOException {
124125
break;
125126
case DATA:
126127
DataFrame dataFrame = (DataFrame) frame;
127-
logger.log(Level.TRACE,"received data frame, length "+dataFrame.body.length+" on stream "+streamId);
128+
logger.log(Level.TRACE,()->"received data frame, length "+dataFrame.body.length+" on stream "+streamId);
128129
if(halfClosed) {
129130
throw new HTTP2Exception(HTTP2ErrorCode.STREAM_CLOSED);
130131
}
131132
if(!streamOpen) {
132133
throw new HTTP2Exception(HTTP2ErrorCode.PROTOCOL_ERROR);
133134
}
134135
pipe.getOutputStream().write(dataFrame.body);
135-
logger.log(Level.TRACE,"wrote data frame to pipe, length "+dataFrame.body.length+" on stream "+streamId);
136136
dataInSize += dataFrame.body.length;
137137
if (dataFrame.getHeader().getFlags().contains(FrameFlag.END_STREAM)) {
138138
if(requestHeaders.containsKey("Content-length")) {
@@ -314,12 +314,13 @@ public void close() throws IOException {
314314

315315
// custom Pipe implementation since JDK version still uses synchronized methods which are not optimal for virtual threads
316316
private static class Pipe {
317-
private final CustomPipedInputStream inputStream;
317+
private final InputStream inputStream;
318318
private final CustomPipedOutputStream outputStream;
319319

320320
public Pipe() {
321-
this.inputStream = new CustomPipedInputStream();
322-
this.outputStream = new CustomPipedOutputStream(this.inputStream);
321+
var pipeIn = new CustomPipedInputStream();
322+
this.inputStream = new NoSyncBufferedInputStream(pipeIn);
323+
this.outputStream = new CustomPipedOutputStream(pipeIn);
323324
}
324325

325326
public InputStream getInputStream() {
@@ -349,8 +350,16 @@ private static class CustomPipedInputStream extends InputStream {
349350
private final Condition notEmpty = lock.newCondition();
350351
private final Condition notFull = lock.newCondition();
351352

353+
private final byte[] single = new byte[1];
354+
352355
@Override
353356
public int read() throws IOException {
357+
int n = read(single, 0, 1);
358+
return n == -1 ? -1 : single[0] & 0xFF;
359+
}
360+
361+
@Override
362+
public int read(byte[] b, int off, int len) throws IOException {
354363
lock.lock();
355364
try {
356365
while (readPos == writePos && !closed) {
@@ -363,30 +372,27 @@ public int read() throws IOException {
363372
if (closed && readPos == writePos) {
364373
return -1;
365374
}
366-
int result = buffer[readPos++] & 0xFF;
375+
376+
int available;
377+
if (readPos <= writePos) {
378+
available = writePos - readPos;
379+
} else {
380+
available = buffer.length - readPos;
381+
}
382+
383+
int bytesToRead = Math.min(len, available);
384+
System.arraycopy(buffer, readPos, b, off, bytesToRead);
385+
readPos += bytesToRead;
367386
if (readPos == buffer.length) {
368387
readPos = 0;
369388
}
370389
notFull.signal();
371-
return result;
390+
return bytesToRead;
372391
} finally {
373392
lock.unlock();
374393
}
375394
}
376395

377-
@Override
378-
public int read(byte[] b, int off, int len) throws IOException {
379-
int bytesRead = 0;
380-
while (bytesRead < len) {
381-
int byteRead = read();
382-
if (byteRead == -1) {
383-
return bytesRead == 0 ? -1 : bytesRead;
384-
}
385-
b[off + bytesRead++] = (byte) byteRead;
386-
}
387-
return bytesRead;
388-
}
389-
390396
@Override
391397
public void close() throws IOException {
392398
lock.lock();

0 commit comments

Comments
 (0)