Multiple class loaders in the same OSGI bundle resulting in class cast exceptions

Viewed 722

Sometimes, after we deploy our application and bundles are being uninstalled and installed, we are getting class cast exceptions where class A cannot be cast to class A. The problem is that the class loader of an instance that have already been in the memory for a while is different than a class loader of the class itself. The deployment is not affecting the bundle in which the instance is stored (in this example bundle Y).

Here is the pseudo code:

Bundle X

public class A extends B {
/* ... */
}

...

/* ... */

@Reference
private InMemoryUserTokenStore inMemoryUserTokenStore;

/* ... */

protected UsersTokenStore getTokenStore() {
    return inMemoryUserTokenStore; // <- reference to service from another bundle where tokens are stored
}

/* The token is created and obtained in the same bundle */

A token = new A(...);
getTokenStore().addToken(token)

/* ... */

B token = getTokenStore().getToken(id)
((A) token).doSomething(); // <- this is when class cast exception is thrown*/

Using the debugger I've found out that the class name of token here is A, class loaders for both returns the same bundle name and id (bundle X, same ID) however they are not equal.

Bundle Y

public class InMemoryUserTokenStore implements UsersTokenStore {
/* ... */
    private ConcurrentMap<String, B> tokens = new ConcurrentHashMap();
    /* ... */
    public B getToken(String id) {
        /* ... */
        return tokens.get(id); // <- instance returned here sometimes has different class loader than class A from bundle **X**
    }
/* ... */
}

I'm not sure if this is a problem with OSGI or maybe some design fault on our end?

2 Answers

You are storing objects of a type created by an older revision of the bundle and expecting them to be castable to a type created by a newer revision of the bundle. Your cache, the token store, should invalidate any objects stored by a bundle when that bundle is stopped.

ClassCastException is thrown if the two class instances in question have different classloaders.

When a bundle is updated or uninstalled and it was exporting packages, these packages are not removed and bundles resolved against the old uninstalled bundle will still be using old/obsolete code until the packages are refreshed. This is the correct behavior as defined by the OSGi specification.

Sling's (which is the underlying framework for AEM) implementation of bundle installer calls a refresh after install, update or delete, so this problem should not appear at all. you should investigate why refresh is failing in some cases - a starting point would be to enable trace logging for the following classes and see whats happening-

org/apache/sling/installer/core/impl/tasks/BundleUpdateTask
org/apache/sling/installer/core/impl/tasks/RefreshBundlesTask
Related