/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.ignite.spi.collision.fifoqueue;
import java.util.Collection;
import java.util.Iterator;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.internal.util.typedef.internal.A;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.resources.LoggerResource;
import org.apache.ignite.spi.IgniteSpiAdapter;
import org.apache.ignite.spi.IgniteSpiConfiguration;
import org.apache.ignite.spi.IgniteSpiException;
import org.apache.ignite.spi.IgniteSpiMBeanAdapter;
import org.apache.ignite.spi.IgniteSpiMultipleInstancesSupport;
import org.apache.ignite.spi.collision.CollisionContext;
import org.apache.ignite.spi.collision.CollisionExternalListener;
import org.apache.ignite.spi.collision.CollisionJobContext;
import org.apache.ignite.spi.collision.CollisionSpi;
/**
* This class provides implementation for Collision SPI based on FIFO queue. Jobs are ordered
* as they arrived and only {@link #getParallelJobsNumber()} number of jobs is allowed to
* execute in parallel. Other jobs will be buffered in the passive queue.
* <h1 class="header">Configuration</h1>
* <h2 class="header">Mandatory</h2>
* This SPI has no mandatory configuration parameters.
* <h2 class="header">Optional</h2>
* This SPI has following optional configuration parameters:
* <ul>
* <li>
* Number of jobs that can execute in parallel (see {@link #setParallelJobsNumber(int)}).
* This number should usually be set to the number of threads in the execution thread pool.
* </li>
* </ul>
* <h2 class="header">Java Example</h2>
* {@code FifoQueueCollisionSpi} can be configured as follows:
* <pre name="code" class="java">
* FifoQueueCollisionSpi colSpi = new FifoQueueCollisionSpi();
*
* // Execute all jobs sequentially by setting parallel job number to 1.
* colSpi.setParallelJobsNumber(1);
*
* IgniteConfiguration cfg = new IgniteConfiguration();
*
* // Override default collision SPI.
* cfg.setCollisionSpi(colSpi);
*
* // Starts grid.
* G.start(cfg);
* </pre>
* <h2 class="header">Spring Example</h2>
* {@code FifoQueueCollisionSpi} can be configured from Spring XML configuration file:
* <pre name="code" class="xml">
* <bean id="grid.custom.cfg" class="org.apache.ignite.configuration.IgniteConfiguration" singleton="true">
* ...
* <property name="collisionSpi">
* <bean class="org.apache.ignite.spi.collision.fifoqueue.FifoQueueCollisionSpi">
* <property name="parallelJobsNumber" value="1"/>
* </bean>
* </property>
* ...
* </bean>
* </pre>
*/
@IgniteSpiMultipleInstancesSupport(true)
public class FifoQueueCollisionSpi extends IgniteSpiAdapter implements CollisionSpi {
/**
* Default number of parallel jobs allowed (set to number of cores times 2).
*/
public static final int DFLT_PARALLEL_JOBS_NUM = Runtime.getRuntime().availableProcessors() * 2;
/**
* Default waiting jobs number. If number of waiting jobs exceeds this number,
* jobs will be rejected. Default value is {@link Integer#MAX_VALUE}.
*/
public static final int DFLT_WAIT_JOBS_NUM = Integer.MAX_VALUE;
/** Number of jobs that can be executed in parallel. */
private volatile int parallelJobsNum = DFLT_PARALLEL_JOBS_NUM;
/** Wait jobs number. */
private volatile int waitJobsNum = DFLT_WAIT_JOBS_NUM;
/** Grid logger. */
@LoggerResource
private IgniteLogger log;
/** Number of jobs that were active last time. */
private volatile int runningCnt;
/** Number of jobs that were waiting for execution last time. */
private volatile int waitingCnt;
/** Number of jobs that are held. */
private volatile int heldCnt;
/**
* See {@link #setParallelJobsNumber(int)}
*
* @return Number of jobs that can be executed in parallel.
*/
public int getParallelJobsNumber() {
return parallelJobsNum;
}
/**
* Sets number of jobs that can be executed in parallel.
*
* @param parallelJobsNum Parallel jobs number.
* @return {@code this} for chaining.
*/
@IgniteSpiConfiguration(optional = true)
public FifoQueueCollisionSpi setParallelJobsNumber(int parallelJobsNum) {
A.ensure(parallelJobsNum > 0, "parallelJobsNum > 0");
this.parallelJobsNum = parallelJobsNum;
return this;
}
/**
* See {@link #setWaitingJobsNumber(int)}
*
* @return Maximum allowed number of waiting jobs.
*/
public int getWaitingJobsNumber() {
return waitJobsNum;
}
/**
* Sets maximum number of jobs that are allowed to wait in waiting queue. If number
* of waiting jobs ever exceeds this number, excessive jobs will be rejected.
*
* @param waitJobsNum Waiting jobs number.
* @return {@code this} for chaining.
*/
@IgniteSpiConfiguration(optional = true)
public FifoQueueCollisionSpi setWaitingJobsNumber(int waitJobsNum) {
A.ensure(waitJobsNum >= 0, "waitingJobsNum >= 0");
this.waitJobsNum = waitJobsNum;
return this;
}
/**
* Gets current number of jobs that wait for the execution.
*
* @return Number of jobs that wait for execution.
*/
public int getCurrentWaitJobsNumber() {
return waitingCnt;
}
/**
* Gets current number of jobs that are active, i.e. {@code 'running + held'} jobs.
*
* @return Number of active jobs.
*/
public int getCurrentActiveJobsNumber() {
return runningCnt + heldCnt;
}
/**
* Gets number of currently running (not {@code 'held}) jobs.
*
* @return Number of currently running (not {@code 'held}) jobs.
*/
public int getCurrentRunningJobsNumber() {
return runningCnt;
}
/**
* Gets number of currently {@code 'held'} jobs.
*
* @return Number of currently {@code 'held'} jobs.
*/
public int getCurrentHeldJobsNumber() {
return heldCnt;
}
/** {@inheritDoc} */
@Override public void spiStart(String igniteInstanceName) throws IgniteSpiException {
assertParameter(parallelJobsNum > 0, "parallelJobsNum > 0");
assertParameter(waitJobsNum >= 0, "waitingJobsNum >= 0");
// Start SPI start stopwatch.
startStopwatch();
// Ack parameters.
if (log.isDebugEnabled())
log.debug(configInfo("parallelJobsNum", parallelJobsNum));
registerMBean(igniteInstanceName, new FifoQueueCollisionSpiMBeanImpl(this), FifoQueueCollisionSpiMBean.class);
// Ack start.
if (log.isDebugEnabled())
log.debug(startInfo());
}
/** {@inheritDoc} */
@Override public void spiStop() throws IgniteSpiException {
unregisterMBean();
// Ack ok stop.
if (log.isDebugEnabled())
log.debug(stopInfo());
}
/** {@inheritDoc} */
@Override public void setExternalCollisionListener(CollisionExternalListener lsnr) {
// No-op.
}
/** {@inheritDoc} */
@Override public void onCollision(CollisionContext ctx) {
assert ctx != null;
Collection<CollisionJobContext> activeJobs = ctx.activeJobs();
Collection<CollisionJobContext> waitJobs = ctx.waitingJobs();
// Save initial sizes to limit iteration.
int activeSize = activeJobs.size();
int waitSize = waitJobs.size();
waitingCnt = waitSize;
runningCnt = activeSize;
heldCnt = ctx.heldJobs().size();
int parallelJobsNum0 = parallelJobsNum;
Iterator<CollisionJobContext> it = null;
if (activeSize < parallelJobsNum0) {
it = waitJobs.iterator();
while (it.hasNext()) {
CollisionJobContext waitCtx = it.next();
waitCtx.activate();
if (--waitSize == 0)
// No more waiting jobs to process (to limit iterations).
return;
// Take actual size, since it might have been changed.
if (activeJobs.size() >= parallelJobsNum0)
// Max active jobs threshold reached.
break;
}
}
int waitJobsNum0 = waitJobsNum;
// Take actual size, since it might have been changed.
if (waitJobs.size() > waitJobsNum0) {
if (it == null)
it = waitJobs.iterator();
while (it.hasNext()) {
CollisionJobContext waitCtx = it.next();
waitCtx.cancel();
if (--waitSize == 0)
// No more waiting jobs to process (to limit iterations).
return;
// Take actual size, since it might have been changed.
if (waitJobs.size() <= waitJobsNum0)
// No need to reject more jobs.
return;
}
}
}
/** {@inheritDoc} */
@Override public FifoQueueCollisionSpi setName(String name) {
super.setName(name);
return this;
}
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(FifoQueueCollisionSpi.class, this);
}
/**
* MBean implementation for FifoQueueCollisionSpi.
*/
private class FifoQueueCollisionSpiMBeanImpl extends IgniteSpiMBeanAdapter implements FifoQueueCollisionSpiMBean {
/** {@inheritDoc} */
FifoQueueCollisionSpiMBeanImpl(IgniteSpiAdapter spiAdapter) {
super(spiAdapter);
}
/** {@inheritDoc} */
@Override public int getParallelJobsNumber() {
return FifoQueueCollisionSpi.this.getParallelJobsNumber();
}
/** {@inheritDoc} */
@Override public int getCurrentWaitJobsNumber() {
return FifoQueueCollisionSpi.this.getCurrentWaitJobsNumber();
}
/** {@inheritDoc} */
@Override public int getCurrentActiveJobsNumber() {
return FifoQueueCollisionSpi.this.getCurrentActiveJobsNumber();
}
/** {@inheritDoc} */
@Override public int getCurrentRunningJobsNumber() {
return FifoQueueCollisionSpi.this.getCurrentRunningJobsNumber();
}
/** {@inheritDoc} */
@Override public int getCurrentHeldJobsNumber() {
return FifoQueueCollisionSpi.this.getCurrentHeldJobsNumber();
}
/** {@inheritDoc} */
@Override public int getWaitingJobsNumber() {
return FifoQueueCollisionSpi.this.getWaitingJobsNumber();
}
/** {@inheritDoc} */
@Override public void setWaitingJobsNumber(int waitJobsNum) {
FifoQueueCollisionSpi.this.setWaitingJobsNumber(waitJobsNum);
}
/** {@inheritDoc} */
@Override public void setParallelJobsNumber(int parallelJobsNum) {
FifoQueueCollisionSpi.this.setParallelJobsNumber(parallelJobsNum);
}
}
}