com.github.dm.jrt.core.AbstractRoutine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jroutine Show documentation
Show all versions of jroutine Show documentation
Parallel programming on the go
/*
* 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.github.dm.jrt.core;
import com.github.dm.jrt.builder.InvocationConfiguration;
import com.github.dm.jrt.channel.InvocationChannel;
import com.github.dm.jrt.channel.ResultChannel;
import com.github.dm.jrt.core.DefaultInvocationChannel.InvocationManager;
import com.github.dm.jrt.core.DefaultInvocationChannel.InvocationObserver;
import com.github.dm.jrt.invocation.Invocation;
import com.github.dm.jrt.invocation.InvocationDeadlockException;
import com.github.dm.jrt.invocation.InvocationInterruptedException;
import com.github.dm.jrt.invocation.TemplateInvocation;
import com.github.dm.jrt.log.Logger;
import com.github.dm.jrt.routine.Routine;
import com.github.dm.jrt.runner.Runner;
import com.github.dm.jrt.runner.Runners;
import com.github.dm.jrt.runner.TemplateExecution;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
/**
* 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 09/07/2014.
*
* @param the input data type.
* @param the output data type.
*/
public abstract class AbstractRoutine extends TemplateRoutine {
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 InvocationConfiguration mConfiguration;
private final int mCoreInvocations;
private final Logger mLogger;
private final int mMaxInvocations;
private final Object mMutex = new Object();
private final SimpleQueue> mObservers =
new SimpleQueue>();
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 volatile DefaultInvocationManager mSyncManager;
/**
* Constructor.
*
* @param configuration the invocation configuration.
*/
@SuppressWarnings("ConstantConditions")
protected AbstractRoutine(@NotNull final InvocationConfiguration configuration) {
mConfiguration = configuration;
mSyncRunner = Runners.syncRunner();
final int priority = configuration.getPriorityOr(InvocationConfiguration.DEFAULT);
final Runner asyncRunner = configuration.getRunnerOr(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);
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(@NotNull final InvocationConfiguration configuration,
@NotNull final Runner syncRunner, @NotNull final Runner asyncRunner,
@NotNull final Logger logger) {
mConfiguration = configuration;
mSyncRunner = syncRunner;
mAsyncRunner = asyncRunner;
mMaxInvocations = DEFAULT_MAX_INVOCATIONS;
mCoreInvocations = DEFAULT_CORE_INVOCATIONS;
mLogger = logger.subContextLogger(this);
}
@NotNull
public InvocationChannel asyncInvoke() {
return invoke(InvocationType.ASYNC);
}
@NotNull
public InvocationChannel parallelInvoke() {
synchronized (mParallelMutex) {
mLogger.dbg("invoking routine: parallel");
if (mParallelRoutine == null) {
mParallelRoutine =
new AbstractRoutine(mConfiguration, mSyncRunner, mAsyncRunner,
mLogger) {
@NotNull
@Override
protected Invocation newInvocation(
@NotNull final InvocationType type) {
return new ParallelInvocation(AbstractRoutine.this);
}
};
}
}
return mParallelRoutine.asyncInvoke();
}
@NotNull
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 Throwable t) {
InvocationInterruptedException.ignoreIfPossible(t);
logger.wrn(t, "ignoring exception while destroying invocation instance");
}
}
syncInvocations.clear();
final LinkedList> asyncInvocations = mAsyncInvocations;
for (final Invocation invocation : asyncInvocations) {
try {
invocation.onDestroy();
} catch (final Throwable t) {
InvocationInterruptedException.ignoreIfPossible(t);
logger.wrn(t, "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.
*/
@NotNull
@SuppressWarnings("UnusedParameters")
protected Invocation convertInvocation(@NotNull final Invocation invocation,
@NotNull final InvocationType type) {
return invocation;
}
/**
* Returns the routine invocation configuration.
*
* @return the invocation configuration.
*/
@NotNull
protected InvocationConfiguration getConfiguration() {
return mConfiguration;
}
/**
* Returns the routine logger.
*
* @return the logger instance.
*/
@NotNull
protected Logger getLogger() {
return mLogger;
}
/**
* Creates a new invocation instance.
*
* @param type the invocation type.
* @return the invocation instance.
*/
@NotNull
protected abstract Invocation newInvocation(@NotNull InvocationType type);
@NotNull
private DefaultInvocationManager getInvocationManager(@NotNull final InvocationType type) {
if (type == InvocationType.ASYNC) {
if (mAsyncManager == null) {
mAsyncManager = new DefaultInvocationManager(type, mAsyncRunner, mAsyncInvocations,
mSyncInvocations);
}
return mAsyncManager;
}
if (mSyncManager == null) {
mSyncManager = new DefaultInvocationManager(type, mSyncRunner, mSyncInvocations,
mAsyncInvocations);
}
return mSyncManager;
}
@NotNull
private InvocationChannel invoke(@NotNull 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 {
SYNC, // synchronous
ASYNC // asynchronous
}
/**
* 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;
private boolean mHasInputs;
/**
* Constructor.
*
* @param routine the routine to invoke in parallel mode.
*/
private ParallelInvocation(@NotNull final Routine routine) {
mRoutine = routine;
}
@Override
public void onInitialize() {
mHasInputs = false;
}
@Override
public void onInput(final IN input, @NotNull final ResultChannel result) {
mHasInputs = true;
result.pass(mRoutine.asyncCall(input));
}
@Override
public void onResult(@NotNull final ResultChannel result) {
if (!mHasInputs) {
result.pass(mRoutine.asyncCall());
}
}
}
/**
* Execution implementation used to delay the creation of invocations.
*/
private class CreateExecution extends TemplateExecution {
private final DefaultInvocationManager mManager;
/**
* Constructor.
*
* @param invocationManager the invocation manager instance.
*/
private CreateExecution(@NotNull final DefaultInvocationManager invocationManager) {
mManager = invocationManager;
}
public void run() {
mManager.create(null, true);
}
}
/**
* Default implementation of an invocation manager supporting recycling of invocation instances.
*/
private class DefaultInvocationManager implements InvocationManager {
private final CreateExecution mCreateExecution;
private final LinkedList> mFallbackInvocations;
private final InvocationType mInvocationType;
private final LinkedList> mPrimaryInvocations;
private final Runner mRunner;
/**
* Constructor.
*
* @param type the invocation type.
* @param runner the invocation runner.
* @param primaryInvocations the primary pool of invocations.
* @param fallbackInvocations the fallback pool of invocations.
*/
private DefaultInvocationManager(@NotNull final InvocationType type,
@NotNull final Runner runner,
@NotNull final LinkedList> primaryInvocations,
@NotNull final LinkedList> fallbackInvocations) {
mInvocationType = type;
mRunner = runner;
mPrimaryInvocations = primaryInvocations;
mFallbackInvocations = fallbackInvocations;
mCreateExecution = new CreateExecution(this);
}
public void create(@NotNull final InvocationObserver observer) {
create(observer, false);
}
public void discard(@NotNull final Invocation invocation) {
final boolean hasDelayed;
synchronized (mMutex) {
final Logger logger = mLogger;
logger.wrn("discarding invocation instance after error: %s", invocation);
try {
invocation.onDestroy();
} catch (final Throwable t) {
InvocationInterruptedException.ignoreIfPossible(t);
logger.wrn(t, "ignoring exception while destroying invocation instance");
}
hasDelayed = !mObservers.isEmpty();
--mRunningCount;
}
if (hasDelayed) {
mRunner.run(mCreateExecution, 0, TimeUnit.MILLISECONDS);
}
}
public void recycle(@NotNull final Invocation invocation) {
final boolean hasDelayed;
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 Throwable t) {
InvocationInterruptedException.ignoreIfPossible(t);
logger.wrn(t, "ignoring exception while destroying invocation instance");
}
}
hasDelayed = !mObservers.isEmpty();
--mRunningCount;
}
if (hasDelayed) {
mRunner.run(mCreateExecution, 0, TimeUnit.MILLISECONDS);
}
}
@SuppressWarnings("ConstantConditions")
private void create(@Nullable final InvocationObserver observer,
final boolean isDelayed) {
InvocationObserver invocationObserver = observer;
try {
Throwable error = null;
Invocation invocation = null;
synchronized (mMutex) {
final SimpleQueue> observers = mObservers;
if (isDelayed) {
if (observers.isEmpty()) {
return;
}
invocationObserver = observers.removeFirst();
}
if (isDelayed || (mRunningCount < (mMaxInvocations + observers.size()))) {
final InvocationType invocationType = mInvocationType;
final int coreInvocations = mCoreInvocations;
final LinkedList> invocations = mPrimaryInvocations;
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) {
++mRunningCount;
}
} else if (mInvocationType == InvocationType.SYNC) {
error = new InvocationDeadlockException(
"cannot wait for invocation instances on a synchronous runner "
+ "thread");
} else {
observers.add(invocationObserver);
return;
}
}
if (invocation != null) {
invocationObserver.onCreate(invocation);
} else {
invocationObserver.onError((error != null) ? error : new NullPointerException(
"null invocation returned"));
}
} catch (final InvocationInterruptedException e) {
throw e;
} catch (final Throwable t) {
mLogger.err(t, "error while creating new invocation instance", mMaxInvocations);
invocationObserver.onError(t);
}
}
}
}