Thursday 25 August 2011

Blueprint 101 - destruction

I have recently come across a couple of cases where a blueprint bean destroy method takes a long time to run. This has resulted in undesired behaviour. In both cases the destroy method took over 5 minutes to run and then failed with a runtime exception.

So what was going on? In this case the destroy method ended up invoking a service that had been injected from a element. Lets look at a quick example. 


In this diagram bean A in the left hand bundle uses bean B in the right hand bundle via a service relationship. Bean A's destroy method calls out to bean B. In blueprint service references are damped, this means that if the target service has gone it blocks the call for a timeout period (the default is 5 minutes) until a replacement can be found. If no replacement is found an unchecked exception is thrown.

The most common reason for the destroy method of bean A being invoked is that the left hand bundle has been stopped. If only the left hand bundle is stopped then this will work just fine, but both are stopped then the right hand bundle must be stopped before the left hand bundle. The problem is there is no way to enforce this shutdown order.

So having outlined the problem the question that will come up is "how to solve it?". The answer is to not call a service in a destroy method. This is quite simple, but doesn't help if you really need to call that service. Instead of placing this call in the destroy method though you can make use of another feature in blueprint called a reference listener. A reference listener gets called when a matching service is initially bound to a reference, or when a service is unbound (if a service is replaced the unbind method is not called, but the bind method will be called to indicate the service has been replaced). Moving the tidy up out of the destroy method and into a reference listener will ensure the call can succeed. The reference listener is called irrespective of the order of the left and right hand bundles being shutdown.

Using a reference listener is slightly more complicated, it involves a little more XML. Instead of defining:
<bean class="my.example.BeanA" destroy-method="destroy">
    <property name="service" ref="serviceRef"/>
</bean>
<reference id="serviceRef"
           interface="my.example.BeanBInterface"/>

you define the following:

<bean id="beanA" class="my.example.BeanA">
    <property name="service" ref="serviceRef"/>
<bean>
<reference id="serviceRef"
           interface="my.example.BeanBInterface">
    <reference-listener ref="beanA"
                        unbind-method="unbindBeanB"/>
</reference>

The signature of the unbind method should be public void unbindBeanB(my.example.BeanBInterface), it could also take a ServiceReference. Normally blueprint does not allow beans to have cycles, that is bean A depends on bean B which depends on Bean C, as is clearly shown in this case. However the blueprint specifically specifically requires blueprint container implementations to support this kind of cycle.

Making this change removes the ordering constraint between the two bundles and produces a more resilient system.

Alasdair

Updated 4th September 2011: Updated to correctly describe the behaviour of a reference listener when a service is switched.

No comments: