Monday, March 15, 2010

Windows file lock issue in eclipse - classloader problem

File lock issue in windows when using URLClassloader objects.

Brief overview of the problem:

In our eclipse application, we configure various services. Each service has its own property sheet (dialog). While launching a service dialog in eclipse platform, we create a custom URLClassloader objects using all its dependencies/ resources (jar files). Once this dialog is Finished/Closed the jar files still get locked up in windows platform and this caused problems.

Normally to handle such locks, we create a custom URLStreamHandlerFactory that implements java.net.URLStreamHandlerFactory where we create our own handlers for different protocols. Sample code is present in Listing 1.

This perfectly works for most of the cases but since my process is an eclipse process and eclipse is already setting its own URLStreamHandlerFactory I couldn't directly use the code in Listing 1 since URL.setURLStreamHandlerFactory(customFactory); has to be called only once in a jvm.


However, I tried some workarounds like using reflection to reset the URLStreamHandlerFactory and to specifically use the following URL constructor.
URL jarUrl = new URL("jar", "", -1, resourceFileUrl.toString()+ "!/");
Iam able to accomplish the task but later I discovered that this broke some other areas and I keep on getting NoClassDefFoundErrors even if the class is available in the classpath and loaded by the same classloader.

Finally Iam able to resolve this with a simple workaround which does 2 things - Setting the field defaultUseCaches in URLConnection class to false, using some utility code to close all Jar connections. The solution is posted below.

Solution that Worked:

1. There is a field defaultUseCaches in URLConnection class. This field has to be set to false when the application loads.

static {
    try{
        Field defaultUseCaches = URLConnection.class.getDeclaredField("defaultUseCaches");
        defaultUseCaches.setAccessible(true);
        defaultUseCaches.set(defaultUseCaches, false);
    } catch(Exception e){
        //do something
    }
}

Note: If this is set to false then setting useCaches to false is not required as in Listing 1.


2. Used an utility class to release all the resources held by URLClassloader when a service dialog is closed.
Used releaseLoader(URLClassLoader classLoader, List jarsClosed) method in sun.misc.ClassLoaderUtil to close the jarfiles. The source code is available here.

http://www.docjar.com/html/api/sun/misc/ClassLoaderUtil.java.html

The classloader problem was resolved after this.

Note: ClassLoaderUtil uses classes like URLClassPath which are restricted by default in eclipse.
To overcome this the following setting has to be changed in eclipse preferences.
Navigate to Preferences -> Java -> Compiler -> Errors and Warnings -> Deprecated and Restricted API and change the value of  Forbidden Reference to Warning.


Listing 1:
Lists the sample custom URLStreamHandlerFactory class and a simple JarHandler. Same code can be used in other handlers like FTP, File etc.
Note: This cannot be used for eclipse process. Works for normal java processes.

-------------------MyURLStreamHandlerFactory -------------------------
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.util.Hashtable;

public class MyURLStreamHandlerFactory implements URLStreamHandlerFactory {


private Hashtable handlers;
public MyURLStreamHandlerFactory() {
    handlers = new Hashtable();
}

public URLStreamHandler createURLStreamHandler(String protocol) {
    URLStreamHandler ush = (URLStreamHandler) handlers.get(protocol);
    if (ush == null) {
        if (protocol.equalsIgnoreCase("file")) {
            URLFileHandler fileHandler = new URLFileHandler();

            ush = fileHandler;
            handlers.put(protocol, fileHandler);
        } else if (protocol.equalsIgnoreCase("jar")) {
            URLJarHandler jarHandler = new URLJarHandler();
            ush = jarHandler;
            handlers.put(protocol, jarHandler);
        } else if (protocol.equalsIgnoreCase("ftp")) {
            URLFtpHandler ftpHandler = new URLFtpHandler();
            ush = ftpHandler;
            handlers.put(protocol, ftpHandler);
        } else if (protocol.equalsIgnoreCase("http")) {
            URLHttpHandler httpHandler = new URLHttpHandler();
            ush = httpHandler;
            handlers.put(protocol, httpHandler);
        } else if (protocol.equalsIgnoreCase("https")) {
            URLHttpsHandler httpsHandler = new URLHttpsHandler();
            ush = httpsHandler;
            handlers.put(protocol, httpsHandler);
        }
    }
return ush;
}


/**
* Clears the URLStream Handler(s) hash table.

*/
public void clear() {
    handlers.clear();
}


}

--------------------- MyURLStreamHandlerFactory --------------

------------------------- URLJarHandler---------------------------
import java.net.URLConnection;
import java.net.URL;
import java.io.IOException;


/**
* Jar URL Stream Handler . It has all the attributes of a URLStreamHandler for the 'jar' protocol.
* openConnection method overriden to prevent locking & caching of file on windows os.
*/
public class URLJarHandler extends sun.net.www.protocol.jar.Handler {

protected URLConnection openConnection(URL url) throws IOException {
    URLConnection u = super.openConnection(url);
    u.setUseCaches(false);//prevents locking&caching of file on windows by jvm.
    return u;
}
 }
-------------------------------URLJarHandler------------------