Executing a Function in vFabric GemFire

In this procedure it is assumed that you have your members and regions defined where you want to run functions.

Main tasks:
  1. Write the function code.
  2. Write the XML or application code to register the function, if needed (generally recommended).
  3. Write the application code to run the function and, if the function returns results, to handle the results.
  4. If your function returns results and you need special results handling, code a custom ResultsCollector implementation and use it in your function execution.

Write the Function Code

To write the function code, you implement the Function interface or extend the FunctionAdapter class. Both are in the com.gemstone.gemfire.cache.execute package. The adapter class provides some default implementations for methods, which you can override.

Code the methods you need for the function. These steps do not have to be done in this order.
  1. Code getId to return a unique name for your function. You can use this name to access the function through the FunctionService API.
  2. For high availability:
    1. Code isHa to return true to indicate to GemFire that it can re-execute your function after one or more members fails
    2. Code your function to return a result
    3. Code hasResult to return true
  3. Code hasResult to return true if your function returns results to be processed and false if your function does not return any data - the fire and forget function. FunctionAdapter hasResult returns true by default.
  4. If the function will be executed on a region, code optimizeForWrite to return false if your function only reads from the cache, and true if your function updates the cache. The method only works if, when you are running the function, the Execution object is obtained through a FunctionService onRegion call. FunctionAdapter optimizeForWrite returns false by default.
  5. Code the execute method to perform the work of the function.
    1. Make execute thread safe to accommodate simultaneous invocations.
    2. For high availability, code execute to accommodate multiple identical calls to the function. Use the RegionFunctionContext isPossibleDuplicate to determine whether the call may be a high-availability re-execution. This boolean is set to true on execution failure and is false otherwise.
      Note: The isPossibleDuplicate boolean can be set following a failure from another member’s execution of the function, so it only indicates that the execution might be a repeat run in the current member.
    3. Use the function context to get information about the execution and the data:
      • The context holds the function ID, the ResultSender object for passing results back to the originator, and function arguments provided by the member where the function originated.
      • The context provided to the function is the FunctionContext, which is automatically extended to RegionFunctionContext if you get the Execution object through a FunctionService onRegion call.
      • For data dependent functions, the RegionFunctionContext holds the Region object, the Set of key filters, and a boolean indicating multiple identical calls to the function, for high availability implementations.
      • For partitioned regions, the PartitionRegionHelper provides access to additional information and data for the region. For single regions, use getLocalDataForContext. For colocated regions, use getLocalColocatedRegions.

Example function code:

package quickstart;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import com.gemstone.gemfire.cache.execute.FunctionAdapter;
import com.gemstone.gemfire.cache.execute.FunctionContext;
import com.gemstone.gemfire.cache.execute.RegionFunctionContext;
import com.gemstone.gemfire.cache.partition.PartitionRegionHelper;

public class MultiGetFunction extends FunctionAdapter {

  public void execute(FunctionContext fc) {
    RegionFunctionContext context = (RegionFunctionContext)fc;
    Set keys = context.getFilter();
    Set keysTillSecondLast = new HashSet(); 
    int setSize = keys.size();
    Iterator keysIterator = keys.iterator();
    for(int i = 0; i < (setSize -1); i++)
    {
      keysTillSecondLast.add(keysIterator.next());
    }
    for (Object k : keysTillSecondLast) {
      context.getResultSender().sendResult(
          (Serializable)PartitionRegionHelper.getLocalDataForContext(context)
              .get(k));
    }
    Object lastResult = keysIterator.next();
    context.getResultSender().lastResult(
        (Serializable)PartitionRegionHelper.getLocalDataForContext(context)
            .get(lastResult));
  }

  public String getId() {
    return getClass().getName();
  }
}

Register the Function

This section applies to functions that are invoked using the Execution.execute(String functionId) signature. When this method is invoked, the calling application sends the function ID to all members where the Function.execute is to be run. Receiving members use the ID to look up the function in the local FunctionService. In order to do the lookup, all of the receiving member must have previously registered the function with the function service.

The alternative to this is the Execution.execute(Function function) signature. When this method is invoked, the calling application serializes the instance of Function and sends it to all members where the Function.execute is to be run. Receiving members deserialize the Function instance, create a new local instance of it, and run execute from that. This option is not available for non-Java client invocation of functions on servers.

Your Java servers must register functions that are invoked by non-Java clients. You may want to use registration in other cases to avoid the overhead of sending Function instances between members.

Register your function using one of these methods:
  • XML:
    <cache>
        ...
        </region>
    <function-service>
      <function>
        <class-name>com.bigFatCompany.tradeService.cache.func.TradeCalc</class-name>
      </function>
    </function-service>
  • Java:
    myFunction myFun = new myFunction();
    FunctionService.registerFunction(myFun);
    Note: Modifying a function instance after registration has no effect on the registered function. If you want to execute a new function, you must register it with a different identifier.

Run the Function

This assumes you’ve already followed the steps for writing and registering the function.

In every member where you want to explicitly execute the function and process the results:
  1. Use one of the FunctionService on* methods to create an Execute object. The on* methods, onRegion, onMembers, etc., define the highest level where the function is run. For colocated partitioned regions, use onRegion and specify any one of the colocated regions. The function run using onRegion is referred to as a data dependent function - the others as data-independent functions.
  2. Use the Execution object as needed for additional function configuration. You can:
    • Provide a key Set to withFilters to narrow the execution scope for onRegion Execution objects. You can retrieve the key set in your Function execute method through RegionFunctionContext.getFilter.
    • Provide function arguments to withArgs. You can retrieve these in your Function execute method through FunctionContext.getArguments.
    • Define a custom ResultCollector
  3. Call the Execution object to execute method to run the function.
  4. If the function returns results, call getResult from the results collector returned from execute and code your application to do whatever it needs to do with the results.
    Note: For high availability, you must call the getResult method.

Example of running the function - for executing members:

MultiGetFunction function = new MultiGetFunction();
FunctionService.registerFunction(function);
    
writeToStdout("Press Enter to continue.");
stdinReader.readLine();
    
Set keysForGet = new HashSet();
keysForGet.add("KEY_4");
keysForGet.add("KEY_9");
keysForGet.add("KEY_7");

Execution execution = FunctionService.onRegion(exampleRegion)
    .withFilter(keysForGet)
    .withArgs(Boolean.TRUE)
    .withCollector(new MyArrayListResultCollector());

ResultCollector rc = execution.execute(function);
// Retrieve results, if the function returns results
List result = (List)rc.getResult();

Write a Custom Results Collector

This topic applies to functions that return results.

When you execute a function that returns results, the function stores the results into a ResultCollector and returns the ResultCollector object. The calling application can then retrieve the results through the ResultCollector getResult method. Example:
ResultCollector rc = execution.execute(function);
List result = (List)rc.getResult();

GemFire’s default ResultCollector collects all results into an ArrayList. Its getResult methods block until all results are received. Then they return the full result set.

To customize results collecting:
  1. Write a class that extends ResultCollector and code the methods to store and retrieve the results as you need. Note that the methods are of two types:
    1. addResult and endResults are called by GemFire when results arrive from the Function instance SendResults methods
    2. getResult is available to your executing application (the one that calls Execution.execute) to retrieve the results
  2. Use high availability for onRegion functions that have been coded for it:
    1. Code the ResultCollector clearResults method to remove any partial results data. This readies the instance for a clean function re-execution.
    2. When you invoke the function, call the result collector getResult method. This enables the high availability functionality.
  3. In your member that calls the function execution, create the Execution object using the withCollector method, and passing it your custom collector. Example:
    Execution execution = FunctionService.onRegion(exampleRegion)
        .withFilter(keysForGet)
        .withArgs(Boolean.TRUE)
        .withCollector(new MyArrayListResultCollector());