Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.gh.bmd.jrt.core.AbstractRoutine Maven / Gradle / Ivy
/*
* Licensed 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.
*/
package com.gh.bmd.jrt.core;
import com.gh.bmd.jrt.builder.InvocationConfiguration;
import com.gh.bmd.jrt.channel.InvocationChannel;
import com.gh.bmd.jrt.channel.ResultChannel;
import com.gh.bmd.jrt.core.DefaultInvocationChannel.InvocationManager;
import com.gh.bmd.jrt.invocation.Invocation;
import com.gh.bmd.jrt.invocation.InvocationInterruptedException;
import com.gh.bmd.jrt.invocation.InvocationTimeoutException;
import com.gh.bmd.jrt.invocation.TemplateInvocation;
import com.gh.bmd.jrt.log.Logger;
import com.gh.bmd.jrt.routine.Routine;
import com.gh.bmd.jrt.routine.TemplateRoutine;
import com.gh.bmd.jrt.runner.Runner;
import com.gh.bmd.jrt.runner.Runners;
import com.gh.bmd.jrt.util.TimeDuration;
import com.gh.bmd.jrt.util.TimeDuration.Check;
import java.util.LinkedList;
import javax.annotation.Nonnull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import static com.gh.bmd.jrt.util.TimeDuration.ZERO;
/**
* Basic abstract implementation of a routine.
*
* This class provides a default implementation of all the routine features. The inheriting class
* just needs to create invocation objects when required.
*
* Created by davide-maestroni on 9/7/14.
*
* @param the input data type.
* @param the output data type.
*/
public abstract class AbstractRoutine extends TemplateRoutine {
private static final TimeDuration DEFAULT_AVAIL_TIMEOUT = ZERO;
private static final int DEFAULT_CORE_INVOCATIONS = 10;
private static final int DEFAULT_MAX_INVOCATIONS = Integer.MAX_VALUE;
private final LinkedList> mAsyncInvocations =
new LinkedList>();
private final Runner mAsyncRunner;
private final TimeDuration mAvailTimeout;
private final InvocationConfiguration mConfiguration;
private final int mCoreInvocations;
private final Logger mLogger;
private final int mMaxInvocations;
private final Object mMutex = new Object();
private final Object mParallelMutex = new Object();
private final LinkedList> mSyncInvocations =
new LinkedList>();
private final Runner mSyncRunner;
private volatile DefaultInvocationManager mAsyncManager;
private AbstractRoutine mParallelRoutine;
private int mRunningCount;
private final Check mIsInvocationAvailable = new Check() {
public boolean isTrue() {
return mRunningCount < mMaxInvocations;
}
};
private volatile DefaultInvocationManager mSyncManager;
/**
* Constructor.
*
* @param configuration the invocation configuration.
*/
@SuppressWarnings("ConstantConditions")
protected AbstractRoutine(@Nonnull final InvocationConfiguration configuration) {
mConfiguration = configuration;
mSyncRunner = configuration.getSyncRunnerOr(Runners.queuedRunner());
final int priority = configuration.getPriorityOr(InvocationConfiguration.DEFAULT);
final Runner asyncRunner = configuration.getAsyncRunnerOr(Runners.sharedRunner());
if (priority != InvocationConfiguration.DEFAULT) {
mAsyncRunner = Runners.priorityRunner(asyncRunner).getRunner(priority);
} else {
mAsyncRunner = asyncRunner;
}
mMaxInvocations = configuration.getMaxInstancesOr(DEFAULT_MAX_INVOCATIONS);
mCoreInvocations = configuration.getCoreInstancesOr(DEFAULT_CORE_INVOCATIONS);
mAvailTimeout = configuration.getAvailInstanceTimeoutOr(DEFAULT_AVAIL_TIMEOUT);
mLogger = configuration.newLogger(this);
mLogger.dbg("building routine with configuration: %s", configuration);
}
/**
* Constructor.
*
* @param configuration the invocation configuration.
* @param syncRunner the runner used for synchronous invocation.
* @param asyncRunner the runner used for asynchronous invocation.
* @param logger the logger instance.
*/
private AbstractRoutine(@Nonnull final InvocationConfiguration configuration,
@Nonnull final Runner syncRunner, @Nonnull final Runner asyncRunner,
@Nonnull final Logger logger) {
mConfiguration = configuration;
mSyncRunner = syncRunner;
mAsyncRunner = asyncRunner;
mMaxInvocations = DEFAULT_MAX_INVOCATIONS;
mCoreInvocations = DEFAULT_CORE_INVOCATIONS;
mAvailTimeout = DEFAULT_AVAIL_TIMEOUT;
mLogger = logger.subContextLogger(this);
}
@Nonnull
public InvocationChannel asyncInvoke() {
return invoke(InvocationType.ASYNC);
}
@Nonnull
public InvocationChannel parallelInvoke() {
synchronized (mParallelMutex) {
mLogger.dbg("invoking routine: parallel");
if (mParallelRoutine == null) {
mParallelRoutine = new AbstractRoutine (mConfiguration, mSyncRunner,
mAsyncRunner, mLogger) {
@Nonnull
@Override
protected Invocation newInvocation(
@Nonnull final InvocationType type) {
return new ParallelInvocation (AbstractRoutine.this);
}
};
}
}
return mParallelRoutine.asyncInvoke();
}
@Nonnull
public InvocationChannel syncInvoke() {
return invoke(InvocationType.SYNC);
}
@Override
public void purge() {
synchronized (mMutex) {
final Logger logger = mLogger;
final LinkedList> syncInvocations = mSyncInvocations;
for (final Invocation invocation : syncInvocations) {
try {
invocation.onDestroy();
} catch (final InvocationInterruptedException e) {
throw e;
} catch (final Throwable ignored) {
logger.wrn(ignored, "ignoring exception while destroying invocation instance");
}
}
syncInvocations.clear();
final LinkedList> asyncInvocations = mAsyncInvocations;
for (final Invocation invocation : asyncInvocations) {
try {
invocation.onDestroy();
} catch (final InvocationInterruptedException e) {
throw e;
} catch (final Throwable ignored) {
logger.wrn(ignored, "ignoring exception while destroying invocation instance");
}
}
asyncInvocations.clear();
}
}
/**
* Converts an invocation instance to the specified type.
*
* @param invocation the invocation to convert.
* @param type the converted invocation type.
* @return the converted invocation.
*/
@Nonnull
@SuppressWarnings("UnusedParameters")
protected Invocation convertInvocation(
@Nonnull final Invocation invocation,
@Nonnull final InvocationType type) {
return invocation;
}
/**
* Returns the routine logger.
*
* @return the logger instance.
*/
@Nonnull
protected Logger getLogger() {
return mLogger;
}
/**
* Creates a new invocation instance.
*
* @param type the invocation type.
* @return the invocation instance.
*/
@Nonnull
protected abstract Invocation newInvocation(@Nonnull InvocationType type);
@Nonnull
private DefaultInvocationManager getInvocationManager(@Nonnull final InvocationType type) {
if (type == InvocationType.ASYNC) {
if (mAsyncManager == null) {
mAsyncManager =
new DefaultInvocationManager(type, mAsyncInvocations, mSyncInvocations);
}
return mAsyncManager;
}
if (mSyncManager == null) {
mSyncManager = new DefaultInvocationManager(type, mSyncInvocations, mAsyncInvocations);
}
return mSyncManager;
}
@Nonnull
private InvocationChannel invoke(@Nonnull final InvocationType type) {
final Logger logger = mLogger;
logger.dbg("invoking routine: %s", type);
final Runner runner = (type == InvocationType.ASYNC) ? mAsyncRunner : mSyncRunner;
return new DefaultInvocationChannel (mConfiguration,
getInvocationManager(type), runner,
logger);
}
/**
* Invocation type enumeration.
*/
protected enum InvocationType {
ASYNC, // asynchronous
SYNC // synchronous
}
/**
* Implementation of an invocation handling parallel mode.
*
* @param the input data type.
* @param the output data type.
*/
private static class ParallelInvocation
extends TemplateInvocation {
private final Routine mRoutine;
/**
* Constructor.
*
* @param routine the routine to invoke in parallel mode.
*/
private ParallelInvocation(@Nonnull final Routine routine) {
mRoutine = routine;
}
@Override
public void onInput(final INPUT input, @Nonnull final ResultChannel result) {
result.pass(mRoutine.asyncCall(input));
}
}
/**
* Default implementation of an invocation manager supporting recycling of invocation instances.
*/
private class DefaultInvocationManager implements InvocationManager {
private final LinkedList> mFallbackInvocations;
private final InvocationType mInvocationType;
private final LinkedList> mPrimaryInvocations;
/**
* Constructor.
*
* @param type the invocation type.
* @param primaryInvocations the primary pool of invocations.
* @param fallbackInvocations the fallback pool of invocations.
*/
private DefaultInvocationManager(@Nonnull final InvocationType type,
@Nonnull final LinkedList> primaryInvocations,
@Nonnull final LinkedList> fallbackInvocations) {
mInvocationType = type;
mPrimaryInvocations = primaryInvocations;
mFallbackInvocations = fallbackInvocations;
}
@Nonnull
public Invocation create() {
synchronized (mMutex) {
final boolean isTimeout;
try {
isTimeout = !mAvailTimeout.waitTrue(mMutex, mIsInvocationAvailable);
} catch (final InterruptedException e) {
mLogger.err(e, "waiting for available instances interrupted [#%d]",
mMaxInvocations);
throw new InvocationInterruptedException(e);
}
if (isTimeout) {
mLogger.wrn("routine instance not available after timeout [#%d]: %s",
mMaxInvocations, mAvailTimeout);
throw new InvocationTimeoutException(
"timeout while waiting for an available invocation instance");
}
final InvocationType invocationType = mInvocationType;
final int coreInvocations = mCoreInvocations;
final LinkedList> invocations = mPrimaryInvocations;
final Invocation invocation;
if (!invocations.isEmpty()) {
invocation = invocations.removeFirst();
mLogger.dbg("reusing %s invocation instance [%d/%d]: %s", invocationType,
invocations.size() + 1, coreInvocations, invocation);
} else {
final LinkedList> fallbackInvocations =
mFallbackInvocations;
if (!fallbackInvocations.isEmpty()) {
final Invocation convertInvocation =
fallbackInvocations.removeFirst();
mLogger.dbg("converting %s invocation instance [%d/%d]: %s", invocationType,
invocations.size() + 1, coreInvocations, convertInvocation);
invocation = convertInvocation(convertInvocation, invocationType);
} else {
mLogger.dbg("creating %s invocation instance [1/%d]", invocationType,
coreInvocations);
invocation = newInvocation(invocationType);
}
}
if (invocation == null) {
throw new NullPointerException("null invocation returned");
}
++mRunningCount;
return invocation;
}
}
@SuppressFBWarnings(value = "NO_NOTIFY_NOT_NOTIFYALL",
justification = "only one invocation is released")
public void discard(@Nonnull final Invocation invocation) {
synchronized (mMutex) {
final Logger logger = mLogger;
logger.wrn("discarding invocation instance after error: %s", invocation);
try {
invocation.onDestroy();
} catch (final InvocationInterruptedException e) {
throw e;
} catch (final Throwable ignored) {
logger.wrn(ignored, "ignoring exception while destroying invocation instance");
}
--mRunningCount;
mMutex.notify();
}
}
@SuppressFBWarnings(value = "NO_NOTIFY_NOT_NOTIFYALL",
justification = "only one invocation is released")
public void recycle(@Nonnull final Invocation invocation) {
synchronized (mMutex) {
final Logger logger = mLogger;
final int coreInvocations = mCoreInvocations;
final LinkedList> primaryInvocations =
mPrimaryInvocations;
final LinkedList> fallbackInvocations =
mFallbackInvocations;
if ((primaryInvocations.size() + fallbackInvocations.size()) < coreInvocations) {
logger.dbg("recycling %s invocation instance [%d/%d]: %s", mInvocationType,
primaryInvocations.size() + 1, coreInvocations, invocation);
primaryInvocations.add(invocation);
} else {
logger.wrn("discarding %s invocation instance [%d/%d]: %s", mInvocationType,
coreInvocations, coreInvocations, invocation);
try {
invocation.onDestroy();
} catch (final InvocationInterruptedException e) {
throw e;
} catch (final Throwable ignored) {
logger.wrn(ignored,
"ignoring exception while destroying invocation instance");
}
}
--mRunningCount;
mMutex.notify();
}
}
}
}