javamultithreadingswingawt-eventqueue

Stopping another thread from an Event Dispatch Thread (EventQueue)


I have a login form that extends JDialog, the user can login via card swipe or by typing in a username and password.

I created a Runnable daemon that communicates to a magnetic stripe reader, when started it will request a card swipe and it will wait until a card is swiped. If the application needs to cancel the request to do something else then it will produce an event that this thread will catch thus cancelling the request for waiting for a card swipe.

When the user swipes the card, the application will read the track for the user ID and validates it, if the authentication is successful, a stop command would be sent to the card swipe daemon and stops the thread.

When the user enters a username and password, the swing package would access a thread (AWT-EventQueue-0) that responds to the click event of the login button and proceeds to evaluate the login credentials.

My problem is whenever the application is on this AWT-EventQueue-0 thread, sending a stop event to the card swipe daemon would not work and the daemon would stay on the thread stack.

EDIT 1: The stop command works perfectly fine on the card swipe login. It ends the card swipe thread gracefully. On this scenario, the current thread scope is on the CardSwipeThread.

The problem happens on the manual login, when the user clicks the login button, the current scoped thread would be the AWT-EventQueue-0 or Event Dispatch Thread. Updating the volatile boolean of the CardSwipeThread to false does not stop it from running.

EDIT 2: The only time the reader would communicate with the application is when a card is swiped, and the problem happens on the manual login which doesn't require a swipe. So there are no issues with the CardSwipeThread not ending properly because of a blocked IO operation. Turns out there's one hiding behind the bushes.

This is a part of my code:

LoginDialog.java

public class LoginDialog extends JDialog implements ActionListener, WindowListener
{
    public LoginDialog()
    {
        super();

        // ..More code that instantiates the objects of this JDialog.

        SwipeReader.getInstance().enable(true);
    }

    class SymAction implements java.awt.event.ActionListener
    {
        public void actionPerformed(java.awt.event.ActionEvent event)
        {
            Object object = event.getSource();
            if (object == logonButton)
            {
                logonButton_actionPerformed(event);
            }

            // ..More conditions for other objects.
        }
    }

    // The keyboard login method, does not terminate the card swipe daemon thread.
    void logonButton_actionPerformed(java.awt.event.ActionEvent event)
    {       
        try
        {
            // ..More code that will evaluate login information.

            if (authenticate == -1)
            {
                // Notify for failed login.
            }
            else if (authenticate == 0)
            {
                SwipeReader.getInstance().enable(false);
            }
        }
        catch (Exception e)
        {
            // Error logger.
        }
    }

    // The card swipe listener used for card login.
    public void deviceDataReceived(Object object)
    {   
        try
        {
            // ..More code that will evaluate login information.

            if (authenticate == -1)
            {
                // Notify for failed login.
            }

            if (authenticate == 0)
            {
                SwipeReader.getInstance().enable(false);
            }
        }
        catch (Exception e)
        {
            // Error logger.
        }
    }
}

SwipeReader.java

public class SwipeReader
{
    // This is a singleton class that creates the thread for the daemon.

    CardSwipeDaemon cardSwipeDaemon;
    Thread cardSwipeThread;
    SwipeReader instance;

    private SwipeReader() {}

    public static SwipeReader getInstance()
    {
        if (instance == null) { instance = new SwipeReader(); }
        return instance;
    }

    public void enable (boolean isEnabled)
    {
        if (isEnabled)
        {
            cardSwipeDaemon = new CardSwipeDaemon();
            cardSwipeThread = new Thread(cardSwipeDaemon, "CardSwipeThread");
            cardSwipeThread.start();
        }
        else
        {
            cardSwipeDaemon.stop();
            cardSwipeThread = null;
        }
    }
}

CardSwipeDaemon.java

public class CardSwipeDaemon implements Runnable
{
    CardSwipeDaemon instance;
    private static volatile boolean listenforswipe = true;

    public CardSwipeDaemon() {}

    public static synchronized CardSwipeDaemon getInstance()
    {
        if (instance == null) { instance = new CardSwipeDaemon(); }
        return instance;
    }

    public run()
    {
        listenforswipe = true;
        while (listenforswipe)
        {
            // Code for reading the magnetic stripe data.
        }
    }

    public void stop()
    {
        listenforswipe = false;
    }
}

Solution

  • If your card swipe reader stops after card login, but doesn't stop after manual login, then it is waiting on blocking io to read the card, it doesn't even finish one loop run! It waits for the data forever, so can't even check that the listenforswipe was set to false!

    You should rather not stop the card reading thread, but depending on the state of your application take proper actions with the event of reading card data from the card reading thread.

    This will resolve the issue with blocking IO, just let the card reading thread wait to receive the data, and in the event of reading the data send it to proper target depending on your app state. It is like with keyboard - you don't start and stop the keyboard - you just change the focus, so the input from keyboard goes to differnet target depending on current state of your desktop.

    My previous answer:

    You have some problem in other part of your code, all instances of CardSwipeThread should finish when you invoke the stop (because listenforswipe is private static volatile boolean), there are two possible reasons why it is not stopping:

    1. The stop() method is never invoked - please debug that it is invoked

    2. The "// Code for reading the magnetic stripe data." is not ending and probably blocks on some IO operation - waiting for data to be read. If that is the case, then if you swipe the card second time it could probably finish the CardSwipeThread - but it depends on how you have implemented this code, please post this code, so we can help you

    Also if you plan to have other people to login/unlogin then why do you stop the CardSwipeThread daemon? Let it run, just serve properly the card read event when someone is already logged in, what should happen then? This way, you don't need to bother that the CardSwipeThread is waiting on blocking IO for someone to enter the card.

    EDIT:

    The idea to use SwingWorker doesn't resolve any issues here, if you call the cancel method, it will anyway wait for the data to be read. This could lead to situation where new SwingWorker with CardSwipeReader can't be started because of the not properly handled resources. This could result that the card reader would be unsusable. This SwingWorker approach doesn't even touch the real problem of BlockingIO in the card reading code.