RFR: jsr166 jdk9 integration wave 2

Peter Levart peter.levart at gmail.com
Wed Nov 25 15:01:39 UTC 2015


Hi Doug,

On 11/25/2015 12:47 PM, Doug Lea wrote:
> On 11/25/2015 03:05 AM, Peter Levart wrote:
>> On 11/25/2015 01:59 AM, Martin Buchholz wrote:
>>> On Tue, Nov 24, 2015 at 2:30 PM, Peter Levart <peter.levart at gmail.com
>>> <mailto:peter.levart at gmail.com>> wrote:
>>>     What do you think of exception cloning?
>>> Making copies of java objects has historically been troublesome (e.g.
>>> Cloneable).
>>
>> I was only thinking of Cloneable. Serialization is troublesome. If 
>> Throwable was
>> retrofitted to implement Cloneable and have the following new static 
>> method:
>
> I still think this would be a useful feature, assuming no hidden snags.
> In addition to CompletableFuture, we might be able to use it instead of
> emulating a variant of it in ForkJoinTask.getThrowableException.
> There are probably other candidate uses elsewhere (although I can't
> think of any offhand).

For ForkJoinTask you would need an additional feature. Reinitializing of 
the cause and clearing any suppressed exceptions on the clone. This 
could be controlled via two additional boolean flags:


     /**
      * Returns a {@link Object#clone() clone} of given {@code exception}
      * which shares all state with original exception (shallow clone) 
and is
      * augmented in the following way:
      * <p>
      * If {@code resetCause} parameter is {@code true}, then clone's
      * {@link #getCause() cause} is reset to a state where it can be
      * {@link #initCause(Throwable) initialized} again. If {@code 
resetCause}
      * parameter is {@code false}, then clone's cause is inherited from 
original
      * exception (either initialized or uninitialized).
      * <p>
      * If {@code resetSuppressed} parameter is {@code true} and 
original exception
      * has suppression enabled, then clone's suppressed exceptions are 
reset
      * to an empty list. If {@code resetSuppressed} parameter is {@code 
false}
      * (or original exception has suppression disabled) then clone's
      * suppressed exceptions are inherited from original exception (or 
clone's
      * suppression is disabled too). In either case, clone's suppressed
      * exceptions are independent of original exception's suppressed
      * exceptions. Any further {@link #addSuppressed(Throwable) 
additions} to
      * the clone's suppressed exceptions do not affect original exception's
      * suppressed exceptions and vice versa.
      *
      * @param exception       the exception to clone.
      * @param <T>             the type of exception
      * @param resetCause      if {@code true}, clone's cause is reset to an
      *                        uninitialized state.
      * @param resetSuppressed if {@code true} and original exception 
has suppression
      *                        enabled, clone's suppressed exceptions 
are cleared.
      * @return shallow clone of given exception augmented according to 
passed-in
      *         flags.
      * @since 1.9
      */
     @SuppressWarnings("unchecked")
     public static <T extends Throwable> T clone(T exception,
                                                 boolean resetCause,
                                                 boolean resetSuppressed) {
         try {
             synchronized (exception) {
                 Throwable clone = (Throwable) exception.clone();
                 if (resetCause) {
                     // reset to uninitialized state
                     clone.cause = clone;
                 }
                 if (clone.suppressedExceptions != null &&
                     clone.suppressedExceptions != SUPPRESSED_SENTINEL) {
                     // suppressedExceptions has already been added to
                     // and suppression is enabled
                     clone.suppressedExceptions = resetSuppressed
                         ? new ArrayList<>()
                         : new ArrayList<>(clone.suppressedExceptions);
                 }
                 return (T) clone;
             }
         } catch (CloneNotSupportedException e) {
             throw new InternalError(e);
         }
     }


In ForkJoinTask.getThrowableException you could then replace the following:

     private Throwable getThrowableException() {
         int h = System.identityHashCode(this);
         ExceptionNode e;
         final ReentrantLock lock = exceptionTableLock;
         lock.lock();
         try {
             expungeStaleExceptions();
             ExceptionNode[] t = exceptionTable;
             e = t[h & (t.length - 1)];
             while (e != null && e.get() != this)
                 e = e.next;
         } finally {
             lock.unlock();
         }
         Throwable ex;
         if (e == null || (ex = e.ex) == null)
             return null;
         if (e.thrower != Thread.currentThread().getId()) {
             Class<? extends Throwable> ec = ex.getClass();
             try {
                 Constructor<?> noArgCtor = null;
                 Constructor<?>[] cs = ec.getConstructors();// public 
ctors only
                 for (int i = 0; i < cs.length; ++i) {
                     Constructor<?> c = cs[i];
                     Class<?>[] ps = c.getParameterTypes();
                     if (ps.length == 0)
                         noArgCtor = c;
                     else if (ps.length == 1 && ps[0] == Throwable.class) {
                         Throwable wx = (Throwable)c.newInstance(ex);
                         return (wx == null) ? ex : wx;
                     }
                 }
                 if (noArgCtor != null) {
                     Throwable wx = (Throwable)(noArgCtor.newInstance());
                     if (wx != null) {
                         wx.initCause(ex);
                         return wx;
                     }
                 }
             } catch (Exception ignore) {
             }
         }
         return ex;
     }


with the following:

     private Throwable getThrowableException() {
         int h = System.identityHashCode(this);
         ExceptionNode e;
         final ReentrantLock lock = exceptionTableLock;
         lock.lock();
         try {
             expungeStaleExceptions();
             ExceptionNode[] t = exceptionTable;
             e = t[h & (t.length - 1)];
             while (e != null && e.get() != this)
                 e = e.next;
         } finally {
             lock.unlock();
         }
         Throwable ex;
         if (e == null || (ex = e.ex) == null)
             return null;
         if (e.thrower != Thread.currentThread().getId()) {
             ex = Throwable.clone(ex, true, true)
                           .fillInStackTrace()
                           .initCause(ex);
         }
         return ex;
     }


Regards, Peter

>
> In the mean time though, I think using addSuppressed in
> CompletableFuture.whenComplete is the best we can do, and
> surely better than not doing it.
>
> -Doug
>
>>
>>      /**
>>       * {@link Object#clone() Clones} given {@code exception} and 
>> returns it's
>> clone which
>>       * shares all state with original exception (shallow clone) 
>> except for the
>> possible list of already
>>       * {@link #addSuppressed(Throwable) added} {@link 
>> #getSuppressed() suppressed}
>>       * exceptions. The suppressed exception instances are not 
>> cloned, just the
>>       * list containing them. Further {@link 
>> #addSuppressed(Throwable) additions}
>>       * to the suppressed exceptions of the returned clone instance
>>       * don't affect the suppressed exceptions of original exception 
>> and vice versa.
>>       *
>>       * @param exception the exception to clone.
>>       * @param <T>       the type of exception
>>       * @return shallow clone of given exception with suppressed 
>> exception
>>       * list shallow-cloned
>>       * @since 1.9
>>       */
>>      @SuppressWarnings("unchecked")
>>      public static <T extends Throwable> T clone(T exception) {
>>          try {
>>              Throwable clone = (Throwable) exception.clone();
>>              if (clone.suppressedExceptions != null &&
>>                  clone.suppressedExceptions != SUPPRESSED_SENTINEL) {
>>                  clone.suppressedExceptions = new
>> ArrayList<>(clone.suppressedExceptions);
>>              }
>>              return (T) clone;
>>          } catch (CloneNotSupportedException e) {
>>              throw new InternalError(e);
>>          }
>>      }
>>
>>
>> ...then would you prefer using it or would you nevertheless prefer 
>> swapping the
>> roles of exceptions in whenComplete?
>>
>> Regards, Peter
>>
>




More information about the core-libs-dev mailing list