A: 

Well, the basic problem is that some Java VM implementations use the same Java thread to do everything.

One of the first thing you need to figure out about the threading model of your VM is who developed it.

There is a list of J2ME licensees here: http://java.sun.com/javame/licensees/index.jsp

From that information, try to figure out how many native threads your VM is using. The 2 usual models are either run all bytecode interpretation into one native thread or run each java thread into its own native thread.

The next step is to gather information about how asynchronous the underlying operating system APIs are. When developing the VM, the licensees would have had to write native code to port the VM to the operating system. Any Inter-Process Communication or use of slow transmission medium (from flash card to GPS signal) might be implemented using a separate native thread that could allow the bytecode interpreter thread to keep running while the system waits for some data.

The next step is to realize how badly implemented the VM is. Typically, your problem happens when the VM uses only one internal java thread for all callbacks methods in the MIDP spec. So, you don't get a chance to react to keypad events until after your connection is opened is you try to open it in the wrong java thread.

Worse, you can actually prevent the screen from being refreshed because Canvas.paint() would be called in the same java thread as javax.microedition.media.PlayerListener.playerUpdate() for example.

From the VM implementation perspective, the golden rule is that any callback that you don't control (because it can end up in "user" code, like a listener) cannot be called from the same java thread you use unblock standard API calls. A lot of VM out there simply break that rule, hence the Sun recommendation that JavaME developers work around it.

QuickRecipesOnSymbianOS
Thanks. Updated the question based on your reply.
Abhi
A: 

Canvas.paint() is a event delivery method, which means it is called by a system event thread.

Assume on a system, both Canvas.paint() calling and user confirmation event handling are implemented to be done by a UI event thread (UT).

Now, when UT is blocked inside Canvas.paint() by Connector.open(), UT sure not able to handle the next coming event, which in this case is the user confirmation event triggered by Connector.open(). It is not possible for UT to process another event when it's blocked inside your application code.

That's why the deadlock occur here, the connection thread is waiting for something will never happen, and blocks UT forever.

In general, you should not expect how will system event thread be implemented, and try to return from event handling method as quickly as possible. Otherwise, you might receive lower performance or deadlock like this.

tingyu
Thanks. I think I sort of get it now. How would you rearrange the code to get it working? They way I see it is make the system call the paint method after the image is fetched from the network. Is there a better way to do it? Is there any pattern associated with the problem I am facing and a standard solution to it? Sorry, I am complete newbie to J2ME and UI stuff.
Abhi
Manoj just showed you a good example, notice thread.join() still blocked UT inside Canvas.paint(). Just try to return from event callbacks as quickly as possible and let the event thread to process next event.
tingyu
+4  A: 

Where can I get the sources for j2me system classes (I want to check out the implementation of Connection classes)?

You cant. Its actually vendor dependent. The way in which Nokia handles this situation may be different from Motorola.

The lesson you have to learn is that don't do expensive computation in system callbacks as that might make the system unresponsive. So put the time consuming operation in a sepearate thread and always return from call backs as early as possible.

Even though you have created a separate thread in your second example, you wait for its completion in paint() and it eventually makes a result of no threading at all!

One thing you can do is

class MyCanvas extends Canvas {

    Image image;
    boolean imageFetchFailed;

    protected void paint(Graphics g) {
        if (image == null) {
            fetchImage();
            g.drawString("Fetching...", getWidth() >> 1, getHeight() >> 1, Graphics.HCENTER | Graphics.TOP)

        } else if (imageFetchFailed) {
            g.drawString("Failed to fetch image", getWidth() >> 1, getHeight() >> 1, Graphics.HCENTER | Graphics.TOP)
        } else {
            g.drawImage(image, 0, 0, 0);
        }
    }


    private void fetchImage() {
        new Thread(new Runnable() {
            public void run() {
                HttpConnection httpConnection = null;
                try {
                    httpConnection = (HttpConnection) Connector
                            .open("http://10.4.71.200/stage/images/front/car.png");
                    image = Image.createImage(httpConnection.openInputStream());
                } catch (IOException e) {
                    e.printStackTrace();
                    imageFetchFailed = true;
                }

                if (httpConnection != null) {
                    try {
                        httpConnection.close();
                    } catch (IOException ignored) {
                    }
                }

                // Following will trigger a paint call 
                // and this time image wont be null and will get painted on screen
                repaint();    
            }
        }).start();
    }
}
Manoj
A: 

Some good ideas, but it seems that there is a race condition in Manoj's example.

Multiple paint calls is possible while an image is downloading, causing multiple threads to be created all downloading the same image (One example of extra paint calls is when the HTTP connection prompt pops up).

Since all paint calls are made on the same thread, we can avoid synchronization by testing and setting a flag inside the paint call. Below is an attempt at an improved version:

    class MyCanvas extends Canvas {

    Image image;
    boolean imageDownloadStarted;
    boolean imageFetchFailed;

    protected void paint(Graphics g) {
        g.fillRect(0, 0, g.getClipWidth(), g.getClipHeight());
        if (image == null) {
            if (imageDownloadStarted)
                return;
            imageDownloadStarted = true;
            fetchImage();
            g.drawString("Fetching...", getWidth() >> 1, getHeight() >> 1, Graphics.HCENTER | Graphics.TOP);

        } else if (imageFetchFailed) {
            g.drawString("Failed to fetch image", getWidth() >> 1, getHeight() >> 1, Graphics.HCENTER | Graphics.TOP);
        } else {
            g.drawImage(image, 0, 0, 0);
        }
    }

    private void fetchImage() {
        new Thread(new Runnable() {

            public void run() {
                try {
                    final HttpConnection httpConnection = (HttpConnection) Connector.open("http://stackoverflow.com/content/img/so/logo.png");
                    try {
                        final InputStream stream = httpConnection.openInputStream();
                        try {
                            image = Image.createImage(stream);
                        } finally {
                            stream.close();
                        }
                    } finally {
                        httpConnection.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    imageFetchFailed = true;
                }

                repaint();
            }
        }).start();
    }
}

Note the use of the final key word to avoid a null test and the explicit closing of the stream returned by openInputStream.

Hans Malherbe