Skip to content

Commit 1bf232d

Browse files
committed
Refactor AbstractNestedMatcher to use per-instance buffer
- Replace ThreadLocal buffer with a per-instance reusable buffer - Improves memory locality and reduces ThreadLocal overhead - Update Javadoc for clarity, performance notes, and subclassing guidance Closes gh-34651 Signed-off-by: Nabil Fawwaz Elqayyim <[email protected]>
1 parent bcfae82 commit 1bf232d

File tree

1 file changed

+26
-37
lines changed

1 file changed

+26
-37
lines changed

spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
*
6262
* @author Arjen Poutsma
6363
* @author Brian Clozel
64+
* @author Nabil Fawwaz Elqayyim
6465
* @since 5.0
6566
*/
6667
public abstract class DataBufferUtils {
@@ -859,23 +860,17 @@ public void reset() {
859860

860861

861862
/**
862-
* An abstract base implementation of {@link NestedMatcher} that looks for
863-
* a specific delimiter in a {@link DataBuffer}.
864-
* <p>
865-
* Uses a thread-local buffer to scan data in chunks, reducing memory
866-
* allocations and improving performance when processing large buffers.
867-
* </p>
863+
* Base {@link NestedMatcher} implementation that scans a {@link DataBuffer}
864+
* for a specific delimiter.
868865
*
869-
* <p>
870-
* Each matcher keeps its own match state, so it is intended for
871-
* single-threaded use. The thread-local buffer ensures that multiple
872-
* threads can run their own matchers independently without interfering.
873-
* </p>
866+
* <p>Relies on a per-instance reusable buffer to scan data in chunks,
867+
* minimizing allocations and improving performance for large or streaming data.</p>
874868
*
875-
* <p>
876-
* Subclasses can extend this class to add custom matching behavior while
877-
* reusing the built-in delimiter tracking and scanning logic.
878-
* </p>
869+
* <p>Each matcher maintains its own state and buffer, ensuring safe use
870+
* in reactive pipelines where execution may shift across threads.</p>
871+
*
872+
* <p>Subclasses may extend this class to customize matching strategies
873+
* while reusing the built-in delimiter tracking and scanning logic.</p>
879874
*
880875
* @see NestedMatcher
881876
* @see DataBuffer
@@ -886,8 +881,7 @@ private abstract static class AbstractNestedMatcher implements NestedMatcher {
886881

887882
private int matches = 0;
888883

889-
// Thread-local chunk buffer to avoid per-call allocations
890-
private static final ThreadLocal<byte[]> LOCAL_BUFFER = ThreadLocal.withInitial(() -> new byte[8 * 1024]);
884+
private final byte[] localBuffer = new byte[8 * 1024]; // Reusable buffer per matcher instance
891885

892886
protected AbstractNestedMatcher(byte[] delimiter) {
893887
this.delimiter = delimiter;
@@ -901,33 +895,29 @@ protected int getMatches() {
901895
return this.matches;
902896
}
903897

904-
protected static void releaseLocalBuffer() {
905-
LOCAL_BUFFER.remove();
906-
}
907-
908898
@Override
909899
public int match(DataBuffer dataBuffer) {
910900
final int readPos = dataBuffer.readPosition();
911901
final int writePos = dataBuffer.writePosition();
912902
final int length = writePos - readPos;
913903

914-
final byte[] delimiter0 = this.delimiter;
915-
final int delimiterLen = delimiter0.length;
916-
final byte delimiter1 = delimiter0[0];
904+
final byte[] delimiterBytes = this.delimiter;
905+
final int delimiterLength = delimiterBytes.length;
906+
final byte delimiterFirstByte = delimiterBytes[0];
917907

918-
int matchIndex = this.matches;
919-
920-
final byte[] chunk = LOCAL_BUFFER.get();
908+
final byte[] chunk = localBuffer;
921909
final int chunkSize = Math.min(chunk.length, length);
922910

911+
int matchIndex = this.matches;
912+
923913
try {
924914
for (int offset = 0; offset < length; offset += chunkSize) {
925915
int currentChunkSize = Math.min(chunkSize, length - offset);
926916

927917
dataBuffer.readPosition(readPos + offset);
928918
dataBuffer.read(chunk, 0, currentChunkSize);
929919

930-
matchIndex = processChunk(chunk, currentChunkSize, delimiter0, delimiterLen, delimiter1, matchIndex, readPos, offset);
920+
matchIndex = processChunk(chunk, currentChunkSize, delimiterBytes, delimiterLength, delimiterFirstByte, matchIndex, readPos, offset);
931921
if (matchIndex < 0) {
932922
return -(matchIndex + 1); // found, returning actual position
933923
}
@@ -938,21 +928,20 @@ public int match(DataBuffer dataBuffer) {
938928
}
939929
finally {
940930
dataBuffer.readPosition(readPos); // restore original position
941-
releaseLocalBuffer();
942931
}
943932
}
944933

945-
private int processChunk(byte[] chunk, int currentChunkSize, byte[] delimiter0, int delimiterLen, byte delimiter1, int matchIndex, int readPos, int offset) {
934+
private int processChunk(byte[] chunk, int currentChunkSize, byte[] delimiterBytes, int delimiterLen, byte delimiterFirstByte, int matchIndex, int readPos, int offset) {
946935
int i = 0;
947936
while (i < currentChunkSize) {
948937
if (matchIndex == 0) {
949-
i = findNextCandidate(chunk, i, currentChunkSize, delimiter1);
938+
i = findNextCandidate(chunk, i, currentChunkSize, delimiterFirstByte);
950939
if (i >= currentChunkSize) {
951940
return matchIndex; // no candidate in this chunk
952941
}
953942
}
954943

955-
matchIndex = updateMatchIndex(chunk[i], delimiter0, delimiterLen, delimiter1, matchIndex);
944+
matchIndex = updateMatchIndex(chunk[i], delimiterBytes, delimiterLen, delimiterFirstByte, matchIndex);
956945
if (matchIndex == -1) {
957946
return -(readPos + offset + i + 1); // return found delimiter position (encoded as negative)
958947
}
@@ -961,24 +950,24 @@ private int processChunk(byte[] chunk, int currentChunkSize, byte[] delimiter0,
961950
return matchIndex;
962951
}
963952

964-
private int findNextCandidate(byte[] chunk, int start, int limit, byte delimiter1) {
953+
private int findNextCandidate(byte[] chunk, int start, int limit, byte delimiterFirstByte) {
965954
int j = start;
966-
while (j < limit && chunk[j] != delimiter1) {
955+
while (j < limit && chunk[j] != delimiterFirstByte) {
967956
j++;
968957
}
969958
return j;
970959
}
971960

972-
private int updateMatchIndex(byte b, byte[] delimiter0, int delimiterLen, byte delimiter1, int matchIndex) {
973-
if (b == delimiter0[matchIndex]) {
961+
private int updateMatchIndex(byte b, byte[] delimiterBytes, int delimiterLen, byte delimiterFirstByte, int matchIndex) {
962+
if (b == delimiterBytes[matchIndex]) {
974963
matchIndex++;
975964
if (matchIndex == delimiterLen) {
976965
reset();
977966
return -1;
978967
}
979968
}
980969
else {
981-
matchIndex = (b == delimiter1) ? 1 : 0;
970+
matchIndex = (b == delimiterFirstByte) ? 1 : 0;
982971
}
983972
return matchIndex;
984973
}

0 commit comments

Comments
 (0)