/* * 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(); }