Understanding CLIENT_STATE_MAX_TOKENS

Introduction

One of our customers routinely received the following error – Because of inactivity you session is no longer active (…) – although the session was still valid. This error is related to Client State settings within web.xml. In this blog post we will explain and show how CLIENT_STATE_MAX_TOKENS works, how you can monitor and debug it on 11gR1/11gR2, and some approaches to better develop your application to avoid it.

Main Article

What state saving method should I use?

For a short pros and cons of each state saving method, please check this great blog post by Spyros Dougeridis. For the sake of this article, please consider the usage of client as the  state saving method because it seems to fit the needs of most web applications. For further details on which state saving method to use, please check Appendix A of ADF Faces documentation (11.1.1.5)

What does CLIENT_STATE_MAX_TOKENS control?

In a nutshell, this parameter is responsible for setting the caching size to hold the state of posted pages (more on this later) so they are browser-back-button-friendly. What this means is that for every page that issued a POST back to the server a ViewState token will be held in memory to track back it’s client state. In 11.1.1.4+, this cache is session based, which means that increasing the number of CLIENT_STATE_MAX_TOKENS will increase the per-session memory footprint on your server. Notice that in 11g R2 this cache is kept per-session, per-browser-window(or tab).

Each main page / portlet consumes a view state token in its host web application. Each JSF UIViewRoot instance consumes one view state instance. Hence the value of CLIENT_STATE_MAX_TOKENS should be >= the maximum number of UIViewRoot instances needed for the largest page in the application

 

How can I run out of  CLIENT_STATE_MAX_TOKENS?

Suppose we have CLIENT_STATE_MAX_TOKENS = 2, and we have 3 pages in our application: Page1, Page2 and Page3, where we sequentially navigate in this order: Page1-> Page3-> Page3

SampleCSTSAppSnap

In this case, 3 tokens will be created: Token1->Token2->Token3. Because we have set CLIENT_STATE_MAX_TOKENS = 2, the first token, Token1, will be removed and we will end up with Token2, Token3. Now, if we navigate back to Page1 using the browser’s back button and then re-posting the page, an error message – Because of inactivity you session is no longer active (…) – will be displayed:

SampleCSTSAppErrorSnap

Problem is, this message is very misleading.  The session didn’t expire; only the page (state) itself expired. If you’re a bit confused, just stay with me a little more – we will take a closer look at this sample application log output to understand what is going on.

How can I check what’s going on with  CLIENT_STATE_MAX_TOKENS?

Let’s run the sample application from the screenshots above and monitor what happens when the CLIENT_STATE_MAX_TOKENS = 2.

Up to 11.1.1.5, the only way to have a glimpse of what is going on with the tokens is to configure WebLogic’s logging.xml with these two entries:

<logger name="org.apache.myfaces.trinidadinternal.application.StateManagerImpl" level="FINEST"/>
<logger name="org.apache.myfaces.trinidadinternal.util.TokenCache" level="FINEST"/>

I’ve also added a custom MonitorPhaseListener for regular navigation and a custom MonitorViewHandler for PPR navigation. This is the generated output when running a sample application with the use case previously described: Page1 -> Page2 -> Page->3, then navigate back to Page1 and hit the submit button:

<MonitorPhaseListener> <renderView> /oracle/webcenter/portalapp/pages/Page1.jspx  (redirect from index.html)
<MonitorPhaseListener> <renderView> /oracle/webcenter/portalapp/pages/Page2.jspx  (Navigate to Page2 using a goLink from Navigation Mode)
<MonitorPhaseListener> <renderView> /oracle/webcenter/portalapp/pages/Page3.jspx  (Navigate to Page3 using a goLink from Navigation Model)
<TokenCache> <_removeTokenIfReady> Removing token ‘-pojokyl38’ (first token from Page1 removed)
    <<Clicked on browser’s back button all the way back to Page1 and pressed the submit (POST) button>>
<MonitorPhaseListener> <restoreView> <null>-/Page1
<StateManagerImpl> <restoreView> Restoring saved view state for token -pojokyl38
<StateManagerImpl> <restoreView> Could not find saved view state for token -pojokyl38
<LifecycleImpl> <_handleException> ADF_FACES-60098:Faces lifecycle receives unhandled exceptions in phase RESTORE_VIEW 1  javax.faces.application.ViewExpiredException: viewId:/Page1 – ADF_FACES-30107:The view state of the page has expired.  Reload the page.
   << Removed the rest of the Stack Trace >>
   <<  A popup is shown to the user telling that the session has expired and to click on ok to reload the page >>
   << Page has been refreshed, a new token will be generated for the GET request; this will remove an existing token >>
<TokenCache> <_removeTokenIfReady> Removing token ‘-pojokyl37’

The framework tried to locate token -pojokyl38 that has been already removed from the cache. The ADF Faces message output in the log file correctly states that the view state for the page has expired.

Although these messages are helpful, we are not quite seeing what’s in the cache and which page/view id the token is related to. Luckily, our development team added a new debug flag in 11.1.16+ and  11g R2 that generates a more understandable output. You can enable it by adding -Dorg.apache.myfaces.trinidadinternal.DEBUG_TOKEN_CACHE=true in the WebLogic server startup command.

 This is what the output looks like in 11.1.1.6+ / R2:

<MonitorPhaseListener> <renderView> /oracle/webcenter/portalapp/pages/Page1.jspx
<TokenCache> <addNewEntry>
Session Id = 1jqZPG0BfcMJG1bL0NrnJhKMWMFS1yhQrJm4DxQkyKh56JZTfTBR!-321710149!1334096993316
Window Id could not be determined, window is null
ADDING dzmu1ebtu (/oracle/webcenter/portalapp/pages/Page1.jspx)
After Additions
cached token keys:
dzmu1ebtu (/oracle/webcenter/portalapp/pages/Page1.jspx)
_pinned token keys:
<MonitorPhaseListener> <renderView> /oracle/webcenter/portalapp/pages/Page2.jspx
<TokenCache> <addNewEntry>
Session Id = 1jqZPG0BfcMJG1bL0NrnJhKMWMFS1yhQrJm4DxQkyKh56JZTfTBR!-321710149!1334096993316
Window Id could not be determined, window is null
ADDING dzmu1ebtv (/oracle/webcenter/portalapp/pages/Page2.jspx)
After Additions
cached token keys:
dzmu1ebtv (/oracle/webcenter/portalapp/pages/Page2.jspx)
dzmu1ebtu (/oracle/webcenter/portalapp/pages/Page1.jspx)
_pinned token keys:
<MonitorPhaseListener> <renderView> /oracle/webcenter/portalapp/pages/Page3.jspx
<TokenCache> <addNewEntry>
Session Id = 1jqZPG0BfcMJG1bL0NrnJhKMWMFS1yhQrJm4DxQkyKh56JZTfTBR!-321710149!1334096993316
Window Id could not be determined, window is null
ADDING dzmu1ebtw (/oracle/webcenter/portalapp/pages/Page3.jspx)
REMOVING dzmu1ebtu (/oracle/webcenter/portalapp/pages/Page1.jspx)
After Additions
cached token keys:
dzmu1ebtw (/oracle/webcenter/portalapp/pages/Page3.jspx)
dzmu1ebtv (/oracle/webcenter/portalapp/pages/Page2.jspx)
_pinned token keys:
<MonitorPhaseListener> <restoreView> <null>-/Page1
<StateManagerImpl> <restoreView> Could not find saved view state for token dzmu1ebtu
<StateManagerImpl> <restoreView>
Session Id = 1jqZPG0BfcMJG1bL0NrnJhKMWMFS1yhQrJm4DxQkyKh56JZTfTBR!-321710149!1334096993316
Window Id could not be determined, window is null
token ‘dzmu1ebtu’ not found
cached token keys:
dzmu1ebtw (/oracle/webcenter/portalapp/pages/Page3.jspx)
dzmu1ebtv (/oracle/webcenter/portalapp/pages/Page2.jspx)
<LifecycleImpl> <_handleException> ADF_FACES-60098:Faces lifecycle receives unhandled exceptions in phase RESTORE_VIEW 1

<<  A popup is shown to the user telling that the session has expired and to click OK to reload the page >>
<< Page has been refreshed, a new token will be generated for the GET request; this will remove an existing token>>

<MonitorPhaseListener> <renderView> /oracle/webcenter/portalapp/pages/Page1.jspx
<TokenCache> <addNewEntry>
Session Id = 1jqZPG0BfcMJG1bL0NrnJhKMWMFS1yhQrJm4DxQkyKh56JZTfTBR!-321710149!1334096993316
Window Id could not be determined, window is null
ADDING dzmu1ebtx (/oracle/webcenter/portalapp/pages/Page1.jspx)
REMOVING dzmu1ebtv (/oracle/webcenter/portalapp/pages/Page2.jspx)
After Additions
cached token keys:
dzmu1ebtx (/oracle/webcenter/portalapp/pages/Page1.jspx)
dzmu1ebtw (/oracle/webcenter/portalapp/pages/Page3.jspx)
_pinned token keys:

The new logging/debugging option provides a much better view of how the view cache token  instances are being managed. We can clearly see that the token for Page1 was removed from the cache to open space for Page3, while after getting the error message and refreshing Page1 we can see its new token been added to the cache and Page2’s token being removed.

How can I fine-tune CLIENT_STATE_MAX_TOKENS?

This will depend on how users navigate and use the application you built, and of course will be also influenced by how you are structuring navigation and submission (GET vs POST requests). Each main page / portlet (UIViewRoot) consumes a view state token in its host web application. Trinidad has an internal default value of 15, while WebCenter Portal apps overwrite this value to 3 on their web.xml files. The best way to gauge what number needs to be set is to select a couple of critical navigation scenarios, for example, a shopping cart, and some regular browsing scenarios. You can then ask your users to execute those scenarios while you record that with a proxy tool like the one that comes with JMeter so you can than play it back and capture how the token cache is behaving.

You can then load test the server to check for session memory footprint, as the cache is kept at the session level – for example, with  CLIENT_STATE_MAX_TOKENS = 3 and 10 active HttpSessions you will end up with 30 elements in memory; if you set CLIENT_STATE_MAX_TOKENS to 10 you end up with 100 elements in memory. The per-cached-element footprint will depend on how complex your pages are.

What happens with CLIENT_STATE_MAX_TOKENS if I use PPR Navigation?

Out of the box, in WebCenter Portal applications you will notice an entry on web.xml defining the usage of PPR navigation to onWithForcePPR. There are differences on how PPR Navigation works on 11g R1 and 11g R2, which will be subject of a future post. For now, I will cover the behavior on PS5 mostly because it is the current release leverage by both WebCenter and SOA products. To get PPR navigation to work on PS5 I will need to change the af:goLink back to af:commandLink for it to work – the usage of af:goLink for WebCenter Portal applications is recommended if you want search engines to be able to crawl your website.

I will slightly modify the sample app to render a navigation menu with af:commandLink right after the af:goLink and re-run it. Notice the new “(PPR)” links added to the template:

TokenBackButtonPPR

The difference in this scenario is that now the navigation is been done through POST rather than GET. When you click the back button the browser is forcefully re-submitting the POST for the page which still has a valid token, and then subsequently doing a GET which will force the generation of a new entry in the cache. In this won’t see the error message because the token is being forcefully re-generated upon the back button operation:

Target URL — http://127.0.0.1:7101/csts/index.html

<< START OF Page1 –> Page2 –> Page3 NAVIGATION >>

<TokenCache> <addNewEntry>
Session Id = vT8qPMJWdqh1KvJmLkhLBgYFrncmbWcS2nM8Zp9PG6wX8dhCBfvW!1506587775!1334593942472
Window Id could not be determined, window is null
ADDING ejbmwawnz (/oracle/webcenter/portalapp/pages/Page1.jspx)
After Additions
cached token keys:
ejbmwawnz (/oracle/webcenter/portalapp/pages/Page1.jspx)
_pinned token keys:
<TokenCache> <addNewEntry>
Session Id = vT8qPMJWdqh1KvJmLkhLBgYFrncmbWcS2nM8Zp9PG6wX8dhCBfvW!1506587775!1334593942472
Window Id could not be determined, window is null
ADDING ejbmwawo0 (/oracle/webcenter/portalapp/pages/Page2.jspx)
After Additions
cached token keys:
ejbmwawnz (/oracle/webcenter/portalapp/pages/Page1.jspx)
ejbmwawo0 (/oracle/webcenter/portalapp/pages/Page2.jspx)
_pinned token keys:
<TokenCache> <addNewEntry>
Session Id = vT8qPMJWdqh1KvJmLkhLBgYFrncmbWcS2nM8Zp9PG6wX8dhCBfvW!1506587775!1334593942472
Window Id could not be determined, window is null
ADDING ejbmwawo1 (/oracle/webcenter/portalapp/pages/Page3.jspx)
REMOVING ejbmwawnz (/oracle/webcenter/portalapp/pages/Page1.jspx)
After Additions
cached token keys:
ejbmwawo1 (/oracle/webcenter/portalapp/pages/Page3.jspx)
ejbmwawo0 (/oracle/webcenter/portalapp/pages/Page2.jspx)
_pinned token keys:

<< END OF Page1 –> Page2 –> Page3 NAVIGATION >>

<< START OF BACK BUTTON NAVIGATION Page3 –> Page2 –> Page1 >>

<< BACK BUTTON: Page3 –> Page2 >>

<TokenCache> <addNewEntry>
Session Id = vT8qPMJWdqh1KvJmLkhLBgYFrncmbWcS2nM8Zp9PG6wX8dhCBfvW!1506587775!1334593942472
Window Id could not be determined, window is null
ADDING ejbmwawo2 (/oracle/webcenter/portalapp/pages/Page2.jspx) << New token generated for Page2 >>
REMOVING ejbmwawo0 (/oracle/webcenter/portalapp/pages/Page2.jspx)
After Additions
cached token keys:
ejbmwawo2 (/oracle/webcenter/portalapp/pages/Page2.jspx)
ejbmwawo1 (/oracle/webcenter/portalapp/pages/Page3.jspx)
_pinned token keys:

<< === BACK BUTTON: Page2 –> Page1 === >>

<TokenCache> <addNewEntry>
Session Id = vT8qPMJWdqh1KvJmLkhLBgYFrncmbWcS2nM8Zp9PG6wX8dhCBfvW!1506587775!1334593942472
Window Id could not be determined, window is null
ADDING ejbmwawo3 (/oracle/webcenter/portalapp/pages/Page1.jspx) << New token generated for Page1 >>
REMOVING ejbmwawo1 (/oracle/webcenter/portalapp/pages/Page3.jspx)
After Additions
cached token keys:
ejbmwawo3 (/oracle/webcenter/portalapp/pages/Page1.jspx)
ejbmwawo2 (/oracle/webcenter/portalapp/pages/Page2.jspx)
_pinned token keys:

What are the other common scenarios where I can get an ADF_FACES-30107/30108 messages?

  • ADF_FACES-30107: A common scenario for receiving it  is when you are not correlating the ViewState variable on your load script. This is a very common mistake and you will see an entry in your log files saying that the ViewState value is null. For more information on how to set up your load scripts for ADF / WebCenter applications, check the links below:
  • ADF_FACES-30108: this means that a  previously created token in trying to be found in the cache, but the (http) session is new, thus the cache is empty. This is a common problem on clustered environments that don’t have the right load-balancer rules to support “sticky sessions”

 Conclusion

In this post I’ve done a deep dive into some of the state saving features of ADF Faces/Trinidad and how to monitor and overcome of its related issues. If you want to know more about this subject feel free to leave a message on the comments area.  Thanks Max Starets and the rest of the ADF Faces dev team for providing some of the information on this post.

Comments

  1. Don Kleppinger says:

    Is there anyway to programatically release a client state token? I don’t need back button state saved but every time I navigate in one window it burns a token which eventually when I run out of tokens it invalidates the state of a page open in another window if that page has not been used in the mean time. I would like it to release page state as soon as I navigate away from page.

Add Your Comment