Java asserts not send to console when called from a runnable
Asked Answered
M

2

6

In my program I made an assert - which evaluates to false - from a runnable, but never see any console output about the assert. I want to know if my asserts are false, but it seems the runnable is catching all the thrown asserts?

Below is the simplest example program I could write to demonstrate. (Assertions are enabled. The program would behave differently if they weren't and print both lines instead of just the one). The output of the program is.

About to assert False

That's it. After that the assert statement throws and is caught by something and I never know about it. I want to know about it, what am I doing wrong?

import java.nio.ByteBuffer;
import java.util.concurrent.*;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

import java.awt.FlowLayout;
import javax.swing.*;

class App
{
  private static final ScheduledExecutorService sExecutor =
    Executors.newSingleThreadScheduledExecutor();

  // Main
  public static void main(String[] args)
  {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() { createAndShowGUI(); } });

  }

  // Swing GUI
  private static void createAndShowGUI()
  {
    // Just create a swing thing. Boring
    JFrame frame = new JFrame("Title String");
    JLabel label = new JLabel("Hello World");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().add(label);
    frame.getContentPane().setLayout(new FlowLayout());
    frame.pack();
    frame.setVisible(true);

    // ********************************************
    // INTERESTING CODE HERE. We schedule a runnable which assert's false
    // but we never see a console assert error!
    // ********************************************
    sExecutor.schedule(new Runnable()
      { @Override public void run() { doAssertFalse(); }}, 0, TimeUnit.SECONDS);

  }

  public static void doAssertFalse()
  {
    System.out.println("About to assert False");
    assert false;
    System.out.println("Done asserting False");
  }
}
Melvamelvena answered 15/1, 2013 at 18:58 Comment(5)
Have you enabled the assertion on the command line when launching your application?Toiletry
Assertions are enabled. The program would behave differently if the were not (it would print both lines instead of just the one)Melvamelvena
Maybe it has to do with the UncaughtExceptionHandler associated with your thread. Have you tried setting a custom one, to see if you can see the assertion exception pass by?Toiletry
@Laf: How do you do that with an executor? The executor creates the Threads for you without giving you access to them (afaik) and you need to put an UncaughtExceptionHandler on the Thread instance.Crum
@DanielKaplan I haven't really tested it, so I might be totally wrong, but a Thread is a Runnable, so you could create your own Thread implementation with your code, and associate a custom UncaughtExceptionHandler to this thread, then use it as a parameter when calling the executor. Don't know if it would make a difference. In any case, you have provided an answer which is probably way more efficient than my suggestion.Toiletry
C
3

This will do it:

private static final ScheduledExecutorService sExecutor =
        Executors.newSingleThreadScheduledExecutor();

// Main
public static void main(String[] args)
{
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            try {
                createAndShowGUI();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } });

}

// Swing GUI
private static void createAndShowGUI() throws ExecutionException, InterruptedException {
    // Just create a swing thing. Boring
    JFrame frame = new JFrame("Title String");
    JLabel label = new JLabel("Hello World");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().add(label);
    frame.getContentPane().setLayout(new FlowLayout());
    frame.pack();
    frame.setVisible(true);

    // ********************************************
    // INTERESTING CODE HERE. We schedule a runnable which assert's false
    // but we never see a console assert error!
    // ********************************************
    ScheduledFuture<?> future = sExecutor.schedule(new Runnable() {
        @Override
        public void run() {
            doAssertFalse();
        }
    }, 0, TimeUnit.SECONDS);
    future.get();

}

public static void doAssertFalse()
{
    System.out.println("About to assert False");
    assert false;
    System.out.println("Done asserting False");
}

Notice I'm saving the result of schedule into a ScheduledFuture variable. The exception isn't returned until you call the get() method on the future. All exceptions are thrown wrapped in an ExecutionException.

Unfortunately this blocks, so another way you can get the exception is like this:

// Swing GUI
private static void createAndShowGUI() {
    // Just create a swing thing. Boring
    JFrame frame = new JFrame("Title String");
    JLabel label = new JLabel("Hello World");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().add(label);
    frame.getContentPane().setLayout(new FlowLayout());
    frame.pack();
    frame.setVisible(true);

    // ********************************************
    // INTERESTING CODE HERE. We schedule a runnable which assert's false
    // but we never see a console assert error!
    // ********************************************
    sExecutor.schedule(new Runnable() {
        @Override
        public void run() {
            try {
                doAssertFalse();
            } catch (Error e) {
                e.printStackTrace();
            }
        }
    }, 0, TimeUnit.SECONDS);

}

public static void doAssertFalse() {
    System.out.println("About to assert False");
    assert false;
    System.out.println("Done asserting False");
}

Notice that I'm catching an Error, not an Exception. I'm doing this because Assertions throw a java.lang.AssertionError, not a *Exception.

I'm having trouble finding any documentation in the Javadoc saying that the ScheduledExecutorService swallows exceptions unless you do these things, but through my tests that appears to be the case.

Crum answered 15/1, 2013 at 19:27 Comment(9)
That's true, the exception thrown by the assert will be caught and printed. But I was under the impression that you could add asserts so you the programmer can know something went wrong. Do I have to add a try catch block around every assert (or asserting method) I write?Melvamelvena
Technically, it's not the exception, but the error that's thrown. That's why you're having issues.Crum
My thought process is "Hm I want to make sure numApples is never negative so I'll add an assert!" assert numApples >= 0. But sometimes when the assert is false I am not informed. Must a try catch block go with every assert?Melvamelvena
I consider this a workaround, and not an answer to the question.Liebfraumilch
@Liebfraumilch Why do you say that?Crum
I couldn't find anything in the docs either. So I looked at the FutureTask::innerRun() code which seems to be running it. It catches all throwables, then sets an inner exception. Assertions are a throwable so they are caught...Melvamelvena
This question has some good insight on when to use assertion vs using exceptions.Toiletry
@DanielKaplan That makes sense about the ScheduledFuture. But now I'm sad because I can't put asserts in my code and expect that they will always inform me of errors.Melvamelvena
@Melvamelvena Well if you follow my advice you'll always be informed. It's just kind of annoying that you have to remember to follow my advice instead of it happening automatically :( That being said, relating this to assertions is a little narrow: This same issue occurs with Runtime exceptions. I guess you've just got to be careful with the schedule method.Crum
S
0

Threads don't do anything with exceptions (How could they? What if you don't use STDERR or STDOUT but log to a database, how could Thread.run() know where to log the exception or who to rethrow it to?)

There is a Thread.UncaughtExceptionHandler that you can use, and when you do so your Asserts should work through that.

I suppose the problem is that the mechanism for Assert is using exceptions and therefore must follow the predetermined rules for exceptions. Personally I wouldn't mind if Assert simply printed a stack dump and did an exit(0) because you shouldn't ever be catching that exception anyway--but it is what it is.

Note you can also use Thread.setDefaultUncaughtExceptionHandler just once for all threads in your app (Notice the "DEFAULT")... this should be a pretty good general solution.

import java.lang.*;

public class ThreadDemo {

   public static void main(String[] args) {

     Thread t = new Thread(new adminThread());

     t.setUncaughtExceptionHandler(new Thread.
     UncaughtExceptionHandler() {
        public void uncaughtException(Thread t2, Throwable e) {
           System.out.println(t2 + " throws exception: " + e);
        }
     });
     // this will call run() function
     t.start();

   }
}

class adminThread implements Runnable {

   public void run() {
      assert(false);
      throw new RuntimeException();
   }
} 

When I run this "Normally" it shows the RuntimeException was thrown. When I run with the "-ea" option it shows an AssertException. Give it a try.

Sagerman answered 15/1, 2013 at 21:20 Comment(5)
This all sounds like it should work, but when I tried it it didn't. I wish there was a way I could paste a code block into a comment to give more detail. I implemented the first line of main() to call Thread.setDefaultUncaughtExceptionHandler and the impl just says e.printStackTrace();. Still no output to the console though.Crum
In addition, if I set a breakpoint on that e.printStackTrace(); it doesn't hit it.Crum
That's kind of a "strawman" answer. In his scenario he's using a scheduled executor. In this case, it makes all the difference.Crum
In that case, the answer here will get you the execption: https://mcmap.net/q/471448/-handling-exceptions-for-threadpoolexecutorSagerman
Yes, that's a similar scenario. I addressed this solution in my answer.Crum

© 2022 - 2024 — McMap. All rights reserved.