All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.dm.jrt.core.AbstractRoutine Maven / Gradle / Ivy

There is a newer version: 5.9.0
Show newest version
/*
 * 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.throwIfInterrupt(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.throwIfInterrupt(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.throwIfInterrupt(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.throwIfInterrupt(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\nTry increasing the max number of instances"); } 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); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy