/** * Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.util.async; import java.util.Set; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.NamedThreadPoolFactory; /** * Represents the production of a result by another thread and potentially allow the original calling thread to * perform another action in the meantime. * * @param <T> type of the result */ public final class AsynchronousOperation<T> { private static final ScheduledExecutorService s_timeouts = createTimeoutExecutor(); private final Class<T> _type; private AsynchronousResult<T> _result; private ResultListener<T> _listener; private static ScheduledExecutorService createTimeoutExecutor() { final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); executor.setKeepAliveTime(60, TimeUnit.SECONDS); executor.allowCoreThreadTimeOut(true); executor.setThreadFactory(new NamedThreadPoolFactory("AsynchronousOperation-Timeout")); return executor; } /** * Creates a new instance. */ private AsynchronousOperation(final Class<T> type) { _type = type; } /** * Creates a new instance typed for the given class. * * @param <T> the type of the result * @param type the class of the result, never null * @return the new instance, never null */ public static <T> AsynchronousOperation<T> create(final Class<T> type) { return new AsynchronousOperation<T>(type); } /** * Creates a new instance typed for a set. * * @param <T> the type within the set * @return the new instance, never null */ @SuppressWarnings({"unchecked", "rawtypes" }) public static <T> AsynchronousOperation<Set<T>> createSet() { return new AsynchronousOperation(Set.class); } /** * Creates a callback object that can be used to post the result when it is available. * * @return the callback object */ public ResultCallback<T> getCallback() { return new ResultCallback<T>(this); } /** * Returns the type of the result. * * @return the type of the result, never null */ protected Class<T> getResultType() { return _type; } /** * Called when a result is available. * * @param result the result value */ protected void setResult(final T result) { setAsynchronousResult(new AsynchronousResult<T>(result, null)); } /** * Called when an exception is available. * * @param exception the exception */ protected void setException(final RuntimeException exception) { ArgumentChecker.notNull(exception, "exception"); setAsynchronousResult(new AsynchronousResult<T>(null, exception)); } /** * Sets the result object, invoking the listener if one is registered. * * @param result the signaled result object */ protected void setAsynchronousResult(final AsynchronousResult<T> result) { final ResultListener<T> resultListener; synchronized (this) { if (_result != null) { throw new IllegalStateException(); } _result = result; resultListener = _listener; } if (resultListener != null) { resultListener.operationComplete(result); } } /** * Called when a result listener has been registered with the exception. * * @param resultListener the listener */ protected void setResultListener(final ResultListener<T> resultListener) { ArgumentChecker.notNull(resultListener, "resultListener"); final AsynchronousResult<T> result; synchronized (this) { if (_listener != null) { throw new IllegalStateException(); } _listener = resultListener; result = _result; } if (result != null) { resultListener.operationComplete(result); } } /** * Called when the exception is blocking on the result. * * @return the result * @throws InterruptedException if interrupted waiting for the result */ protected T waitForResult() throws InterruptedException { final LinkedBlockingDeque<AsynchronousResult<T>> results = new LinkedBlockingDeque<AsynchronousResult<T>>(); setResultListener(new ResultListener<T>() { @Override public void operationComplete(final AsynchronousResult<T> result) { results.add(result); } }); return results.take().getResult(); } /** * Returns control to the calling thread. If a result or exception has already been signaled, the * result is returned or the exception thrown. If no result or exception is available, the checked * {@link AsynchronousExecution} is thrown. * * @return the result, if available * @throws AsynchronousExecution if the result is not available */ public T getResult() throws AsynchronousExecution { AsynchronousResult<T> result; synchronized (this) { result = _result; } if (result == null) { throw new AsynchronousExecution(this); } else { return result.getResult(); } } /** * Declares a timeout on an asynchronous operation. The {@link Cancelable#cancel} callback is made after the timeout period * unless the handle returned by the timeout is itself canceled. * * @param cancelation the user callback, not null * @param timeoutMillis the timeout period in milliseconds * @return a cancellation handle for the timeout */ public static Cancelable timeout(final Cancelable cancelation, final int timeoutMillis) { ArgumentChecker.notNull(cancelation, "cancelation"); ArgumentChecker.notNegativeOrZero(timeoutMillis, "timeoutMillis"); final ScheduledFuture<?> future = s_timeouts.schedule(new Runnable() { @Override public void run() { cancelation.cancel(true); } }, timeoutMillis, TimeUnit.MILLISECONDS); return new Cancelable() { @Override public boolean cancel(final boolean mayInterruptIfRunning) { return future.cancel(mayInterruptIfRunning); } }; } /** * Returns the result (or throws the signaled exception), blocking the caller until it is available. This is equivalent to {@link AsynchronousExecution#getResult} but wraps any * {@link InterruptedException} in a {@link OpenGammaRuntimeException}. * * @param <T> type of the result * @param ex the caught exception * @return the result */ @SuppressWarnings("unchecked") public static <T> T getResult(final AsynchronousExecution ex) { try { return (T) ex.getResult(); } catch (final InterruptedException e) { throw new OpenGammaRuntimeException("interrupted", e); } } }