The Source for Java Technology Collaboration


Networking and Xlet lifecycle in BD-J

Introduction

The BD-Live standard opens up network communications to Blu-ray Disc Java xlets. This is one of the most exciting areas of BD-J, but it can also be one of the most demanding. Networking is an area that is inherently multi-threaded, unless you use a library or framework that insulates you from the complexities of multi-threaded programming. In addition, the BD-J environment can be more demanding of application authors, due to it being a resource-constrained platform.

In this page, we'll discuss some of the considerations that go into a well-written framework for doing networking in BD-J, and discuss an application of such a framework to a BD-J demo xlet. We'll draw from the twitter client demo that's featured in the xlets/demos/twitterGRIN directory of the HD cookbook open-source project (https://hdcookbook.dev.java.net).

Basic Java Networking

The basic Java networking APIs available in Personal Basis Profile consist of java.net.Socket, plus networking facilities like java.net.URLConnection that are built on top of Socket. Computer networks can be unpredictable and unreliable. It can be difficult to write truly robust code that deals with all possible failure modes. In desktop java, the java.nio package was created to address some of the difficulties in using the older java.net package in a demanding network environment. In BD-J, we can only count on the presence of the PBP 1.0 networking APIs. These are synchronous APIs whose direct use requires application-level multithreading.

One Thread per Connection

java.net.Socket is synchronous in nature. When opening, reading from, or writing to a socket, the calling thread blocks. For this reason, it is almost always necessary to create at least one thread to read and write from each open socket. In some applications, you might even create one thread for reading from a socket, and another for writing to that socket.

For example, In the twitterGRIN xlet, we make one thread to manage our single network connection to the Twitter server. This is done with the NetworkManager class. This class creates a daemon thread to service networking requests. It's started and stopped from the main xlet, via start() and shutdown().

When the xlet requests a refresh of the twitter feed, that request happens in the animation thread. Of course, we can't block the animation thread for something as unpredictable as a network connection, so the xlet enqueues a networking task for processing by the networking thread, by calling NetworkManager.enqueue(). In the twitterGRIN demo we took the shortcut of making a network task a Runnable, rather than defining a NetworkTask interface. In any case, the network task, in this case an instance of TwitterPoll is enqueued, and TwitterPoll.run() is called on the networking thread.

When TwitterPoll.run() executes, it creates a URL to the server, opens it, and reads from it. The interaction with the server uses JSON. Part of what it gets back from the server is a list of URLs for peoples' icons, so this same call of run() is used to read in the icon images, sequentially.

When the poll of the twitter server is complete, the TwitterPoll enqueues itself to the GRIN show that contains the UI, by calling Show.runCommand(this). This has the effect of adding the TwitterPoll object to a queue attached to the show. The next time the animation thread does a model update, this command gets dequeued, and the execute() method of TwitterPoll is called. At this time, the data that was read from the server is fed into the UI, for display in the next frame of animation.

Stripped of debugging and error handling, this code looks like this:

    /**
     * Run the networking task.  This happens in the networking thread.
     **/
    public void run() {
	Vector v = director.twitter.requestPublicTimeline(null);
	    // Opens URL and reads it
	icons = new ManagedImage[v.size()];
	tweets = new Status[v.size()];
	for (int i = 0; i < v.size(); i++) {
	    tweets[i] = (Status) v.elementAt(i);
	    URL url = new URL(tweets[i].getProfileImageURL());
	    icons[i] = ImageManager.getImage(url);
	    icons[i].prepare();
	    icons[i].load(director.getShow().component);
	}
	director.setPendingCommand(this);
	show.runCommand(this);	// To update the UI in the animation thread
    }

    /**
     * Update the screen.  This happens in the animation thread.
     **/
    public void execute() {
	director.unsetPendingCommand(this);
	director.updateScreen(tweets, icons);
    }

Having a networking thread to manage a connection to server in this way is essential. Using a second queue to send the results back to an animation thread is a good way to take the results of networking, and update an xlet's UI in a thread-safe manner.

Interaction with Reference Counting

In BD-J, well-behaved xlets must clean up any resources they use when they terminate. Probably the most important such resource is image pixmap memory. Because garbage collection doesn't always reclaim image pixmap memory in a timely manner, applications are required to call java.awt.Image.flush() when they are done with an image. One good way to ensure that this happens while still allowing the sharing of images that are used in more than one place is to use reference counting. For example, in the twitterGrin xlet, many of the tweets might share the same image (e.g. the default image used when the user has no profile picture). There's no reason to load the same image multiple times, so reference counting is called for. The GRIN ManagedImage class is built to be reference-counted in just this way.

Accurately tracking reference counts in a multi-threaded environment requires some care, particularly when handling xlet termination. Some of the TwitterPoll code left out of the above code sample deals with an xlet that gets a request to terminate while the networking thread is in the process of loading the tweet images. The full code is as follows:

        for (int i = 0; i < v.size(); i++) {
            if (Thread.interrupted()) {
                // Note that NetworkManager calls Thread.interrupt() for us
                // when it wants us to terminate.
                //
                // For added robustness, we could add a synchronized check
                // of NetworkManager.destroyed here too - that would provide
                // some robustness against buggy libraries that catch
                // InterruptedException without re-posting the
                // Thread.interrupt().
                destroy();
                return;
            }
            tweets[i] = (Status) v.elementAt(i);
            try {
                URL url = new URL(tweets[i].getProfileImageURL());
                icons[i] = ImageManager.getImage(url);
            } catch (MalformedURLException ignored) {
                icons[i] = null;
            }
            if (icons[i] != null) {
                icons[i].prepare();
                icons[i].load(director.getShow().component);
            }
        }
        director.setPendingCommand(this);
        show.runCommand(this);  // To update the UI in the animation thread

Note that we poll Thread.interrupted() before each time-consuming operation. This is good practice in BD-J generally. In our case, NetworkManager also uses Thread.interrupt() to request that we terminate quickly.

As you can see, when we bail out of reading the tweet images in the middle, we call destroy(). This method adjusts the reference counts of all of the images held by our TwitterPoll object. You'll also notice the call director.setPendingCommand(this). This is done to handle the case where the xlet is destroyed after the networking activity completes, but before the UI is updated in the animation thread. Digging into TwitterDirector, we find this code to adjust the reference counts of the images in this case:

    public void notifyDestroyed() {
	...
        TwitterPoll poll = null;
        synchronized(this) {
            poll = pendingCommand;
        }
        if (poll != null) {
            poll.destroy();
        }
	...
    }

As you can see, it is essential that we maintain a chain of ownership of any object we're responsible for freeing. At any given point in time, if the xlet is asked to terminate, we must be able to determine all of the objects that need to have their reference counts adjusted, or that need to be otherwise freed. This is one of the challenges of doing networking in a resource-constrained environment like BD-J.

Shutting Down the Xlet and Platform Support

As you can see from the discussion so far, a lot of the effort of writing robust networked xlets has to do with cleaning up when the xlet terminates. In the last section, we discussed generic concerns that would be true in any resource-constrained device where GC can't be relied on to clear all resources. In addition, there are some challenges specific to the PBP 1.0 API set, and other API and platform guarantee issues.

Network Timeout

In PBP 1.0, it is impossible to set a socket timeout before a socket connection is made. In PBP 1.1, this is partially addressed by adding the public constructor Socket(), and the method Socket.connect(SocketAddress). This allows an xlet to set a reasonable timeout when it uses the Socket API directly, but sockets opened e.g. with URL.openStream() or with the image loading APIs aren't affected. PBP 1.0 does not specify a default timeout. As of this writing (in May 2009), some BD-J players have been observed to have quite long default socket timeouts -- on the order of a minute or more. This might change in the future.

It is possible for an xlet to use reflection to detect if it is running on a PBP 1.1 implementation, and if it is to use the new networking API. To do this, the xlet really does have to use reflection -- an attempt to directly access the new Socket APIs should be flagged by a disc verifier as an illegal API access. For this reason, the gateway class pattern discussed in Dealing with Optional APIs in this wiki cannot be applied -- the xlet needs to use a fully reflective mechanism to call the new APIs.

Despite the complexities, it's a good idea for xlets to set the socket timeout to a reasonable number, perhaps between five and twenty seconds, at least until BD-Live players are updated to more uniformly have reasonable defaults.

Interruptible I/O

Interruptible I/O is a well-known pain point in the Java networking APIs. You will find that there has been an exception called java.io.InterruptedIOException since JDK 1.0. From this, one might conclude that calling Thread.interrupt() on a thread doing any kind of I/O will result in an InterruptedIOException in a timely manner. Unfortunately, this is not the case.

The problem is that many operating systems do not support interrupting a networking call at the native layer. Because of this, a call to Thread.interrupt() might or might not result in an InterruptedIOException being generated. This is discussed in much detail in the Java SE bugs database; a good place for the interested reader to start looking is bug 4385444, at http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4385444.

It's a good idea for xlets to use Thread.interrupt() in an attempt to abort network I/O in a timely manner, but xlet authors shouldn't count on it working reliably. In twitterXlet's NetworkManager, we use Thread.interrupt() in this way.

Closing an Active Socket

One workaround for the lack of interruptible I/O is to close a network connection "out from under" the thread that is reading it. For example, a networking thread is reading from an InputStream taken from a socket, another thread can call InputStream.close() on that input stream. If the first thread is blocked in a call to InputStream.read(), that call might abort with an IOException. This behavior is not required, but it is allowed, and indeed encouraged. For this reason, closing server connections in order to encourage fast shutdown is a good thing for an xlet author to consider.

In the twitterGrin example, we did not do this, because the amount of data actually read from the socket is quite small. Most of the data read during a poll of the twitter server is the reading of images, where the InputStream is not available to us, rendering this technique of limited value. However, if your application keeps a socket open long enough, this technique can be worth pursuing.

Daemon Threads and Xlet Termination

As was mentioned above, the networking thread can be viewed as a daemon thread. In PBP, the API Thread.setDaemon(boolean) exists, but its interaction with the xlet lifecycle is not specified. To make matters worse, PBP adds some confusing an irrelevant text about an interaction with System.exit() in PBP. Since xlets can't call System.exit() anyway, this text should be ignored.

To find out what marking a thread as a daemon thread means, a good place to start is the Java SE documentation for later versions of the Java platform. As early as JDK 1.4, we see this text in java.lang.Runtime.addShutdownHook(Thread):

The Java virtual machine shuts down ... when the last non-daemon thread exits ...

From this, we can see that daemon thread in Java mean what you'd expect from their Unix meaning: They are threads that run in the background, performing some service. When the only thread that are executing are daemon threads, that means that the application has finished. Note that in desktop Java, the thread that calls main() and the AWT thread are not daemon threads.

When applied to xlets, marking a thread as a daemon has a clear meaning: The xlet manager can consider an xlet to have terminated, even if one or more daemon threads are still running. If the VM is capable of it, it might even forcibly terminate those daemon threads, but this is of course not required, and xlet managers can forcibly terminate all of an xlet's threads after Xlet.destroyXlet() returns anyway.

The important part of the meaning of daemon threads is that an xlet manager can consider the xlet to have terminated, even if some daemon threads are still running. Thus, it can e.g. continue with a title selection operation, even if a title-bound xlet that is being terminated still has one or more daemon threads running. A well-behaved xlet will, of course, terminate the daemon threads as soon as possible, and a good xlet manager will attempt to terminate those threads as well. This termination might take some time, however. For example, a networking thread on a platform that does not support interruptible I/O and that has a socket timeout of a minute or more might not terminate until the socket timeout happens, if it is trying to connect to a non-responsive server.

This player behavior around daemon threads is not required (at least as of this writing, in May of 2009), but it is permitted by the specification. It is also encouraged, by virtue of the parallel with the behavior of desktop Java. It is hoped that players implement this recommended behavior, because it makes it possible for title-bound xlets to do networking, without worrying about making title selection non-responsive for periods of tens of seconds, or even a minute.

In the absence of widespread respect of the semantics of daemon threads, it might be best to avoid marking xlets that can do networking as title-bound, and to instead do xlet lifecycle management in the application, e.g. by relying on a monitor xlet.

Summary

BD-Live opens up BD-J to networking, which is a very valuable and exciting capability. In Java, networking is inherently multi-threaded. Because both multi-threading and networking are difficult, particularly on resource-constrained devices, it's almost certainly worth the investment to develop a networking framework. This framework should be written with great care, particularly in the area of proper termination when the enclosing xlet is destroyed. In this document we looked at such a framework, in the form of the HD Cookbook project's twitterGrin demo.

This document explored several mechanisms to try to stop time-consuming network operations that can be in progress when an xlet is trying to shut down. It further explored the Java concept of daemon thread, and recommends an application of that concept to xlet management. We hope that this analysis is helpful, and will lead to more robust and reliable player implementations, and to better BD-Live applications that work within the constraints of the players they run on.

-- Main.billf - 18 May 2009

Topic BDJNetworking . { Edit | Ref-By | Printable | Diffs r2 < r1 | More }
 XML java.net RSS

Revision r2 - 19 May 2009 - 00:38:25 - Main.billf
Parents: WebHome > Blu-RayDisc