/*
* 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.geode.cache.client.internal;
import org.apache.geode.InternalGemFireError;
import org.apache.geode.cache.client.ServerConnectivityException;
import org.apache.geode.cache.client.ServerOperationException;
import org.apache.geode.cache.execute.Function;
import org.apache.geode.cache.execute.FunctionException;
import org.apache.geode.cache.execute.ResultCollector;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.distributed.internal.ServerLocation;
import org.apache.geode.internal.Version;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.cache.execute.AbstractExecution;
import org.apache.geode.internal.cache.execute.FunctionStats;
import org.apache.geode.internal.cache.execute.InternalFunctionException;
import org.apache.geode.internal.cache.execute.InternalFunctionInvocationTargetException;
import org.apache.geode.internal.cache.execute.MemberMappedArgument;
import org.apache.geode.internal.cache.execute.ServerFunctionExecutor;
import org.apache.geode.internal.cache.tier.MessageType;
import org.apache.geode.internal.cache.tier.sockets.ChunkedMessage;
import org.apache.geode.internal.cache.tier.sockets.Message;
import org.apache.geode.internal.cache.tier.sockets.Part;
import org.apache.geode.internal.i18n.LocalizedStrings;
import org.apache.geode.internal.logging.LogService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.logging.log4j.Logger;
/**
* Executes the function on server (possibly without region/cache).<br>
* Also gets the result back from the server
*
* @since GemFire 5.8
*/
public class ExecuteFunctionOp {
private static final Logger logger = LogService.getLogger();
/** index of allMembers in flags[] */
public static final int ALL_MEMBERS_INDEX = 0;
/** index of ignoreFailedMembers in flags[] */
public static final int IGNORE_FAILED_MEMBERS_INDEX = 1;
private ExecuteFunctionOp() {
// no instances allowed
}
/**
* Does a execute Function on a server using connections from the given pool to communicate with
* the server.
*
* @param pool the pool to use to communicate with the server.
* @param function of the function to be executed
* @param args specified arguments to the application function
*/
public static void execute(final PoolImpl pool, Function function,
ServerFunctionExecutor executor, Object args, MemberMappedArgument memberMappedArg,
boolean allServers, byte hasResult, ResultCollector rc, boolean isFnSerializationReqd,
UserAttributes attributes, String[] groups) {
final AbstractOp op = new ExecuteFunctionOpImpl(function, args, memberMappedArg, hasResult, rc,
isFnSerializationReqd, (byte) 0, groups, allServers, executor.isIgnoreDepartedMembers());
if (allServers && groups.length == 0) {
if (logger.isDebugEnabled()) {
logger.debug(
"ExecuteFunctionOp#execute : Sending Function Execution Message:{} to all servers using pool: {}",
op.getMessage(), pool);
}
List callableTasks = constructAndGetFunctionTasks(pool, function, args, memberMappedArg,
hasResult, rc, isFnSerializationReqd, attributes);
SingleHopClientExecutor.submitAll(callableTasks);
} else {
boolean reexecuteForServ = false;
AbstractOp reexecOp = null;
int retryAttempts = 0;
boolean reexecute = false;
int maxRetryAttempts = 0;
if (function.isHA())
maxRetryAttempts = pool.getRetryAttempts();
final boolean isDebugEnabled = logger.isDebugEnabled();
do {
try {
if (reexecuteForServ) {
if (isDebugEnabled) {
logger.debug(
"ExecuteFunctionOp#execute.reexecuteForServ : Sending Function Execution Message:{} to server using pool: {} with groups:{} all members:{} ignoreFailedMembers:{}",
op.getMessage(), pool, Arrays.toString(groups), allServers,
executor.isIgnoreDepartedMembers());
}
reexecOp = new ExecuteFunctionOpImpl(function, args, memberMappedArg, hasResult, rc,
isFnSerializationReqd, (byte) 1/* isReExecute */, groups, allServers,
executor.isIgnoreDepartedMembers());
pool.execute(reexecOp, 0);
} else {
if (isDebugEnabled) {
logger.debug(
"ExecuteFunctionOp#execute : Sending Function Execution Message:{} to server using pool: {} with groups:{} all members:{} ignoreFailedMembers:{}",
op.getMessage(), pool, Arrays.toString(groups), allServers,
executor.isIgnoreDepartedMembers());
}
pool.execute(op, 0);
}
reexecute = false;
reexecuteForServ = false;
} catch (InternalFunctionInvocationTargetException e) {
if (isDebugEnabled) {
logger.debug(
"ExecuteFunctionOp#execute : Received InternalFunctionInvocationTargetException. The failed node is {}",
e.getFailedNodeSet());
}
reexecute = true;
rc.clearResults();
} catch (ServerConnectivityException se) {
retryAttempts++;
if (isDebugEnabled) {
logger.debug(
"ExecuteFunctionOp#execute : Received ServerConnectivityException. The exception is {} The retryAttempt is : {} maxRetryAttempts {}",
se, retryAttempts, maxRetryAttempts);
}
if (se instanceof ServerOperationException) {
throw se;
}
if ((retryAttempts > maxRetryAttempts && maxRetryAttempts != -1))
throw se;
reexecuteForServ = true;
rc.clearResults();
}
} while (reexecuteForServ);
if (reexecute && function.isHA()) {
ExecuteFunctionOp.reexecute(pool, function, executor, rc, hasResult, isFnSerializationReqd,
maxRetryAttempts - 1, groups, allServers);
}
}
}
public static void execute(final PoolImpl pool, String functionId,
ServerFunctionExecutor executor, Object args, MemberMappedArgument memberMappedArg,
boolean allServers, byte hasResult, ResultCollector rc, boolean isFnSerializationReqd,
boolean isHA, boolean optimizeForWrite, UserAttributes properties, String[] groups) {
final AbstractOp op = new ExecuteFunctionOpImpl(functionId, args, memberMappedArg, hasResult,
rc, isFnSerializationReqd, isHA, optimizeForWrite, (byte) 0, groups, allServers,
executor.isIgnoreDepartedMembers());
if (allServers && groups.length == 0) {
if (logger.isDebugEnabled()) {
logger.debug(
"ExecuteFunctionOp#execute : Sending Function Execution Message:{} to all servers using pool: {}",
op.getMessage(), pool);
}
List callableTasks = constructAndGetFunctionTasks(pool, functionId, args, memberMappedArg,
hasResult, rc, isFnSerializationReqd, isHA, optimizeForWrite, properties);
SingleHopClientExecutor.submitAll(callableTasks);
} else {
boolean reexecuteForServ = false;
AbstractOp reexecOp = null;
int retryAttempts = 0;
boolean reexecute = false;
int maxRetryAttempts = 0;
if (isHA) {
maxRetryAttempts = pool.getRetryAttempts();
}
final boolean isDebugEnabled = logger.isDebugEnabled();
do {
try {
if (reexecuteForServ) {
reexecOp = new ExecuteFunctionOpImpl(functionId, args, memberMappedArg, hasResult, rc,
isFnSerializationReqd, isHA, optimizeForWrite, (byte) 1, groups, allServers,
executor.isIgnoreDepartedMembers());
pool.execute(reexecOp, 0);
} else {
if (isDebugEnabled) {
logger.debug(
"ExecuteFunctionOp#execute : Sending Function Execution Message:{} to server using pool:{} with groups:{} all members:{} ignoreFailedMembers:{}",
op.getMessage(), pool, Arrays.toString(groups), allServers,
executor.isIgnoreDepartedMembers());
}
pool.execute(op, 0);
}
reexecute = false;
reexecuteForServ = false;
} catch (InternalFunctionInvocationTargetException e) {
if (isDebugEnabled) {
logger.debug(
"ExecuteFunctionOp#execute : Received InternalFunctionInvocationTargetException. The failed node is {}",
e.getFailedNodeSet());
}
reexecute = true;
rc.clearResults();
} catch (ServerConnectivityException se) {
retryAttempts++;
if (isDebugEnabled) {
logger.debug(
"ExecuteFunctionOp#execute : Received ServerConnectivityException. The exception is {} The retryAttempt is : {} maxRetryAttempts {}",
se, retryAttempts, maxRetryAttempts);
}
if (se instanceof ServerOperationException) {
throw se;
}
if ((retryAttempts > maxRetryAttempts && maxRetryAttempts != -1))
throw se;
reexecuteForServ = true;
rc.clearResults();
}
} while (reexecuteForServ);
if (reexecute && isHA) {
ExecuteFunctionOp.reexecute(pool, functionId, executor, rc, hasResult,
isFnSerializationReqd, maxRetryAttempts - 1, args, isHA, optimizeForWrite, groups,
allServers);
}
}
}
public static void reexecute(ExecutablePool pool, Function function,
ServerFunctionExecutor serverExecutor, ResultCollector resultCollector, byte hasResult,
boolean isFnSerializationReqd, int maxRetryAttempts, String[] groups, boolean allMembers) {
boolean reexecute = true;
int retryAttempts = 0;
final boolean isDebugEnabled = logger.isDebugEnabled();
do {
reexecute = false;
AbstractOp reExecuteOp = new ExecuteFunctionOpImpl(function, serverExecutor.getArguments(),
serverExecutor.getMemberMappedArgument(), hasResult, resultCollector,
isFnSerializationReqd, (byte) 1, groups, allMembers,
serverExecutor.isIgnoreDepartedMembers());
if (isDebugEnabled) {
logger.debug(
"ExecuteFunction#reexecute : Sending Function Execution Message:{} to Server using pool:{} with groups:{} all members:{} ignoreFailedMembers:{}",
reExecuteOp.getMessage(), pool, Arrays.toString(groups), allMembers,
serverExecutor.isIgnoreDepartedMembers());
}
try {
pool.execute(reExecuteOp, 0);
} catch (InternalFunctionInvocationTargetException e) {
if (isDebugEnabled) {
logger.debug(
"ExecuteFunctionOp#reexecute : Recieved InternalFunctionInvocationTargetException. The failed nodes are {}",
e.getFailedNodeSet());
}
reexecute = true;
resultCollector.clearResults();
} catch (ServerConnectivityException se) {
if (isDebugEnabled) {
logger.debug("ExecuteFunctionOp#reexecute : Received ServerConnectivity Exception.");
}
if (se instanceof ServerOperationException) {
throw se;
}
retryAttempts++;
if (retryAttempts > maxRetryAttempts && maxRetryAttempts != -2)
throw se;
reexecute = true;
resultCollector.clearResults();
}
} while (reexecute);
}
public static void reexecute(ExecutablePool pool, String functionId,
ServerFunctionExecutor serverExecutor, ResultCollector resultCollector, byte hasResult,
boolean isFnSerializationReqd, int maxRetryAttempts, Object args, boolean isHA,
boolean optimizeForWrite, String[] groups, boolean allMembers) {
boolean reexecute = true;
int retryAttempts = 0;
final boolean isDebugEnabled = logger.isDebugEnabled();
do {
reexecute = false;
final AbstractOp op =
new ExecuteFunctionOpImpl(functionId, args, serverExecutor.getMemberMappedArgument(),
hasResult, resultCollector, isFnSerializationReqd, isHA, optimizeForWrite, (byte) 1,
groups, allMembers, serverExecutor.isIgnoreDepartedMembers());
if (isDebugEnabled) {
logger.debug(
"ExecuteFunction#reexecute : Sending Function Execution Message:{} to Server using pool:{} with groups:{} all members:{} ignoreFailedMembers:{}",
op.getMessage(), pool, Arrays.toString(groups), allMembers,
serverExecutor.isIgnoreDepartedMembers());
}
try {
pool.execute(op, 0);
} catch (InternalFunctionInvocationTargetException e) {
if (isDebugEnabled) {
logger.debug(
"ExecuteFunctionOp#reexecute : Recieved InternalFunctionInvocationTargetException. The failed nodes are {}",
e.getFailedNodeSet());
}
reexecute = true;
resultCollector.clearResults();
} catch (ServerConnectivityException se) {
if (isDebugEnabled) {
logger.debug("ExecuteFunctionOp#reexecute : Received ServerConnectivity Exception.");
}
if (se instanceof ServerOperationException) {
throw se;
}
retryAttempts++;
if (retryAttempts > maxRetryAttempts && maxRetryAttempts != -2)
throw se;
reexecute = true;
resultCollector.clearResults();
}
} while (reexecute);
}
static List constructAndGetFunctionTasks(final PoolImpl pool, final Function function,
Object args, MemberMappedArgument memberMappedArg, byte hasResult, ResultCollector rc,
boolean isFnSerializationReqd, UserAttributes attributes) {
final List<SingleHopOperationCallable> tasks = new ArrayList<SingleHopOperationCallable>();
List<ServerLocation> servers = pool.getConnectionSource().getAllServers();
for (ServerLocation server : servers) {
final AbstractOp op = new ExecuteFunctionOpImpl(function, args, memberMappedArg, hasResult,
rc, isFnSerializationReqd, (byte) 0, null/* onGroups does not use single-hop for now */,
false, false);
SingleHopOperationCallable task =
new SingleHopOperationCallable(server, pool, op, attributes);
tasks.add(task);
}
return tasks;
}
static List constructAndGetFunctionTasks(final PoolImpl pool, final String functionId,
Object args, MemberMappedArgument memberMappedArg, byte hasResult, ResultCollector rc,
boolean isFnSerializationReqd, boolean isHA, boolean optimizeForWrite,
UserAttributes properties) {
final List<SingleHopOperationCallable> tasks = new ArrayList<SingleHopOperationCallable>();
List<ServerLocation> servers = pool.getConnectionSource().getAllServers();
for (ServerLocation server : servers) {
final AbstractOp op = new ExecuteFunctionOpImpl(functionId, args, memberMappedArg, hasResult,
rc, isFnSerializationReqd, isHA, optimizeForWrite, (byte) 0,
null/* onGroups does not use single-hop for now */, false, false);
SingleHopOperationCallable task =
new SingleHopOperationCallable(server, pool, op, properties);
tasks.add(task);
}
return tasks;
}
static byte[] getByteArrayForFlags(boolean... flags) {
byte[] retVal = null;
if (flags.length > 0) {
retVal = new byte[flags.length];
for (int i = 0; i < flags.length; i++) {
if (flags[i]) {
retVal[i] = 1;
} else {
retVal[i] = 0;
}
}
}
return retVal;
}
static class ExecuteFunctionOpImpl extends AbstractOp {
private ResultCollector resultCollector;
// To get the instance of the Function Statistics we need the function name or instance
private String functionId;
private Function function;
private Object args;
private MemberMappedArgument memberMappedArg;
private byte hasResult;
private boolean isFnSerializationReqd;
private String[] groups;
/**
* [0] = allMembers [1] = ignoreFailedMembers
*/
private byte[] flags;
/**
* number of parts in the request message
*/
private static final int MSG_PARTS = 6;
/**
* @throws org.apache.geode.SerializationException if serialization fails
*/
public ExecuteFunctionOpImpl(Function function, Object args,
MemberMappedArgument memberMappedArg, byte hasResult, ResultCollector rc,
boolean isFnSerializationReqd, byte isReexecute, String[] groups, boolean allMembers,
boolean ignoreFailedMembers) {
super(MessageType.EXECUTE_FUNCTION, MSG_PARTS);
byte fnState = AbstractExecution.getFunctionState(function.isHA(), function.hasResult(),
function.optimizeForWrite());
addBytes(isReexecute, fnState);
if (isFnSerializationReqd) {
getMessage().addStringOrObjPart(function);
} else {
getMessage().addStringOrObjPart(function.getId());
}
getMessage().addObjPart(args);
getMessage().addObjPart(memberMappedArg);
getMessage().addObjPart(groups);
this.flags = getByteArrayForFlags(allMembers, ignoreFailedMembers);
getMessage().addBytesPart(this.flags);
resultCollector = rc;
if (isReexecute == 1) {
resultCollector.clearResults();
}
this.functionId = function.getId();
this.function = function;
this.args = args;
this.memberMappedArg = memberMappedArg;
this.hasResult = fnState;
this.isFnSerializationReqd = isFnSerializationReqd;
this.groups = groups;
}
public ExecuteFunctionOpImpl(String functionId, Object args2,
MemberMappedArgument memberMappedArg, byte hasResult, ResultCollector rc,
boolean isFnSerializationReqd, boolean isHA, boolean optimizeForWrite, byte isReexecute,
String[] groups, boolean allMembers, boolean ignoreFailedMembers) {
super(MessageType.EXECUTE_FUNCTION, MSG_PARTS);
byte fnState = AbstractExecution.getFunctionState(isHA, hasResult == (byte) 1 ? true : false,
optimizeForWrite);
addBytes(isReexecute, fnState);
getMessage().addStringOrObjPart(functionId);
getMessage().addObjPart(args2);
getMessage().addObjPart(memberMappedArg);
getMessage().addObjPart(groups);
this.flags = getByteArrayForFlags(allMembers, ignoreFailedMembers);
getMessage().addBytesPart(this.flags);
resultCollector = rc;
if (isReexecute == 1) {
resultCollector.clearResults();
}
this.functionId = functionId;
this.args = args2;
this.memberMappedArg = memberMappedArg;
this.hasResult = fnState;
this.isFnSerializationReqd = isFnSerializationReqd;
this.groups = groups;
}
public ExecuteFunctionOpImpl(ExecuteFunctionOpImpl op, byte isReexecute) {
super(MessageType.EXECUTE_FUNCTION, MSG_PARTS);
this.resultCollector = op.resultCollector;
this.function = op.function;
this.functionId = op.functionId;
this.hasResult = op.hasResult;
this.args = op.args;
this.memberMappedArg = op.memberMappedArg;
this.isFnSerializationReqd = op.isFnSerializationReqd;
this.groups = op.groups;
this.flags = op.flags;
addBytes(isReexecute, this.hasResult);
if (this.isFnSerializationReqd) {
getMessage().addStringOrObjPart(function);
} else {
getMessage().addStringOrObjPart(function.getId());
}
getMessage().addObjPart(this.args);
getMessage().addObjPart(this.memberMappedArg);
getMessage().addObjPart(this.groups);
getMessage().addBytesPart(this.flags);
if (isReexecute == 1) {
resultCollector.clearResults();
}
}
private void addBytes(byte isReexecute, byte fnStateOrHasResult) {
if (GemFireCacheImpl
.getClientFunctionTimeout() == GemFireCacheImpl.DEFAULT_CLIENT_FUNCTION_TIMEOUT) {
if (isReexecute == 1) {
getMessage().addBytesPart(
new byte[] {AbstractExecution.getReexecuteFunctionState(fnStateOrHasResult)});
} else {
getMessage().addBytesPart(new byte[] {fnStateOrHasResult});
}
} else {
byte[] bytes = new byte[5];
if (isReexecute == 1) {
bytes[0] = AbstractExecution.getReexecuteFunctionState(fnStateOrHasResult);
} else {
bytes[0] = fnStateOrHasResult;
}
Part.encodeInt(GemFireCacheImpl.getClientFunctionTimeout(), bytes, 1);
getMessage().addBytesPart(bytes);
}
}
/**
* ignoreFaileMember flag is at index 1
*/
private boolean getIgnoreFailedMembers() {
boolean ignoreFailedMembers = false;
if (this.flags != null && this.flags.length > 1) {
if (this.flags[IGNORE_FAILED_MEMBERS_INDEX] == 1) {
ignoreFailedMembers = true;
}
}
return ignoreFailedMembers;
}
@Override
protected Object processResponse(Message msg) throws Exception {
ChunkedMessage executeFunctionResponseMsg = (ChunkedMessage) msg;
try {
// Read the header which describes the type of message following
executeFunctionResponseMsg.readHeader();
switch (executeFunctionResponseMsg.getMessageType()) {
case MessageType.EXECUTE_FUNCTION_RESULT:
if (logger.isDebugEnabled()) {
logger.debug(
"ExecuteFunctionOpImpl#processResponse: received message of type EXECUTE_FUNCTION_RESULT.");
}
Exception exception = null;
// Read the chunk
do {
executeFunctionResponseMsg.receiveChunk();
Object resultResponse = executeFunctionResponseMsg.getPart(0).getObject();
Object result;
if (resultResponse instanceof ArrayList) {
result = ((ArrayList) resultResponse).get(0);
} else {
result = resultResponse;
}
if (result instanceof FunctionException) {
// String s = "While performing a remote " + getOpName();
FunctionException ex = ((FunctionException) result);
if (ex instanceof InternalFunctionException || getIgnoreFailedMembers()) {
Throwable cause = ex.getCause() == null ? ex : ex.getCause();
DistributedMember memberID =
(DistributedMember) ((ArrayList) resultResponse).get(1);
this.resultCollector.addResult(memberID, cause);
FunctionStats.getFunctionStats(this.functionId).incResultsReceived();
continue;
} else {
exception = ex;
}
} else if (result instanceof Throwable) {
String s = "While performing a remote " + getOpName();
exception = new ServerOperationException(s, (Throwable) result);
// Get the exception toString part.
// This was added for c++ thin client and not used in java
// Part exceptionToStringPart = msg.getPart(1);
} else {
DistributedMember memberID =
(DistributedMember) ((ArrayList) resultResponse).get(1);
resultCollector.addResult(memberID, result);
FunctionStats.getFunctionStats(this.functionId).incResultsReceived();
}
} while (!executeFunctionResponseMsg.isLastChunk());
if (exception != null) {
throw exception;
}
if (logger.isDebugEnabled()) {
logger.debug(
"ExecuteFunctionOpImpl#processResponse: received all the results from server successfully.");
}
return null;
case MessageType.EXCEPTION:
if (logger.isDebugEnabled()) {
logger.debug(
"ExecuteFunctionOpImpl#processResponse: received message of type EXCEPTION");
}
// Read the chunk
executeFunctionResponseMsg.receiveChunk();
Part part0 = executeFunctionResponseMsg.getPart(0);
Object obj = part0.getObject();
if (obj instanceof FunctionException) {
FunctionException ex = ((FunctionException) obj);
throw ex;
} else {
String s =
": While performing a remote execute Function" + ((Throwable) obj).getMessage();
throw new ServerOperationException(s, (Throwable) obj);
}
case MessageType.EXECUTE_FUNCTION_ERROR:
if (logger.isDebugEnabled()) {
logger.debug(
"ExecuteFunctionOpImpl#processResponse: received message of type EXECUTE_FUNCTION_ERROR");
}
// Read the chunk
executeFunctionResponseMsg.receiveChunk();
String errorMessage = executeFunctionResponseMsg.getPart(0).getString();
throw new ServerOperationException(errorMessage);
default:
throw new InternalGemFireError(LocalizedStrings.Op_UNKNOWN_MESSAGE_TYPE_0
.toLocalizedString(Integer.valueOf(executeFunctionResponseMsg.getMessageType())));
}
} finally {
executeFunctionResponseMsg.clear();
}
}
@Override
protected boolean isErrorResponse(int msgType) {
return msgType == MessageType.EXECUTE_FUNCTION_ERROR;
}
@Override
protected long startAttempt(ConnectionStats stats) {
return stats.startExecuteFunction();
}
protected String getOpName() {
return "executeFunction";
}
@Override
protected void endSendAttempt(ConnectionStats stats, long start) {
stats.endExecuteFunctionSend(start, hasFailed());
}
@Override
protected void endAttempt(ConnectionStats stats, long start) {
stats.endExecuteFunction(start, hasTimedOut(), hasFailed());
}
@Override
protected Message createResponseMessage() {
return new ChunkedMessage(1, Version.CURRENT);
}
}
public static final int MAX_FE_THREADS = Integer.getInteger("DistributionManager.MAX_FE_THREADS",
Math.max(Runtime.getRuntime().availableProcessors() * 4, 16)).intValue();
}