org.jgroups.protocols.Executing Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote Jakarta Enterprise Beans and Jakarta Messaging, including
all dependencies. It is intended for use by those not using maven, maven users should just import the Jakarta Enterprise Beans and
Jakarta Messaging BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
package org.jgroups.protocols;
import org.jgroups.*;
import org.jgroups.annotations.MBean;
import org.jgroups.annotations.ManagedAttribute;
import org.jgroups.annotations.Property;
import org.jgroups.blocks.executor.ExecutionService.DistributedFuture;
import org.jgroups.blocks.executor.ExecutorEvent;
import org.jgroups.blocks.executor.ExecutorNotification;
import org.jgroups.stack.Protocol;
import org.jgroups.util.Streamable;
import org.jgroups.util.Util;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.*;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* This is the base protocol used for executions.
* @author wburns
* @see org.jgroups.protocols.CENTRAL_EXECUTOR
*/
@MBean(description="Based class for executor service functionality")
abstract public class Executing extends Protocol {
@Property(description="bypasses message bundling if set")
protected boolean bypass_bundling=true;
protected Address local_addr;
protected View view;
/**
* This is a queue on the client side that holds all of the tasks that
* are awaiting a consumer to pick them up
*/
protected final Queue _awaitingConsumer =
new ConcurrentLinkedQueue<>();
/**
* This is a map on the client side showing for all of the current pending
* requests
*/
protected final ConcurrentMap _requestId =
new ConcurrentHashMap<>();
/**
* This is essentially a set on the consumer side of id's of all the threads
* currently running as consumers. This is basically a set, but since
* there is no ConcurrentHashSet we use a phoney value
*/
protected final ConcurrentMap _consumerId =
new ConcurrentHashMap<>();
protected final ConcurrentMap, ExecutorNotification> notifiers =
new ConcurrentHashMap<>();
/**
* This is a map on the server side that shows which owner is currently
* tied to the runnable so we can return to them the results
*/
protected final Map _running;
/**
* This is a map on the client side that shows for which
* owner(consumer, request) the runnable they are currently using. This
* also allows us to set the values on a future when finished.
*/
protected final Map _awaitingReturn;
/**
* This is a server side store of all the tasks that want to be ran on a
* given thread. This map should be updated by an incoming request before
* awaking the task with the latch. This map should only be retrieved after
* first waiting on the latch for a consumer
*/
protected ConcurrentMap _tasks = new ConcurrentHashMap<>();
/**
* This is a server side store of all the barriers for respective tasks
* requests. When a consumer is starting up they should create a latch
* place in map with it's id and wait on it until a request comes in to
* wake it up it would only then touch the {@link _tasks} map. A requestor
* should first place in the {@link _tasks} map and then create a latch
* and notify the consumer
*/
protected ConcurrentMap _taskBarriers =
new ConcurrentHashMap<>();
/**
* This is a server side map to show which threads are running for a
* given runnable. This is used to interrupt those threads if needed.
*/
protected final ConcurrentMap _runnableThreads =
new ConcurrentHashMap<>();
/**
* This lock is to protect the incoming run requests and the incoming
* consumer queues
*/
protected Lock _consumerLock = new ReentrantLock();
/**
* This is stored on the coordinator side. This queue holds all of the
* addresses that currently want to run something. If this queue has
* elements the consumer queue must be empty.
*/
protected Queue _runRequests = new ArrayDeque<>();
/**
* This is stored on the coordinator side. This queue holds all of the
* addresses that currently are able to run something. If this queue has
* elements the run request queue must be empty.
*/
protected Queue _consumersAvailable = new ArrayDeque<>();
protected static enum Type {
RUN_REQUEST, // request to coordinator from client to tell of a new task request
CONSUMER_READY, // request to coordinator from server to tell of a new consumer ready
CONSUMER_UNREADY, // request to coordinator from server to tell of a consumer stopping
CONSUMER_FOUND, // response to client from coordinator of the consumer to send the task to
RUN_SUBMITTED, // request to consumer from client the task to run
RUN_REJECTED, // response to client from the consumer due to the consumer being gone (usually because the runner was stopped)
RESULT_EXCEPTION, // response to client from the consumer when an exception was encountered
RESULT_SUCCESS, // response to client from the consumer when a value is returned
INTERRUPT_RUN, // request to consumer from client to interrupt the task
CREATE_RUN_REQUEST, // request to backups from coordinator to create a new task request. Used by CENTRAL_LOCKING
CREATE_CONSUMER_READY, // request to backups from coordinator to create a new consumer ready. Used by CENTRAL_LOCKING
DELETE_RUN_REQUEST, // request to backups from coordinator to delete a task request. Used by CENTRAL_LOCKING
DELETE_CONSUMER_READY // request to backups from coordinator to delete a consumer ready. Used by CENTRAL_LOCKING
}
public Executing() {
_awaitingReturn = Collections.synchronizedMap(new HashMap());
_running = Collections.synchronizedMap(new HashMap());
}
public boolean getBypassBundling() {
return bypass_bundling;
}
public void setBypassBundling(boolean bypass_bundling) {
this.bypass_bundling=bypass_bundling;
}
public void addExecutorListener(Future> future,
ExecutorNotification listener) {
if(listener != null)
notifiers.put(future, listener);
}
@ManagedAttribute
public String getAddress() {
return local_addr != null? local_addr.toString() : null;
}
@ManagedAttribute
public String getView() {
return view != null? view.toString() : null;
}
public Object down(Event evt) {
switch(evt.getType()) {
case ExecutorEvent.TASK_SUBMIT:
Runnable runnable = (Runnable)evt.getArg();
// We are limited to a number of concurrent request id's
// equal to 2^63-1. This is quite large and if it
// overflows it will still be positive
long requestId = Math.abs(counter.getAndIncrement());
if(requestId == Long.MIN_VALUE) {
// TODO: need to fix this it isn't safe for concurrent
// modifications
counter.set(0);
requestId = Math.abs(counter.getAndIncrement());
}
// Need to make sure to put the requestId in our map before
// adding the runnable to awaiting consumer in case if
// coordinator sent a consumer found and their original task
// is no longer around
// see https://issues.jboss.org/browse/JGRP-1744
_requestId.put(runnable, requestId);
_awaitingConsumer.add(runnable);
sendToCoordinator(Type.RUN_REQUEST, requestId, local_addr);
break;
case ExecutorEvent.CONSUMER_READY:
Thread currentThread = Thread.currentThread();
long threadId = currentThread.getId();
_consumerId.put(threadId, PRESENT);
try {
for (;;) {
CyclicBarrier barrier = new CyclicBarrier(2);
_taskBarriers.put(threadId, barrier);
// We only send to the coordinator that we are ready after
// making the barrier, wait for request to come and let
// us free
sendToCoordinator(Type.CONSUMER_READY, threadId, local_addr);
try {
barrier.await();
break;
}
catch (BrokenBarrierException e) {
if (log.isDebugEnabled())
log.debug("Producer timed out before we picked up"
+ " the task, have to tell coordinator"
+ " we are still good.");
}
}
// This should always be non nullable since the latch
// was freed
runnable = _tasks.remove(threadId);
_runnableThreads.put(runnable, currentThread);
return runnable;
}
catch (InterruptedException e) {
if (log.isDebugEnabled())
log.debug("Consumer " + threadId +
" stopped via interrupt");
sendToCoordinator(Type.CONSUMER_UNREADY, threadId, local_addr);
Thread.currentThread().interrupt();
}
finally {
// Make sure the barriers are cleaned up as well
_taskBarriers.remove(threadId);
_consumerId.remove(threadId);
}
break;
case ExecutorEvent.TASK_COMPLETE:
Object arg = evt.getArg();
Throwable throwable = null;
if (arg instanceof Object[]) {
Object[] array = (Object[])arg;
runnable = (Runnable)array[0];
throwable = (Throwable)array[1];
}
else {
runnable = (Runnable)arg;
}
Owner owner = _running.remove(runnable);
// This won't remove anything if owner doesn't come back
_runnableThreads.remove(runnable);
Object value = null;
boolean exception = false;
if (throwable != null) {
// InterruptedException is special telling us that
// we interrupted the thread while waiting but still got
// a task therefore we have to reject it.
if (throwable instanceof InterruptedException) {
if (log.isDebugEnabled())
log.debug("Run rejected due to interrupted exception returned");
sendRequest(owner.address, Type.RUN_REJECTED, owner.requestId, null);
break;
}
value = throwable;
exception = true;
}
else if (runnable instanceof RunnableFuture>) {
RunnableFuture> future = (RunnableFuture>)runnable;
boolean interrupted = false;
boolean gotValue = false;
// We have the value, before we interrupt at least get it!
while (!gotValue) {
try {
value = future.get();
gotValue = true;
}
catch (InterruptedException e) {
interrupted = true;
}
catch (ExecutionException e) {
value = e.getCause();
exception = true;
gotValue = true;
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
}
if (owner != null) {
final Type type;
final Object valueToSend;
if (value == null) {
type = Type.RESULT_SUCCESS;
valueToSend = value;
}
// Both serializable values and exceptions would go in here
else if (value instanceof Serializable ||
value instanceof Externalizable ||
value instanceof Streamable) {
type = exception ? Type.RESULT_EXCEPTION : Type.RESULT_SUCCESS;
valueToSend = value;
}
// This would happen if the value wasn't serializable,
// so we have to send back to the client that the class
// wasn't serializable
else {
type = Type.RESULT_EXCEPTION;
valueToSend = new NotSerializableException(
value.getClass().getName());
}
if (local_addr.equals(owner.getAddress())) {
if(log.isTraceEnabled())
log.trace("[redirect] <--> [" + local_addr + "] "
+ type.name() + " [" + value
+ (owner.requestId != -1 ? " request id: " +
owner.requestId : "")
+ "]");
final Owner finalOwner = owner;
if (type == Type.RESULT_SUCCESS) {
handleValueResponse(local_addr,
finalOwner.requestId, valueToSend);
}
else if (type == Type.RESULT_EXCEPTION){
handleExceptionResponse(local_addr,
finalOwner.requestId, (Throwable)valueToSend);
}
}
else {
sendRequest(owner.getAddress(), type, owner.requestId,
valueToSend);
}
}
else {
if (log.isTraceEnabled()) {
log.trace("Could not return result - most likely because it was interrupted");
}
}
break;
case ExecutorEvent.TASK_CANCEL:
Object[] array = (Object[])evt.getArg();
runnable = (Runnable)array[0];
if (_awaitingConsumer.remove(runnable)) {
_requestId.remove(runnable);
ExecutorNotification notification = notifiers.remove(runnable);
if (notification != null) {
notification.interrupted(runnable);
}
if (log.isTraceEnabled())
log.trace("Cancelled task " + runnable +
" before it was picked up");
return Boolean.TRUE;
}
// This is guaranteed to not be null so don't take cost of auto unboxing
else if (array[1] == Boolean.TRUE) {
owner = removeKeyForValue(_awaitingReturn, runnable);
if (owner != null) {
Long requestIdValue = _requestId.remove(runnable);
// We only cancel if the requestId is still available
// this means the result hasn't been returned yet and
// we still have a chance to interrupt
if (requestIdValue != null) {
if (requestIdValue != owner.getRequestId()) {
log.warn("Cancelling requestId didn't match waiting");
}
sendRequest(owner.getAddress(), Type.INTERRUPT_RUN,
owner.getRequestId(), null);
}
}
else {
if (log.isTraceEnabled())
log.warn("Couldn't interrupt server task: " + runnable);
}
ExecutorNotification notification = notifiers.remove(runnable);
if (notification != null) {
notification.interrupted(runnable);
}
return Boolean.TRUE;
}
else {
return Boolean.FALSE;
}
case ExecutorEvent.ALL_TASK_CANCEL:
array = (Object[])evt.getArg();
// This is a RunnableFuture> so this cast is okay
@SuppressWarnings("unchecked")
Set runnables = (Set)array[0];
Boolean booleanValue = (Boolean)array[1];
List notRan = new ArrayList<>();
for (Runnable cancelRunnable : runnables) {
// Removed from the consumer
if (!_awaitingConsumer.remove(cancelRunnable) &&
booleanValue == Boolean.TRUE) {
synchronized (_awaitingReturn) {
owner = removeKeyForValue(_awaitingReturn, cancelRunnable);
if (owner != null) {
Long requestIdValue = _requestId.remove(cancelRunnable);
if (requestIdValue != owner.getRequestId()) {
log.warn("Cancelling requestId didn't match waiting");
}
sendRequest(owner.getAddress(), Type.INTERRUPT_RUN,
owner.getRequestId(), null);
}
ExecutorNotification notification = notifiers.remove(cancelRunnable);
if (notification != null) {
log.trace("Notifying listener");
notification.interrupted(cancelRunnable);
}
}
}
else {
_requestId.remove(cancelRunnable);
notRan.add(cancelRunnable);
}
}
return notRan;
case Event.SET_LOCAL_ADDRESS:
local_addr=(Address)evt.getArg();
break;
case Event.VIEW_CHANGE:
handleView((View)evt.getArg());
break;
}
return down_prot.down(evt);
}
protected static V removeKeyForValue(Map map, K value) {
synchronized (map) {
Iterator> iter =
map.entrySet().iterator();
while (iter.hasNext()) {
Entry entry = iter.next();
if (entry.getValue().equals(value)) {
iter.remove();
return entry.getKey();
}
}
}
return null;
}
public Object up(Event evt) {
switch(evt.getType()) {
case Event.MSG:
Message msg=(Message)evt.getArg();
ExecutorHeader hdr=(ExecutorHeader)msg.getHeader(id);
if(hdr == null)
break;
Request req=(Request)msg.getObject();
if(log.isTraceEnabled())
log.trace("[" + local_addr + "] <-- [" + msg.getSrc() + "] " + req);
switch(req.type) {
case RUN_REQUEST:
handleTaskRequest(req.request, (Address)req.object);
break;
case CONSUMER_READY:
handleConsumerReadyRequest(req.request, (Address)req.object);
break;
case CONSUMER_UNREADY:
handleConsumerUnreadyRequest(req.request, (Address)req.object);
break;
case CONSUMER_FOUND:
handleConsumerFoundResponse(req.request, (Address)req.object);
break;
case RUN_SUBMITTED:
RequestWithThread reqWT = (RequestWithThread)req;
Object objectToRun = reqWT.object;
Runnable runnable;
if (objectToRun instanceof Runnable) {
runnable = (Runnable)objectToRun;
}
else if (objectToRun instanceof Callable) {
@SuppressWarnings("unchecked")
Callable
© 2015 - 2025 Weber Informatics LLC | Privacy Policy