| --- |
| title: Function Execution |
| --- |
| |
| <!-- |
| 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. |
| --> |
| |
| A client can invoke a server-resident function, with paramaters, and can collect and operate on the returned results. |
| |
| ## Server-side Requirements |
| |
| To be callable from your client, a function must be resident on the server and registered as available for client access. |
| See [Executing a Function in <%=vars.product_name_long%>](/serverman/developing/function_exec/function_execution.html) |
| in the <%=vars.product_name%> User Guide for details on how to write and register server-resident functions. |
| |
| ## Client-side Requirements |
| |
| The client must connect to the server through a connection pool in order to invoke a server-side function. |
| |
| ## How Functions Execute |
| |
| 1. The calling client application runs the `execute` method on the `Execution` object. The object must already be registered on the servers. |
| 2. The function is invoked on all servers where it needs to run. The locations are determined by the `FunctionService on*` |
| method calls, region configuration, and any filters. |
| 3. If the function has results, the result is returned in a `ResultCollector` object. |
| 4. The client collects results using the result collector `getResult`. |
| |
| In every client where you want to execute the function and process the results: |
| |
| - Use one of the `FunctionService on*` methods to create an `Execution` object. The `on*` methods, |
| `onRegion`, `onServer` and `onServers`, define the highest level where the function is run. If |
| you use `onRegion` you can further narrow your run scope by setting key filters. The function run |
| using `onRegion` is a data dependent function – the others are data-independent functions. |
| |
| You can run a data dependent function against custom partitioned and colocated partitioned regions. From the client, provide the appropriate key |
| sets to the function call. |
| |
| - Use the `Execution` object as needed for additional function configuration. You can: |
| - Provide a set of data keys to `withFilter` to narrow the execution scope. This works only for `onRegion` Execution objects. |
| - Provide function arguments to `withArgs`. |
| - Define a custom `ResultCollector` to `withCollector`. |
| |
| - Call the `Execution` object execute method to run the function. |
| |
| ## Processing Function Results |
| |
| The client may use the default result collector. If the client needs special results handling, code |
| a custom `ResultsCollector` implementation to replace the default. Use the |
| `Execution::withCollector` method to define the custom collector. |
| |
| For example, to program your client to get the results from a function, use the result collector returned from the function execution, like this: |
| |
| ```cpp |
| ResultCollectorPtr rc = FunctionService::onRegion(region) |
| ->withArgs(args) |
| ->withFilter(keySet) |
| ->withCollector(new MyCustomResultCollector()) |
| .execute(Function); |
| CacheableVectorPtr functionResult = rc.getResult(); |
| ``` |
| |
| The `getResult` methods of the default result collector block until all results are received, then return the full result set. |
| |
| To handle the results in a custom manner: |
| |
| 1. Write a class that implements the `ResultCollector` interface to handle the results in a custom manner. The methods are of two types: one handles data and information from <%=vars.product_name%> and populates the results set, while the other returns the compiled results to the calling application: |
| - `addResult` is called when results arrive from the `Function` methods. Use `addResult` to add a single result to the ResultCollector. |
| - `endResults` is called to signal the end of all results from the function execution. |
| - `getResult` is available to your executing application (the one that calls `Execution.execute`) to retrieve the results. This may block until all results are available. |
| - `clearResults` is called to clear partial results from the results collector. This is used only for highly available `onRegion` functions where the calling application waits for the results. If the call fails, before <%=vars.product_name%> retries the execution, it calls `clearResults` to ready the instance for a clean set of results. |
| 2. Use the `Execution` object in your executing member to call `withCollector`, passing your custom collector, as shown in the example above. |
| |
| # Examples |
| |
| The native client source release contains examples of function execution written for .NET and |
| C++. The examples are located in `../examples/dotnet/FunctionExecutionCs` and |
| `../examples/cpp/function-execution`, respectively. |
| |
| Both examples begin with a server-side script that runs `gfsh` commands to create |
| a region, simply called "partition_region", which is preloaded with a JAR file containing the |
| server-side Java function code. The function, called "ExampleMultiGetFunction", is defined in the |
| `examples/utilities` directory of your distribution. As its input parameter, the function takes an array of keys, |
| then performs a `get` on each key and returns an array containing the results. |
| The function does not load values into the data store. That is a separate operation, performed in these examples by |
| the client, and does not involve the server-side function. |
| |
| As prerequisites, the client code must be aware of the connection to the server, the name of the function, and the expected type/format |
| of the input parameter and return value. |
| |
| The client: |
| |
| - creates an execution object |
| - populates the execution object with input parameters |
| - invokes the object's execute method to invoke the server-side function. |
| |
| If the client expects results, it must create a result |
| object. Optionally, the client can use a provided result collector which offers some predefined |
| methods for iterating over and processing return values. |
| |
| ## .NET Example |
| This section contains code snippets showing highlights of the .NET function execution example. They are not intended for cut-and-paste execution. |
| For the complete source, see the example source directory. |
| |
| The .NET example creates a cache, then uses it to create a connection pool. |
| |
| ```csharp |
| var cacheFactory = new CacheFactory() |
| .Set("log-level", "none"); |
| var cache = cacheFactory.Create(); |
| |
| var poolFactory = cache.GetPoolFactory() |
| .AddLocator("localhost", 10334); |
| var pool = poolFactory.Create("pool"); |
| ``` |
| |
| The example uses the connection pool to create a region, with the same characteristics and name as the server-side region (`partition_region`). |
| |
| ```csharp |
| var regionFactory = cache.CreateRegionFactory(RegionShortcut.PROXY) |
| .SetPoolName("pool"); |
| var region = regionFactory.Create<object, object>("partition_region"); |
| ``` |
| |
| The sample client populates the server's datastore with values, using the API and some sample key-value pairs. |
| |
| ```csharp |
| string rtimmonsKey = "rtimmons"; |
| string rtimmonsValue = "Robert Timmons"; |
| string scharlesKey = "scharles"; |
| string scharlesValue = "Sylvia Charles"; |
| region.Put(rtimmonsKey, rtimmonsValue, null); |
| region.Put(scharlesKey, scharlesValue, null); |
| ``` |
| |
| To confirm that the data has been stored, the client uses the API to retrieve the values and write them to the console. |
| This is done without reference to the server-side example function. |
| |
| ```csharp |
| var user1 = region.Get(rtimmonsKey, null); |
| var user2 = region.Get(scharlesKey, null); |
| |
| Console.WriteLine(rtimmonsKey + " = " + user1); |
| Console.WriteLine(scharlesKey + " = " + user2); |
| ``` |
| |
| Next, the client retrieves those same values using the server-side example function. |
| The client code creates the input parameter, an array of keys whose values are to be retrieved. |
| |
| ```csharp |
| ArrayList keyArgs = new ArrayList(); |
| keyArgs.Add(rtimmonsKey); |
| keyArgs.Add(scharlesKey); |
| ``` |
| |
| The client creates an execution object using `Client.FunctionService.OnRegion` and specifying the region. |
| |
| ```csharp |
| var exc = Client.FunctionService<object>.OnRegion<object, object>(region); |
| ``` |
| |
| The client then calls the server side function with its input arguments and stores the results in a Client.IResultCollector. |
| |
| ```csharp |
| Client.IResultCollector<object> rc = exc.WithArgs<object>(keyArgs).Execute("ExampleMultiGetFunction"); |
| ``` |
| |
| It then loops through the results and prints the retrieved values. |
| |
| ```csharp |
| ICollection<object> res = rc.GetResult(); |
| |
| Console.WriteLine("Function Execution Results:"); |
| Console.WriteLine(" Count = {0}", res.Count); |
| |
| foreach (List<object> item in res) |
| { |
| foreach (object item2 in item) |
| { |
| Console.WriteLine(" value = {0}", item2.ToString()); |
| } |
| } |
| ``` |
| |
| ## C++ Example |
| This section contains code snippets showing highlights of the C++ function execution example. They are not intended for cut-and-paste execution. |
| For the complete source, see the example source directory. |
| |
| The C++ example creates a cache. |
| |
| ```cpp |
| Cache setupCache() { |
| return CacheFactory() |
| .set("log-level", "none") |
| .create(); |
| } |
| ``` |
| |
| The example client uses the cache to create a connection pool, |
| |
| ```cpp |
| void createPool(const Cache& cache) { |
| auto pool = cache.getPoolManager() |
| .createFactory() |
| .addServer("localhost", EXAMPLE_SERVER_PORT) |
| .create("pool"); |
| } |
| ``` |
| |
| Then, using that pool, the client creates a region with the same characteristics and name as the server-side region (`partition_region`). |
| |
| ```cpp |
| std::shared_ptr<Region> createRegion(Cache& cache) { |
| auto regionFactory = cache.createRegionFactory(RegionShortcut::PROXY); |
| auto region = regionFactory.setPoolName("pool").create("partition_region"); |
| |
| return region; |
| } |
| ``` |
| |
| The sample client populates the server's datastore with values, using the API and some sample key-value pairs. |
| |
| ```cpp |
| void populateRegion(const std::shared_ptr<Region>& region) { |
| for (int i = 0; i < keys.size(); i++) { |
| region->put(keys[i], values[i]); |
| } |
| } |
| ``` |
| |
| As confirmation that the data has been stored, the sample client uses the API to retrieve the values and write them to the console. |
| This is done without reference to the server-side example function. |
| |
| ```cpp |
| std::shared_ptr<CacheableVector> populateArguments() { |
| auto arguments = CacheableVector::create(); |
| for (int i = 0; i < keys.size(); i++) { |
| arguments->push_back(CacheableKey::create(keys[i])); |
| } |
| return arguments; |
| } |
| ``` |
| |
| Next, the client retrieves those same values using the server-side example function. |
| The client code creates the input parameter, an array of keys whose values are to be retrieved. |
| |
| ```cpp |
| std::vector<std::string> executeFunctionOnServer(const std::shared_ptr<Region> region, |
| const std::shared_ptr<CacheableVector> arguments) { |
| std::vector<std::string> resultList; |
| ``` |
| |
| The client creates an execution object using `Client.FunctionService.OnRegion` and specifying the region. |
| |
| ```cpp |
| auto functionService = FunctionService::onServer(region->getRegionService()); |
| ``` |
| |
| The client then calls the server side function with its input arguments and stores the results in a Client.IResultCollector. |
| |
| ```cpp |
| if(auto executeFunctionResult = functionService.withArgs(arguments).execute(getFuncIName)->getResult()) { |
| for (auto &arrayList: *executeFunctionResult) { |
| for (auto &cachedString: *std::dynamic_pointer_cast<CacheableArrayList>(arrayList)) { |
| resultList.push_back(std::dynamic_pointer_cast<CacheableString>(cachedString)->value()); |
| } |
| } |
| } else { |
| std::cout << "get executeFunctionResult is NULL\n"; |
| } |
| |
| return resultList; |
| } |
| ``` |
| |
| It then loops through the results and prints the retrieved values. |
| |
| ```cpp |
| void printResults(const std::vector<std::string>& resultList) { |
| std::cout << "Result count = " << resultList.size() << std::endl << std::endl; |
| int i = 0; |
| for (auto &cachedString: resultList) { |
| std::cout << "\tResult[" << i << "]=" << cachedString << std::endl; |
| ++i; |
| } |
| ``` |
| |