Monday, January 18, 2010

I mostly work on GUI software for controlling and viewing video from security cameras. The software is written in Java, and mostly written by people who are no longer with the company. The usual app I work on is a Swing application, but there is also an applet, which displays just a viewer; the controls for it are HTML buttons around the applet.

We use MPEG-4 (and are adding H.264) extensively, and Java's support for that is, well, non-existent, so we use ffmpeg, via JNI bindings generated by SWIG.

If I was starting again, and for some reason still thought using Java was a good idea, I'd try xuggler out. Xuggler appears to provide the kind of wrapping library around ffmpeg that I would hope to evolve our code towards. But even Xuggler wouldn't fix these recent issues.

The first problem, which was mostly solved before I got involved, is that you cannot load a native library directly from your JAR file. You need to extract it (typically into java.io.tmpdir), and then load it. Not too hard, but it does start to feel icky especially when you consider that an abnormal termination of the browser will cause the file to be left taking up space until manually deleted.

The second problem is the strange interaction between classloaders and JNI - even within trusted code, two classloaders in the same JVM cannot each load the same library. I'm sure somebody somewhere thinks this is a feature rather than a bug. Our code does nothing fancy at all with classloaders; it just gets affected by the fact that two applets executed by the same browser instance will share a JVM. This is still true after Java 6 Update 10, in which the JVM that applets run in is no longer the same process as the browser. If the codebase that you specify to the applet tag is different between two instantiations of the applet, then each instantiation occurs in a different classloader, and the JVM prevents the second instantiation from loading the same library (from the same filename).

This wasn't too bad, and we changed our web pages so that the codebase was the same in each instantiation. As it happened, we weren't loading anything other than the applet jar itself, so that was quite okay.

The applets and web pages are hosted on our image servers, which are units that can deliver data from and to multiple cameras. So then when one browses from an applet hosted on one image server to an applet hosted on another, despite the codebase in the applet tag being the same, two classloaders are used, and the JVM prevents the second applet from loading its native library.

The obvious solution to this is to, when writing the native library to disk, choose a unique name. But that's where the third problem comes in..

Most of our users run Windows, and on Windows if you try to delete a .dll that is in use the OS prevents that. This time, however, the relevant users are all using Solaris, and Solaris allows the .so to be deleted even while it's in use. Our library-extraction code would try to delete libffmpeg-1.so, and if it succeeded (which on Windows would be because the library was not in use), it would then use that as the filename to give the library extracted from the jar file, and then load it. As you can probably see, this works well on Windows, and like a wooden saw elsewhere, because the JVM then believes two classloaders are trying to load the same library.

The fourth problem is that the applet is self-contained; we have a separate build for Windows, Linux, OS X and Solaris, so JavaScript in the web pages has to detect the OS. Detecting the OS is ok, but we support Solaris Intel and Solaris Sparc, and there's no way of detecting those (please correct me!) from JavaScript. We have a mechanism for dealing with this, but I'm not happy with it.

I do plan on making the applet no longer self-contained, so that we distribute one platform-independent section, which detects the OS it's on and loads the correct native library, but really, this is just a case where Java doesn't come with batteries included. I realise applets are not even on the backburner anymore; they've been behind the stove getting mouldy for years, but this does seem like something that should be a lot more straightforward.

An approach that looks promising is that of applet-launcher, which wraps your applet up in another applet that handles all the library loading. I couldn't try this easily as I only control the Java code, not our web pages - it would have required the addition of lots of .getSubApplet() calls in the JavaScript code.

3 comments:

Might I suggest moving to using Java Web Start (if possible)? I know that Java Web Start allows you to specify a .jar file to read native libraries from, and I maintained a JWS app that used native libs for ~4 years (from 2003 - 2007).

You mention Java Applets, so migrating to JWS may not be easy, but I do know that I was able to support Solaris, OSX, Windows, and Linux with one JNLP + lots of per-platform .jar files for the native libs using this approach.