-
Type: Bug
-
Status: Resolved
-
Priority: Critical
-
Resolution: Fixed
-
Affects Version/s: 5.8
-
Fix Version/s: 5.8.0-HF36, 6.0-HF19, 7.4
-
Component/s: WebEngine
-
Tags:
If an exception is thrown during a webengine calls, let's say at service level, the Transaction is not Rolled back but committed.
This is because jersey catches and processes the exception without re-throwing it.
Typical use case to reproduce is the travel-expenses sample.
Let's create a task with nature = 'lunch', there is a directory validator on the dc:nature field. Because the entry 'lunch does not exist', a DocumentValidationException is thrown in org.nuxeo.ecm.platform.routing.core.impl.GraphNodeImpl.setAllVariables(Map<String, Object>, boolean).
In the stack below:
Daemon Thread [http-bio-0.0.0.0-8080-exec-6] (Suspended (breakpoint at line 376 in org.nuxeo.ecm.platform.routing.core.impl.GraphNodeImpl)) (out of synch) owns: org.apache.tomcat.util.net.SocketWrapper<E> (id=17551) org.nuxeo.ecm.platform.routing.core.impl.GraphNodeImpl.setAllVariables(java.util.Map<java.lang.String,java.lang.Object>, boolean) line: 376 org.nuxeo.ecm.platform.routing.core.impl.GraphRunner.resume(org.nuxeo.ecm.core.api.CoreSession, org.nuxeo.ecm.platform.routing.api.DocumentRouteElement, java.lang.String, java.lang.String, java.util.Map<java.lang.String,java.lang.Object>, java.lang.String) line: 123 org.nuxeo.ecm.platform.routing.core.impl.GraphRouteImpl(org.nuxeo.ecm.platform.routing.core.impl.DocumentRouteElementImpl).resume(org.nuxeo.ecm.core.api.CoreSession, java.lang.String, java.lang.String, java.util.Map<java.lang.String,java.lang.Object>, java.lang.String) line: 79 org.nuxeo.ecm.platform.routing.core.impl.DocumentRoutingEngineServiceImpl.resume(org.nuxeo.ecm.platform.routing.api.DocumentRoute, java.lang.String, java.lang.String, java.util.Map<java.lang.String,java.lang.Object>, java.lang.String, org.nuxeo.ecm.core.api.CoreSession) line: 46 org.nuxeo.ecm.platform.routing.core.impl.DocumentRoutingServiceImpl$CompleteTaskRunner.run() line: 347 org.nuxeo.ecm.platform.routing.core.impl.DocumentRoutingServiceImpl$CompleteTaskRunner(org.nuxeo.ecm.core.api.UnrestrictedSessionRunner).runUnrestricted() line: 139 org.nuxeo.ecm.platform.routing.core.impl.DocumentRoutingServiceImpl.completeTask(java.lang.String, java.lang.String, org.nuxeo.ecm.platform.task.Task, java.util.Map<java.lang.String,java.lang.Object>, java.lang.String, org.nuxeo.ecm.core.api.CoreSession) line: 313 org.nuxeo.ecm.platform.routing.core.impl.DocumentRoutingServiceImpl.endTask(org.nuxeo.ecm.core.api.CoreSession, org.nuxeo.ecm.platform.task.Task, java.util.Map<java.lang.String,java.lang.Object>, java.lang.String) line: 841 org.nuxeo.ecm.restapi.server.jaxrs.routing.TaskObject.completeTask(java.lang.String, java.lang.String, org.nuxeo.ecm.restapi.server.jaxrs.routing.model.TaskCompletionRequest) line: 85 sun.reflect.NativeMethodAccessorImpl.invoke0(java.lang.reflect.Method, java.lang.Object, java.lang.Object[]) line: not available [native method] sun.reflect.NativeMethodAccessorImpl.invoke(java.lang.Object, java.lang.Object[]) line: 62 sun.reflect.DelegatingMethodAccessorImpl.invoke(java.lang.Object, java.lang.Object[]) line: 43 java.lang.reflect.Method.invoke(java.lang.Object, java.lang.Object...) line: 497 com.sun.jersey.spi.container.JavaMethodInvokerFactory$1.invoke(java.lang.reflect.Method, java.lang.Object, java.lang.Object...) line: 60 com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider$ResponseOutInvoker._dispatch(java.lang.Object, com.sun.jersey.api.core.HttpContext) line: 205 com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider$ResponseOutInvoker(com.sun.jersey.server.impl.model.method.dispatch.ResourceJavaMethodDispatcher).dispatch(java.lang.Object, com.sun.jersey.api.core.HttpContext) line: 75 com.sun.jersey.server.impl.uri.rules.HttpMethodRule.accept(java.lang.CharSequence, java.lang.Object, com.sun.jersey.spi.uri.rules.UriRuleContext) line: 302 com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(java.lang.CharSequence, java.lang.Object, com.sun.jersey.spi.uri.rules.UriRuleContext) line: 147 com.sun.jersey.server.impl.uri.rules.SubLocatorRule.accept(java.lang.CharSequence, java.lang.Object, com.sun.jersey.spi.uri.rules.UriRuleContext) line: 137 com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(java.lang.CharSequence, java.lang.Object, com.sun.jersey.spi.uri.rules.UriRuleContext) line: 147 com.sun.jersey.server.impl.uri.rules.SubLocatorRule.accept(java.lang.CharSequence, java.lang.Object, com.sun.jersey.spi.uri.rules.UriRuleContext) line: 137 com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(java.lang.CharSequence, java.lang.Object, com.sun.jersey.spi.uri.rules.UriRuleContext) line: 147 com.sun.jersey.server.impl.uri.rules.ResourceClassRule.accept(java.lang.CharSequence, java.lang.Object, com.sun.jersey.spi.uri.rules.UriRuleContext) line: 108 com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(java.lang.CharSequence, java.lang.Object, com.sun.jersey.spi.uri.rules.UriRuleContext) line: 147 com.sun.jersey.server.impl.uri.rules.RootResourceClassesRule.accept(java.lang.CharSequence, java.lang.Object, com.sun.jersey.spi.uri.rules.UriRuleContext) line: 84 com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(com.sun.jersey.server.impl.application.WebApplicationContext, com.sun.jersey.spi.container.ContainerRequest) line: 1542 com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(com.sun.jersey.server.impl.application.WebApplicationContext, com.sun.jersey.spi.container.ContainerRequest, com.sun.jersey.spi.container.ContainerResponse) line: 1473 com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(com.sun.jersey.spi.container.ContainerRequest, com.sun.jersey.spi.container.ContainerResponse) line: 1419 com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(com.sun.jersey.spi.container.ContainerRequest, com.sun.jersey.spi.container.ContainerResponseWriter) line: 1409 com.sun.jersey.spi.container.servlet.ServletContainer$InternalWebComponent(com.sun.jersey.spi.container.servlet.WebComponent).service(java.net.URI, java.net.URI, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) line: 409 com.sun.jersey.spi.container.servlet.ServletContainer.service(java.net.URI, java.net.URI, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) line: 558 com.sun.jersey.spi.container.servlet.ServletContainer.service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) line: 733 org.nuxeo.ecm.webengine.app.jersey.WebEngineServlet.containerService(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) line: 171 (out of synch) org.nuxeo.ecm.webengine.app.jersey.WebEngineServlet.service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) line: 148 (out of synch) org.nuxeo.ecm.webengine.app.jersey.WebEngineServlet(javax.servlet.http.HttpServlet).service(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 727 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 303 org.apache.catalina.core.ApplicationFilterChain.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 208 org.apache.tomcat.websocket.server.WsFilter.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) line: 52 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 241 org.apache.catalina.core.ApplicationFilterChain.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 208 org.nuxeo.ecm.webengine.app.WebEngineFilter.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) line: 92 (out of synch) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 241 org.apache.catalina.core.ApplicationFilterChain.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 208 org.nuxeo.ecm.webengine.jaxrs.session.SessionCleanupFilter.run(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain) line: 43 org.nuxeo.ecm.webengine.jaxrs.session.SessionCleanupFilter(org.nuxeo.ecm.webengine.jaxrs.HttpFilter).doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) line: 42 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 241 org.apache.catalina.core.ApplicationFilterChain.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 208 org.nuxeo.ecm.webengine.jaxrs.context.RequestContextFilter.run(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain) line: 41 org.nuxeo.ecm.webengine.jaxrs.context.RequestContextFilter(org.nuxeo.ecm.webengine.jaxrs.HttpFilter).doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) line: 42 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 241 org.apache.catalina.core.ApplicationFilterChain.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 208 org.nuxeo.ecm.platform.web.common.requestcontroller.filter.NuxeoThreadTrackerFilter.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) line: 28 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 241 org.apache.catalina.core.ApplicationFilterChain.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 208 org.nuxeo.ecm.core.management.jtajca.internal.Log4jWebFilter.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) line: 67 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 241 org.apache.catalina.core.ApplicationFilterChain.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 208 org.nuxeo.ecm.platform.ui.web.rest.FancyURLFilter.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) line: 118 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 241 org.apache.catalina.core.ApplicationFilterChain.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 208 org.nuxeo.ecm.platform.web.common.requestcontroller.filter.NuxeoRequestControllerFilter.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) line: 140 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 241 org.apache.catalina.core.ApplicationFilterChain.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 208 org.nuxeo.ecm.webdav.service.WIRequestFilter.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) line: 59 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 241 org.apache.catalina.core.ApplicationFilterChain.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 208 org.nuxeo.ecm.platform.ui.web.auth.NuxeoAuthenticationFilter.doFilterInternal(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) line: 580 org.nuxeo.ecm.platform.ui.web.auth.service.NuxeoAuthFilterChain.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 36 org.nuxeo.ecm.platform.ui.web.auth.oauth.NuxeoOAuthFilter.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) line: 120 org.nuxeo.ecm.platform.ui.web.auth.service.NuxeoAuthFilterChain.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 34 org.nuxeo.ecm.platform.ui.web.auth.oauth2.NuxeoOAuth2Filter.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) line: 68 org.nuxeo.ecm.platform.ui.web.auth.service.NuxeoAuthFilterChain.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 34 org.nuxeo.ecm.platform.ui.web.auth.NuxeoAuthenticationFilter.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) line: 393 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 241 org.apache.catalina.core.ApplicationFilterChain.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 208 org.nuxeo.ecm.platform.web.common.requestcontroller.filter.NuxeoCorsFilter(com.thetransactioncompany.cors.CORSFilter).doFilter(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain) line: 208 org.nuxeo.ecm.platform.web.common.requestcontroller.filter.NuxeoCorsFilter(com.thetransactioncompany.cors.CORSFilter).doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) line: 274 org.nuxeo.ecm.platform.web.common.requestcontroller.filter.NuxeoCorsFilter.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) line: 47 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 241 org.apache.catalina.core.ApplicationFilterChain.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 208 org.nuxeo.ecm.platform.web.common.exceptionhandling.NuxeoExceptionFilter.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) line: 75 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 241 org.apache.catalina.core.ApplicationFilterChain.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 208 org.nuxeo.ecm.platform.web.common.encoding.NuxeoEncodingFilter.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) line: 73 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 241 org.apache.catalina.core.ApplicationFilterChain.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse) line: 208 org.apache.catalina.core.StandardWrapperValve.invoke(org.apache.catalina.connector.Request, org.apache.catalina.connector.Response) line: 220 org.apache.catalina.core.StandardContextValve.invoke(org.apache.catalina.connector.Request, org.apache.catalina.connector.Response) line: 122 org.apache.catalina.authenticator.NonLoginAuthenticator(org.apache.catalina.authenticator.AuthenticatorBase).invoke(org.apache.catalina.connector.Request, org.apache.catalina.connector.Response) line: 503 org.apache.catalina.core.StandardHostValve.invoke(org.apache.catalina.connector.Request, org.apache.catalina.connector.Response) line: 170 org.apache.catalina.valves.ErrorReportValve.invoke(org.apache.catalina.connector.Request, org.apache.catalina.connector.Response) line: 103 org.apache.catalina.core.StandardEngineValve.invoke(org.apache.catalina.connector.Request, org.apache.catalina.connector.Response) line: 116 org.apache.catalina.connector.CoyoteAdapter.service(org.apache.coyote.Request, org.apache.coyote.Response) line: 421 org.apache.coyote.http11.Http11Processor(org.apache.coyote.http11.AbstractHttp11Processor<S>).process(org.apache.tomcat.util.net.SocketWrapper<S>) line: 1070 org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler(org.apache.coyote.AbstractProtocol$AbstractConnectionHandler<S,P>).process(org.apache.tomcat.util.net.SocketWrapper<S>, org.apache.tomcat.util.net.SocketStatus) line: 611 org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run() line: 316 org.apache.tomcat.util.threads.ThreadPoolExecutor(java.util.concurrent.ThreadPoolExecutor).runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) line: 1142 java.util.concurrent.ThreadPoolExecutor$Worker.run() line: 617 org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run() line: 61 org.apache.tomcat.util.threads.TaskThread(java.lang.Thread).run() line: 745
we can check that com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationContext, ContainerRequest, ContainerResponse) will catch the exception wihtout re-throwing it.
Then, later, the org.nuxeo.ecm.webengine.app.WebEngineFilter.doFilter(ServletRequest, ServletResponse, FilterChain) will call org.nuxeo.ecm.webengine.app.WebEngineFilter.cleanup(Config, AbstractWebContext, HttpServletRequest, HttpServletResponse) which will commit the Transaction despite there was an exception thrown.
As a result, regarding the travel-expenses sample, the task is marked as ended but the GraphRunner did not resume the workflow and we end up in a inconsistent workflow state.
I am not really sure about the fix, it seems that jersey has a bad design to handle this use case.
A simple (and dirty) fix could be:
--- i/nuxeo-webengine/nuxeo-webengine-core/src/main/java/org/nuxeo/ecm/webengine/app/WebEngineExceptionMapper.java +++ w/nuxeo-webengine/nuxeo-webengine-core/src/main/java/org/nuxeo/ecm/webengine/app/WebEngineExceptionMapper.java @@ -30,6 +30,7 @@ import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.api.validation.DocumentValidationException; import org.nuxeo.ecm.webengine.WebException; import org.nuxeo.ecm.webengine.model.exceptions.WebResourceNotFoundException; +import org.nuxeo.runtime.transaction.TransactionHelper; import com.sun.jersey.api.NotFoundException; @@ -46,6 +47,7 @@ public class WebEngineExceptionMapper implements ExceptionMapper<Throwable> { @Override public Response toResponse(Throwable cause) { + TransactionHelper.setTransactionRollbackOnly(); if (headers.getAcceptableMediaTypes().contains(APPLICATION_JSON_TYPE)) { if (cause instanceof DocumentValidationException) { DocumentValidationException dve = (DocumentValidationException) cause;
But ideally we should have a dedicated filter for Transaction Management correctly inserted in the filter execution chain. This filter would be able to detect exception as soon as possible and roll back transactions before jersey catches it.