org.jgroups.protocols.Executing Maven / Gradle / Ivy
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, ExecutorNotification>();
/**
* 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