Thursday, December 27, 2012

Self-suppression not permitted

A few weeks ago I ran into a very interesting bug that manifested itself with an exception I had never seen before: Exception in thread "main" java.lang.IllegalArgumentException: Self-suppression not permitted.

The strange thing was that the exception was thrown from a line with no code.
The real code executes a Groovy script written by the end-user in a background thread. Managing the thread, the queue and executing a Groovy script is a bit too long for this post so I wrote a simplified use case:
public class SelfSuppression {
    public static void main(String[] args) throws Exception {
        try (MyCloseable myCloseable = new MyCloseable()) {
            for (int i = 0; i < 5; i++) {
                myCloseable.enqueueWork();
            }
        } // <== IllegalArgumentException
    }
}

At first I thought I had the wrong version of the source so I looked at the history but I found out I had the right version and that there was only one recent change. The last version known to work used a try/finally instead of a try-with-resources
Here is the version that worked:
public class SelfSuppression {
    public static void main(String[] args) throws Exception {
        MyCloseable myCloseable = new MyCloseable();
        try {
            for (int i = 0; i < 5; i++) {
                myCloseable.enqueueWork();
            }
        } finally {
            myCloseable.close();
        }
    }
}
As I said, in my case this executes code written by the end-user so very often it will throw an exception, typically a MissingPropertyException, informing the user of an error in his script. So when I say "the version that worked", I mean a version that reports the error in the Groovy script instead of throwing the IllegalArgumentException.

The culprit is the MyCloseable: enqueueWork() throws an exception so you leave the try block and Java implicitely executes myCloseable.close(). But that method also throws an exception so Java wants to report two exceptions at the same time
To do so, Java 7 introduced the "suppressedExceptions" to Throwable. The problem is that MyCloseable throws the same Exception object in the two methods:
public class MyCloseable implements AutoCloseable {
    private Exception _lastException;

    public void enqueueWork() throws Exception {
        checkHasException();
        doSomethingInBackgroundThread();
    }

    @Override
    public void close() throws Exception {
        checkHasException();
    }

    private void checkHasException() throws Exception {
        if (_lastException != null) {
            throw _lastException;
        }
    }

    private void doSomethingInBackgroundThread(){
        _lastException = new NullPointerException();
    }
}
So Java tries something like "throwable.addSuppressed(throwable)" which is not allowed so Throwable itself throws the IllegalArgumentException and the real exception is lost.
The solution? Reporting the exception once is enough.
    private void checkHasException() throws Exception {
        if (_lastException != null) {
            try {
                throw _lastException;
            } finally {
                _lastException = null;
            }
        }
    }

Friday, September 9, 2011

Using ZeroClipboard with GWT

I have used ZeroClipboard in bug4j to copy stack traces to the clipboard.
It took me quite a while to make this work so I thought I should share the technique.


The most important thing to understand is that you do not tell ZeroClipboard to put text in the clipboard.
The way it works is that ZeroClipboard puts an invisible region over a widget that acts as a button and it is when that region is clicked that it copies to the clipboard the text you have defined.



Step 1: Include and initialize ZeroClipboard in your html page. In bug4j the static assets are stored in the /static/ directory, this is why initZeroClipboard() is necessary. If you store ZeroClipboard.js and ZeroClipboard.swf in / then that step is not necessary.
<html>
<head>
    ...
    <script type="text/javascript" language="javascript" src="user/user.nocache.js"></script>
    <script type="text/javascript" src="static/ZeroClipboard.js"></script>
    <script type="text/javascript">
        function initZeroClipboard() {
            ZeroClipboard.setMoviePath('static/ZeroClipboard.swf');
        }
    </script>
</head>
<body onload="initZeroClipboard();">
...


Step 2: Create an anchor that you link to ZeroClipboard and define the text to be copied to the clipboard.
...
        final Anchor copy = new Anchor("Copy");
        copy.getElement().setId("copyId");
...
        glueCopy("Hello World");
...
    public static native void glueCopy(String text) /*-{
        var clip = new $wnd.ZeroClipboard.Client();
        clip.setText(text);
        clip.glue('copyId');
    }-*/;




And here is the result. As you can see, there is a "Copy" anchor at the bottom. When clicked, it copies the stack trace to the clipboard.