Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8153487

ThreadPoolExecutor: awaitTermination does not wait for UncaughtExceptionHandler to complete!

XMLWordPrintable

      FULL PRODUCT VERSION :
      java version "1.8.0_74"
      Java(TM) SE Runtime Environment (build 1.8.0_74-b02)
      Java HotSpot(TM) 64-Bit Server VM (build 25.74-b02, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Microsoft Windows [Version 6.1.7601]

      A DESCRIPTION OF THE PROBLEM :
      We have developed a batch processing tool for transferring a large number of documents from one content management system to another. In order to increase throughput we make use of a thread-pool-executor which executes several migration tasks in parallel. In case of an unexpected exception our application should notify a human administrator by sending an e-mail. We wanted to achieve this by means of an uncaught-exception-handler that gets called whenever a runtime exception or JVM error occurs.

      Unfortunately the thread-pool-executor - when it is shut down - does not wait for a pool thread to terminate if that thread is currently in the state of running the uncaught-exception-handler. So the bug is that the thread-pool-executor reports that is has been terminated successfully although a pool thread is still occupied with uncaught exception handling. In our scenario this misbehaviour caused a premature termination of the whole application preventing the expected notification e-mail from being delivered!!!

      It was very hard to figure out why our application did not behave in the expected way!!!

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      I have constructed a JUnit test case for reproducing the problem.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      We expect that all pool threads have been terminated if method awaitTermination() returns true.
      ACTUAL -
      But this is not guaranteed if one of the pool threads is in the state of running an uncaught-exception-handler.

      REPRODUCIBILITY :
      This bug can be reproduced occasionally.

      ---------- BEGIN SOURCE ----------
      package de.cenit.test;

      import java.lang.Thread.UncaughtExceptionHandler;
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.LinkedBlockingQueue;
      import java.util.concurrent.ThreadFactory;
      import java.util.concurrent.ThreadPoolExecutor;
      import java.util.concurrent.TimeUnit;
      import java.util.concurrent.atomic.AtomicInteger;

      import org.junit.Assert;
      import org.junit.Test;

      public class UncaughtExceptionHandlerTest {
          
          private static final int THREAD_POOL_SIZE = 5;

          private volatile boolean mailSent;

          // We use an uncaught-exception-handler for notifying an administrator of unexpected exceptions.
          private final UncaughtExceptionHandler uncaughtExceptionHandler = new UncaughtExceptionHandler() {
              @Override
              public void uncaughtException(Thread thread, Throwable throwable) {
                  System.out.printf("Thread %s is sending mail...\n", Thread.currentThread().getName());
                  // Instead of sending an e-mail we do some useless stuff here.
                  for (int i = 0; i < 10000; i++) {
                      System.out.println(i);
                  }
                  mailSent = true;
              }
          };

          private final ThreadFactory threadFactory = new ThreadFactory() {
              private final AtomicInteger threadNumber = new AtomicInteger(1);

              public Thread newThread(Runnable r) {
                  Thread t = new Thread(r, "test-" + threadNumber.getAndIncrement());

                  if (t.isDaemon()) {
                      t.setDaemon(false);
                  }
                  if (t.getPriority() != Thread.NORM_PRIORITY) {
                      t.setPriority(Thread.NORM_PRIORITY);
                  }
                  t.setUncaughtExceptionHandler(uncaughtExceptionHandler);
                  return t;
              }
          };

          @Test
          public void testUncaughtExceptionHandler() throws InterruptedException {
              final ExecutorService executor = new ThreadPoolExecutor(THREAD_POOL_SIZE, THREAD_POOL_SIZE,
                      0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),
                          threadFactory, new ThreadPoolExecutor.AbortPolicy());
              
              executor.execute(new Runnable() {
                  @Override
                  public void run() {
                      // Throwing an exception here will result in an invocation of the uncaught-exception-handler.
                      throw new RuntimeException("Test exception!");
                  }
              });
              
              executor.shutdown();
              Assert.assertTrue("A task is still running!", executor.awaitTermination(30, TimeUnit.SECONDS));
              Assert.assertTrue("JVM terminates without having sent the expected e-mail!", mailSent);
          }
      }

      ---------- END SOURCE ----------

            martin Martin Buchholz
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated:
              Resolved: