dynamic reconfiguration
jhalliday Aug 31, 2011 6:36 AMThe configuration mechanism in JBossTS has evolved over the years, most recently to use a set of javabeans to hold the myriad config values. There are a dozen or so EnvironmentBeans, roughly one per module, and a central repository (BeanPopulator) that holds a singleton instance of each. The singletons are instantiated on demand with initial values populated from an embedded properties file or system properties. These defaults can then be overridden programatically, either by a bean wiring framework using xml (as in older JBossAS transaction-jboss-beans.xml) or in java code using the bean's setter methods (as in AS6 ArjunaTransactionManagerService etc). So far so good - this approach provides type safety for the values as well as compatibility with modern DI frameworks and is a big improvement over what we has before.
It still has one significant shortcoming though: once the transaction system is initialized, any subsequent config changes are ignored. That would not be so bad, except that the initialization can happen very early. Specifically we have a lot of static initializers that read and cache config values at class load time. e.g.
{code}
public class SomeThing
{
private static final boolean XA_TRANSACTION_TIMEOUT_ENABLED;
static
{
XA_TRANSACTION_TIMEOUT_ENABLED = jtaPropertyManager.getJTAEnvironmentBean().isXaTransactionTimeoutEnabled();
}
public void doThing()
{
XA_TRANSACTION_TIMEOUT_ENABLED ? thing() : otherThing();
}
}
public class jtaPropertyManager
{
public static JTAEnvironmentBean getJTAEnvironmentBean()
{
return BeanPopulator.getDefaultInstance(JTAEnvironmentBean.class);
}
}
public class BeanPopulator
{
private static final ConcurrentMap<String, Object> beanInstances = new ConcurrentHashMap<String, Object>();
public static <T> T getDefaultInstance(Class<T> beanClass) throws RuntimeException {
return (T) beanInstances.get( beanClass.getName() );
}
}
{code}
This provides very fast runtime execution, but a call to e.g.
{code}jtaPropertyManager.getJTAEnvironmentBean().setXaTransactionTimeoutEnabled(true);{code}
must come before the classload of SomeThing if it's going to have any effect.
So what are the alternatives?
Option A:
{code}
public class SomeThing
{
public void doThing()
{
if(jtaPropertyManager.getJTAEnvironmentBean().isXaTransactionTimeoutEnabled())
{
thing()
}
else
{
otherThing();
}
}
}
{code}
looks attractive at first glance. However the system is now so highly tuned that the runtime overhead of the underlying hashmap lookup for
{code}beanInstances.get( beanClass.getName() );{code} is going to cause significant performance impact, see e.g. JBTM-853.
Additionally, usage patterns of the form
{code}
public class SomeThing
{
public void doThing()
{
if(jtaPropertyManager.getJTAEnvironmentBean().isXaTransactionTimeoutEnabled())
{
thing()
}
unrelatedStuff();
if(jtaPropertyManager.getJTAEnvironmentBean().isXaTransactionTimeoutEnabled())
{
relatedThing();
}
}
}
{code}
will need to be hunted down and replaced with
{code}
public class SomeThing
{
public void doThing()
{
boolean cachedConfig = jtaPropertyManager.getJTAEnvironmentBean().isXaTransactionTimeoutEnabled();
if(cachedConfig)
{
thing()
}
unrelatedStuff();
if(cachedConfig)
{
relatedThing();
}
}
}
{code}
including all the more complex cases where the usages are in the same call chain but not necessarily the same method, class or even package.
Option B:
{code}
public class SomeThing
{
private final ConfigBeanHolder config;
public SomeThing(ConfigBeanHolder config)
{
this.config = config;
}
public void doThing()
{
if(config.getJTAEnvironmentBean().isXaTransactionTimeoutEnabled())
{
thing()
}
unrelatedStuff();
if(config.getJTAEnvironmentBean().isXaTransactionTimeoutEnabled())
{
relatedThing();
}
}
}
{code}
which eliminates the expensive hashmap operation and potentially allows us to do nifty stuff like running multiple different configurations in parallel e.g.
{code}
jtaThing = new SomeThing(jtaConfig)
jtsThing = new SomeThing(jtsConfig)
if( needDistributedTx ) {
useThing( jtsThing );
} else {
useThing( jtaThing );
}
{code}
However, we would need to retrofit the entire codebase with suitable constructors as well as hunting down the repeated usages of the same parameter as mentioned for the previous option.
Option C:
{code}
public class SomeThing
{
private static boolean XA_TRANSACTION_TIMEOUT_ENABLED;
static
{
XA_TRANSACTION_TIMEOUT_ENABLED = jtaPropertyManager.getJTAEnvironmentBean().isXaTransactionTimeoutEnabled();
jtaPropertyManager.getJTAEnvironmentBean().registerPropertyChangeListener(
new PropertyChangeListener() {
public void propertyUpdated(String name, Object value) {
if("XA_TRANSACTION_TIMEOUT_ENABLED".equals(name) {
XA_TRANSACTION_TIMEOUT_ENABLED = value;
}
}
}
}
public void doThing()
{
XA_TRANSACTION_TIMEOUT_ENABLED ? thing() : otherThing();
}
}
{code}
which should be fairly quick at runtime but introduces a lot of boilerplate code and still suffers from some of the problems identified for other options.
There are additional concerns that affect all options. For example, some properties cannot be considered in isolation as they interact with others:
{code}
public class SomeThing
{
private Object someProperty;
private Object relatedProperty;
public boolean isConfigValid()
{
return someProperty.equals(relatedProperty);
}
}
{code}
maintaining validity in the face of mutability would require e.g.
{code}
public class SomeThing
{
private final ImmutableConfigHolder config;
public void updateConfig(ImmutableConfigHolder replacementConfig) {
config = replacementConfig;
}
public boolean isConfigValid()
{
return config.getSomeProperty().equals(config.getRelatedProperty());
}
}
{code}
which given the complex interrelationships between properties may bean we can't effectively decouple individual module's configs from one another any more and wind up with a single huge environment bean containing all the properties.
Finally, we need to consider the problems that arise in trying to support a system that is reconfigurable. The content of the properties file is no longer sufficient to determine the runtime config. To diagnose or reproduce issues we'd additionally require either a comprehensive log of runtime property changes or individual error log messages to be retrofitted to contain a dump of any relevent config option values.
The final option is a bit of a wildcard: don't change anything in the JBossTS codebase at all. Instead, use the JBossMC module system to effectively isolate multiple copies of the transaction system with different configs and hot swap between them to effect a config change. That's not without its problems, particularly where recovery is concerned, but they may turn out to be more tractable or at least less labor intensive than solutions that require wholesale audit and refactoring of the entire JBossTS codebase. On the other hand if we plan to substantially refactor the codebase anyhow then making changes to the config system along the way may not be so bad.
We can also potentially tackle the issue on a property by property basis, giving priority to those options for which dynamic reconfiguration has most benefit. Or go for a more limited objective of keeping the config static but delaying its effective freeze beyond classloading time, for example by requiring an explicit lifecycle with an instantiate/start step that snapshots the config values at that point and builds the system using them.
Thoughts?