This is an automated email from the ASF dual-hosted git repository.
adoroszlai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new 2a05dc0860a HDDS-15783. Handle empty lists in ScmListCodec
deserialization (#10690)
2a05dc0860a is described below
commit 2a05dc0860a39d28d97959cd3b84d91a5759eac6
Author: Priyesh Karatha <[email protected]>
AuthorDate: Fri Jul 10 16:16:20 2026 +0530
HDDS-15783. Handle empty lists in ScmListCodec deserialization (#10690)
---
.../apache/hadoop/hdds/scm/ha/io/ScmListCodec.java | 6 ++
.../hadoop/hdds/scm/ha/io/TestScmListCodec.java | 69 ++++++++++++++++++++++
2 files changed, 75 insertions(+)
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/io/ScmListCodec.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/io/ScmListCodec.java
index 36c0531b812..0276c758b3e 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/io/ScmListCodec.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/io/ScmListCodec.java
@@ -69,6 +69,12 @@ public Object deserialize(ByteString value) throws
InvalidProtocolBufferExceptio
"Missing ListArgument.type: " + argument);
}
+ // Empty list was serialized with type=Object.class.getName() as a
sentinel.
+ // Skip element-type resolution — there are no elements to decode.
+ if (argument.getValueCount() == 0) {
+ return new ArrayList<>();
+ }
+
final Class<?> elementClass = resolver.get(argument.getType());
final ScmCodec<?> elementCodec =
ScmCodecFactory.getInstance().getCodec(elementClass);
diff --git
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/io/TestScmListCodec.java
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/io/TestScmListCodec.java
index f7230bcd2f4..bec07f239b5 100644
---
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/io/TestScmListCodec.java
+++
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/io/TestScmListCodec.java
@@ -17,10 +17,14 @@
package org.apache.hadoop.hdds.scm.ha.io;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import org.apache.hadoop.hdds.protocol.proto.SCMRatisProtocol;
import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
import
org.apache.ratis.thirdparty.com.google.protobuf.InvalidProtocolBufferException;
@@ -49,4 +53,69 @@ public void testListDecodeMissingTypeShouldFail() throws
Exception {
assertTrue(ex.getMessage().contains("Missing ListArgument.type"));
}
+
+ /**
+ * An empty list serialized with the Object.class sentinel must round-trip
+ * cleanly without triggering "Failed to resolve java.lang.Object".
+ */
+ @Test
+ public void testEmptyListRoundTrip() throws Exception {
+ ScmListCodec codec = new ScmListCodec(
+ new ScmCodecFactory.ClassResolver(Collections.emptyList()));
+
+ List<?> result = (List<?>) codec.deserialize(codec.serialize(new
ArrayList<>()));
+
+ assertEquals(0, result.size());
+ }
+
+ /**
+ * The EMPTY_LIST sentinel (type=java.lang.Object, no values) stored in an
+ * existing Ratis log must deserialize successfully.
+ */
+ @Test
+ public void testEmptyListSentinelDeserialization() throws Exception {
+ SCMRatisProtocol.ListArgument sentinel =
+ SCMRatisProtocol.ListArgument.newBuilder()
+ .setType(Object.class.getName())
+ // no values
+ .build();
+
+ ScmListCodec codec = new ScmListCodec(
+ new ScmCodecFactory.ClassResolver(Collections.emptyList()));
+
+ List<?> result = (List<?>) codec.deserialize(sentinel.toByteString());
+
+ assertEquals(0, result.size());
+ }
+
+ /**
+ * Deserialized empty lists must be concrete {@link ArrayList} instances.
+ * Generated invokers (e.g. DeletedBlockLogStateManagerInvoker) cast the
+ * decoded argument directly to {@code ArrayList}; returning an unmodifiable
+ * or fixed-size list would cause a ClassCastException during Ratis log
+ * replay even though the list is logically empty.
+ */
+ @Test
+ public void testEmptyListDeserializedAsArrayList() throws Exception {
+ ScmListCodec codec = new ScmListCodec(
+ new ScmCodecFactory.ClassResolver(Collections.emptyList()));
+
+ // Round-trip path: serialize an empty list then deserialize it.
+ Object roundTrip = codec.deserialize(codec.serialize(new ArrayList<>()));
+ assertInstanceOf(ArrayList.class, roundTrip,
+ "round-trip empty list must be an ArrayList, not " +
roundTrip.getClass());
+
+ // Sentinel path: the exact bytes that older logs contain.
+ SCMRatisProtocol.ListArgument sentinel =
+ SCMRatisProtocol.ListArgument.newBuilder()
+ .setType(Object.class.getName())
+ .build();
+ Object fromSentinel = codec.deserialize(sentinel.toByteString());
+ assertInstanceOf(ArrayList.class, fromSentinel,
+ "sentinel empty list must be an ArrayList, not " +
fromSentinel.getClass());
+
+ // Verify the cast that invokers actually perform does not throw.
+ ArrayList<?> cast = (ArrayList<?>) roundTrip;
+ assertEquals(0, cast.size());
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]