wang-jiahua commented on code in PR #10444:
URL: https://github.com/apache/rocketmq/pull/10444#discussion_r3373794792


##########
common/src/main/java/org/apache/rocketmq/common/message/MessageDecoder.java:
##########
@@ -602,64 +602,373 @@ public static List<MessageExt> decodes(ByteBuffer 
byteBuffer, final boolean read
         return msgExts;
     }
 
+    private static final ThreadLocal<StringBuilder> REUSABLE_SB =
+        ThreadLocal.withInitial(() -> new StringBuilder(256));
+
     public static String messageProperties2String(Map<String, String> 
properties) {
         if (properties == null) {
             return "";
         }
-        int len = 0;
+        StringBuilder sb = REUSABLE_SB.get();
+        sb.setLength(0);
+        for (final Map.Entry<String, String> entry : properties.entrySet()) {
+            final String name = entry.getKey();
+            final String value = entry.getValue();
+
+            if (value == null) {
+                continue;
+            }
+            sb.append(name);
+            sb.append(NAME_VALUE_SEPARATOR);
+            sb.append(value);
+            sb.append(PROPERTY_SEPARATOR);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * UTF-8 byte serialization of properties, mirror of {@link 
#messageProperties2String} but
+     * skipping the StringBuilder + String + String.getBytes() round-trip on 
the broker write
+     * hot path. Output bytes are identical to {@code 
messageProperties2String(properties).getBytes(UTF_8)}.
+     * Returns null for null/empty maps (encoders treat null and 0-length 
identically).
+     */
+    public static byte[] messageProperties2Bytes(Map<String, String> 
properties) {
+        if (properties == null || properties.isEmpty()) {
+            return null;
+        }

Review Comment:
   Fixed: the Javadoc no longer claims byte-for-byte equivalence with 
`messageProperties2String(...).getBytes(UTF_8)` for null/empty input. It now 
explicitly states the method returns `null` for null/empty maps (encoders treat 
null and 0-length identically) and that callers needing a 0-length array should 
check for null. Added `testMessageProperties2BytesNullAndEmpty`.



##########
common/src/main/java/org/apache/rocketmq/common/message/MessageDecoder.java:
##########
@@ -602,64 +602,373 @@ public static List<MessageExt> decodes(ByteBuffer 
byteBuffer, final boolean read
         return msgExts;
     }
 
+    private static final ThreadLocal<StringBuilder> REUSABLE_SB =
+        ThreadLocal.withInitial(() -> new StringBuilder(256));
+
     public static String messageProperties2String(Map<String, String> 
properties) {
         if (properties == null) {
             return "";
         }
-        int len = 0;
+        StringBuilder sb = REUSABLE_SB.get();
+        sb.setLength(0);
+        for (final Map.Entry<String, String> entry : properties.entrySet()) {
+            final String name = entry.getKey();
+            final String value = entry.getValue();
+
+            if (value == null) {
+                continue;
+            }
+            sb.append(name);
+            sb.append(NAME_VALUE_SEPARATOR);
+            sb.append(value);
+            sb.append(PROPERTY_SEPARATOR);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * UTF-8 byte serialization of properties, mirror of {@link 
#messageProperties2String} but
+     * skipping the StringBuilder + String + String.getBytes() round-trip on 
the broker write
+     * hot path. Output bytes are identical to {@code 
messageProperties2String(properties).getBytes(UTF_8)}.
+     * Returns null for null/empty maps (encoders treat null and 0-length 
identically).
+     */
+    public static byte[] messageProperties2Bytes(Map<String, String> 
properties) {
+        if (properties == null || properties.isEmpty()) {
+            return null;
+        }
+        int totalLen = 0;
         for (final Map.Entry<String, String> entry : properties.entrySet()) {
             final String name = entry.getKey();
             final String value = entry.getValue();
             if (value == null) {
                 continue;
             }
             if (name != null) {
-                len += name.length();
+                totalLen += utf8ByteLength(name);
             }
-            len += value.length();
-            len += 2; // separator
+            totalLen += utf8ByteLength(value);
+            totalLen += 2;
+        }
+        if (totalLen == 0) {
+            return null;
         }
-        StringBuilder sb = new StringBuilder(len);
+        byte[] out = new byte[totalLen];
+        int idx = 0;
         for (final Map.Entry<String, String> entry : properties.entrySet()) {
             final String name = entry.getKey();
             final String value = entry.getValue();
+            if (value == null) {
+                continue;
+            }
+            if (name != null) {
+                idx = writeUtf8(name, out, idx);
+            }
+            out[idx++] = (byte) NAME_VALUE_SEPARATOR;
+            idx = writeUtf8(value, out, idx);
+            out[idx++] = (byte) PROPERTY_SEPARATOR;

Review Comment:
   Fixed by making both paths consistent: `messageProperties2String` and 
`messageProperties2Bytes` now skip entries whose key OR value is `null`, so the 
literal `"null"` no longer leaks into the encoded form. Added 
`testMessageProperties2StringAndBytesSkipNullKeyAndValue` to lock this in.



##########
common/src/main/java/org/apache/rocketmq/common/message/MessageDecoder.java:
##########
@@ -602,64 +602,373 @@ public static List<MessageExt> decodes(ByteBuffer 
byteBuffer, final boolean read
         return msgExts;
     }
 
+    private static final ThreadLocal<StringBuilder> REUSABLE_SB =
+        ThreadLocal.withInitial(() -> new StringBuilder(256));

Review Comment:
   Fixed: added a `REUSABLE_SB_CAP_LIMIT` (64 KB). When 
`messageProperties2String` sees a builder whose retained capacity exceeds the 
cap, it replaces the ThreadLocal with a fresh 256-char builder, so a single 
oversized message no longer inflates per-thread memory permanently.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to