Google App Engine HTTP Session vs JSF [en]

Google App Engine (GAE) is a Platform as a Service system providing (among others) Java platform with a servlet container. GAE uses multiple web servers to run an application. A given request may be routed to any server and it may not be the same server that handled a previous request from the same user. GAE provides (after turning it on) an implementation of sessions using the servlet session interface. The session is replicated between server instances which causes serious problems with JSF session beans.

According to the documentation, the session attributes that are set with HttpSession#setAttribute during the request are saved to the datastore at the end of the request. App Engine stores a serialized session in the memcache and datastore to replicate it between server instances. The problem is that JSF session beans are put into the session only once after being created. Usually, they are not immutable and thus changing their state doesn’t cause invoking HttpSession#setAttribute. What’s more, I’ve noticed that the session state is persisted only when a different object than the one already present in the session is added for a given key in HttpSession#setAttribute. I’ve observed that the decision to mark the session as being designated for serialization is made inside HttpSession#setAttribute in this way:

 Object previousValue = internalMap.put(beanKey, bean);
 boolean mark = (previousValue == null) ? true : bean.equals(previousValue);

As a result, internal session bean state changes may not be visible in other server instances. (This is most probable in new Mojarra versions if javax.faces.STATE_SAVING_METHOD context parameter is set to client in web.xml.)


There are three ways to tackle this problem:

  1. not to use nonimmutable session beans
  2. force session replication at the end of processing each request
  3. force session replication at the end of processing a request if any session bean state changes

The decision not to use nonimmutable session beans can cause many difficulties (e.g. when migrating from another platform) and is definitely troublesome.

The second solution, i.e. forcing the session replication after processing each request, assures compatibility with JSF in a simple way, but it adds significant overhead. Forcing the session replication is not easy. There is no API for this. According to my experience, you need to put an attribute into a session each time using a different value. It will cause serialization and replication of the whole session. Since filter com.google.apphosting.runtime.jetty.SaveSessionFilter is responsible for running the replication after processing a request, placing the right code in PhaseListener (or servlet filter) solves the problem:

public class ForceSessionSerializationPhaseListener implements PhaseListener {
 
    private static final Logger LOGGER = Logger.getLogger(ForceSessionSerializationPhaseListener.class.getName());
 
    private void touchSession() {
        LOGGER.finest("forcing session serialization");
        final FacesContext facesContext = FacesContext.getCurrentInstance();
        final Map sessionMap = facesContext.getExternalContext().getSessionMap();
        sessionMap.put("forceGaeSessionSerialization", System.currentTimeMillis());
    }
    public void afterPhase(final PhaseEvent event) {
        touchSession();
    }
    public PhaseId getPhaseId() {
        return PhaseId.INVOKE_APPLICATION;
    }
    public void beforePhase(PhaseEvent event) {
    }
}

The last solution, i.e. forcing session replication if any session bean state changes, is the most complicated one, but its performance is better than that of the approach described previously. There are two possible ways to implement it.
One is to provide a method like ForceSessionSerializationPhaseListener#touchSession (presented above) in all session beans. This method should be invoked by a programmer after each and every change in the bean state. I must admit it’s not very practical.
The most advanced approach is the authomatic detection of session bean state changes. I’ve managed to achieve this by using AspectJ. I have created an aspect responsible for detecting any changes in the bean states annotated with @SessionScoped. This soultion, however, works only for session beans with annotations.

import java.lang.reflect.Field;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
 
/**
 * @author Zacheusz
 */
@Aspect
public class WatchSessionBeanModification {
 
    private static ThreadLocal<Boolean> threadLocalFlag = new ThreadLocal<Boolean>();
 
    public static boolean wasModified() {
        return Boolean.TRUE.equals(threadLocalFlag.get());
    }
    private void markChanged() {
        threadLocalFlag.set(true);
    }
    @Around("set(!static !final !transient * (@javax.faces.bean.SessionScoped *) . *) "
        + "&& args(newVal) && target(t)")
    public final void afterSetField(final ProceedingJoinPoint jp,
            final Object t, final Object newVal) throws Throwable {
        final Object oldVal = getOldVal(jp, t);
        if((oldVal != null && !oldVal.equals(newVal) )||
            (newVal != null && !newVal .equals(oldVal))) {
            markChanged();
        }
        jp.proceed();
    }
    private Object getOldVal(final ProceedingJoinPoint jp, final Object t) throws Exception{
        final Field field = t.getClass().getDeclaredField(jp.getSignature().getName());
        field.setAccessible(true);
        return field.get(t);
    }
}

Bearing in mind performance I have decided to use compile time waving, but this solution doesn’t require us to do so. In my opinion, using Perscope instead of direct ThreadLocal in the aspect is worth considering. To force the session replication I have employed a smiliar PhaseListener to the one mentioned earlier:

public class ForceModifiedSessionSerializationPhaseListener implements PhaseListener {
 
    private static final Logger LOGGER = Logger.getLogger(ForceModifiedSessionSerializationPhaseListener.class.getName());
 
    private void touchSession() {
        LOGGER.finest("forcing session serialization");
        final FacesContext facesContext = FacesContext.getCurrentInstance();
        final Map<String, Object> sessionMap = facesContext.getExternalContext().getSessionMap();
        sessionMap.put("forceGaeSessionSerialization", System.currentTimeMillis());
    }
    public void afterPhase(final PhaseEvent event) {
        if(WatchSessionBeanModification.wasModified()) {
            touchSession();
        }
    }
    public PhaseId getPhaseId() {
        return PhaseId.RENDER_RESPONSE;
    }
    public void beforePhase(PhaseEvent event) {
    }
}

The WatchSessionBeanModification aspect detects changes of the beans states and turns the flag on. The ForceModifiedSessionSerializationPhaseListener phase listener examines the flag and forces the replication of the session at the end of processing the request. Phase listener could be easily substituted with a servlet filter.

All the above mentioned solutions work. They were tested with given settings in appengine-web.xml:

<sessions-enabled>true</sessions-enabled>
<threadsafe>false</threadsafe> <!-- default value - send requests serially to a given web server -->
<async-session-persistence enabled="false" queue-name=""/>  <!-- default value - synchronous session replication -->

By default, GAE sends requests serially to a given web server. It is possible to disable this feature, but then we will have to face a greater number of potential concurrency hazards. If you configure your application to asynchronously write https session data by adding <async-session-persistence enabled="true"/> to appengine-web.xml, you have to take into account additional risks that stem from the asynchronous replication. The fact that documentation of this mechanism has not been published yet makes it more unpredictable. I would like to point out that you have to pay close attention to all the possible changes that may occur in the future in the implementation of session replication mechanism. It is important, since all the presented solutions are based on the assumption, that with each change the whole session is persisted and replicated.

Particularly interesting is the fact that a lot of people use JFS on GAE and for sure most of them use session beans, but still the Internet does not contain many traces of the problem touched upon here. I assume that the same problem may also occur in other frameworks.

Zacheusz Siedlecki

Komentarze

4 komentarzy do “Google App Engine HTTP Session vs JSF [en]”

  1. Lionel on September 20th, 2011 2:51 am

    Thanks so much. I’ve had the same problem with spring beans using session scope where the changes were not being persisted between requests. Googling didn’t give me any information on this limitation so I’ve spent hours trying to figure out what was wrong with my app. Took me a while to realise the problem was that sessions weren’t being persisted. Really feels like a google bug to me but your approach is a good work around.

  2. Lionel on September 20th, 2011 11:51 am

    Is there are reason why you used INVOKE_APPLICATION phase? I found its not always executed, like when using event listeners instead of action methods. RENDER_RESPONSE seems a more reliable phase to be invoking after.

  3. Zacheusz Siedlecki on September 20th, 2011 2:26 pm

    @Lionel: you are right – RENDER_RESPONSE will be more appropriate.

  4. libik on October 24th, 2011 4:42 pm

    OH MY GOD, It is working!!!!

    Tx, tx a lot, you saved my ass :)

Chcesz coś napisać?





*