Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 293
dendini Avatar asked Jul 20 '13 08:07

dendini


2 Answers

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();
        }
    }
}
like image 82
Gilbert Le Blanc Avatar answered Nov 09 '22 12:11

Gilbert Le Blanc


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);
        }
    }
}
like image 32
ruediste Avatar answered Nov 09 '22 13:11

ruediste