-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix usage of ProcessCanceledException to support 2024.2 #1047
Fix usage of ProcessCanceledException to support 2024.2 #1047
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple overview thoughts:
- We should not be using
System.out.println
anywhere, we should be writing to a log file. - In the spots where
System.out.println
is being used (the "unsuccessful" cases), has it been considered how we should handle being "unsuccessful"? Is it OK to just log an error and move on? Is there other handling we need to do? - I still see patterns of code that are duplicated quite often, mainly this pattern here:
Boolean success = ExceptionUtil.executeWithExceptionHandling(
() -> {
WorkspaceEdit we = context.convertToWorkspaceEdit(proposal);
toResolve.setEdit(we);
return true;
},
e -> LOGGER.log(Level.WARNING, "Unable to create workspace edit for code action to insert missing annotation", e)
);
if (success == null || !success) {
System.out.println("An error occurred during the code action resolution.");
}
It should be possible to write a common method to cover this code pattern.
@@ -93,7 +93,10 @@ public String validate(String value, AnnotationAttributeRule rule) { | |||
return null; | |||
} | |||
return rule.validate(value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the code below, the usage of the try block is different, so I used standard exception handling for those cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks structurally similar to what you refactored into the utility method. I'm not sure why the pattern wouldn't also apply here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @mrglavas , I made the changes based on your suggestions
private static <T> T executeWithExceptionHandling(Supplier<T> action, Supplier<T> fallback) { | ||
try { | ||
// Construct a stream of only the annotations applied to the type that are also | ||
// recognised annotations found in scopes. | ||
return Arrays.stream(type.getAnnotations()).map(annotation -> annotation.getNameReferenceElement().getQualifiedName()) | ||
.filter(scopes::contains).distinct().collect(Collectors.toList()); | ||
} catch (IndexNotReadyException | ProcessCanceledException | CancellationException e) { | ||
return action.get(); | ||
} catch (ProcessCanceledException e) { | ||
//Since 2024.2 ProcessCanceledException extends CancellationException so we can't use multi-catch to keep backward compatibility | ||
throw e; | ||
} catch (IndexNotReadyException | CancellationException e) { | ||
throw e; | ||
} catch (Exception e) { | ||
return Collections.<String>emptyList(); | ||
return fallback.get(); // Return fallback value | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see this method is private and only called by getScopeAnnotations()
. Is there some way to combine this with the more widely used utility method you introduced in ExceptionUtil
? Otherwise this has just introduced more complexity without reducing code duplication in this spot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes @mrglavas , I made the changes , please take a look , Thanks
I believe there's been some iteration on this PR since @TrevCraw 's initial review, but I'm still seeing duplication of the exception handling pattern in this PR. In the multiple place where the utility method is being used, I think those look great but wanted to point out that many of the changes made are in LSP4MP4IJ which is code that we basically copy out of Quarkus Tools. I think it would be beneficial to contribute a PR to IntelliJ Quarkus (https://github.com/redhat-developer/intellij-quarkus) which introduces the utility method which we would also use in Liberty Tools. This would eliminate the cost of having to apply these changes every time we do an update for LSP4MP4IJ. |
Hi @mrglavas I can open PR against repo https://github.com/redhat-developer/intellij-quarkus to update for |
} | ||
return rule.validate(value); | ||
}, | ||
null, // Fallback value in case of exception |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure this will generate a NullPointerException
. Should be a supplier that returns null. I see other instances of this in the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mrglavas , Thanks for the suggestions, Yes you are correct that will get a NullPointerException
.
So I initially have a thought of changing code like below
public String validate(String value, AnnotationAttributeRule rule) {
return ExceptionUtil.executeWithExceptionHandling(
() -> {
if (rule == null) {
return null;
}
return rule.validate(value);
},
() -> null, // Fallback value supplier in case of exception
e -> LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e)
);
}
Then based on your below comment
NullPointerException? Based on the above code, the lambda expression should be re-throwing the exception. Seems like you need a function (with the exception as the parameter) for the fallback rather than a supplier.
I made change to the method executeWithExceptionHandling
public static <T> T executeWithExceptionHandling(Supplier<T> action, Function<Exception, T> fallback) {
try {
return action.get();
} catch (ProcessCanceledException e) {
//Since 2024.2 ProcessCanceledException extends CancellationException so we can't use multi-catch to keep backward compatibility
//TODO delete block when minimum required version is 2024.2
throw e;
} catch (IndexNotReadyException | CancellationException e) {
throw e;
} catch (Exception e) {
// Invoke the fallback function with the exception to generate a safe return value.
return fallback.apply(e);
}
}
Please review the changes and provide any suggestions.
getInstance().beginDiagnostics(context); | ||
return true; | ||
}, | ||
null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NullPointerException
? See my previous comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made the change like this
@Override
public void beginDiagnostics(JavaDiagnosticsContext context) {
ExceptionUtil.executeWithExceptionHandling(
() -> {
getInstance().beginDiagnostics(context);
return true;
},
e -> {
LOGGER.log(Level.WARNING, "Error while calling beginDiagnostics", e);
return null;
}
);
}
This won't give NullPointerException
.
getInstance().endDiagnostics(context); | ||
return true; | ||
}, | ||
null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NullPointerException
? See my previous comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did change similar to above comment
@Override
public void endDiagnostics(JavaDiagnosticsContext context) {
ExceptionUtil.executeWithExceptionHandling(
() -> {
getInstance().endDiagnostics(context);
return true;
},
e -> {
LOGGER.log(Level.WARNING, "Error while calling endDiagnostics", e);
return null;
}
);
}
PsiType methodReturnType = node.getReturnType(); | ||
return methodReturnType.getCanonicalText(); | ||
}, | ||
null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NullPointerException
? Based on the above code, the lambda expression should be re-throwing the exception. Seems like you need a function (with the exception as the parameter) for the fallback rather than a supplier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion , I made the change in method executeWithExceptionHandling
please check my previous comment here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vaisakhkannan It does not seem like you resolved this issue. It looks like the code is returning null
instead of re-throwing the exception which is in the original code. If you're looking for ideas on how to handle this scenario, this post on Stack Overflow might be helpful: https://stackoverflow.com/questions/18198176/java-8-lambda-function-that-throws-exception
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mrglavas, Thanks for review comment and link you shared, I modified the code to re-throwing the exception, We can't use throw e
, so I used,
throw new RuntimeException("Failed to validate asynchronous annotation", e);
By throwing a RuntimeException, it signals that something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vaisakhkannan The link I shared demonstrates exactly how you can re-throw the exception using a custom functional interface, so this is technically possible to do. The code you are modifying comes from IntelliJ Quarkus and without in-depth understanding of all the paths we could enter this method, just changing this to a generic RuntimeException
could change the behaviour. A wrapped exception is not the same. The goal here is to improve how we do this exception handling without changing what it's doing. If the current usage can only produce unchecked exceptions, perhaps we should change the catch from Exception
to RuntimeException
and then the function could re-throw the RuntimeException
without any restrictions.
toResolve.setEdit(we); | ||
return true; | ||
}, | ||
null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NullPointerException
? See previous comments.
I am attaching the recording of the failed test and logging the error message. failed-test-new.mov |
@vaisakhkannan To be clear, I think what you're trying to show here is a demo of the fallback path and you've done that by forcing an exception to be thrown (with temporary code which divides by 0 that isn't in the PR). Thanks for showing that. I was concerned for a moment that this was about test failures in the build. |
Yes @mrglavas , I tested reproducing exception by adding temporary code that forces an exception by dividing by zero, but that code isn't included in this PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my most recent comment about support for re-throwing exceptions.
}, | ||
e -> { | ||
logger.log(Level.WARNING, logMessage, e); | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mrglavas ,I replaced null
with false
, so it’s better to stick with false
to clearly indicate failure without adding null
ambiguity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my most recent comment about re-throwing exceptions.
src/main/java/io/openliberty/tools/intellij/util/ExceptionUtil.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good now. Thanks for addressing the review comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me
0178ba9
into
OpenLiberty:intellij-2024.2-support
Part of #767
I have created a new common class called
ExceptionUtil
, which other quick-fix classes use to call the common exception methods vialambda expression
, in order to avoid code duplication.