Extend Trinidad HA testing support to check whether modified Session and App
Attributes have been dirtied
---------------------------------------------------------------------------------------------------------
Key: TRINIDAD-1856
URL: https://issues.apache.org/jira/browse/TRINIDAD-1856
Project: MyFaces Trinidad
Issue Type: New Feature
Components: Archetype
Affects Versions: 1.0.12-core, 2.0.0.3-core
Environment: All
Reporter: Blake Sullivan
A common developer error when supporting high availability is to modify a
Serializable Session or ServletContext(Application) attribute and forget to
dirty the relevant attribute by resetting it in the Session or ServletContext.
This is even easier to forget when the contents of the attribute are mutable.
For example, a Session attribute that contains a Map. In this case, whenever
the map is modified, the relevant Session attribute would need to be modified
as well. We propose that we extend the current Trinidad high availability
checking to handle this case as well.
Trinidad currently supports the following system property for checking various
high availability features:
org.apache.myfaces.trinidad.CHECK_STATE_SERIALIZATION
With the following case insensitive comma-separated options:PROPERTY,
COMPONENT, TREE, SESSION, APPLICATION, NONE, ALL
To support checking for mutated beans, we will add the following additional
option:BEANS
If the Beans property is specified and Session attribute serialization is being
checked (either by the presence of ALL or SESSION), the Session attributes
will be checked for mutation without dirtying. If Application attribute
serialization is being checked (either by the presence of ALL or APPLICATION),
the Application attributes will be checked for mutation without dirtying.
New apis:
on org.apache.myfaces.trinidad.bean.util.StateUtils:
/**
* Returns <code>true</code> if the attributes of the session and application
Maps should be
* checked for cases where the attribute was mutated but not dirtied for
failover. If
* <code>checkSessionSerialization</code> returns <code>true</code>, the
contents of the
* Session should be checked. If <code>checkApplicationSerialization</code>
returns
* <code>true</code>, the Serializable content of the Application should be
checked.
* @return true if the contents of scopes should be checked for mutation
without dirtying.
* @see #checkApplicationSerialization
* @see #checkSessionSerialization
*/
public static boolean checkManagedBeanMutation(ExternalContext extContext)
{
return _CHECK_MANAGED_BEAN_MUTATATION;
}
In order to catch mutations to non-HTTP servlet environments we need need to
wrap the Session and Application Maps. (for HttpRequests, we wrpa the
underlying ServletContext and Session objects in order to catch direct
mutations). To do so, the following apis are added to
org.apache.myfaces.trinidad.util.CollectionUtils
/**
* Interface for trapping mutations to a Map.
* @param <K> the type of the keys of the Map that MapMutationHooks are
associated with
* @param <V> the type of the values of the Map that MapMutationHooks are
associated with
* @see #newMutationHookedMap
*/
public interface MapMutationHooks<K, V>
{
/**
* Called when the associated Map of the MapMutationHooks is written to
* @param key key of entry that has changed
* @param value value of entry that has changed
*/
public void writeNotify(K key, V value);
/**
* Called when an entry is removed from the associated Map of the
MapMutationHooks
* @param key key of entry that has been removed
*/
public void removeNotify(Object key);
}
/**
* Creates a new Map that informs the MapMutationHooks of any direct
mutations. Mutations to
* the underlying Map will not be caught.
* If the base map is Serializable, the returned Map will be Serializable
* @param <K> type of the keys of the Map
* @param <V> type of the values of the Map
* @param map Underlying map to trap mutations of
* @param hooks MapMutationHooks to inform of mutations to the returned Map
* @return a new Map that traps the mutations to the underlying Map
* @throws NullPointerException if map or hooks are null
*/
public static <K,V> Map<K, V> newMutationHookedMap(Map<K, V> map,
MapMutationHooks<K, V> hooks)
How it works:
We want to check that attributes that existed in the scope to be checked at the
beginning of the request are either identical at the end of the request, or
were dirtied. To do so, we hook the TrinidadImpl and at the beginning of the
request, to check a scope like the Session:
1) Iterate the attributes of the Session
2) Get the value
3) If the value is known to be immutable, do nothing
4) If it is mutable and Serializable, serialize it and save the bytes
We then wrap all of the means of mutating the scope in question and trap the
mutations. When a mutation occurs, we remove our serialized entry
At the end of the request, for each remaining entry, we fetch the current value
from the scope, serialize it and compare the bytes. If the bytes are different
we have a problem and we log a severe error (since it is too late to throw an
exception)
Debugging problems found by this mechanism is unpleasant, so to make things
easier, we do the following:
Deserialize both the old and new bytes back to objects for dumping into the
message
We call .equals() on the objects. If the objects implement a equals(), this
can make finding the difference much easier.
In general, good toString() and equals() implementations are quite handy. A
general purpose object tree diffing tool would be extremely helpful, but
outside the scope of this work
--
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.