Java Swing EDT: How do I know which threads are waiting for an EventDisplay to run via SwingUtilities.invokeAndWait?

I have a rather complicated problem. In my current project, I have a GUI written in Java and a compute engine written in C ++.

This maps to Java who are accessing data in C ++ and I have some concurrency issues.

There is a long history in this code, so I can't just rewrite everything (even if I want it to be random: p).

When the engine changes data, it gets a mutex. It's pretty clean on this side.

The problem is with the GUI. This is Java Swing and it accesses data without any control, from EventDispatchThread or from any thread, and acquires C ++ mutexes (via JNI) for every unitary kernel access (which is bad for performance and data consistency).

I am already refactoring it to encapsulate the Java locking code in a "NativeMutex" that calls native function locking and JNI unlocking.

I want to write "ReentrantNativeLock" to avoid overwriting everyone and just add a high level lock.

But this ReentrantNativeLock has to deal with EventDisplayThread.

I defined that this locking implementation should rule out that the EDT accepts a mutex (by throwing an exception when the lock method is called from the EDT), but just returns when the lock is already owned by another thread (to work with SwingUtilities.InvokeAndWait, without rewriting all the dirty code of this applications)

Conceptually, this is ok because I am focusing on synchronization between C ++ engine and JAVA GUI, but it is not safe from Java side.

So, I want to move on. If I can find out which threads are waiting for the EDT (which are the threads that called "InvokeAndWait"), I can implement something more secure. I will be able to check if the thread is expecting an EDT owner and avoid some obscure but likely mistakes that will annoy my future - and my colleague.

So how can I find out which threads are waiting for the EDT (which are the threads that called "InvokeAndWait")

(If I describe context, it's because I'm open to listening to other ideas that might solve my problem ... unless they involve a lot of rewriting.)

As some comments lead me to believe the context is not well described, I am posting some code that I hope poses my problem.

This is the main decorator, it m_NativeLock

is a non-reentrant nativeLock.

public class ReentrantNativeLock implements NativeLock {

  /**
   * Logger
   */
  private static final Logger LOGGER = Logger.getLogger(ReentrantNativeLock.class);

  public ReentrantNativeLock(NativeLock adaptee) {
    m_NativeLock = adaptee;
  }

  public void lock() {
    if (!SwingUtilities.isEventDispatchThread()) {
      m_ReentrantLock.lock();
      if (m_ReentrantLock.getHoldCount() == 1) { // Only the first lock from a thread lock the engine, to avoid deadlock with the same thread
        m_NativeLock.lock();
      }
    }
    else if (m_ReentrantLock.isLocked()) {
      // It EDT, but some thread has lock the mutex, so it ok... We assume that the locked thread as called SwingUtilities.invokeAndWait... But if I can check it, it will be better.
      LOGGER.debug("Lock depuis EDT (neutre). Le lock a été verrouillé, l'accès moteur est (à priori) safe", new Exception());
    }
    else {
      // We try to avoid this case, so we throw an exception which will be tracked and avoided before release, if possible
      throw new UnsupportedOperationException("L'EDT ne doit pas locker elle-même le moteur.");
    }
  }

  public boolean tryLock() {
    if (!SwingUtilities.isEventDispatchThread()) {
      boolean result = m_ReentrantLock.tryLock();
      if (result && m_ReentrantLock.getHoldCount() == 1) {
        result = m_NativeLock.tryLock();// Only the first lock from a thread lock the engine, to avoid deadlock with the same thread
        if (!result) {
          m_ReentrantLock.unlock(); // If the trylock on engin fail, we free the lock (I will put it in a try{}finally{} if I valid this solution.
        }
      }
      return result;
    }
    else if (m_ReentrantLock.isLocked()) {
      // It EDT, but some thread has lock the mutex, so it ok... We assume that the locked thread as called SwingUtilities.invokeAndWait... But if I can check it, it will be better.
      LOGGER.debug("Lock depuis EDT (neutre). Le lock a été verrouillé, l'accès moteur est (à priori) safe", new Exception());
      return true;
    }
    else {
      // We try to avoid this case, so we throw an exception which will be tracked and avoided before release, if possible
      throw new UnsupportedOperationException("L'EDT ne doit pas locker elle-même le moteur.");
    }
  }

  public void unlock() {
    if (!SwingUtilities.isEventDispatchThread()) {
      if (m_ReentrantLock.getHoldCount() == 1) {
        m_NativeLock.unlock(); 
      }
      m_ReentrantLock.unlock();
    }
    else {
      LOGGER.debug("Unlock depuis EDT (neutre). Le lock a été verrouillé, l'accès moteur est (à priori) safe", new Exception());
    }
  }
  final ReentrantLock m_ReentrantLock = new ReentrantLock();
  final NativeLock m_NativeLock;
}

      

+3


source to share


2 answers


What you can do is have your own EventQueue

, which records the events to be dispatched from, from which Thread

they are generated, and if the Thread

event is waiting to be dispatched (so, in the event Thread

calls invokeAndWait

).

First push your own turn:

  ThreadTrackingEventQueue queue = new ThreadTrackingEventQueue();
        Toolkit.getDefaultToolkit().getSystemEventQueue().push(queue);

      

In your queue implementation:



  • override postEvent

    , check if it InvocationEvent

    is there and if it is pending notification. In this case, track the Thread

    corresponding event as well.
  • override dispatchEvent

    to remove the calling thread while waiting for the EDT.

Full example (watch out, it sleeps on EDT for collisions to occur, but this should never be done in the app ):

import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InvocationEvent;
import java.lang.reflect.Field;
import java.util.Hashtable;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class TestEventQueue {

    private final ThreadTrackingEventQueue queue;

    public static class ThreadTrackingEventQueue extends EventQueue {

        private Field notifierField;
        private Hashtable<AWTEvent, Thread> waitingThreads = new Hashtable<AWTEvent, Thread>();

        public ThreadTrackingEventQueue() throws NoSuchFieldException, SecurityException {
            notifierField = InvocationEvent.class.getDeclaredField("notifier");
            notifierField.setAccessible(true);
        }

        @Override
        public void postEvent(AWTEvent event) {
            if (!SwingUtilities.isEventDispatchThread() && event.getClass() == InvocationEvent.class) {
                try {
                    Object object = notifierField.get(event);
                    if (object != null) {
                        // This thread is waiting to be notified: record it
                        waitingThreads.put(event, Thread.currentThread());
                    }
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            super.postEvent(event);
        }

        @Override
        protected void dispatchEvent(AWTEvent event) {
            try {
                super.dispatchEvent(event);
            } finally {
                if (event.getClass() == InvocationEvent.class) {

                    waitingThreads.remove(event);
                }
            }

        }

        public Hashtable<AWTEvent, Thread> getWaitingThreads() {
            return waitingThreads;
        }
    }

    public TestEventQueue(ThreadTrackingEventQueue queue) {
        this.queue = queue;
    }

    private void initUI() {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final JTextArea textArea = new JTextArea(30, 80);
        JButton button = new JButton("Start");
        button.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    start();
                } catch (InterruptedException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
            }
        });
        frame.add(new JScrollPane(textArea));
        frame.add(button, BorderLayout.SOUTH);
        frame.pack();
        frame.setVisible(true);
        Timer t = new Timer(100, new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                Hashtable<AWTEvent, Thread> waitingThreads = (Hashtable<AWTEvent, Thread>) queue.getWaitingThreads().clone();
                if (waitingThreads.size() > 0) {
                    for (Thread t : queue.getWaitingThreads().values()) {
                        textArea.append("Thread " + t.getName() + " is waiting for EDT\n");
                    }
                } else {
                    textArea.append("No threads are waiting\n");
                }
            }
        });
        t.start();
    }

    protected void start() throws InterruptedException {
        final Random random = new Random();
        ExecutorService pool = Executors.newFixedThreadPool(50);
        for (int i = 0; i < 50; i++) {
            pool.submit(new Callable<Boolean>() {
                @Override
                public Boolean call() throws Exception {
                    System.out.println("sleeping before invoke and wait");
                    Thread.sleep(random.nextInt(2000) + 200);
                    System.out.println("invoke and wait");
                    SwingUtilities.invokeAndWait(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                System.out.println("sleeping on EDT, bwark :-(");
                                // Very very bad, but trying to make collisions
                                // happen
                                Thread.sleep(random.nextInt(200) + 100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                    return true;
                }
            });
        }
        System.out.println("Invoked all");
    }

    public static void main(String[] args) throws NoSuchFieldException, SecurityException {
        final ThreadTrackingEventQueue queue = new ThreadTrackingEventQueue();
        Toolkit.getDefaultToolkit().getSystemEventQueue().push(queue);
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    TestEventQueue test = new TestEventQueue(queue);
                    test.initUI();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

      

+2


source


You wrote:

The point is that some developers have written a lot of code in JAVA that accesses data that can be updated from a C ++ stream to an application that I have to support. These codes are called from various threads including EDT.

The problem is the EDT that is accessing the data. You may need to make some changes to the code you write so that EDT never works directly with shared data. This means that the EDT has to provide data related tasks for some other threads:

  • If the EDT needs to change some data, it creates a new thread to complete the job.

  • If a thread needs to update the changes in the GUI, it calls InvokeLater()

    either InvokeAndWait()

    .



---------- My answer (second edition) ----------

Hey, there are still lights at the end of the path.

  • Allow refactoring of all code to make sure there is only one InvokeAndWait()

    at a time. How to do it? First you need to write a new global method called MyInvokeAndWait()

    . This method uses blocking to ensure that only one thread at a time can call InvokeAndWait()

    . Then use the IDE to find all InvokeAndWait()

    and replace them with MyInvokeAndWait()

    .

  • Now, internally MyInvokeAndWait()

    , make sure that when called, the InvokeAndWait()

    atomic variable is threadId

    set to the id of the calling thread (note that the call will InvokeAndWait()

    block the calling thread). Clears when InvokeAndWait()

    completed threadId

    .

  • This way, whenever the EDT accesses the data, you can check if the owner thread has the same ID with threadId

    . If so, let EDT do its job, otherwise throw an exception.

Well ... you don't want only one thread to call at a time InvokeAndWait()

. You can add all the calling thread ids to the collection and then check that the owner thread ids are in the collection.

+1


source







All Articles