| package org.eclipse.aether.util.concurrency; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.concurrent.locks.LockSupport; |
| |
| /** |
| * A utility class to forward any uncaught {@link Error} or {@link RuntimeException} from a {@link Runnable} executed in |
| * a worker thread back to the parent thread. The simplified usage pattern looks like this: |
| * |
| * <pre> |
| * RunnableErrorForwarder errorForwarder = new RunnableErrorForwarder(); |
| * for ( Runnable task : tasks ) |
| * { |
| * executor.execute( errorForwarder.wrap( task ) ); |
| * } |
| * errorForwarder.await(); |
| * </pre> |
| */ |
| public final class RunnableErrorForwarder |
| { |
| |
| private final Thread thread = Thread.currentThread(); |
| |
| private final AtomicInteger counter = new AtomicInteger(); |
| |
| private final AtomicReference<Throwable> error = new AtomicReference<Throwable>(); |
| |
| /** |
| * Creates a new error forwarder for worker threads spawned by the current thread. |
| */ |
| public RunnableErrorForwarder() |
| { |
| } |
| |
| /** |
| * Wraps the specified runnable into an equivalent runnable that will allow forwarding of uncaught errors. |
| * |
| * @param runnable The runnable from which to forward errors, must not be {@code null}. |
| * @return The error-forwarding runnable to eventually execute, never {@code null}. |
| */ |
| public Runnable wrap( final Runnable runnable ) |
| { |
| if ( runnable == null ) |
| { |
| throw new IllegalArgumentException( "runnable missing" ); |
| } |
| |
| counter.incrementAndGet(); |
| |
| return new Runnable() |
| { |
| public void run() |
| { |
| try |
| { |
| runnable.run(); |
| } |
| catch ( RuntimeException e ) |
| { |
| error.compareAndSet( null, e ); |
| throw e; |
| } |
| catch ( Error e ) |
| { |
| error.compareAndSet( null, e ); |
| throw e; |
| } |
| finally |
| { |
| counter.decrementAndGet(); |
| LockSupport.unpark( thread ); |
| } |
| } |
| }; |
| } |
| |
| /** |
| * Causes the current thread to wait until all previously {@link #wrap(Runnable) wrapped} runnables have terminated |
| * and potentially re-throws an uncaught {@link RuntimeException} or {@link Error} from any of the runnables. In |
| * case multiple runnables encountered uncaught errors, one error is arbitrarily selected. <em>Note:</em> This |
| * method must be called from the same thread that created this error forwarder instance. |
| */ |
| public void await() |
| { |
| awaitTerminationOfAllRunnables(); |
| |
| Throwable error = this.error.get(); |
| if ( error != null ) |
| { |
| if ( error instanceof RuntimeException ) |
| { |
| throw (RuntimeException) error; |
| } |
| else if ( error instanceof ThreadDeath ) |
| { |
| throw new IllegalStateException( error ); |
| } |
| else if ( error instanceof Error ) |
| { |
| throw (Error) error; |
| } |
| throw new IllegalStateException( error ); |
| } |
| } |
| |
| private void awaitTerminationOfAllRunnables() |
| { |
| if ( !thread.equals( Thread.currentThread() ) ) |
| { |
| throw new IllegalStateException( "wrong caller thread, expected " + thread + " and not " |
| + Thread.currentThread() ); |
| } |
| |
| boolean interrupted = false; |
| |
| while ( counter.get() > 0 ) |
| { |
| LockSupport.park(); |
| |
| if ( Thread.interrupted() ) |
| { |
| interrupted = true; |
| } |
| } |
| |
| if ( interrupted ) |
| { |
| Thread.currentThread().interrupt(); |
| } |
| } |
| |
| } |