/*
* JBoss, Home of Professional Open Source.
* Copyright 2014, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.wildfly.extension.requestcontroller;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import org.jboss.as.server.logging.ServerLogger;
import org.jboss.as.server.suspend.ServerActivityCallback;
import java.util.concurrent.Executor;
/**
* A representation of an entry point into the application server, represented as both a deployment
* name and an entry point name.
* <p/>
* This should only be created using the top level name, as it does not generally make sense to shut
* down individual parts of a deployment.
* <p/>
* Note that requests are tracked at two levels, both at the entry point level and the request controller level.
* This allows for individual deployments/interfaces to be gracefully suspended, and also allows for the global
* request controller to limit the total number of active requests.
*
* @author Stuart Douglas
*/
public class ControlPoint {
private static final AtomicIntegerFieldUpdater<ControlPoint> activeRequestCountUpdater = AtomicIntegerFieldUpdater.newUpdater(ControlPoint.class, "activeRequestCount");
private static final AtomicReferenceFieldUpdater<ControlPoint, ServerActivityCallback> listenerUpdater = AtomicReferenceFieldUpdater.newUpdater(ControlPoint.class, ServerActivityCallback.class, "listener");
private final RequestController controller;
private final String deployment;
private final String entryPoint;
private final boolean trackIndividualControlPoints;
/**
* The number of active requests that are using this entry point
*/
@SuppressWarnings("unused")
private volatile int activeRequestCount = 0;
/**
* If this entry point is paused
*/
private volatile boolean paused = false;
@SuppressWarnings("unused")
private volatile ServerActivityCallback listener = null;
/**
* The number of services that are using this entry point.
* This is a deployment time measurement, not a runtime one
*/
private int referenceCount = 0;
ControlPoint(RequestController controller, String deployment, String entryPoint, boolean trackIndividualControlPoints) {
this.controller = controller;
this.deployment = deployment;
this.entryPoint = entryPoint;
this.trackIndividualControlPoints = trackIndividualControlPoints;
}
public String getEntryPoint() {
return entryPoint;
}
public String getDeployment() {
return deployment;
}
/**
* Pause the current entry point, and invoke the provided listener when all current requests have finished.
*
* If individual control point tracking is not enabled then the listener will be invoked straight away
*
* @param requestCountListener The listener to invoke
*/
public void pause(ServerActivityCallback requestCountListener) {
if (paused) {
throw ServerLogger.ROOT_LOGGER.serverAlreadyPaused();
}
this.paused = true;
listenerUpdater.set(this, requestCountListener);
if (activeRequestCountUpdater.get(this) == 0) {
if (listenerUpdater.compareAndSet(this, requestCountListener, null)) {
requestCountListener.done();
}
}
}
/**
* Cancel the pause operation
*/
public void resume() {
this.paused = false;
ServerActivityCallback listener = listenerUpdater.get(this);
if (listener != null) {
listenerUpdater.compareAndSet(this, listener, null);
}
}
/**
* All tasks entering the system via this entry point must call this method. If it returns REJECTED then the
* task cannot be run, and its failure should be signaled back to the originator.
* <p/>
* If it returns {@code RUN} then the task should proceed as normal, and the {@link #requestComplete()} method
* must be called once the task is complete, usually via a try/finally construct.
*/
public RunResult beginRequest() throws Exception {
if (paused) {
return RunResult.REJECTED;
}
if(trackIndividualControlPoints) {
activeRequestCountUpdater.incrementAndGet(this);
}
RunResult runResult = controller.beginRequest(false);
if (runResult == RunResult.REJECTED) {
decreaseRequestCount();
}
return runResult;
}
/**
* This task should only be called by a thread that has already been accepted from an entry point. It is used when
* an existing running thread is about to offload to another thread, such as an executor service or async EJB.
* <p>
* Note that this can still be rejected if the global request limit has been hit.
* <p/>
* If it returns {@code RUN} then the task should proceed as normal, and the {@link #requestComplete()} method
* must be called once the task is complete, usually via a try/finally construct.
*/
public RunResult forceBeginRequest() throws Exception {
if(trackIndividualControlPoints) {
activeRequestCountUpdater.incrementAndGet(this);
}
return controller.beginRequest(true);
}
/**
* Called when a queued task is executed.
*/
void beginExistingRequest() {
if(trackIndividualControlPoints) {
activeRequestCountUpdater.incrementAndGet(this);
}
}
/**
* Method that should be invoked once (and only once) to signify that a request has finished.
* <p/>
* This cannot be done automatically when the handleRequest method completes, as some
*/
public void requestComplete() {
decreaseRequestCount();
controller.requestComplete();
}
private void decreaseRequestCount() {
if (trackIndividualControlPoints) {
int result = activeRequestCountUpdater.decrementAndGet(this);
if (paused && result == 0) {
ServerActivityCallback listener = listenerUpdater.get(this);
if (listener != null) {
if (listenerUpdater.compareAndSet(this, listener, null)) {
listener.done();
}
}
}
}
}
/**
* Queues a task to run when the request controller allows it. There are two use cases for this:
* <ol>
* <li>This allows for requests to be queued instead of dropped when the request limit has been hit</li>
* <li>Timed jobs that are supposed to execute while the container is suspended can be queued to execute
* when it resumes</li>
* </ol>
* <p>
* Note that the task will be run within the context of a {@link #beginRequest()} call, if the task
* is executed there is no need to invoke on the control point again.
* </p>
*
*
* @param task The task to run
* @param timeout The timeout in milliseconds, if this is larger than zero the task will be timed out after
* this much time has elapsed
* @param timeoutTask The task that is run on timeout
* @param rejectOnSuspend If the task should be rejected if the container is suspended, if this happens the timeout task is invoked immediately
*/
public void queueTask(Runnable task, Executor taskExecutor, long timeout, Runnable timeoutTask, boolean rejectOnSuspend) {
controller.queueTask(this, task, taskExecutor, timeout, timeoutTask, rejectOnSuspend, false);
}
/**
* Queues a task to run when the request controller allows it. This allows tasks not to be dropped when the max request
* limit has been hit. If the container has been suspended then this
* <p/>
* Note that the task will be run withing the context of a {@link #beginRequest()} call, if the task
* is executed there is no need to invoke on the control point again.
*
*
*
* @param task The task to run
* @param taskExecutor The executor to run the task in
*/
public void forceQueueTask(Runnable task, Executor taskExecutor) {
controller.queueTask(this, task, taskExecutor, -1, null, false, true);
}
public boolean isPaused() {
return paused;
}
public int getActiveRequestCount() {
return activeRequestCountUpdater.get(this);
}
synchronized int increaseReferenceCount() {
return ++referenceCount;
}
synchronized int decreaseReferenceCount() {
return --referenceCount;
}
}