Distributable J2EE Web Applications
A Container Provider's View of the current Servlet Specification.

The 'Java(tm) Servlet Specification, Version 2.4' makes a number of references to 'distributable' web applications and httpsession 'migration'. It states that compliant deployments "...can ensure scalability and quality of service features like load-balancing and failover..." (SRV.7.7.2). In today's demanding enterprise environments, such features are increasingly required. This paper sets out to distil and understand the relevant contents of the specification, construct a model of the functionality that this seems to support, assess this functionality with regard to feasibility and popular requirements and finally make suggestions as to how a compliant implementation might be architected.

Prerequisites.

TODO - A good understanding of what an HttpSession is, what it is used for and how it behaves will be necessary for a full understanding of this content. A comprehensive grasp of the requirements driving architectures towards clustering and of common cluster components (such as load-balancers) will also be highly beneficial.

The Servlet Specification - distilled:

When a webapp declares itself <distributable/> it enters into a contract with it's container. The Servlet Specification includes a dry bones description of this contract which we will distil from it and flesh out in this paper.

For a successful outcome the implementors of both Container and Containee need to be agreed on exactly what behaviour is expected of each other. For a really deep understanding of the contract they will need to know why it is as it is (TODO - This paper will provide such a view, from both sides).

The Specification mandates the following behaviour for distributable Servlets:

Non-Distributable Servlets

Only Servlets deployed within a webapp may be distributable. (TODO - Ed.: is there any other standard way to deploy a Servlet? Perhaps through the InvokerServlet?) (SRV.3.2) TODO - WHY?

Single Threaded Servlets

SingleThreadedModel Servlets, whilst discouraged (since it is generally more efficient for the Servlet writer, who understands the problem domain, to deal with application synchronisation issues) are limited to a single instance pool per JVM.(SRV.2.3.3.1)

Multi-Threaded Servlets

Multithreaded HttpServlets are restricted to one Servlet instance per JVM, thus delegating all application synchronisation issues to a single point where the Servlet's writer may resolve them with application-level knowledge (SRV.2.2).

Distributable State

The only state to be distributed will be the HttpSession. Thus all application state that requires distribution must be housed in an HttpSession or alternative distributed resource (e.g. EJB, DB, etc.). The contents of the ServletContext are NOT distributed. (SRV.3.2, SRV.3.4.1, SRV.14.2.8)

HttpSession Migration

Moving HttpSessions between process boundaries (i.e. from JVM to JVM, or JVM to store) is termed 'migration'.In order that the container should know how to migrate application-space Objects, stored in an HttpSession, they must be of mutually agreed type.

In a J2EE (Version 1.4) environment (e.g. in a web container embedded in an application server), the set of supported types for HttpSession attributes is as follows, although web containers are free to extend this set (J2EE.6.4): (Note that using an extended type would impact your webapp's portability).

Breaking this contract through use of an unagreed type will result in the container throwing an IllegalArgumentException upon its introduction to the HttpSession, since the container must maintain the migratability of this resource (SRV.7.7.2).

Migration Implementation

How migration is actually implemented is undefined and left up to the container provider (SRV.7.7.2). The application is not even guaranteed that the container will use readObject() and writeObject() (TODO explain) methods if they are present on an attribute. The only guarantee given by the specification is that their "serializable closure" will be "preserved" (SRV.7.7.2). This is to allow the container provider maximum flexibility in this area.

HttpSessionActivationListener

The specification describes an HttpSessionActivationListener interface. Attributes requiring notification before or after migration can implement this. The container will call their willPassivate() method just before passivation, thus giving them the chance to e.g. release non-serialisable resources. Immediately after activation the container will call their didActivate() method, giving them the chance to e.g. reacquire such resources. (SRV.7.7.2, SRV.10.2.1, SRV.15.1.7, SRV.15.1.8). Support for a number of other such listeners are required in a compliant implementation, but these are not directly related to session migration.

HttpSession Affinity

Given that: we can see that any implementation must resolve these apparently contradictory issues satisfactorily.

The Servlet Specification states:

"All requests that are part of a session must be handled by one Java Virtual Machine (JVM) at a time." (SRV.7.7.2).

The intention of this statement is to resolve such concurrency issues. It prunes the tree of possible implementations substantially, insisting that all concurrent requests for a particular session are delivered to the same node.

Delivering requests for the same session to the same node is known variously as 'session affinity', 'sticky sessions', persistent sessions' etc., depending on your container's vendor. The specification is trading complexity in the web-container tier for complexity in the load-balancer tier. This added requirement will impact the latency of this tier, in that the load-balancer will generally need to parse the uri or headers of each http request travelling through it (in a non-encrypted form) in order to extract the target session id. However, the reduction of potentially awkward concurrency issues/race conditions in the web-container tier is a gain considered worth this sacrifice.

It is worth noting that, since we have now introduced a requirement for the load-balancer tier to have knowledge of the location of httpsessions within the web-container tier, the ability to 'migrate' these objects may, therefore, require a certain amount of coordination between the two tiers.

Background Threads

The previous requirement reduces our problem from race conditions between distributed objects in different JVMs, to a situation where we simply have to manage coordination between multiple threads in the same JVM. The purpose of this coordination is to ensure that access to container managed resources that are available to multiple concurrent application space threads is properly synchronised.

Whilst the container has implicit knowledge about any thread, executing application code, for the lifecycle of which it is responsible (i.e. request threads), it has no control over any thread that is entirely managed by application code - Background thread. Such threads might execute across request boundaries, accessing otherwise predictably dormant resources that might otherwise be passivated or migrated elsewhere.

Fortunately, the specification also recommends that references to container-managed objects should not be given to threads that have been created by an application (SRV.2.3.3.3, SRV.S.17) and whose lifecycle is not entirely bounded by that of a request thread. The container is encouraged to generate warnings if this should occur. Application developers should understand that recommendations such as this become all the more important when working in a distributed environment.

We shall take "container-managed objects" to include any object that has been placed into an httpsession. By virtue of this placement, its lifecycle is now the responsibility of the encompassing session and ultimately therefore of the container. This is a useful constraint, since the container-provider may now prove that, provided that there are no request threads active for a session within the container, entirely thread-safe access may be made not only to an httpsession but also to its attributes, although their locking scheme, being application space components, is completely unknown to the container-provider.

HttpSession Events

Finally, given that HttpSessions are the only type to be distributed and that they should only ever be in one JVM at one time, it should come as no surprise that ServletContext and HttpSession events are not propagated outside the JVM in which they were raised (SRV.10.7) as this would result in container owned objects becoming active in a JVM through which no relevant request thread was passing.

Is this adequate ?

Armed now with a deeper understanding of exactly what the specification says about distributable webapps, we can begin to speculate on what a compliant implementation might look like.

The specification has done a reasonably good job of outlining our area of interest. Before implementing a container, however, there are a number of issues that we still need to address.

Catastrophic failure

TODO - Looking at what this specification actually says about distributable webapps, it can be seen immediately that it seems to reliably outline a mechanism for the controlled shutdown of a node and the attendant migration of it's sessions to [an]other node[s], or persistant storage.

The ability to migrate sessions on controlled shutdown is useful functionality (maintenance will be one of the main reasons behind the occurrence of session migration), but it does not go far enough for many enterprise-level users, who require a solution capable of transparent recovery, without data loss, even in the case of a node's catastrophic failure. If a node is simply switched off, thus having no chance to perform a shutdown sequence, then volatile state will simply be lost. It is too late to call HttpSessionActivationListener.willPassivate() where necessary and serialise all user state to a safe place! Container implementors must ask themselves the question - 'What, within the bounds of the current specification, can we do to mitigate this event?'.

Session Backup - When

The answer to the concern of lost data is to frequently ship backup copies off-node, so that in the case of its catastrophic failure, we have a fallback position. The freshness of our backup data depends directly on the frequency of this process. This frequency is bounded by resource concerns and the contract between container and containee, as discussed above.

Let us examine some of the possibilities:

REFACTORING HAS GOT TO HERE...

Session Backup - At what granularity ?

(TODO - requests do not have transactional semantics)

(TODO - if a single request reset an attribute a number of times, immediate xfer would be expensive, batching would also be expensive since each reset would involve a serialisation of which only the last would be useful (or can we leave this til the last moment?))

What can we do?