spring-webflow icon indicating copy to clipboard operation
spring-webflow copied to clipboard

infinite loop possible in FlowExecutionImpl.handleException when application code throws exceptions [SWF-428]

Open spring-operator opened this issue 18 years ago • 8 comments

ChienHsing Wu opened SWF-428 and commented

FlowExecutionImpl.handleException tries state exception handlers and then flow exception handlers if no state handlers can process that exception. this method expects application side exception handling code not to throw any exceptions, which might not be true sometimes. I think a more robust algorithm for this method is:

  1. try State handlers, catch the exception if it occurs.
  2. try the flow handlers, throw the exception to the upper level if one occurs

I think throwing the exception out is better then retry the handlers since Webflow is in a web container and the web container has means to allow applications to handle exceptions too. This forms a hierarchy of exception handlers:

  1. State level - typically for state-specific application exceptions

  2. Flow level- typically for flow-wide application exceptions

  3. Application level - typically for system runtime exceptions

     try {
     	try {
     		// the state could be null if the flow was attempting a start operation
     		ViewSelection selectedView = tryStateHandlers(exception, context);
     		if (selectedView != null) {
     			return selectedView;
     		}
     	}
     	catch (FlowExecutionException ex) {
     		logger.warn("tryStateHandlers", ex);
     	}
    
     	ViewSelection selectedView = tryFlowHandlers(exception, context);
     	if (selectedView != null) {
     		return selectedView;
     	}
     }
     catch (FlowExecutionException newException) {
     	logger.error("handleException: failed", newException);
     }
    
     if (logger.isDebugEnabled()) {
     	logger.debug("Rethrowing unhandled flow execution exception");
     }
     throw exception;
    

Affects: 1.0.5

Attachments:

6 votes, 7 watchers

spring-operator avatar Nov 21 '07 07:11 spring-operator

Agim Emruli commented

Hello,

i think the current behavior is useful and shouldn't be changed. The ExceptionHandler has a canHandle method, there you have the possibility to return false if you can't handle the exception.

The actual implementation has been added in SWF 1.0.4 (or maybe 1.0.3), and allows you to handle exceptions which happens while handling the exception itself. If you implement the canHandle method properly, then you will not run into any issue with that.

Best regards agim

spring-operator avatar Nov 27 '07 18:11 spring-operator

ChienHsing Wu commented

Hi Agim,

Thanks for your reply.

Could you tell me where that canHanlde method is defined? I got the 2.0M1 code and I did not find it in org.springframework.webflow.engine. FlowExecutionExceptionHandler or org.springframework.webflow.engine.support. TransitionExecutingFlowExecutionExceptionHandler.

Could you tell me how the current implementation is useful? Maybe there is a way to achieve the intended "usefulness" and at the same time avoid the potential infinite recursive calls?

spring-operator avatar Nov 28 '07 06:11 spring-operator

Agim Emruli commented

Hi,

<qoute> Could you tell me where that canHanlde method is defined? I got the 2.0M1 code and I did not find it in org.springframework.webflow.engine. FlowExecutionExceptionHandler or org.springframework.webflow.engine.support. TransitionExecutingFlowExecutionExceptionHandler. </qoute>

http://static.springframework.org/spring-webflow/docs/2.0.x/api/org/springframework/webflow/engine/FlowExecutionExceptionHandler.html#canHandle(org.springframework.webflow.execution.FlowExecutionException)

<qoute> Could you tell me how the current implementation is useful? Maybe there is a way to achieve the intended "usefulness" and at the same time avoid the potential infinite recursive calls? </qoute>

In short, the canHandle Method must be implemented that it returns false for all exception that will be thrown if the exceptionhandler is called again.

I.e. you have your Exception MyCustomFlowExecutionListenerException that is thrown from your handler (every time the handler is called), in this case you must return false inside the canHandle Method to avoid a infinite loop through your exception handlers.

regards agim

spring-operator avatar Dec 01 '07 23:12 spring-operator

ChienHsing Wu commented

Hi Agim,

I downloaded the night build to see how FlowExecutionExceptionHandler.canHandle is implemented. That interface is implemented by org.springframework.webflow.engine.support.TransitionExecutingFlowExecutionExceptionHandler and it just check if getTargetStateResolver(e) != null. This means I'd need to have my own custom TransitionExecutingFlowExecutionExceptionHandler class to override it.

TransitionExecutingFlowExecutionExceptionHandler is used in org.springframework.webflow.engine.builder.xml.XmlFlowBuilder.parseTransitionExecutingExceptionHandler. That method is PRIVATE. I'd like to override this for all the transitions since it could happen that some code could throw 1) runtime exceptions such as DB connection errors continuously as long as the code could not get a connection, or 2) application exceptions such as some validation logic that would fail by the given form data from the user.

It'd be very difficult for me to have a custom XmlFlowBuilder so that I can use my custom TransitionExecutingFlowExecutionExceptionHandler and I believe XmlFlowBuilder and TransitionExecutingFlowExecutionExceptionHandler probably are not intended to be over ridden.

All this comes back to the question I had in the beginning: what exactly you guys want to achieve with that infinite loop? Maybe there are ways to achieve the same goal while avoid using an infinite loop?

I think a potential infinite loop should be avoided directly because we never know when it occurs and when it occurs it WILL bring down the program, which I believe is not a good thing in production environments.

Thanks,

ChienHsing

spring-operator avatar Dec 03 '07 01:12 spring-operator

John Case commented

You don't need custom exception handling to run into this problem. I created a flow with a global transition on-exception="java.lang.Throwable" that should transition to an error page. If I have a view state with exit-actions, and one of those actions throws an exception then I end up in this loop because webflow always tries to execute the exit-actions before transitioning out of the view-state. I have tested this in both 1.0.4 and 1.0.5

I have a small project that demonstrates the issue that I can attach if needed, otherwise the following flow definition should be enough:

<?xml version="1.0" encoding="UTF-8"?>

<flow xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-1.0.xsd">

<start-state idref="page1"/>

<view-state id="page1" view="page1">
	<transition on="next" to="page2" />

	<exit-actions>
		<action bean="throwExceptionAction" />
	</exit-actions>
</view-state>   

<end-state id="page2" view="page2" />

<end-state id="error" view="error" />

<global-transitions>
	<transition on-exception="java.lang.Throwable" to="error" />
</global-transitions>

<import resource="testbug-flow-beans.xml"/>

</flow>

Below is the stack trace from one of the iterations in the loop which clearly shows the loop at FlowExecutionImpl.java:313 (version 1.0.5)

[#|2008-02-06T15:41:19.903-0600|INFO|sun-appserver9.1|javax.enterprise.system.stream.out|_ThreadID=16;_ThreadName=httpSSLWorkerThread-8080-0;|2008-02-06 15:41:19,903 DEBUG [org.springframework.webflow.engine.support.TransitionExecutingStateExceptionHandler] - Handling state exception org.springframework.webflow.engine.ActionExecutionException: Exception thrown executing [AnnotatedAction@11ffcc2 targetAction = my.pkg.webflow.ThrowExceptionAction@10b5037, attributes = map[[empty]]] in state 'page1' of flow 'testbug-flow' -- action execution attributes were 'map[[empty]]'; nested exception is java.lang.Exception: Test Exception org.springframework.webflow.engine.ActionExecutionException: Exception thrown executing [AnnotatedAction@11ffcc2 targetAction = my.pkg.webflow.ThrowExceptionAction@10b5037, attributes = map[[empty]]] in state 'page1' of flow 'testbug-flow' -- action execution attributes were 'map[[empty]]'; nested exception is java.lang.Exception: Test Exception Caused by: java.lang.Exception: Test Exception at my.pkg.webflow.ThrowExceptionAction.execute(ThrowExceptionAction.java:16) at org.springframework.webflow.engine.AnnotatedAction.execute(AnnotatedAction.java:146) at org.springframework.webflow.engine.ActionExecutor.execute(ActionExecutor.java:59) at org.springframework.webflow.engine.ActionList.execute(ActionList.java:153) at org.springframework.webflow.engine.TransitionableState.exit(TransitionableState.java:129) at org.springframework.webflow.engine.Transition.execute(Transition.java:202) at org.springframework.webflow.engine.impl.RequestControlContextImpl.execute(RequestControlContextImpl.java:222) at org.springframework.webflow.engine.support.TransitionExecutingStateExceptionHandler.handle(TransitionExecutingStateExceptionHandler.java:113) at org.springframework.webflow.engine.FlowExecutionExceptionHandlerSet.handleException(FlowExecutionExceptionHandlerSet.java:115) at org.springframework.webflow.engine.Flow.handleException(Flow.java:576) at org.springframework.webflow.engine.impl.FlowExecutionImpl.tryFlowHandlers(FlowExecutionImpl.java:341) at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java:304) at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java:313) at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java:313) at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java:313) at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java:313) at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java:313) at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java:313) at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java:313) at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java:313) at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java:313) at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java:313) at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java:313) at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java:313) at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java:313) at org.springframework.webflow.engine.impl.FlowExecutionImpl.signalEvent(FlowExecutionImpl.java:205) at org.springframework.webflow.executor.FlowExecutorImpl.resume(FlowExecutorImpl.java:222) at org.springframework.webflow.executor.support.FlowRequestHandler.handleFlowRequest(FlowRequestHandler.java:111) at org.springframework.webflow.executor.mvc.FlowController.handleRequestInternal(FlowController.java:165) at org.springframework.web.servlet.mvc.AbstractController.handleRequest(AbstractController.java:153) at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:48) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:857) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:792) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:475) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:440) at javax.servlet.http.HttpServlet.service(HttpServlet.java:738) at javax.servlet.http.HttpServlet.service(HttpServlet.java:831) at org.apache.catalina.core.ApplicationFilterChain.servletService(ApplicationFilterChain.java:411) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:290) at org.apache.catalina.core.StandardContextValve.invokeInternal(StandardContextValve.java:271) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:202) at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:632) at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:577) at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:94) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:206) at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:632) at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:577) at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:571) at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:1080) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:150) at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:632) at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:577) at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:571) at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:1080) at org.apache.coyote.tomcat5.CoyoteAdapter.service(CoyoteAdapter.java:272) at com.sun.enterprise.web.connector.grizzly.DefaultProcessorTask.invokeAdapter(DefaultProcessorTask.java:637) at com.sun.enterprise.web.connector.grizzly.DefaultProcessorTask.doProcess(DefaultProcessorTask.java:568) at com.sun.enterprise.web.connector.grizzly.DefaultProcessorTask.process(DefaultProcessorTask.java:813) at com.sun.enterprise.web.connector.grizzly.DefaultReadTask.executeProcessorTask(DefaultReadTask.java:341) at com.sun.enterprise.web.connector.grizzly.DefaultReadTask.doTask(DefaultReadTask.java:263) at com.sun.enterprise.web.connector.grizzly.DefaultReadTask.doTask(DefaultReadTask.java:214) at com.sun.enterprise.web.connector.grizzly.TaskBase.run(TaskBase.java:265) at com.sun.enterprise.web.connector.grizzly.ssl.SSLWorkerThread.run(SSLWorkerThread.java:106)

spring-operator avatar Feb 06 '08 07:02 spring-operator

John Case commented

I have attached the project I created that you can use to re-create the problem the way I described it.

spring-operator avatar Feb 06 '08 08:02 spring-operator

Lalit Murmu commented

We use spring webflow 1.0.5 and are experiencing JVM heapdumps because of this issue . In each flow document we have defined a common exception handler such as below.

<exception-handler bean="commonFlowExecutionExceptionHandler"/>

However this does not handle the exception gracefully. As a result the following infinite loop occurs.

at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java(Compiled Code))
at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java(Compiled Code))
at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java(Compiled Code))
at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java(Compiled Code))
at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java(Compiled Code))
at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java(Compiled Code))
at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java(Compiled Code))
at org.springframework.webflow.engine.impl.FlowExecutionImpl.handleException(FlowExecutionImpl.java(Compiled Code))

We have a number of spring webflows and want a generic solution instead of having to modify each webflow.

Was there a fix given in 1.0.6 ?

spring-operator avatar Jul 13 '09 06:07 spring-operator

Silas Christiansen commented

We just upgraded to 2.2.1 and still see this issue. The issue now states it will be fixed in webflow 3.0. For now the only work around is to put try/catch blocks around all your exit actions to avoid the infinite looping (or avoid exit actions all together).

spring-operator avatar Feb 01 '12 15:02 spring-operator