How to check a Swing application for correct use of the EDT (Event DIspatch Thread)
Asked Answered
O

2

8

I have found many tutorials and examples on using the EDT correctly, I would however like to hear how one should go the other way around: check a complex application which has a Swing GUI and many functionalities involving long network operations and find where the EDT is improperly used.

I have found that

SwingUtilities.isEventDispatchThread()

could be used to check whether one piece of code is inside the EDT or not, so I could check that all long operations don't happen to be inside places where SwingUtilities.isEventDispatchThread() returns true.

Is it right? is there something better I could to in a way debug the whole application in search of incorrect use of the EDT? Thank you.

Ortrude answered 20/7, 2013 at 8:10 Comment(3)
Take a look at: weblogs.java.net/blog/alexfromsun/archive/2006/02/…Blair
the typical checking strategies are the other way round: they find places where you access Swing components off the EDT (vs. check whether long-running code does not happen on the EDT) - logically, the latter isn't possible without adding code at the place that you suspect to be long-runningAloud
ANd how does one find places where you access Swing components off the EDT?Ortrude
D
7

Is it right?

Yes, checking the value of SwingUtilities.isEventDispatchThread() is one way to see if your code is on the Event Dispatch thread (EDT) or not.

Another way would be to display or print Thread.currentThread().getName(). The EDT almost always has the name "AWT-EventQueue-0".

This nifty piece of code comes from the article, Debugging Swing, the final summary. However, it's not a complete Swing debugger. This code only checks repaint violations.

The article lists other debugging methods that are more complete.

import javax.swing.JComponent;
import javax.swing.RepaintManager;
import javax.swing.SwingUtilities;

public class CheckThreadViolationRepaintManager extends RepaintManager {
    // it is recommended to pass the complete check
    private boolean completeCheck   = true;

    public boolean isCompleteCheck() {
        return completeCheck;
    }

    public void setCompleteCheck(boolean completeCheck) {
        this.completeCheck = completeCheck;
    }

    public synchronized void addInvalidComponent(JComponent component) {
        checkThreadViolations(component);
        super.addInvalidComponent(component);
    }

    public void addDirtyRegion(JComponent component, int x, int y, int w, int h) {
        checkThreadViolations(component);
        super.addDirtyRegion(component, x, y, w, h);
    }

    private void checkThreadViolations(JComponent c) {
        if (!SwingUtilities.isEventDispatchThread()
                && (completeCheck || c.isShowing())) {
            Exception exception = new Exception();
            boolean repaint = false;
            boolean fromSwing = false;
            StackTraceElement[] stackTrace = exception.getStackTrace();
            for (StackTraceElement st : stackTrace) {
                if (repaint && st.getClassName().startsWith("javax.swing.")) {
                    fromSwing = true;
                }
                if ("repaint".equals(st.getMethodName())) {
                    repaint = true;
                }
            }
            if (repaint && !fromSwing) {
                // no problems here, since repaint() is thread safe
                return;
            }
            exception.printStackTrace();
        }
    }
}
Delossantos answered 20/7, 2013 at 9:57 Comment(2)
I'm more interested on techniques to verify the EDT is used correctly than ways of finding if I'm inside or outside the EDT (also because isEventDispatchThread seems to do its job). Someone suggested doing the other way round and "finding places where you access Swing components off the EDT"Ortrude
It seems to me that checking for thread name = "AWT-EventQueue-0" should not be encouraged. It's not part of the public API so there's no telling whether the name will be the same in future releases. In fact the "-0" suffix leads me to suspect otherwise.Reuben
L
3

One way of checking correct use of the EDT of a whole Application is to use a java agent. The Code below is an improved version of the Agent posted under Debugging Swing, the final Summary. It works with ASM 4.1. Create a Jar containing asm-all-4.1.jar (unpacked), the compiled code and a manifest specifying the agent as Premain-Class and get going.

/**
 * A java agent which transforms the Swing Component classes in such a way that a stack
 * trace will be dumped or an exception will be thrown when they are accessed from a wrong thread.
 * 
 * To use it, add
 * <pre>
 * -javaagent:${workspace_loc:MyProject/tool/util/swingEDTCheck}/swingEDTCheck.jar
 * </pre>
 * 
 * to the VM arguments of a run configuration. This will cause the stack traces to be dumped.
 * 
 * Use
 * <pre>
 * -javaagent:${workspace_loc:MyProject/tool/util/swingEDTCheck}/swingEDTCheck.jar=throw
 * </pre>
 * to throw exceptions.
 * 
 */
public class SwingEDTCheckAgent {

    public static void premain(String args, Instrumentation inst) {
        boolean throwing = false;
        if ("throw".equals(args)) {
            throwing = true;
        }
        inst.addTransformer(new Transformer(throwing));
    }

    private static class Transformer implements ClassFileTransformer {

        private final boolean throwing;

        public Transformer(boolean throwing) {
            this.throwing = throwing;
        }

        @Override
        public byte[] transform(ClassLoader loader,
            String className,
            Class classBeingRedefined,
            ProtectionDomain protectionDomain,
            byte[] classfileBuffer)
            throws IllegalClassFormatException {
            // Process all classes in javax.swing package which names start with J
            if (className.startsWith("javax/swing/J")) {
                ClassReader cr = new ClassReader(classfileBuffer);
                ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
                ClassVisitor cv = new EdtCheckerClassAdapter(cw, throwing);
                cr.accept(cv, 0);
                return cw.toByteArray();
            }
            return classfileBuffer;
        }
    }

    private static class EdtCheckerClassAdapter extends ClassVisitor {

        private final boolean throwing;

        public EdtCheckerClassAdapter(ClassVisitor classVisitor, boolean throwing) {
            super(Opcodes.ASM4, classVisitor);
            this.throwing = throwing;
        }

        @Override
        public MethodVisitor visitMethod(final int access,
            final String name,
            final String desc,
            final String signature,
            final String[] exceptions) {
            MethodVisitor mv =
                cv.visitMethod(access, name, desc, signature, exceptions);

            if (name.startsWith("set") || name.startsWith("get") || name.startsWith("is")) {
                return new EdtCheckerMethodAdapter(mv, throwing);
            } else {
                return mv;
            }
        }
    }

    private static class EdtCheckerMethodAdapter extends MethodVisitor {

        private final boolean throwing;

        public EdtCheckerMethodAdapter(MethodVisitor methodVisitor, boolean throwing) {
            super(Opcodes.ASM4, methodVisitor);
            this.throwing = throwing;
        }

        @Override
        public void visitCode() {
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/awt/EventQueue", "isDispatchThread", "()Z");
            Label l1 = new Label();
            mv.visitJumpInsn(Opcodes.IFNE, l1);
            Label l2 = new Label();
            mv.visitLabel(l2);

            if (throwing) {
                // more Aggressive: throw exception
                mv.visitTypeInsn(Opcodes.NEW, "java/lang/RuntimeException");
                mv.visitInsn(Opcodes.DUP);
                mv.visitLdcInsn("Swing Component called from outside the EDT");
                mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V");
                mv.visitInsn(Opcodes.ATHROW);

            } else {
                // this just dumps the Stack Trace
                mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Thread", "dumpStack", "()V");
            }
            mv.visitLabel(l1);
        }
    }
}
Leyla answered 13/9, 2013 at 13:24 Comment(3)
I am new to java agent. Can you give more detailed instruction on how to use the Swing EDTCheckAgent class in an existing project?Demodulator
I updated the answer. It already describes how to pack it, now I added the -javaagent: option. It might make sense to create a github project for it. What do you think? Interested in doing that?Leyla
Thank you for your update. I have actually managed to make it works with my application. But It seems cannot find anything wrong from my application. With the RepaintManager, several violations are reported and I checked the findings are violations.Demodulator

© 2022 - 2024 — McMap. All rights reserved.