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

com.gh.bmd.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.gh.bmd.jrt.core;

import com.gh.bmd.jrt.builder.RoutineConfiguration;
import com.gh.bmd.jrt.channel.ParameterChannel;
import com.gh.bmd.jrt.channel.ResultChannel;
import com.gh.bmd.jrt.common.InvocationInterruptedException;
import com.gh.bmd.jrt.core.DefaultParameterChannel.InvocationManager;
import com.gh.bmd.jrt.invocation.Invocation;
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.RoutineDeadlockException;
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.time.TimeDuration;
import com.gh.bmd.jrt.time.TimeDuration.Check;

import java.util.LinkedList;

import javax.annotation.Nonnull;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import static com.gh.bmd.jrt.time.TimeDuration.ZERO;

/**
 * Basic abstract implementation of a routine.
 * 

* This class provides a default implementation of all the routine functionalities. The inheriting * class just need to create invocation objects when required. *

* Created by davide 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 RoutineConfiguration 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 routine configuration. * @throws java.lang.IllegalArgumentException if at least one of the parameter is invalid. * @throws java.lang.NullPointerException if the specified configuration is null. */ @SuppressWarnings("ConstantConditions") protected AbstractRoutine(@Nonnull final RoutineConfiguration configuration) { mConfiguration = configuration; mSyncRunner = configuration.getSyncRunnerOr(Runners.queuedRunner()); mAsyncRunner = configuration.getAsyncRunnerOr(Runners.sharedRunner()); mMaxInvocations = configuration.getMaxInvocationsOr(DEFAULT_MAX_INVOCATIONS); mCoreInvocations = configuration.getCoreInvocationsOr(DEFAULT_CORE_INVOCATIONS); mAvailTimeout = configuration.getAvailTimeoutOr(DEFAULT_AVAIL_TIMEOUT); mLogger = Logger.newLogger(configuration, this); mLogger.dbg("building routine with configuration: %s", configuration); } /** * Constructor. * * @param configuration the routine 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 RoutineConfiguration 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 ParameterChannel invokeAsync() { return invoke(true); } @Nonnull public ParameterChannel invokeParallel() { synchronized (mParallelMutex) { mLogger.dbg("invoking routine: parallel"); if (mParallelRoutine == null) { mParallelRoutine = new AbstractRoutine(mConfiguration, mSyncRunner, mAsyncRunner, mLogger) { @Nonnull @Override protected Invocation newInvocation(final boolean async) { return new ParallelInvocation(AbstractRoutine.this); } }; } } return mParallelRoutine.invokeAsync(); } @Nonnull public ParameterChannel invokeSync() { return invoke(false); } @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 from synchronous to asynchronous or the contrary. * * @param async whether the converted invocation is asynchronous. * @param invocation the invocation to convert. * @return the converted invocation. */ @Nonnull @SuppressWarnings("UnusedParameters") protected Invocation convertInvocation(final boolean async, @Nonnull final Invocation invocation) { return invocation; } /** * Returns the routine logger. * * @return the logger instance. */ @Nonnull protected Logger getLogger() { return mLogger; } /** * Creates a new invocation instance. * * @param async whether the invocation is asynchronous. * @return the invocation instance. */ @Nonnull protected abstract Invocation newInvocation(boolean async); @Nonnull private DefaultInvocationManager getInvocationManager(final boolean async) { if (async) { if (mAsyncManager == null) { mAsyncManager = new DefaultInvocationManager(true); } return mAsyncManager; } if (mSyncManager == null) { mSyncManager = new DefaultInvocationManager(false); } return mSyncManager; } @Nonnull private ParameterChannel invoke(final boolean async) { final Logger logger = mLogger; logger.dbg("invoking routine: %ssync", (async) ? "a" : ""); return new DefaultParameterChannel(mConfiguration, getInvocationManager(async), (async) ? mAsyncRunner : mSyncRunner, logger); } /** * 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.callAsync(input)); } } /** * Default implementation of an invocation manager supporting recycling of invocation instances. */ private class DefaultInvocationManager implements InvocationManager { private final boolean mAsync; /** * Constructor. * * @param async whether the invocation is asynchronous. */ private DefaultInvocationManager(final boolean async) { mAsync = async; } @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 RoutineDeadlockException( "deadlock while waiting for an available invocation instance"); } ++mRunningCount; final boolean async = mAsync; final LinkedList> invocations = (async) ? mAsyncInvocations : mSyncInvocations; if (!invocations.isEmpty()) { final Invocation invocation = invocations.removeFirst(); mLogger.dbg("reusing %ssync invocation instance [%d/%d]: %s", (async) ? "a" : "", invocations.size() + 1, mCoreInvocations, invocation); return invocation; } else { final LinkedList> fallbackInvocations = (async) ? mSyncInvocations : mAsyncInvocations; if (!fallbackInvocations.isEmpty()) { final Invocation invocation = fallbackInvocations.removeFirst(); mLogger.dbg("converting %ssync invocation instance [%d/%d]: %s", (async) ? "a" : "", invocations.size() + 1, mCoreInvocations, invocation); return convertInvocation(async, invocation); } } mLogger.dbg("creating %ssync invocation instance [1/%d]", (async) ? "a" : "", mCoreInvocations); return newInvocation(async); } } @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 boolean async = mAsync; final LinkedList> syncInvocations = mSyncInvocations; final LinkedList> asyncInvocations = mAsyncInvocations; if ((syncInvocations.size() + asyncInvocations.size()) < mCoreInvocations) { final LinkedList> invocations = (async) ? asyncInvocations : syncInvocations; logger.dbg("recycling %ssync invocation instance [%d/%d]: %s", (async) ? "a" : "", invocations.size() + 1, mCoreInvocations, invocation); invocations.add(invocation); } else { logger.wrn("discarding %ssync invocation instance [%d/%d]: %s", (async) ? "a" : "", mCoreInvocations, mCoreInvocations, 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(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy