Skip to content
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

Conversation

vaisakhkannan
Copy link
Contributor

Part of #767

I have created a new common class called ExceptionUtil, which other quick-fix classes use to call the common exception methods via lambda expression, in order to avoid code duplication.

Copy link
Contributor

@TrevCraw TrevCraw left a 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:

  1. We should not be using System.out.println anywhere, we should be writing to a log file.
  2. 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?
  3. 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);
Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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

Comment on lines 49 to 60
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
}
}
Copy link
Contributor

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.

Copy link
Contributor Author

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

@mrglavas
Copy link
Contributor

A couple overview thoughts:

1. We should not be using `System.out.println` anywhere, we should be writing to a log file.

2. 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?

3. 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.

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.

@vaisakhkannan
Copy link
Contributor Author

A couple overview thoughts:

1. We should not be using `System.out.println` anywhere, we should be writing to a log file.

2. 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?

3. 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.

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 LSP4MP4IJ

}
return rule.validate(value);
},
null, // Fallback value in case of exception
Copy link
Contributor

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.

Copy link
Contributor Author

@vaisakhkannan vaisakhkannan Nov 5, 2024

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,
Copy link
Contributor

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.

Copy link
Contributor Author

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,
Copy link
Contributor

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.

Copy link
Contributor Author

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,
Copy link
Contributor

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.

Copy link
Contributor Author

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

Copy link
Contributor

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

Copy link
Contributor Author

@vaisakhkannan vaisakhkannan Nov 7, 2024

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.

Copy link
Contributor

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NullPointerException? See previous comments.

@vaisakhkannan
Copy link
Contributor Author

I am attaching the recording of the failed test and logging the error message.

failed-test-new.mov

@mrglavas
Copy link
Contributor

mrglavas commented Nov 5, 2024

I am attaching the recording of the failed test and logging the error message.

@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.

@vaisakhkannan
Copy link
Contributor Author

done that by forcing an exception to be thrown (with temporary code which divides by 0 that isn't in the PR)

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.

Copy link
Contributor

@mrglavas mrglavas left a 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;
Copy link
Contributor Author

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.

Copy link
Contributor

@mrglavas mrglavas left a 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.

Copy link
Contributor

@mrglavas mrglavas left a 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.

Copy link
Contributor

@dessina-devasia dessina-devasia left a 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

@vaisakhkannan vaisakhkannan merged commit 0178ba9 into OpenLiberty:intellij-2024.2-support Nov 8, 2024
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants