8 Replies Latest reply on Sep 11, 2006 9:31 AM by knaas

    Long Running Conversation Timeouts are never occuring

      We are having issues with conversations being timed out in 1.0.1GA. This may be fixed in the CVS version.

      We have set the org.jboss.seam.core.Manager.conversationTimeout to 15 seconds through use of a components.xml.

      After pausing on a conversational page for 30 seconds, we attempt to see if the conversation did timeout. When we click the "Save" link on our page, it allows the request to go through without redirecting to the "No Conversation" page.

      In order to debug the issue, we set some breakpoints in Manager.setLongRunningConversation, Manager.storeConversation, and Manager.conversationTimeout to
      see how things are playing out.

      When we click the Save link, the first thing that happens is the conversation is restored.

      At this point, the longRunningConversation is set to "true".
      Stack trace

      Thread [http-0.0.0.0-8080-2] (Suspended (entry into method setLongRunningConversation in Manager))
       Manager.setLongRunningConversation(boolean) line: 294
       Manager.restoreConversation(String) line: 587
       Manager.restoreConversation(Map) line: 521
       AbstractSeamPhaseListener.restoreAnyConversationContext(FacesContext) line: 41
       SeamPhaseListener.afterPhase(PhaseEvent) line: 63
       PhaseListenerManager.informPhaseListenersAfter(PhaseId) line: 89
       LifecycleImpl.restoreView(FacesContext, PhaseListenerManager) line: 181
       LifecycleImpl.execute(FacesContext) line: 66
       FacesServlet.service(ServletRequest, ServletResponse) line: 137
       ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 252
       ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 173
       ExtensionsFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 144
       ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 202
       ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 173
       SeamRedirectFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 30
       ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 202
       ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 173
       ReplyHeaderFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 96
       ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 202
       ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 173
       StandardWrapperValve.invoke(Request, Response) line: 213
       StandardContextValve.invoke(Request, Response) line: 178
       SecurityAssociationValve.invoke(Request, Response) line: 175
       BasicAuthenticator(AuthenticatorBase).invoke(Request, Response) line: 524
       JaccContextValve.invoke(Request, Response) line: 74
       StandardHostValve.invoke(Request, Response) line: 126
       ErrorReportValve.invoke(Request, Response) line: 105
       StandardEngineValve.invoke(Request, Response) line: 107
       CoyoteAdapter.service(Request, Response) line: 148
       Http11Processor.process(InputStream, OutputStream) line: 869
       Http11Protocol$JmxHttp11ConnectionHandler(Http11BaseProtocol$Http11ConnectionHandler).processConnection(TcpConnection, Object[]) line: 664
       PoolTcpEndpoint.processSocket(Socket, TcpConnection, Object[]) line: 527
       MasterSlaveWorkerThread.run() line: 112
       ThreadWithAttributes(Thread).run() line: 595
      


      Because the longRunningConversation is "true", the conversation is touched. When the conversation is touched, the ConversationEntry.lastRequestTime is updated to the current time.

       public void storeConversation(ContextAdaptor session, Object response)
       ....
       if ( isLongRunningConversation() )
       {
       touchConversationStack();
       if ( !Seam.isSessionInvalid() )
       {
       forceMutableComponentReplication();
       storeLongRunningConversation(response);
       }
       }
       else
       {
       discardTemporaryConversation(session, response);
       }
      
      

      Stack trace
      Thread [http-0.0.0.0-8080-1] (Suspended (entry into method touchConversationStack in Manager))
       Manager.touchConversationStack() line: 164
       Manager.storeConversation(ContextAdaptor, Object) line: 368
       AbstractSeamPhaseListener.storeAnyConversationContext(FacesContext) line: 69
       SeamStateManager.saveSerializedView(FacesContext) line: 45
       FaceletViewHandler.renderView(FacesContext, UIViewRoot) line: 578
       LifecycleImpl.render(FacesContext) line: 384
       FacesServlet.service(ServletRequest, ServletResponse) line: 138
       ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 252
       ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 173
       ExtensionsFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 144
       ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 202
       ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 173
       SeamRedirectFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 30
       ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 202
       ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 173
       ReplyHeaderFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 96
       ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 202
       ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 173
       StandardWrapperValve.invoke(Request, Response) line: 213
       StandardContextValve.invoke(Request, Response) line: 178
       SecurityAssociationValve.invoke(Request, Response) line: 175
       BasicAuthenticator(AuthenticatorBase).invoke(Request, Response) line: 524
       JaccContextValve.invoke(Request, Response) line: 74
       StandardHostValve.invoke(Request, Response) line: 126
       ErrorReportValve.invoke(Request, Response) line: 105
       StandardEngineValve.invoke(Request, Response) line: 107
       CoyoteAdapter.service(Request, Response) line: 148
       Http11Processor.process(InputStream, OutputStream) line: 869
       Http11Protocol$JmxHttp11ConnectionHandler(Http11BaseProtocol$Http11ConnectionHandler).processConnection(TcpConnection, Object[]) line: 664
       PoolTcpEndpoint.processSocket(Socket, TcpConnection, Object[]) line: 527
       MasterSlaveWorkerThread.run() line: 112
       ThreadWithAttributes(Thread).run() line: 595
      


      Finally, the conversation timeout check occurs. Because the ConversationEntry.lastRequestTime was updated a few milliseconds ago, the delta is never going to be bigger than the conversationEntry.getTimeout(). Note that at this point conversationEntry.getTimeout() is correctly returning "15000".

      /**
       * Clean up timed-out conversations
       */
       public void conversationTimeout(ExternalContext externalContext)
       {
       long currentTime = System.currentTimeMillis();
       Iterator<Map.Entry<String, ConversationEntry>> entries = getConversationIdEntryMap().entrySet().iterator();
       while ( entries.hasNext() )
       {
       Map.Entry<String, ConversationEntry> entry = entries.next();
       ConversationEntry conversationEntry = entry.getValue();
       long delta = currentTime - conversationEntry.getLastRequestTime();
       if ( delta > conversationEntry.getTimeout() )
       {
       String conversationId = entry.getKey();
       log.debug("conversation timeout for conversation: " + conversationId);
       ContextAdaptor session = ContextAdaptor.getSession(externalContext, true);
       destroyConversation(conversationId, session, entries);
       }
       }
       }
      


      Stack trace
      Thread [http-0.0.0.0-8080-1] (Suspended (entry into method conversationTimeout in Manager))
       Manager.conversationTimeout(ExternalContext) line: 316
       SeamPhaseListener.afterPhase(PhaseEvent) line: 93
       PhaseListenerManager.informPhaseListenersAfter(PhaseId) line: 89
       LifecycleImpl.render(FacesContext) line: 391
       FacesServlet.service(ServletRequest, ServletResponse) line: 138
       ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 252
       ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 173
       ExtensionsFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 144
       ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 202
       ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 173
       SeamRedirectFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 30
       ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 202
       ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 173
       ReplyHeaderFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 96
       ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 202
       ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 173
       StandardWrapperValve.invoke(Request, Response) line: 213
       StandardContextValve.invoke(Request, Response) line: 178
       SecurityAssociationValve.invoke(Request, Response) line: 175
       BasicAuthenticator(AuthenticatorBase).invoke(Request, Response) line: 524
       JaccContextValve.invoke(Request, Response) line: 74
       StandardHostValve.invoke(Request, Response) line: 126
       ErrorReportValve.invoke(Request, Response) line: 105
       StandardEngineValve.invoke(Request, Response) line: 107
       CoyoteAdapter.service(Request, Response) line: 148
       Http11Processor.process(InputStream, OutputStream) line: 869
       Http11Protocol$JmxHttp11ConnectionHandler(Http11BaseProtocol$Http11ConnectionHandler).processConnection(TcpConnection, Object[]) line: 664
       PoolTcpEndpoint.processSocket(Socket, TcpConnection, Object[]) line: 527
       MasterSlaveWorkerThread.run() line: 112
       ThreadWithAttributes(Thread).run() line: 595
      


      If you artificially put a 20 second sleep in the Manager.conversationTimeout function, it will correctly detect that the conversation did indeed timeout.
      It will then call destroyConversation. However, it fails to redirect to the outcome we specified in our @Conversational annotation. Instead, it
      allows our "Save" request to continue as normal and renders the "Saved" page.

      Finally, if we click a link on this "Saved" page, it detects that the conversation did indeed timeout, and thus redirects
      to the @Conversational Annotations's ifNotBegunOutcome.

      So there are multiple issues:

      1. The long running conversation will never timeout since the lastRequestTime is always updated before the conversationTimeout check occurs.
      2. Even if the conversation timeout check picks up the conversation was timedout, it still allows the method to continue. Note that fixing Issue 1, may
      prevent Issue 2 from ever occurring.




        • 1. Re: Long Running Conversation Timeouts are never occuring
          gavin.king

          The "foreground conversation" never times out. You need >1 conversation to observe timeouts.

          • 2. Re: Long Running Conversation Timeouts are never occuring

            In order to get the correct behavior, I will assume that we need to make our conversationTimeout to be equal to the session timeout.

            This is good to know since it is the single biggest issue reported by the users of our Seam based application.




            • 3. Re: Long Running Conversation Timeouts are never occuring

              Also, just to clarify, what is the "foreground conversation"?

              Is it the conversation associated with the current request?

              So the only conversations that would ever get cleaned up are those not part of the current request.

              Scenario:

              I have two browser instances open and I leave my desk for 20 minutes. When I come back to my desk, I click on a link in one of the browser windows.

              If I clicked a link in Browser A, it will only expire the conversation for Browser B. However, If I clicked a link in Browser B, it will expire the conversation for Browser A.

              This seems unintuitive.

              • 4. Re: Long Running Conversation Timeouts are never occuring
                gavin.king

                Right.

                And this is very desirable behavior :-) Not at all unintuitive.

                • 5. Re: Long Running Conversation Timeouts are never occuring

                  This way "idle" conversations are timed out, yet the conversation that you're currently in will last as long as your session lasts or until you finish the conversation.

                  You don't want the foreground work you're currently doing to be ripped away from you. But you do want a way for the resources tied up by background conversations to be cleaned up.

                  • 6. Re: Long Running Conversation Timeouts are never occuring
                    gavin.king

                    Right, exactly :-)

                    • 7. Re: Long Running Conversation Timeouts are never occuring
                      gavin.king

                      By the way, if anyone would find it useful, I can provide an alternative policy that enforces a maximum number of conversations per user, instead of the simple timeout.

                      • 8. Re: Long Running Conversation Timeouts are never occuring



                        Thanks for the clarifications on this.

                        If I were developing a new application that allowed users to hold onto tickets for five minutes, I would have originally thought that I could simply use the Conversation timeout. But since this is not the case, it would be important that I understand that I need to design the the ticket timeout behavior into my code.

                        In order to avoid further confusion on this issue, the documentation in 11.4 and 11.8 should probably be clarified.

                        E.g in 11.8


                        Internal component for Seam page and conversation context management. Always installed.
                        * org.jboss.seam.core.manager.conversationTimeout ? the conversation context timeout in milliseconds.


                        Could be changed to something like

                        Internal component for Seam page and conversation context management. Always installed.
                        * org.jboss.seam.core.manager.conversationTimeout ? the non foreground conversation context timeout in milliseconds. A non foreground conversation is a conversation not associated with the current request.