| /******************************************************************************* |
| * Copyright (c) 2010, 2011 Sonatype, Inc. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Sonatype, Inc. - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.aether.util.concurrency; |
| |
| 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(); |
| } |
| } |
| |
| } |