JDK-8071638 : [JAVADOC] Buggy example in javadoc for afterExecute to access a submitted job's Throwable
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util.concurrent
  • Affected Version: 9
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • Submitted: 2015-01-25
  • Updated: 2015-11-09
  • Resolved: 2015-09-21
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 9
9 b88Resolved
Related Reports
Duplicate :  
Description
A DESCRIPTION OF THE PROBLEM :
Example implementation given for afterExecute method invokes 'get' method of 'Future' without verifying the state of 'Future'. In case of a periodically scheduled future task like the ones created using 'scheduleAtFixedRate(...)' or 'scheduleWithFixedDelay(...)' methods of 'ScheduledThreadPoolExecutor', future is run and reset by the scheduled thread pool executor. As a result, post a normal execution of runnable, the call to Future's get() method will lead to an indefinite wait.

Instead, the call to Future's get() method should only be made if Future's isDone() method returns true.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Method invoked upon completion of execution of the given Runnable. This method is invoked by the thread that executed the task. If non-null, the Throwable is the uncaught RuntimeException or Error that caused execution to terminate abruptly.

This implementation does nothing, but may be customized in subclasses. Note: To properly nest multiple overridings, subclasses should generally invoke super.afterExecute at the beginning of this method.

Note: When actions are enclosed in tasks (such as FutureTask) either explicitly or via methods such as submit, these task objects catch and maintain computational exceptions, and so they do not cause abrupt termination, and the internal exceptions are not passed to this method. If you would like to trap both kinds of failures in this method, you can further probe for such cases, as in this sample subclass that prints either the direct cause or the underlying exception if a task has been aborted:

 class ExtendedExecutor extends ThreadPoolExecutor {
   // ...
   protected void afterExecute(Runnable r, Throwable t) {
     super.afterExecute(r, t);
     if (t == null && r instanceof Future<?> && ((Future<?>) r).isDone()) {
       try {
         Object result = ((Future<?>) r).get();
       } catch (CancellationException ce) {
           t = ce;
       } catch (ExecutionException ee) {
           t = ee.getCause();
       } catch (InterruptedException ie) {
           Thread.currentThread().interrupt(); // ignore/reset
       }
     }
     if (t != null)
       System.out.println(t);
   }
 }

Parameters:
    r - the runnable that has completed
    t - the exception that caused termination, or null if execution completed normally
ACTUAL -
Method invoked upon completion of execution of the given Runnable. This method is invoked by the thread that executed the task. If non-null, the Throwable is the uncaught RuntimeException or Error that caused execution to terminate abruptly.

This implementation does nothing, but may be customized in subclasses. Note: To properly nest multiple overridings, subclasses should generally invoke super.afterExecute at the beginning of this method.

Note: When actions are enclosed in tasks (such as FutureTask) either explicitly or via methods such as submit, these task objects catch and maintain computational exceptions, and so they do not cause abrupt termination, and the internal exceptions are not passed to this method. If you would like to trap both kinds of failures in this method, you can further probe for such cases, as in this sample subclass that prints either the direct cause or the underlying exception if a task has been aborted:

 class ExtendedExecutor extends ThreadPoolExecutor {
   // ...
   protected void afterExecute(Runnable r, Throwable t) {
     super.afterExecute(r, t);
     if (t == null && r instanceof Future<?>) {
       try {
         Object result = ((Future<?>) r).get();
       } catch (CancellationException ce) {
           t = ce;
       } catch (ExecutionException ee) {
           t = ee.getCause();
       } catch (InterruptedException ie) {
           Thread.currentThread().interrupt(); // ignore/reset
       }
     }
     if (t != null)
       System.out.println(t);
   }
 }

Parameters:
    r - the runnable that has completed
    t - the exception that caused termination, or null if execution completed normally

URL OF FAULTY DOCUMENTATION :
http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html#afterExecute(java.lang.Runnable,%20java.lang.Throwable)


line to fix:
http://hg.openjdk.java.net/jdk9/dev/jdk/file/7c6d6f1b7a56/src/java.base/share/classes/java/util/concurrent/ThreadPoolExecutor.java#l1971


Comments
fixed in: http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/ThreadPoolExecutor.java?view=markup
05-03-2015

The submitter's suggestion is a good one. It is legal to create Futures that do not complete upon execution. We'll add the "&& ((Future<?>) r).isDone())" check in the example.
30-01-2015

afterExecute is called _after_ execution of the task. By the time it is called the Future must have completed, by definition, so unless the implementation of Future is buggy isDone must return true and so does not need to be checked.
27-01-2015