This is an automated email from the ASF dual-hosted git repository.
Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new e562313ed1 perf(sync): avoid a full-payload byte allocation for the
websocket size log (#7836) (#7851)
e562313ed1 is described below
commit e562313ed1c2cadf51466eca514092324dc03519
Author: Daming <[email protected]>
AuthorDate: Tue Oct 20 11:55:46 2026 +0800
perf(sync): avoid a full-payload byte allocation for the websocket size log
(#7836) (#7851)
handleResult logged result.getBytes(StandardCharsets.UTF_8).length for its
INFO size
diagnostic on every received frame. Java evaluates arguments before
dispatching the
call, so a message-sized byte array was allocated on the sync hot path even
when INFO
was disabled.
Guard the diagnostic with LOG.isInfoEnabled() and measure the UTF-8 byte
length without
allocating: a new utf8ByteLength helper walks the string and mirrors
getBytes(UTF_8).length, including the single-byte '?' replacement of
unpaired
surrogates, so the logged size stays an honest UTF-8 byte count.
String.length() counts
UTF-16 code units and is not an equivalent byte count. A test asserts
parity with
getBytes(UTF_8) across ascii, CJK, emoji, mixed and unpaired-surrogate
samples.
This covers the "avoid full-size byte-array allocation solely for disabled
diagnostics"
acceptance item, which is deterministic and needs no benchmark. The larger
round-trip
removal (parse the envelope once and adapt directly to each group's typed
DTOs) and any
fingerprint-representation change are intentionally deferred: they alter
the DataHandler
contract across all group handlers and the deduplication identity, which
the issue gates
behind before/after measurements and fingerprint-equivalence tests. Sync
and dedup
behavior are unchanged here.
Co-authored-by: Luke Haochao Zhuang <[email protected]>
---
.../websocket/client/ShenyuWebsocketClient.java | 49 ++++++++++++++++++++--
.../client/ShenyuWebsocketClientTest.java | 27 ++++++++++++
2 files changed, 72 insertions(+), 4 deletions(-)
diff --git
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClient.java
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClient.java
index 663fe8b83e..5332c80935 100644
---
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClient.java
+++
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/main/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClient.java
@@ -50,7 +50,6 @@ import
org.apache.shenyu.common.concurrent.ShenyuThreadFactory;
import org.apache.shenyu.common.concurrent.ShenyuThreadPoolExecutor;
import java.net.URI;
-import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -502,9 +501,11 @@ public final class ShenyuWebsocketClient extends
WebSocketClient {
LOG.warn("Failed to parse websocket message from server[{}], the
message will be ignored", this.getURI(), ex);
return;
}
- LOG.info("server [{}] received config message: groupType: {},
eventType: {}, namespaceId: {}, size: {} bytes",
- this.getURI(), websocketData.getGroupType(),
websocketData.getEventType(),
- websocketData.getNamespaceId(),
result.getBytes(StandardCharsets.UTF_8).length);
+ if (LOG.isInfoEnabled()) {
+ LOG.info("server [{}] received config message: groupType: {},
eventType: {}, namespaceId: {}, size: {} bytes",
+ this.getURI(), websocketData.getGroupType(),
websocketData.getEventType(),
+ websocketData.getNamespaceId(), utf8ByteLength(result));
+ }
ConfigGroupEnum groupEnum;
String eventType;
String json;
@@ -542,6 +543,46 @@ public final class ShenyuWebsocketClient extends
WebSocketClient {
nextSyncRetryAt = 0;
}
+ /**
+ * Compute the UTF-8 encoded byte length of a string without allocating a
message-sized byte array.
+ * Mirrors {@code value.getBytes(StandardCharsets.UTF_8).length},
including the single-byte {@code '?'}
+ * replacement of unpaired surrogates, so the logged size stays an honest
UTF-8 byte count while avoiding
+ * a full-payload allocation on every received frame. {@code
String.length()} is not used because it counts
+ * UTF-16 code units, not UTF-8 bytes.
+ *
+ * @param value the string to measure
+ * @return the number of bytes the value occupies when encoded as UTF-8
+ */
+ static int utf8ByteLength(final String value) {
+ if (Objects.isNull(value)) {
+ return 0;
+ }
+ int bytes = 0;
+ int i = 0;
+ final int length = value.length();
+ while (i < length) {
+ final char c = value.charAt(i);
+ if (c <= 0x7F) {
+ bytes++;
+ i++;
+ } else if (c <= 0x7FF) {
+ bytes += 2;
+ i++;
+ } else if (Character.isHighSurrogate(c) && i + 1 < length &&
Character.isLowSurrogate(value.charAt(i + 1))) {
+ bytes += 4;
+ i += 2;
+ } else if (Character.isSurrogate(c)) {
+ // an unpaired surrogate is encoded as a single replacement
byte, matching getBytes(UTF_8)
+ bytes++;
+ i++;
+ } else {
+ bytes += 3;
+ i++;
+ }
+ }
+ return bytes;
+ }
+
/**
* Recover failed configuration application without abandoning the timer.
* @param ex application failure
diff --git
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClientTest.java
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClientTest.java
index 5409433f3f..aded076a7e 100644
---
a/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClientTest.java
+++
b/shenyu-sync-data-center/shenyu-sync-data-websocket/src/test/java/org/apache/shenyu/plugin/sync/data/websocket/client/ShenyuWebsocketClientTest.java
@@ -45,6 +45,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -806,4 +807,30 @@ public class ShenyuWebsocketClientTest {
verify(client, times(2)).close();
verify(client, never()).nowClose();
}
+
+ @Test
+ void testUtf8ByteLengthMatchesGetBytes() {
+ final String cjk = new String(Character.toChars(0x4E2D));
+ final String emoji = new String(Character.toChars(0x1F600));
+ final String accent = new String(Character.toChars(0x00E9));
+ final String loneHigh = String.valueOf((char) 0xD83D);
+ final String loneLow = String.valueOf((char) 0xDE00);
+ final String[] samples = {
+ "",
+ "abc",
+
"{\"groupType\":\"PLUGIN\",\"eventType\":\"REFRESH\",\"data\":[]}",
+ cjk + cjk + cjk,
+ "emoji " + emoji + " tail",
+ "mixed " + cjk + " " + emoji + " ascii " + accent,
+ loneHigh,
+ loneLow,
+ loneHigh + "abc",
+ };
+ for (String sample : samples) {
+ final int expected =
sample.getBytes(StandardCharsets.UTF_8).length;
+ assertEquals(expected,
ShenyuWebsocketClient.utf8ByteLength(sample),
+ () -> "utf8ByteLength mismatch for sample of " + expected
+ " bytes");
+ }
+ assertEquals(0, ShenyuWebsocketClient.utf8ByteLength(null));
+ }
}