 |
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
|