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

WIP: WELD-2503 Create Exception handler for applications run using StartMain #1838

Conversation

smoyer64
Copy link
Contributor

@smoyer64 smoyer64 commented Jun 6, 2018

Work in progress towards WELD-2503 - please review.

Theory

ExceptionHandler's can be registered with StartMain through the ServiceLoader mechanism. Each ExceptionHandler specifies one or more types of that it will support (one danger is that only the first ExceptionHandler found for a type will be called and the ServiceLoader has no mechanism for ordering the services it loads).

An exception handler can:

  • Analyze the supplied Throwable
  • Produce an appropriate log message (or more than one line if desired)
  • Suppress or log the stack-trace of the Throwable
  • Terminate the program with a custom error code using System.exit() - StartMain

StartMain has been altered to return an exit code of zero if the program terminates normally or an exit code of one if the program terminates due to an exception. Of course, the application itself is still free to exit with its chosen error code at any time - the ExceptionHandler is only called when any checked or unchecked exception is allowed to reach the main() method.

It's recognized that some Throwable instances may contain very complex internal structures - understanding this content is solely the responsibility of an ExceptionHandler registered for the offending type.

Outstanding tasks

  • Add ExceptionHandler registration to StartMain
  • Add test coverage
  • Add javadocs
  • Add documentation
  • Create POC example using Wildfly's Microprofile-Config and DeploymentException

@WeldJenkins
Copy link

Can one of the admins verify this patch?

@weld weld deleted a comment from WeldJenkins Jun 7, 2018
@weld weld deleted a comment from WeldJenkins Jun 7, 2018
@manovotn
Copy link
Contributor

manovotn commented Jun 7, 2018

ok to test

Generic array types are problematic
smoyer64 pushed a commit to PennState/weld-se-config-shade-poc that referenced this pull request Jun 7, 2018
@smoyer64
Copy link
Contributor Author

smoyer64 commented Jun 7, 2018

I've updated a previous POC we created here at Penn State to use the new ExceptionHandler mechanism. The application output below shows that the DefaultExceptionHandler was used (since there are no additional ExceptionHandlers found by the ServiceLoader. Notice that this default execution is available at commit PennState/weld-se-config-shade-poc@d69e5f9.

08:36:36.729 [] [main] ERROR org.jboss.weld.environment.se.DefaultExceptionHandler - Weld-SE application exited with an exception org.jboss.weld.exceptions.DeploymentException: Error while validating Configuration
No Config Value exists for another.string
No Config Value exists for a.third.string
No Config Value exists for some.string
        at org.jboss.weld.bootstrap.events.AbstractDeploymentContainerEvent.fire(AbstractDeploymentContainerEvent.java:38)
        at org.jboss.weld.bootstrap.events.AfterDeploymentValidationImpl.fire(AfterDeploymentValidationImpl.java:28)
        at org.jboss.weld.bootstrap.WeldStartup.validateBeans(WeldStartup.java:499)
        at org.jboss.weld.bootstrap.WeldBootstrap.validateBeans(WeldBootstrap.java:93)
        at org.jboss.weld.environment.se.Weld.initialize(Weld.java:800)
        at org.jboss.weld.environment.se.StartMain.go(StartMain.java:52)
        at org.jboss.weld.environment.se.StartMain.main(StartMain.java:62)
Caused by: javax.enterprise.inject.spi.DeploymentException: Error while validating Configuration
No Config Value exists for another.string
No Config Value exists for a.third.string
No Config Value exists for some.string
        at org.wildfly.microprofile.config.inject.ConfigExtension.validate(ConfigExtension.java:122)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.jboss.weld.injection.StaticMethodInjectionPoint.invoke(StaticMethodInjectionPoint.java:95)
        at org.jboss.weld.injection.MethodInvocationStrategy$SpecialParamPlusBeanManagerStrategy.invoke(MethodInvocationStrategy.java:144)
        at org.jboss.weld.event.ObserverMethodImpl.sendEvent(ObserverMethodImpl.java:330)
        at org.jboss.weld.event.ExtensionObserverMethodImpl.sendEvent(ExtensionObserverMethodImpl.java:123)
        at org.jboss.weld.event.ObserverMethodImpl.sendEvent(ObserverMethodImpl.java:308)
        at org.jboss.weld.event.ObserverMethodImpl.notify(ObserverMethodImpl.java:286)
        at javax.enterprise.inject.spi.ObserverMethod.notify(ObserverMethod.java:124)
        at org.jboss.weld.util.Observers.notify(Observers.java:170)
        at org.jboss.weld.event.ObserverNotifier.notifySyncObservers(ObserverNotifier.java:285)
        at org.jboss.weld.event.ObserverNotifier.notify(ObserverNotifier.java:273)
        at org.jboss.weld.event.ObserverNotifier.fireEvent(ObserverNotifier.java:177)
        at org.jboss.weld.event.ObserverNotifier.fireEvent(ObserverNotifier.java:171)
        at org.jboss.weld.bootstrap.events.AbstractContainerEvent.fire(AbstractContainerEvent.java:53)
        at org.jboss.weld.bootstrap.events.AbstractDeploymentContainerEvent.fire(AbstractDeploymentContainerEvent.java:35)
        ... 6 common frames omitted

swm16@swm16-xps-15:~/git/psu/weld-se-config-shade-poc$ echo $?
1

After this commit, we've added a custom (and admittedly simplified) ExceptionHandler that only outputs the DeploymentException's message and changes the application's exit code to 2. The output of this program is now:

09:30:02.399 [] [main] ERROR edu.psu.swe.poc.DeploymentExceptionHandler - Error while validating Configuration
No Config Value exists for another.string
No Config Value exists for a.third.string
No Config Value exists for some.string
swm16@swm16-xps-15:~/git/psu/weld-se-config-shade-poc$ echo $?
2

@manovotn
Copy link
Contributor

manovotn commented Jun 7, 2018

retest this please

@manovotn
Copy link
Contributor

Alright, I finally got down to this, so here are some thoughts...

Thanks for PoC, it makes things a lot clearer. I can see the point of this effort and I think it's a good idea to be able to handle exception in a custom way.

One thing I find a bit weird is that this approach will only work if you are using the built-in main method.
If you were to write your own main (which I think is pretty common), then this has no effect. As a matter of fact, you can even now write your own main with just this code and it will work.

So the question is, whether it's good "default" behaviour. Since it doesn't change the way it behaved up until now, I'd say it's fine.

P.S. I know streams are cool and all, but what's wrong with List? :)


Stream<Class<? extends Throwable>> getExceptionTypes();

void handle(Throwable t);
Copy link
Member

Choose a reason for hiding this comment

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

I would simplify the API so that ExceptionHandler only declares boolean handle(Throwable t) which returns true if the handler is able to handle the given 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 would lean towards two methods, supports() and handle(), much like it's designed in Junit for instance.

Copy link
Contributor Author

@smoyer64 smoyer64 Jul 6, 2018

Choose a reason for hiding this comment

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

The JUnit 5 ParameterResolver was actually my inspiration for this interface ... I've simplified it a bit now but the user will be responsible for all the supports() logic now. Any chance that weld-core can be setup to also run JUnit 5 tests?

Copy link
Contributor

Choose a reason for hiding this comment

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

Any chance that weld-core can be setup to also run JUnit 5 tests?

That shouldn't be problem, it can be run in parallel with JUnit 4, though I am not sure what is the actual benefit?
Plus any more complex testing that pure unit testing has to stick with JUnit 4 because Arquillian doesn't support it ATM (at least last time I checked it didn't).


@Override
public Stream<Class<? extends Throwable>> getExceptionTypes() {
return null;
Copy link
Member

Choose a reason for hiding this comment

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

I think this would result in NPE (see also the default supports() implementation).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The DefaultExceptionHandler has to be at the end of the list and isn't registered via the ServiceLoader. See the orElse clause in StartMain.

@@ -0,0 +1,21 @@
package org.jboss.weld.environment.se;
Copy link
Member

Choose a reason for hiding this comment

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

Missing license header...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also in the ExceptionHandler interface.

new StartMain(args).go();
System.exit(0);
} catch (Throwable t) {
StreamSupport.stream(exceptionHandlerLoader.spliterator(), false)
Copy link
Member

Choose a reason for hiding this comment

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

I think there is no need to use streams here...

Copy link
Contributor Author

@smoyer64 smoyer64 Jul 6, 2018

Choose a reason for hiding this comment

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

I like the use of the Stream here but agree that it's a bit out of place in the ExceptionHandler interface. (in this case, the findFirst() and orElse() methods eliminate a lot of if/elseif logic)

@smoyer64
Copy link
Contributor Author

So the question is, whether it's good "default" behaviour. Since it doesn't change the way it behaved up until now, I'd say it's fine.

It doesn't change the default behavior but it does represent code that has to be maintained in the future. My opinion is that it's probably not the type of code that will cause maintenance issues as it's functionality is pretty clear. In any case, I'll leave that decision up to you.

One thing I find a bit weird is that this approach will only work if you are using the built-in main method.

Should the ExceptionHandlers be executed as part of the CDI life-cycle instead? This would only change the behavior of existing applications if they registered a handler.

P.S. I know streams are cool and all, but what's wrong with List? :)

I was looking over this code with a colleague and we both realized that the signature in ExceptionHandler should probably contain a Set. Using stream processing in the StartMain catch block made the code's behavior much clearer and I simply returned a Stream to make that easier. Since only the first "supporting" handler is executed, a list might help as the developer can then specify the order of the handlers. An alternative would be to execute all supporting handlers, though it's my opinion that would increase the application's complexity. What do you both think?

I have in fact "wrapped" the StartMain class to perform the functionality included in #2502 - the ability to include one ore more ExceptionHandlers as defined in this WIP allows us essentially put a help message into the logs output when there's a configuration or infrastructure problem.

@manovotn
Copy link
Contributor

Should the ExceptionHandlers be executed as part of the CDI life-cycle instead?

Can't see how this would work. All I was saying is that I 'think' (I cannot know for sure) that a lot of people using Weld SE also define their own Main. Hence they won't even be aware of this change and anyone willing to adopt it will basically have to go to our code and copy-paste the code executing handlers which is a pity but I have no other solution for the time being.

An alternative would be to execute all supporting handlers, though it's my opinion that would increase the application's complexity.

Yea it would, but it's complexity that user themselves can introduce if they wish to. Not really our concern. What we could do is to allow using @Priority on the implementations and then sorting the list based on that to impose some ordering. Or does that sounds too crazy?

I'd either allow for one handler only, or enable some way to order your handlers. WDYT @mkouba ?

smoyer64 pushed a commit to PennState/weld-se-config-shade-poc that referenced this pull request Jul 6, 2018
@mkouba mkouba added the hold label Jul 10, 2018
@smoyer64
Copy link
Contributor Author

I just removed the System.exit(0) as in WELD-2502 so I wouldn't forget

@smoyer64
Copy link
Contributor Author

smoyer64 commented Sep 4, 2018

@mkouba Is there something I need to do to move this off "hold"?

@manovotn
Copy link
Contributor

manovotn commented Sep 5, 2018

Hey Steve, thanks for the nudge, this has gone somewhat stall.

So, I talked this through with Martin and here is what we agreed on:

  • Rename ExceptionHandler to WeldMainExceptionHandler StartMainExceptionHandler
    • To make the name less vague and indicating that it goes together with our main
  • WeldMainExceptionHandler will implement javax.enterprise.inject.spi.Prioritized and java.lang.Comparable
    • there will be a default implementation of compareTo() right on the interface, leveraging getPriority() method
    • I would not add default impl of getPriority()
  • Allow to execute all exception handlers based on priority (not just one/first)
    • Get List of all handlers, sort them
    • Handlers with the lowest priority go first (this has to be documented)
    • if no handlers are found, use the default one
  • Correct License headers (some files are missing it, some are outdated)
  • Last but not least, add tests :)

Let me know if it sounds reasonable and if you are fine making these changes.

@smoyer64
Copy link
Contributor Author

smoyer64 commented Sep 5, 2018

That all sounds reasonable ... I'm really busy at work this month but I can dig into this work in a few weeks. Would StartMainExceptionHandler be a better name than WeldMainExceptionHandler given it's only going to work if you launch via StartMain?

I've been pushing the https://github.com/eclipse/ConfigJsr and https://github.com/smallrye/smallrye-config projects to clean up how their validation errors are reported so I might yet be able to catch DeploymentExceptions in Java SE CDI projects and display an alternate (configuration oriented) help screen.

Thanks!

@mkouba
Copy link
Member

mkouba commented Sep 5, 2018

Would StartMainExceptionHandler be a better name...

+1, this would be my choice as well ;-)

@manovotn
Copy link
Contributor

manovotn commented Sep 5, 2018

Would StartMainExceptionHandler be a better name...

Ok, I have been outvoted, StartMainExceptionHandler it is! :)

@manovotn
Copy link
Contributor

manovotn commented Oct 3, 2018

@smoyer64 Just to let you know, next release is 3.1.0 meaning this PR should get in.
Now is the time to move in forward ;-)

@manovotn
Copy link
Contributor

@smoyer64 a casual friendly inconspicuous poke

@smoyer64
Copy link
Contributor Author

smoyer64 commented Nov 26, 2018 via email

@manovotn
Copy link
Contributor

Weld release is scheduled very early in Feb, this issue should either get a solid PR by the end of Jan, or we will have to postpone it.

@smoyer64
Copy link
Contributor Author

Okay ... I've got perhaps a day I can spare and I don't think it's too onerous. I think I'm good to go based on the previous discussion but I'm wondering how much documentation should be included. I'm assuming that needs to go in the users guide?

@manovotn
Copy link
Contributor

The SE part of Weld docs might be the place I think - http://docs.jboss.org/weld/reference/latest/en-US/html_single/#weld-se
In code, that translated into [this asciidoc|https://github.com/weld/core/blob/master/docs/reference/src/main/asciidoc/environments.asciidoc].

@manovotn
Copy link
Contributor

Closing because the PR has been stale for far too long.

@manovotn manovotn closed this Aug 25, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
4 participants