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

Future doesnt throw ExecutionException for Tasks submitted to ThreadPoolExecutor

    Details

    • Type: Bug
    • Status: Open
    • Priority: P3
    • Resolution: Unresolved
    • Affects Version/s: 8, 9, 10, openjfx11
    • Fix Version/s: tbd
    • Component/s: javafx
    • Subcomponent:
    • CPU:
      x86_64
    • OS:
      generic

      Description

      A DESCRIPTION OF THE PROBLEM :
      If you construct a javafx.concurrent.Task, and submit it to a ThreadPoolExecutor, the resulting Future that is handed back doesn't work properly, in that it doesn't throw an ExecutionException when you call get(), when an exception did indeed, happen.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      See code. https://github.com/darmbrust/OracleBugs/blob/master/src/RunTest2.java Also attached below.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Throw an exception.
      ACTUAL -
      the exception gets eaten.

      ---------- BEGIN SOURCE ----------
      import java.util.concurrent.BlockingQueue;
      import java.util.concurrent.ExecutionException;
      import java.util.concurrent.Future;
      import java.util.concurrent.FutureTask;
      import java.util.concurrent.LinkedBlockingQueue;
      import java.util.concurrent.RunnableFuture;
      import java.util.concurrent.ThreadPoolExecutor;
      import java.util.concurrent.TimeUnit;
      import javafx.application.Platform;
      import javafx.concurrent.Task;
      import javafx.embed.swing.JFXPanel;

      public class RunTest2
      {
      public static void main(String[] args) throws InterruptedException, ExecutionException
      {
      new JFXPanel(); //start javafx / task API code (why oh why does the Task API require JavaFX to be up?)


      {
      ThreadPoolExecutor tpe = new ThreadPoolExecutor(1, 1, 5, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>());

      TaskThrows tw = new TaskThrows();

      Future<?> future = tpe.submit(tw);

      System.out.println("Expect an exception....");
      future.get(); //This should throw an ExecutionException........

      System.out.println("Yet it didn't throw... BUG!!!");

      tpe.shutdown();
      }

      System.out.println("Try with fix....");

      {
      ThreadPoolExecutor tpe = new WorkingThreadPoolExecutor(1, 1, 5, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>());

      TaskThrows tw = new TaskThrows();

      Future<?> future = tpe.submit(tw);

      System.out.println("Expect an exception....");
      try
      {
      future.get(); //This should throw an ExecutionException........
      }
      catch (Exception e)
      {
      System.out.println("This is the expected path - thread pool with override works.... " + e);
      }
      tpe.shutdown();
      }

      Platform.exit();
      }

      private static class TaskThrows extends Task<Void>
      {
      @Override
      public Void call() throws Exception
      {
      throw new Exception("oops");
      }
      }

      private static class WorkingThreadPoolExecutor extends ThreadPoolExecutor
      {
      public WorkingThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
      {
      super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
      }

      @Override
      protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value)
      {
      if (runnable instanceof FutureTask)
      {
      return (FutureTask<T>)runnable;
      }
      else
      {
      return super.newTaskFor(runnable, value);
      }
      }
      }
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      If you extend the ThreadPoolExecutor, and override the newTaskFor methods, to make it so that it doesn't generate a FutureTask to wrap a FutureTask, rather, just using the FutureTask it already had, then exceptions propagate properly.

      The bug appears to be in the chain that happens from AbstractExecutorService.submit(Runnable) - where it is trying to get a RunnableFuture, but doesn't check if it already has one. The way it wraps the runnable that comes in, it ends up only calling run() on the RunnableFuture, which doesn't throw an exception, and it never calls the get() or checks for an exception to map back into the RunnableFuture that it created to (unnecessarily) wrap the Runnable.

      FREQUENCY : always


        Attachments

          Activity

            People

            • Assignee:
              Unassigned
              Reporter:
              webbuggrp Webbug Group
            • Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

              • Created:
                Updated: