2

I have a simple restarting Runnable:

static void launchThreads(){
    ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
    try {
        exec.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("line"); <--breakpoint
                }
                catch (Exception e) {
                    e.printStackTrace(); <--breakpoint
                }
            }
        }, 1, 1000, TimeUnit.MILLISECONDS);
    }catch (Exception e) {
        e.printStackTrace(); <--breakpoint
    }
}

If I launch that method from the main() method of the class, it works as expected - writes a line that looks like "line", once a second, forever.

line
line
line
line
...

But if I launch that from a TestNG test method:

@Test
public class PostpackagesIntegratorTest {
    @Test
    public void testLaunchThreads10SmallestWithoutFees() {
        PostpackagesIntegrator.launchThreads();
    }
}

,it outputs only one "line" and the test is passed. "Successfully".

If I make a JUnit4 test to launch the same method,

public class PostpackagesIntegratorJUnit4Test {
    @Test
    public void launchThreadsTest() {
        PostpackagesIntegrator.launchThreads();
    }
}

, the test is also passed, again with only one "line" in output.

If I am not running, but debugging the tests, my IntelliJ stops at printing the "line", but does not notice any catch content.

I do not understand, what prevents the ScheduledExecutorService from repetitions. According to docs, such non-repeating should happen at an exception, but no exception happens.

Is it possible to make ScheduledExecutorService in TestNG tests or must I use other classes? Due to the whole project, I am limited by Java 6 version and TestNG.

Edit: @Eugene advised to declare exec as private static final ScheduledExecutorService exec, for blocking erroneous GC, but it did not help and even didn't change anything - the problem is elsewhere.

Gangnus
  • 24,044
  • 16
  • 90
  • 149
  • before you think about testing, I recommend you rethink creating a pool in a method. Here [is one Q&A](https://stackoverflow.com/questions/58714980/rejectedexecutionexception-inside-single-executor-service) that should make you re-think this approach. – Eugene Feb 19 '21 at 20:52
  • could you consider the SchedulerExecutorService as tested code and extract the Runnable to its own class to test it individually? – germanio Feb 19 '21 at 20:54
  • What is the expected result/behavior you want? Do you want to wait forever in the unit test as the job will not be cancelled? – Progman Feb 19 '21 at 20:56
  • @Progman Exactly. Of course, the real task does not contain endless tests, but it is much more complicated, so I simplified it maximally and that simplified test supposes endless repeating. – Gangnus Feb 19 '21 at 21:36
  • @germanio What is the sense to extract one line? The reference to it will contain one line anyway. – Gangnus Feb 19 '21 at 21:39
  • @Gangnus Is calling `awaitTermination()` on the `ScheduledExecutorService` object an option? It is not clear if you want to wait or not or what the behavior of the unit test should be. – Progman Feb 19 '21 at 21:41
  • @Eugene The method with the pool works correctly when not testing... – Gangnus Feb 19 '21 at 21:44
  • so did our, for about 1/2 a year, until we noticed the behavior in the question. – Eugene Feb 19 '21 at 21:45
  • @Eugene So, you think, it is all about a bug? It is possible, for I have noticed many questions about one-run instead of repeating run, with very weak answers, but the questions' code is not minimized enough to really localize the problem... But then, could you advise another pool/schedule class, that works? – Gangnus Feb 19 '21 at 22:00
  • this isn't about the pool itself, which has no problems. just declare it as a "gc root", as a static final... or use a java version higher then the one in the question and where the answer mentions that. – Eugene Feb 19 '21 at 22:01
  • @Eugene I am terribly sorry, after Covid a have lost at least 20 points of my IQ and I am not catching... Could you explain that 'just declare it as a "gc root"' as an answer with example, please? – Gangnus Feb 19 '21 at 22:33
  • this would not address the question itself. So, let me may be explain this: since you declare your pool inside your method - there is nothing stopping GC to recollect that memory. When that happens, your tasks will fail. As the answer and question that I provided initially explained. Moving to to `private static final ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor()` makes it a so-called "gc root", a GC wii _not_ collect that. makes sense now? – Eugene Feb 19 '21 at 23:34
  • @Eugene Perfectly, thank you. But... It did not help. No behaviour changed, neither of the main, or of the TestNG test, or JUnit4 test. So, the problem is elsewhere. Thank you greatly anyway. – Gangnus Feb 20 '21 at 00:00
  • You edited the question quite a lot since u first wrote it. My comment was not about solving this, but simply a heads up – Eugene Feb 20 '21 at 00:27
  • @Gangnus What do you expect in your unit test method when you run it? Keep in mind that the executor is running on a different thread (by definition), so your test method finishes with a "passed" while the executor is running and might be even get killed as your unit test completes. How do you want your unit test method to run regarding the parallel running tasks in the executor? Also, can you provide a MCVE which is more closely to what you are trying to do (maybe not an endless task)? – Progman Feb 20 '21 at 18:39
  • @Progman No, your logic is bad. Just now, because I need working code, I changed executor for a thread that simply repeats a callable. It runs in one thread and without any problems in tests and launched from main, it is repeating that println in callable infinitely, once for a second, as must be. – Gangnus Feb 20 '21 at 19:24
  • @Gangnus When you change it from executor to one thread and you run your unit test, the unit test will not complete as long as the thread is running, even though the test method has completed? Can you edit the question to include the new code you have? Please include the results when you run your code from the `main()` method and from the unit test. – Progman Feb 20 '21 at 20:21
  • @Gangnus well, I'm asking this because I want to understand what's the ultimate goal of this test – germanio Feb 22 '21 at 15:36
  • @germanio This test or this task by itself has no sense. It is the minimalized extraction of a real case, up to SO rules. In the real case, it seemed that the same method works differently in the test than in the normal run. I peeled the code down to the pool and one runnable of one line and found that it is really so. And now I am absolutely sure that the reason of such behaviour is a bug. I have no time to localize the bug, but I suppose it is in the "queue" part of the pool code. I had already met a bug in java concurrent queues. Up to version 8 they are practically unusable. – Gangnus Feb 22 '21 at 20:34

1 Answers1

1

I would start by dumping a lot of thread details.

Thread.currentThread().dumpStack() (or just (new Throwable()).printStackTrace()) would show any peculiar classes frames above your runnable. These could be quite different if junit/ng are fiddling with thread factories or such.

Then you can also inspect the thread.currentThread() for isDaemon() and the threadgroup's isDeamon(). Your new executorsvc may be part (and making worker threads in) a threadgroup that is interrupted. You might be able to reveal that by writing your own thread factory and issuing threads whose interrupt() is proxied for the sake of trapping it (before forwarding it). A main() is normally a non-daemon thread, so it would spawn non daemon threads too for the execsvc. I wouldn't be surprised is junit/ng are wrapping the test in a pseudo thread sandbox to 'try' to detect and perhaps stop leaked/forgotten threads from a test.

If your are in a debugger, you should be able to browse the top frame local variables and the thread instance already without much code, to reveal all of the above (except the unanticipated interrupt call, if any).

Dharman
  • 30,962
  • 25
  • 85
  • 135
user2023577
  • 1,752
  • 1
  • 12
  • 23
  • Thank you very much for your answer. Surely, your way would allow to find the bug, but for me it was more important to understand there IS a bug, and a simple hand-made one thread repeater already works in all necessary cases and that is enough for me. Anyway, I will use your pieces of advice when I'll need to localize problems with multithread programming the next time. Just now I suppose the source of the problem is hiding in the queue bugs (queues from java.concurrent ARE bugged at least up to the 1.8 version). And the sheduled executor is overcomplicated enough to use these queues. – Gangnus Feb 20 '21 at 19:37