HDDS-14846. Implement ScmCodecFactory without reflection#9952
HDDS-14846. Implement ScmCodecFactory without reflection#9952szetszwo merged 7 commits intoapache:masterfrom
Conversation
|
Hi @szetszwo, could you please help review this PR when you have time? Thanks! |
|
@Russole , When reviewing this, I found that the change can be simplified after remove the type parameter from ScmCodec.deserialize(..); filed HDDS-14875. |
szetszwo
left a comment
There was a problem hiding this comment.
@Russole , thanks for working on this! Please see the comments inlined and also https://issues.apache.org/jira/secure/attachment/13081315/9952_review.patch
BTW, please wait for HDDS-14875 -- 9952_review.patch requires it.
| final String parameterType = parameterTypes[paramCounter++].getName(); | ||
| argBuilder.setType(parameterType); | ||
| argBuilder.setValue(ScmCodecFactory.getInstance().getCodec(parameterType) |
There was a problem hiding this comment.
When the class is available, we should getCodec using the class.
BTW, could you also remove this args list? We should just use the proto builder.
public Message encode() throws InvalidProtocolBufferException {
final SCMRatisRequestProto.Builder requestProtoBuilder =
SCMRatisRequestProto.newBuilder();
requestProtoBuilder.setType(type);
final Method.Builder methodBuilder = Method.newBuilder();
methodBuilder.setName(operation);
for (int i = 0; i < parameterTypes.length; i++) {
// Set actual method parameter type, not actual argument type.
// This is done to avoid MethodNotFoundException in case if argument is
// subclass type, where as method is defined with super class type.
final Class<?> parameterType = parameterTypes[i];
methodBuilder.addArgs(MethodArgument.newBuilder()
.setType(parameterType.getName())
.setValue(FACTORY.getCodec(parameterType).serialize(arguments[i]))
.build());
}
requestProtoBuilder.setMethod(methodBuilder.build());
final SCMRatisRequestProto requestProto = requestProtoBuilder.build();
return Message.valueOf(requestProto.toByteString());
}| final String argumentType = argument.getType(); | ||
| final Class<?> clazz = ReflectionUtil.getClass(argument.getType()); | ||
| parameterTypes[paramCounter++] = clazz; | ||
| args.add(ScmCodecFactory.getCodec(clazz) | ||
| args.add(ScmCodecFactory.getInstance().getCodec(argumentType) |
There was a problem hiding this comment.
Use factory to resolve the class:
final String argumentType = argument.getType();
final Class<?> clazz = FACTORY.resolve(argumentType);
parameterTypes[paramCounter++] = clazz;
args.add(FACTORY.getCodec(clazz).deserialize(argument.getValue()));| final Class<?> type = result.getClass(); | ||
| final String typeName = type.getName(); |
There was a problem hiding this comment.
Pass the declaration type as a parameter:
public static Message encode(Object result, Class<?> type) throws InvalidProtocolBufferException {
if (result == null) {
return Message.EMPTY;
}
final SCMRatisResponseProto response = SCMRatisResponseProto.newBuilder()
.setType(type.getName())
.setValue(FACTORY.getCodec(type).serialize(result))
.build();
return Message.valueOf(UnsafeByteOperations.unsafeWrap(response.toByteString().asReadOnlyByteBuffer()));
}| final Class<?> type = ReflectionUtil.getClass(responseProto.getType()); | ||
| return new SCMRatisResponse(ScmCodecFactory.getCodec(type) | ||
| final String typeName = responseProto.getType(); | ||
| final Class<?> type = ReflectionUtil.getClass(typeName); |
There was a problem hiding this comment.
We should use the factory to resolve it.
final Class<?> type = FACTORY.resolve(responseProto.getType());
return new SCMRatisResponse(FACTORY.getCodec(type).deserialize(responseProto.getValue()));| public static ScmCodec getCodec(Class<?> type) | ||
| public ScmCodec getCodec(String className) |
There was a problem hiding this comment.
Let's keep the parameter declared using Class.
For the className case, we should resolve the class first.
hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/io/ScmCodecFactory.java
Show resolved
Hide resolved
|
Thanks @szetszwo for the review. I will update the patch after HDDS-14875 is merged. |
szetszwo
left a comment
There was a problem hiding this comment.
+1 the change looks good.
| classes.addAll(ClassUtils.getAllSuperclasses(type)); | ||
| classes.addAll(ClassUtils.getAllInterfaces(type)); |
There was a problem hiding this comment.
I think removing supertypes causes the following error:
org.apache.hadoop.hdds.scm.exceptions.SCMException: org.apache.ratis.thirdparty.com.google.protobuf.InvalidProtocolBufferException: Codec not found for class java.util.ArrayList
at org.apache.hadoop.hdds.scm.ha.SCMHAInvocationHandler.translateException(SCMHAInvocationHandler.java:163)
at org.apache.hadoop.hdds.scm.ha.SCMHAInvocationHandler.invokeRatis(SCMHAInvocationHandler.java:113)
at org.apache.hadoop.hdds.scm.ha.SCMHAInvocationHandler.invoke(SCMHAInvocationHandler.java:72)
at jdk.proxy2/jdk.proxy2.$Proxy41.addTransactionsToDB(Unknown Source)
at org.apache.hadoop.hdds.scm.block.SCMDeletedBlockTransactionStatusManager.addTransactions(SCMDeletedBlockTransactionStatusManager.java:470)
at org.apache.hadoop.hdds.scm.block.DeletedBlockLogImpl.addTransactions(DeletedBlockLogImpl.java:253)
at org.apache.hadoop.ozone.shell.TestDeletedBlocksTxnShell.testGetDeletedBlockSummarySubcommand(TestDeletedBlocksTxnShell.java:178)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:387)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1312)
at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1843)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1808)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188)
Caused by: org.apache.ratis.thirdparty.com.google.protobuf.InvalidProtocolBufferException: Codec not found for class java.util.ArrayList
at org.apache.hadoop.hdds.scm.ha.io.ScmCodecFactory.getCodec(ScmCodecFactory.java:107)
at org.apache.hadoop.hdds.scm.ha.SCMRatisRequest.encode(SCMRatisRequest.java:107)
at org.apache.hadoop.hdds.scm.ha.SCMRatisServerImpl.submitRequest(SCMRatisServerImpl.java:238)
at org.apache.hadoop.hdds.scm.ha.SCMHAInvocationHandler.invokeRatisServer(SCMHAInvocationHandler.java:121)
at org.apache.hadoop.hdds.scm.ha.SCMHAInvocationHandler.invokeRatis(SCMHAInvocationHandler.java:110)
Maybe we can fix it by changing ArrayList parameter to List in DeletedBlockLogStateManager and its implementation:
Declaring as List is good practice in any case.
There was a problem hiding this comment.
There was a problem hiding this comment.
@adoroszlai , thanks for debugging the test failures!
We should put ArrayList to the factory for backward compatibility.
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/io/ScmCodecFactory.java
@@ -75,7 +75,9 @@ public final class ScmCodecFactory {
// Must be the last one
final ClassResolver resolver = new ClassResolver(codecs.keySet());
- codecs.put(List.class, new ScmListCodec(resolver));
+ final ScmListCodec listCodec = new ScmListCodec(resolver);
+ codecs.put(List.class, listCodec);
+ codecs.put(ArrayList.class, listCodec);
}|
Thanks @adoroszlai and @szetszwo for the suggestion. I think the issue is that the method parameter types and the codec-resolved types were previously treated as the same, which can lead to method lookup failures during decoding and codec lookup failures during encoding. So I separated the two concerns:
I also added a CI tests have passed: https://github.com/Russole/ozone/actions/runs/23406070564 |
szetszwo
left a comment
There was a problem hiding this comment.
@Russole , thanks for the update!
Please see the comments inlined and also https://issues.apache.org/jira/secure/attachment/13081331/9952_review2.patch
| throw new InvalidProtocolBufferException("Codec not found for " + resolved); | ||
| } | ||
|
|
||
| public Class<?> getClass(String className) |
There was a problem hiding this comment.
Add one more map to avoid calling Class.forName(..) multiple times for the same class.
public Class<?> getClass(String className) throws InvalidProtocolBufferException {
final Class<?> found = classes.get(className);
if (found != null) {
return found;
}
final Class<?> clazz;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new InvalidProtocolBufferException("Class not found for " + className, e);
}
classes.put(className, clazz);
return clazz;
}| try { | ||
| clazz = Class.forName(className); | ||
| } catch (ClassNotFoundException e) { | ||
| throw new InvalidProtocolBufferException("Class not found for " + className, e); | ||
| } |
There was a problem hiding this comment.
Reuse the getClass method
clazz = getInstance().getClass(className);| } | ||
|
|
||
| public static ScmCodec getCodec(Class<?> type) | ||
| public Class<?> resolve(String className) |
There was a problem hiding this comment.
Add one more resolve method for Class<?>.
public Class<?> resolve(Class<?> clazz) throws InvalidProtocolBufferException {
return resolver.get(clazz);
}
szetszwo
left a comment
There was a problem hiding this comment.
+1 the change looks good.
What changes were proposed in this pull request?
ScmCodecFactory.getCodecwith class name (String)getCodectoClassResolverScmCodecFactoryinto a singleton (Convert codec map to non-static and add a static instance)What is the link to the Apache JIRA
https://issues.apache.org/jira/browse/HDDS-14846
How was this patch tested?