 |
Debugging Swing - is it really difficult ?
Posted by alexfromsun on November 23, 2005 at 01:57 AM | Comments (33)
Every experienced Swing developer knows that Swing components must be accessed from Event Dispatch Thread (EDT) only. Working with JComponents from any other thread may lead to unpredictable result.
Consider the following code:
import javax.swing.*;
import java.awt.*;
public class BadCode {
public static void main(String args[]) {
createGui();
}
private static void createGui() {
//this code must be run on EventDispatch thread!
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(new GridBagLayout());
JEditorPane pane = new JEditorPane();
pane.setText("Edt matters!");
pane.setSelectionEnd(pane.getText().length());
frame.getContentPane().add(pane);
frame.setSize(new Dimension(200, 100));
frame.setVisible(true);
//clear selection
pane.setSelectionStart(0);
pane.setSelectionEnd(0);
}
}
Running this code I get the desirable unselected JEdiorPane, ... sometimes
Sometimes I get it like this:
Working with Swing not from EDT, you might see your components and layouts distorted or not reflected the current state.
It can work most of the times, but sometimes not, without any guarantee.
Actually the main rule is "If you work with Swing not from EDT all sorts of strange things can happen".
It's been so many articles like this and this, so I suppose that most of Swing programmers know how it works and do the right thing.
Our previous example might be fixed this way:
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createGui();
}
});
}
Pretty easy, right?
The fact that every JComponent's listener is automatically invoked on EDT makes it even more easy.
In my opinion, understanding that Swing is a single-threaded library is not difficult (but it is not a big deal, since I am a Swing developer).
Funny thing, I took part in interviewing several java programmers who claimed to know Swing well, but at the same time some of them had no idea what EDT is.
One guy said that he'd seen Swing's strange behaviour only once, but he fixed it by changing code lines' order.
(Of course, he worked with Swing from incorrect thread)
That's why I am really interested in your opinion,
please look to this questions and give your valuable suggestions.
Set of questions
- Is it a common situation when developers incorrectly use Swing components from another thread and get the strange behaviour I mentioned earlier ?
- Is it difficult to make sure that all Swing code is invoked on EDT and find places when it is not ?
- Should we invent something to make Swing debugging easier?
- Would it be useful if we mark all EDT-only methods with special annotaion ?
Any comments would be appreciated!
I also posted new topic to javadesktop forum
Thanks
Alex
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
To all your questions: yes. Especially the last one. Well, at least something to avoid the boilerplate produced by SwingUtilities.invokeLater() and/or SwingWorker :)
Posted by: gfx on November 23, 2005 at 02:39 AM
-
In answer to all your questions: yes, especially (2) and (4). Something like throwing some sort of OutwithEDTException on unsafe method calls, hopefully with very little overhead. As this might break quite a lot of code, it would be very helpful to have annotations to mark EDT-only code, so that modern IDEs and annotation-processing tools can inspect code accordingly; for those who can't make the change due to the scale of their codebase, you could add the following system properties:
swing.edt.unsafe-access.suppress-exceptions
swing.edt.unsafe-access.enable-logging
The first would just stop exceptions occurring, in other words, current behaviour would be unchanged. The second would dump trimmed stack traces to show from where the unsafe call was made. By default, runtime exceptions would be thrown. It's quite a change, but I think it can be justified by the fact that SwingWorker is now included (the error message for the exception could helpfully suggest using SwingWorker or SwingUtilities).
I know about using the EDT, but a lot of other developers I know just read the bare minimum of javadocs and no tutorials before getting into Swing programming, and have never shown the reflex of looking at the appropriate documentation to understand the "random" behaviour they sometimes observe...
Posted by: chris_e_brown on November 23, 2005 at 04:27 AM
-
I think yes is the answer to all four. Those annotations would be great and would allow for static analysis tools (like IntelliJ IDEA or FindBugs) to warn you when your code might call them from non-EDT thread.
Posted by: keithkml on November 23, 2005 at 07:49 AM
-
3. Should we invent something to make Swing debugging easier?
Some time ago i read a blog with the title "Optimize a Swing App by Slowing It Down" and the first comment in this blog describe a problem i have with the showed functionality. Please check it out: http://weblogs.java.net/blog/tball/archive/2005/04/optimize_a_swin.html
4. Would it be useful if we mark all EDT-only methods with special annotaion?
Can you give use a example for this? When i imaging this right this would be useful, but i want to be sure that what i imaging is what you are talking about.
Posted by: lordy on November 23, 2005 at 07:57 AM
-
It seems to be very common that developers do not understand threads and Swing. I am now in the process of fixing a large application that is completely riddled with threading errors (even SwingWorker is not used correctly). I don't understand why this is such a problem as it is documented in every article on Swing I have read.
Ensuring code is executed on the EDT is not difficult, but I do find that debugging an existing codebase can be difficult without some tool. I have made use of Scott Delap's code from http://clientjava.com (Threading chapter of Desktop Java Live) that helps find threading problems (excellent site as well as book).
Posted by: tborak on November 23, 2005 at 08:51 AM
-
1. Yes: I see it in my team almost daily
2. It looks simple for me now, but it took me me a lot until I got to this point
3. For sure
4. Don't know
Posted by: peyrona on November 23, 2005 at 08:51 AM
-
4 suggesting that practically all Swing methods are marked? And presumably any user method that calls those methods should be marked as well. I don't see that as particularly workable or usable.
Listing and marking those methods cause something to happen on the EDT could be useful.
It's difficult to write safe code with Swing, because Swing is full of bugs and desperately poor code.
Posted by: tackline on November 23, 2005 at 09:03 AM
-
1) Yes.
2) Yes. I don't always know when to use SwingUtilities.invokeLater or when not. For example, my guess is that in your example code only a couple lines of code should really be called within the EDT but I'm not sure wish ones :-( Shame on me!
3) If it helps to understand what the problem is ... sure!
4) YES! YES! YES! PLEASE !!! That way the IDE could tell me when I'm doing something wrong and in the end I'll learn the lesson.
Posted by: urddd on November 23, 2005 at 10:09 AM
-
This is a difficult problem. Many developers continue to run into UI threading issues with their applications. My belief is that the implicit kicking off of the EDT rather than explicitly starting it confuses people. Since they didn't start the thread a lot of them don't even know it exists. Just like you mentioned, a number of Swing developers don't even know what the EDT is.
Other toolkits plain don't allow people to call UI code from the wrong thread. They instead blow up with a message to the user. I know swing can't just put these tests in and allow existing apps to explode, but swing should add assert statements that could be enabled at runtime. After all, this is what asserts were meant to be used for.
Posted by: dmouse on November 23, 2005 at 06:22 PM
-
What would be _fantastic_:
Provide a debug version of the JDK. This should:
- have all debug info in it. It is sometimes quite hard to find an error in your own code when the effect is that something goes wrong a dozen of stackframes into the JDK.
- output diagnostics when some API is used incorrectly
- preferably this diagnostics would define levels where I can configure some levels to cause exceptions and others to be logged or just ignored
But I guess I am only dreaming here...
Posted by: skelvin on November 24, 2005 at 01:14 AM
-
Using the jvmti interface from jdk1.5 and the example code in the docs/ dir of jdk1.5 i have written an agent that can be used to detect such problems via bytecode instrumentation. Jvmti provides a hook upon class load where you can instrument classes and where a callback into java code is provided. It is not quite trivial to use some reasonable filter-set for finding all swing related class. Maybe even classname.startsWith("javax/swing/") is good enough for a start, since all swing code eventually will end up calling these methods. This would not track all possible failures, but many of them.
Let me know if anyone is interested in making a full blown EDT checker application out of my prototype code.
I am still not sure on how to come up with a generic criteria when a method call must be on the EDT or not. Instrumenting the classfiles with that check is easy once a criteria exists and due to the great speed of instrumented bytecode this has little overhead and would be done during debugging sessions only anyhow.
Regards,
Stepan Rutz
stepan.rutz AT gmx.de
Posted by: stepanrutz on November 24, 2005 at 04:52 AM
-
Is it only me? If I need to debug Swing (not my app code) I copy the directory tree starting with javax.swing.* to a new project, compile it with debugging on and prepend it to the boot class path with -Xbootclasspath/P:myswing.jar.Works perfect for me...
Posted by: christian_schlichtherle on November 24, 2005 at 05:02 AM
-
I think the main problems fall into two catagories:
(a) AWT didn't work like this, and a lot of Swing programmers (including those who wrote a lot of the early books and tutorials on Swing) didn't realise the need to work strictly via the EDT until it was too late, bad habits were already well formed and masses of code was already 'out the door'. (Guilty! btw :-)
(b) having to wrap everything in anonymous inner classes whenever you need to run Swing code from outside an event handler is ugly, counter intuitive and just too much hassle for lazy programmers to bother with. When you need to go to all the hassle of creating a separate class just to call a single method on a single object (once!) then many programmers who just want to get something up on screen quickly to please their paymasters will be tempted to cheat - even if they know about the EDT. It gets even more of a pain when we have to interleaf access to Swing objects with our own objects and data.
The other week, in reply to another blog, I suggested a modification to the Java language syntax which would hide much of the boilerplate for creating anonymous inner Runnable classes like this, making the code easier to read and the meaning clearer. I believe this (or something like it) is only way to encourage programmers to use the EDT correctly, as it reduces the rather clunky syntax down to something akin to thread locking.
Posted by: javakiddy on November 24, 2005 at 06:58 AM
-
Reading comp.lang.java.*, beginners still do not use EventQueue.invokeLater. If my memory serves, it wasn't until 1.5 was in beta (with its better optimisation?), that anyone ever mentioned it. Presumably most books that are in use don't use it. It may come as a surprise, but the vast majority of programmers don't read books unless they have to, let alone odd articles posted up around the back corners of the net.
The two sources linked disagree on whether you need to use EventQueue.invokeAndWait from JApplet.init. The second article is plain wrong about listeners.
Posted by: tackline on November 24, 2005 at 08:08 AM
-
I guess the consensus is that most folk generally don't know about, don't care about, or don't know how to use the EDT. At the same time, there's still lots of FUD about how slow Swing is, and I reckon that both these things are generally connected...
I've seen so many frozen GUIs and grey rectangles that I know just how little awareness there is of the EDT.
The comments by "javakiddy" ring true, especially all the boilerplate code required to set one single property on the EDT (needing an anonymous inner class and so on, which can be quite messy when you need to pass context variables about or "hacks" like declaring stuff final).
So more awareness of the EDT is a definite priority IMHO... and less hassle. I would suggest simplifying the current approach through either using C#-style method delegates or Groovy-style closures as an alternative to anonymous inner classes, with support for that in the JVM to avoid multiplication of anonymous class files. I had thought that methods annotated as needing to be called on the EDT might be sufficient (the compiler could transparently add SwingUtilities.isEventDispatchThread for example), but then I'm not sure if that should be synchronous or asychronous, and I don't see anyway that the caller could specify a preference without adding extra special-case keywords.
Posted by: chris_e_brown on November 24, 2005 at 12:55 PM
-
I consider myself well versed in the swing and awt api, but I am not a full time swing developer. I often do not do any swing for a couple of months or more and find that I make little mistakes in the swing code that causes large problems in the display and takes a long and frustrating time to debug.
Any help in the api for debugging would be great in my view.
Or even an additional simplified API that could be used to quickly whip up simple screens.
Posted by: jonesdean on November 24, 2005 at 03:12 PM
-
No answers to your questions here, but have you seen an AspectJ approach in Laddad's book to guarantee that all Swing code is run on EDT?
What do you think of that solution?
Posted by: escordeiro on November 24, 2005 at 03:26 PM
-
Yes, no*, yes, yes.
* That depends on if you know about the RepaintManager trick. Are there any gaps in that technique?
There are two important EDT rules.
Certain methods must only be used from the EDT.
Everything else--especially time consuming methods--must never be used on the EDT.
There is an "EDT hearbeat technique" for the second rule, but I'm not sure if that's really adequate or if there is a better technique.
Ben Galbraith has a really nice presentation on JavaLobby about Swing threading. There are also some very good JavaOne presentations about Swing threading. Both are far more obscure than the Swing Trail because registration is required to view them.
The Swing Tail is a great thing, but it is largely responsible for Swing Threading problems. It really helps Java programmers learn about the parts of Swing they need as they need to use them to accomplish something. That is usually a really good thing, but it means many programmers will never learn about the EDT.
Does anybody here know enough about the various Java security APIs to know if they would be of any use enforcing the EDT rules?
stepanrutz, please post a link to your code.
Posted by: coxcu on November 24, 2005 at 07:31 PM
-
A remark to your source example: The comment is misleading, not all of the code below the comment dealing with Swing objects needs to be in the EDT. It's save to construct a Swing component outside of EDT.
But after a component has been realized, i.e. calling setVisible(true), pack() , or show() on a top-level component, you have to call from EDT. And even than, there are exceptions: It's save to call repaint(), revalidate(), and to add/remove listeners from other threads.
This bunch of rules and the missing "build-in thread-awareness"* of the Java language itself makes it so difficult to write Swing code without breaking these rules.
* means some syntax sugar to bind source code to a specific thread
Posted by: xix on November 25, 2005 at 12:36 AM
-
After reading this last comment I just had an idea.
You propose to mark all EDT-only methods with special annotaion . That's great but why not go a step further. If it is so important to call these method into the EDT then why not build into these methods the capability to add themselves to the EDT to do their job?
So instead of doing :
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.setVisible(true);
}
});
you could do only:
frame.setVisible(true);
and the method setVisible would automatically add a task to the EDT.
Is it a silly idea? Why?
Posted by: urddd on November 25, 2005 at 01:40 AM
-
Referring to "urddd"'s post, I have implemented something along the lines of what you're suggesting: a simple helper method that takes a Runnable as a parameter and that uses SwingUtilities to determine if the current thread is the EDT, and if it is, it just calls the Runnable's "run" method directly, and if it doesn't, it passes it to "invokeLater".
It'd still be nicer to be able to do away with all these anonymous runnables though...
Posted by: chris_e_brown on November 25, 2005 at 02:27 AM
-
Hi
Seems that to answer all questions I need to write one more blog
keep in touch
Thanks
Alex
Posted by: alexfromsun on November 25, 2005 at 03:40 AM
-
Alex,
If you really want to get good data about what percentage of Swing programmers follow the EDT rules, you can't get it by asking programmers. Programmers who don't follow the rules often don't know about them.
If you really want to get good data, you need to use a tool that tests for EDT rule violations. Then, pick a few dozen applications at random using some sensible sampling methodology. I bet you find that more than 80% of the applications contain violations. It would be very interesting to see what factors were related to EDT violations: lines of code, number of developers, maximum number of threads used, etc...
It isn't always easy to determine what constitutes a violation, however. Consider jEdit, which performs very well when all file systems are local. I've found it to be relatively slow and flaky when a slow network file system is involved.
urddd and chris_e_brown,
You could use cglib to easily produce components that do this automatically. Or, you could just write wrappers by hand. Both approaches seem worth investigating.
- Curt
Posted by: coxcu on November 25, 2005 at 08:08 AM
-
(1) Is it a common mistake? Yes, I still see junior/intermediate/senior developers do this year after year.
(2) It is not hard to fix, you just always have to be aware of the concepts.
(3) Need extra tool support? Nope.
(4) Should you enfoce assertions or some other policy? Yes!
Pretty much every JComponent should have this assertion check at the top of every setter or mutator method. This would also allow you to easily create swing worker threads that can modify swing components before they are visible.
if(!EventQueue.isDispatchThread() && isVisible()) {
throw new IllegalStateException("Non-UI thread modifying a visible swing component.");
}
Posted by: metalotus on November 25, 2005 at 09:52 PM
-
In response to those who opt for 'automatic' EDT dispatching, this is totally the wrong approach. You don't want to move work to the EDT unless it is explicit. There are many good example when you want another thread to modify a swing component off the EDT.
For example, you can create a Window, create all the components, customize them, set their sizes, pack() the window... then finally, make the window visible. And the only thing about all that you need to do on the EDT is the 'setVisible()' method. The rest of the work should be done in an application thread (i.e, it could be performed in the non-UI portion of a swing worker who builds and opens a new JFrame). Automatic EDT dispatching would not be a good idea in this case.
Posted by: metalotus on November 25, 2005 at 10:04 PM
-
metalotus:
the example you give, if you think about it is not good enough to counter the argument for 'automatic' EDT. Why? The code logic that would eventually add a task to the EDT should be intelligent enough to know/check whether the component is visible or not. So in the example you describe the logic would put the task in the EDT only when you 'setVisible()', not before.
Posted by: imjames on November 25, 2005 at 11:02 PM
-
metalotus,
You appear to be making a speed/efficiency argument against automatic EDT dispatching. I'm always suspicious about optimizations that don't come from profiling. I have a lot of experience with Swing threads being misused. I don't have any with Swing being too slow.
- Curt
Posted by: coxcu on November 26, 2005 at 08:41 AM
-
I read a few comments above setVisible() and how it would it make a difference on whether the EDT must be used or not.
If i understand memory-barriers correctly this is just wrong and hazardous.
If you have two or more threads accessing the same memory, infact you must treat them as if each one has its own copy of the memory. Imagine each thread runs on a different CPU with own caches. If you treat the memory as shared, this would mean caches are synchronized between cpus per cycle (or asm instruction). This would render a multicpu system about 1000 times slower than a single-cpu system.
So lets say you construct your gui on some thread and then do setVisible(true) and then use the EDT, there is no guarantee (at least not well documented or assured by contract) that memory between those two threads is synced. Thus the mandatory idiom of running a swing app like this
class App {
private static void launchGUI (String[] args) {
/* do it all here, create and initialize swing components, do a
setVisible on the main window. */
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
launchGUI(args);
}
});
}
}
If you don't do this you end up with an undeterministic memory state.
http://java.sun.com/developer/JDCTechTips/2005/tt0419.html#1 has the same information and agrees on this one.
Regards,
Stepan
Posted by: stepanrutz on November 27, 2005 at 12:12 AM
-
metalotus> Creating components off the EDT is good in theory. Unfortunately there are invokeLater booby traps hidden in Swing. Even without that, there is some really bad coding in Swing and it is thread-hostile in places.
For those that want to automatically forward Swing methods onto the EDT> How are you going to handle methods such as isVisible? invokeAndWait is going to cause mysterious deadlocks. Sequences of method calls will become non-atomic, which will lead to all sorts of Heisenbugs.
Not optimising speculatively is fine. But do remember what happens when naïvely mixing OO and distribution.
Posted by: tackline on November 27, 2005 at 01:48 PM
-
For example, you can create a Window, create all the components, customize them, set their sizes, pack() the window... then finally, make the window visible. And the only thing about all that you need to do on the EDT is the 'setVisible()' method. The rest of the work should be done in an application thread (i.e, it could be performed in the non-UI portion of a swing worker who builds and opens a new JFrame). Automatic EDT dispatching would not be a good idea in this case.
Actually, this is incorrect. In the past it was recommended that all calls to Swing components occurs on the EDT after the component has been realized (pack() and setVisible(true) both realize the component). The current recommendation is to do everything on the EDT. Race conditions may exist when using text components, even before the UI is realized.
Posted by: rbair on November 28, 2005 at 09:24 AM
-
:-) I just love that. Almost nobody has it all right !
Posted by: urddd on November 28, 2005 at 09:43 AM
-
tackline,
Why would invokeAndWait introduce any new deadlocks? As for the mysterious part, the auto EDT layer could dump stack, log an error, etc.. whenever a maximum timeout was exceeded. This would be better than the current (complete lack of) information Swing gives when a Runnable stalls the EDT.
You have a definite point about non-atomic Heisenbugs. However, won't this only be a problem when more than one thread is updating the UI? Wouldn't a simple batch/transaction API solve the problem?
Also, can you expand on "OO and distribution"?
- Curt
Posted by: coxcu on November 28, 2005 at 09:55 AM
-
Thanks rbair,
I now construct the window in a thread then call both pack() setVisible() on the EDT, and has solved some issues :)
Posted by: jvaudry on October 20, 2007 at 12:24 PM
|