Cross-Language Invocation of Stateless and Stateful Functions#
openYuanrong supports cross-language invocation of stateless and stateful functions. This section introduces how to develop through example projects.
Calling C++ Stateless and Stateful Functions from Python Programs
Calling Java Stateless and Stateful Functions from Python Programs
Calling Python Stateless and Stateful Functions from C++ Programs
Calling Java Stateless and Stateful Functions from C++ Programs
Calling C++ Stateless and Stateful Functions from Java Programs
Prerequisites#
Refer to Deploy on Hosts to complete openYuanrong deployment.
Note
Ensure the --enable_meta_service=true parameter is configured during deployment to start the meta service component for function registration.
Calling C++ Stateless and Stateful Functions from Python Programs#
We compile C++ stateless and stateful functions into dynamic libraries, and use yr.cpp_function and yr.cpp_instance_class APIs in the Python main program for invocation.
Prepare the example project
Create a new directory on all nodes in the openYuanrong cluster, for example:
/opt/mycode/python-invoke-cpp, to store C++ stateless and stateful function code packages.Create the project directory
python-invoke-cppand files as follows:calculator.cpp: Defines C++ stateless function
Squareand stateful functionCounter.CMakeLists.txt: CMake configuration file for building the C++ project.
build: Empty directory for storing files generated during C++ compilation.
main.py: Python main program, calling C++ stateless function
Squareand stateful functionCounter.
python-invoke-cpp ├── calculator.cpp ├── CMakeLists.txt ├── build └── main.py
calculator.cpp file content
#include "yr/yr.h" int Square(int x) { return x * x; } // Define stateless function Square YR_INVOKE(Square) class Counter { public: Counter() {} Counter(int init) { count = init; } static Counter *FactoryCreate(int init) { return new Counter(init); } void Add() { count += 1; } int Get() { return count; } YR_STATE(count); public: int count; }; // Define stateful function Counter YR_INVOKE(Counter::FactoryCreate, &Counter::Add, &Counter::Get);
CMakeLists.txt file content, need to modify YR_INSTALL_PATH to your openYuanrong installation path
cmake_minimum_required(VERSION 3.16.1) project(calculator CXX) set(CMAKE_CXX_STANDARD 17) set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(BINARY_DIR ${SOURCE_DIR}/build) set(BUILD_SHARED_LIBS ON) # Replace YR_INSTALL_PATH value with actual openYuanrong installation path set(YR_INSTALL_PATH "/usr/local/lib/python3.9/site-packages/yr") link_directories(${YR_INSTALL_PATH}/cpp/lib) include_directories( ${YR_INSTALL_PATH}/cpp/include ) add_library(calculator SHARED calculator.cpp) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BINARY_DIR})
main.py file content
import yr yr.init() # We will register C++ functions in subsequent steps, generating the following URN cpp_function_urn = "sn:cn:yrk:default:function:0-yr-mycpp:$latest" # Call C++ stateless function Square cpp_function = yr.cpp_function("Square", cpp_function_urn) res = cpp_function.invoke(2) print(yr.get(res)) # Call C++ stateful function Counter's Add and Get methods cpp_function_instance = yr.cpp_instance_class("Counter", "Counter::FactoryCreate", cpp_function_urn).invoke(0) res = cpp_function_instance.Add.invoke() yr.get(res) res = cpp_function_instance.Get.invoke() print(yr.get(res)) # Destroy function instance cpp_function_instance.terminate() yr.finalize()
Build C++ functions into dynamic library
In the
python-invoke-cpp/builddirectory, execute the following command to build:cmake .. makeSuccessful build will generate dynamic library file
libcalculator.soin this directory. Copy the file to the code package directory you created on all nodes, for example:/opt/mycode/python-invoke-cpp.Register C++ functions as openYuanrong functions
Use curl tool to call the Register Function API to register an openYuanrong function named
0-yr-mycpp, with corresponding function URNsn:cn:yrk:default:function:0-yr-mycpp:$latest.# Replace /opt/mycode/python-invoke-cpp with your compiled dynamic library directory META_SERVICE_ENDPOINT=<meta service component endpoint, default: http://{master node IP}:31182> curl -H "Content-type: application/json" -X POST -i ${META_SERVICE_ENDPOINT}/serverless/v1/functions -d '{"name":"0-yr-mycpp","runtime":"cpp11","kind":"yrlib","cpu":500,"memory":500,"timeout":60,"storageType":"local","codePath":"/opt/mycode/python-invoke-cpp"}'
Run Python main program for testing
Run the main program in the
python-invoke-cppdirectorypython main.py # Outputs 4 and 1
Calling Java Stateless and Stateful Functions from Python Programs#
We use yr.cpp_function and yr.cpp_instance_class APIs in the Python main program to call Java stateless and stateful functions.
Prepare the example project
Create a new directory on all nodes in the openYuanrong cluster, for example:
/opt/mycode/python-invoke-java, to store Java stateless and stateful function code packages.Create the project directory
python-invoke-javaand files as follows:main.py: Python main program, calling Java stateless function
squareand stateful functionCounter.pom.xml: Maven configuration file for building the Java project.
Square.java: Defines stateless function
square.Counter.java: Defines stateful function
Counter.
python-invoke-java ├── main.py ├── pom.xml └── src └── main └── java └── com └── yuanrong └── demo ├── Counter.java └── Square.java
main.py file content
import yr yr.init() # We will register Java functions in subsequent steps, generating the following URN java_function_urn = "sn:cn:yrk:default:function:0-yr-myjava:$latest" # Call Java stateless function square java_function = yr.java_function("com.yuanrong.demo.Square", "square", java_function_urn) res = java_function.invoke(2) print(yr.get(res)) # Call Java stateful function Counter's Add and Get methods java_instance = yr.java_instance_class("com.yuanrong.demo.Counter", java_function_urn).invoke(1) res = java_instance.add.invoke() yr.get(res) res = java_instance.get.invoke() print(yr.get(res)) # Destroy function instance java_instance.terminate() yr.finalize()
pom.xml file content
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.yuanrong.demo</groupId> <artifactId>calculator</artifactId> <version>1.0.0</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <appendAssemblyId>false</appendAssemblyId> </configuration> </plugin> </plugins> </build> </project>
Square.java file content
package com.yuanrong.demo; public class Square { public static int square(int x) { return x * x; } }
Counter.java file content
package com.yuanrong.demo; public class Counter { private int count = 0; public Counter(int count) { this.count = count; } public void add() { this.count += 1; } public int get() { return this.count; } }
Build Java project
In the
python-invoke-javadirectory, execute the following command to build:mvn clean package
Successful build will generate
calculator-1.0.0.jarfile in thepython-invoke-java/targetdirectory. Copy the file to the code package directory you created on all nodes, for example:/opt/mycode/python-invoke-java.Register Java functions as openYuanrong functions
Use curl tool to call the Register Function API to register an openYuanrong function named
0-yr-myjava, with corresponding function URNsn:cn:yrk:default:function:0-yr-myjava:$latest.# Replace /opt/mycode/python-invoke-java with your code package directory META_SERVICE_ENDPOINT=<meta service component endpoint, default: http://{master node IP}:31182> curl -H "Content-type: application/json" -X POST -i ${META_SERVICE_ENDPOINT}/serverless/v1/functions -d '{"name":"0-yr-myjava","runtime":"java1.8","kind":"yrlib","cpu":500,"memory":500,"timeout":60,"storageType":"local","codePath":"/opt/mycode/python-invoke-java"}'
Run Python main program for testing
Run the main program in the
python-invoke-javadirectorypython main.py # Outputs 4 and 2
Calling Python Stateless and Stateful Functions from C++ Programs#
We use PyFunction and PyInstanceClass::FactoryCreate APIs in the C++ main program to call Python stateless and stateful functions.
Prepare the example project
Create a new directory on all nodes in the openYuanrong cluster, for example:
/opt/mycode/cpp-invoke-python, to store Python stateless and stateful function code packages.Create the project directory
cpp-invoke-pythonand files as follows:calculator.py: Defines stateless function
squareand stateful functionCounter.main.cpp: C++ main program, calling Python stateless function
squareand stateful functionCounter.CMakeLists.txt: CMake configuration file for building the C++ project.
build: Empty directory for storing files generated during C++ compilation.
cpp-invoke-python ├── calculator.py ├── main.cpp ├── CMakeLists.txt └── build
calculator.py file content
def square(x): return x * x class Counter: def __init__(self, count): self.count = count def add(self): self.count += 1 def get(self): return self.count
main.cpp file content
#include <iostream> #include "yr/yr.h" int main(int argc, char **argv) { // We will register Python functions in subsequent steps, generating the following URN std::string pyFunctionUrn = "sn:cn:yrk:default:function:0-yr-mypython:$latest"; YR::Config conf; YR::Init(conf, argc, argv); // Call Python stateless function square auto resFutureSquare = YR::PyFunction<int>("calculator", "square").SetUrn(pyFunctionUrn).Invoke(2); auto resSquare = *YR::Get(resFutureSquare); std::cout << resSquare << std::endl; // Call Python stateful function Counter's add and get methods auto instanceHandler = YR::PyInstanceClass::FactoryCreate("calculator", "Counter"); auto instance = YR::Instance(instanceHandler).SetUrn(pyFunctionUrn).Invoke(0); auto resFutureAdd = instance.PyFunction<void>("add").Invoke(); YR::Wait(resFutureAdd); auto resFutureGet = instance.PyFunction<int>("get").Invoke(); auto resGet = *YR::Get(resFutureGet); std::cout << resGet << std::endl; // Destroy function instance instance.Terminate(); YR::Finalize(); return 0; }
CMakeLists.txt file content, need to modify YR_INSTALL_PATH to your openYuanrong installation path
cmake_minimum_required(VERSION 3.16.1) project(main CXX) set(CMAKE_CXX_STANDARD 17) set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(BINARY_DIR ${SOURCE_DIR}/build) # Replace YR_INSTALL_PATH value with actual openYuanrong installation path set(YR_INSTALL_PATH "/usr/local/lib/python3.9/site-packages/yr") link_directories(${YR_INSTALL_PATH}/cpp/lib) include_directories( ${YR_INSTALL_PATH}/cpp/include/faas ${YR_INSTALL_PATH}/cpp/include ) add_executable(main main.cpp) target_link_libraries(main yr-api) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BINARY_DIR})
Register Python functions as openYuanrong functions
First copy the
calculator.pyfile to the code package directory on all nodes. Then use curl tool to call the Register Function API to register an openYuanrong function named0-yr-mypython, with corresponding function URNsn:cn:yrk:default:function:0-yr-mypython:$latest.# Replace /opt/mycode/cpp-invoke-python with your code package directory META_SERVICE_ENDPOINT=<meta service component endpoint, default: http://{master node IP}:31182> curl -H "Content-type: application/json" -X POST -i ${META_SERVICE_ENDPOINT}/serverless/v1/functions -d '{"name":"0-yr-mypython","runtime":"python3.9","kind":"yrlib","cpu":500,"memory":500,"timeout":60,"storageType":"local","codePath":"/opt/mycode/cpp-invoke-python"}'
Run C++ main program for testing
In the
cpp-invoke-python/builddirectory, execute the following command to build:cmake .. makeSuccessful build will generate executable file
mainin this directory. Execute the following command to run the main program../main # Outputs 4 and 1
Calling Java Stateless and Stateful Functions from C++ Programs#
We use PyFunction and PyInstanceClass::FactoryCreate APIs in the C++ main program to call Java stateless and stateful functions.
Prepare the example project
Create a new directory on all nodes in the openYuanrong cluster, for example:
/opt/mycode/cpp-invoke-java, to store Java stateless and stateful function code packages.Create the project directory
cpp-invoke-javaand files as follows:main.cpp: C++ main program, calling Java stateless function
squareand stateful functionCounter.CMakeLists.txt: CMake configuration file for building the C++ project.
build: Empty directory for storing files generated during C++ compilation.
pom.xml: Maven configuration file for building the Java project.
Counter.java: Defines Java stateful function
Counter.Square.java: Defines Java stateless function
square.
cpp-invoke-java ├── cpp-project │ ├── main.cpp │ ├── CMakeLists.txt │ └── build └── java-project ├── pom.xml └── src └── main └── java └── com └── yuanrong └── demo ├── Counter.java └── Square.java
main.cpp file content
#include <iostream> #include "yr/yr.h" int main(int argc, char **argv) { // We will register Java functions in subsequent steps, generating the following URN std::string javaFunctionUrn = "sn:cn:yrk:default:function:0-yr-myjava:$latest"; YR::Config conf; YR::Init(conf, argc, argv); // Call Java stateless function square auto resFutureSquare = YR::JavaFunction<int>("com.yuanrong.demo.Square", "square").SetUrn(javaFunctionUrn).Invoke(2); auto resSquare = *YR::Get(resFutureSquare); std::cout << resSquare << std::endl; // Call Java stateful function Counter's add and get methods auto instanceHandler = YR::JavaInstanceClass::FactoryCreate("com.yuanrong.demo.Counter"); auto instance = YR::Instance(instanceHandler).SetUrn(javaFunctionUrn).Invoke(1); auto resFutureAdd = instance.JavaFunction<void>("add").Invoke(); // YR::Get(resFutureAdd); auto resFutureGet = instance.JavaFunction<int>("get").Invoke(); auto resGet = *YR::Get(resFutureGet); std::cout << resGet << std::endl; // Destroy function instance instance.Terminate(); YR::Finalize(); }
CMakeLists.txt file content, need to modify YR_INSTALL_PATH to your openYuanrong installation path
cmake_minimum_required(VERSION 3.16.1) project(main CXX) set(CMAKE_CXX_STANDARD 17) set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(BINARY_DIR ${SOURCE_DIR}/build) # Replace YR_INSTALL_PATH value with actual openYuanrong installation path set(YR_INSTALL_PATH "/usr/local/lib/python3.9/site-packages/yr") link_directories(${YR_INSTALL_PATH}/cpp/lib) include_directories( ${YR_INSTALL_PATH}/cpp/include/faas ${YR_INSTALL_PATH}/cpp/include ) add_executable(main main.cpp) target_link_libraries(main yr-api) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BINARY_DIR})
pom.xml file content
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.yuanrong.demo</groupId> <artifactId>calculator</artifactId> <version>1.0.0</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <appendAssemblyId>false</appendAssemblyId> </configuration> </plugin> </plugins> </build> </project>
Square.java file content
package com.yuanrong.demo; public class Square { public static int square(int x) { return x * x; } }
Counter.java file content
package com.yuanrong.demo; public class Counter { private int count = 0; public Counter(int count) { this.count = count; } public void add() { this.count += 1; } public int get() { return this.count; } }
Build Java project
In the
cpp-invoke-java/java-projectdirectory, execute the following command to build:mvn clean package
Successful build will generate
calculator-1.0.0.jarfile in thecpp-invoke-java/java-project/targetdirectory. Copy the file to the code package directory on all nodes, for example:/opt/mycode/cpp-invoke-java.Register Java functions as openYuanrong functions
Use curl tool to call the Register Function API to register an openYuanrong function named
0-yr-myjava, with corresponding function URNsn:cn:yrk:default:function:0-yr-myjava:$latest.# Replace /opt/mycode/cpp-invoke-java with your code package directory META_SERVICE_ENDPOINT=<meta service component endpoint, default: http://{master node IP}:31182> curl -H "Content-type: application/json" -X POST -i ${META_SERVICE_ENDPOINT}/serverless/v1/functions -d '{"name":"0-yr-myjava","runtime":"java1.8","kind":"yrlib","cpu":500,"memory":500,"timeout":60,"storageType":"local","codePath":"/opt/mycode/cpp-invoke-java"}'
Build and run C++ main program for testing
In the
cpp-invoke-java/cpp-project/builddirectory, execute the following command to build:cmake .. makeSuccessful build will generate executable file
mainin this directory. Execute the following command to run the main program../main # Outputs 4 and 2
Calling C++ Stateless and Stateful Functions from Java Programs#
We compile C++ stateless and stateful functions into dynamic libraries, and use CppFunction and CppInstanceClass APIs in the Java main program for invocation.
Prepare the example project
Create a new directory on all nodes in the openYuanrong cluster, for example:
/opt/mycode/java-invoke-cpp, to store C++ stateless and stateful function code packages.Create the project directory
java-invoke-cppand files as follows:calculator.cpp: Defines C++ stateless function
Squareand stateful functionCounter.CMakeLists.txt: CMake configuration file for building the C++ project.
build: Empty directory for storing files generated during C++ compilation.
pom.xml: Maven configuration file for building the Java project.
Main.java: Java main program, calling C++ stateless function
Squareand stateful functionCounter.
java-invoke-cpp ├── cpp-project │ ├── calculator.cpp │ ├── CMakeLists.txt │ └── build └── java-project ├── pom.xml └── src └── main └── java └── com └── yuanrong └── demo └── Main.java
Main.java file content
package com.yuanrong.demo; import org.yuanrong.api.YR; import org.yuanrong.runtime.client.ObjectRef; import org.yuanrong.call.CppInstanceHandler; import org.yuanrong.function.CppFunction; import org.yuanrong.function.CppInstanceMethod; import org.yuanrong.function.CppInstanceClass; public class Main { public static void main(String[] args) throws Exception { YR.init(); // We will register C++ functions in subsequent steps, generating the following URN String cppFunctionUrn = "sn:cn:yrk:default:function:0-yr-mycpp:$latest"; try { // Call stateless function Square ObjectRef ref = YR.function(CppFunction.of("Square", int.class)) .setUrn(cppFunctionUrn) .invoke(2); int res = (int) YR.get(ref, 30); System.out.println(res); // Call stateful function Counter's Add and Get methods CppInstanceHandler cppInstance = YR.instance(CppInstanceClass.of("Counter", "FactoryCreate")) .setUrn(cppFunctionUrn) .invoke(1); ObjectRef addRef = cppInstance.function(CppInstanceMethod.of("Add", void.class)).invoke(); YR.get(addRef, 30); ObjectRef getRef = cppInstance.function(CppInstanceMethod.of("Get", int.class)).invoke(); res = (int) YR.get(getRef, 30); System.out.println(res); cppInstance.terminate(); } finally { YR.Finalize(); } } }
pom.xml file content, refer to Install Java SDK and configure dependency distributed-actortask-sdk
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.yuanrong.demo</groupId> <artifactId>yrdemo</artifactId> <version>1.0.0</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <!-- Modify version number to your actual version --> <groupId>org.yuanrong</groupId> <artifactId>yr-api-sdk</artifactId> <version>9.9.9</version> </dependency> <dependency> <!-- Modify version number to your actual version --> <groupId>org.yuanrong</groupId> <artifactId>faas-function-sdk</artifactId> <version>9.9.9</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> <configuration> <archive> <manifest> <mainClass>com.yuanrong.demo.Main</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <appendAssemblyId>false</appendAssemblyId> </configuration> </plugin> </plugins> </build> </project>
calculator.cpp file content
#include "yr/yr.h" int Square(int x) { return x * x; } // Define stateless function Square YR_INVOKE(Square) class Counter { public: Counter() {} Counter(int init) { count = init; } static Counter *FactoryCreate(int init) { return new Counter(init); } void Add() { count += 1; } int Get() { return count; } YR_STATE(count); public: int count; }; // Define stateful function Counter YR_INVOKE(Counter::FactoryCreate, &Counter::Add, &Counter::Get);
CMakeLists.txt file content, need to modify YR_INSTALL_PATH to your openYuanrong installation path
cmake_minimum_required(VERSION 3.16.1) project(calculator CXX) set(CMAKE_CXX_STANDARD 17) set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(BINARY_DIR ${SOURCE_DIR}/build) set(BUILD_SHARED_LIBS ON) # Replace YR_INSTALL_PATH value with actual openYuanrong installation path set(YR_INSTALL_PATH "/usr/local/lib/python3.9/site-packages/yr") link_directories(${YR_INSTALL_PATH}/cpp/lib) include_directories( ${YR_INSTALL_PATH}/cpp/include ) add_library(calculator SHARED calculator.cpp) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BINARY_DIR})
Build C++ functions into dynamic library
In the
java-invoke-cpp/cpp-project/builddirectory, execute the following command to build:cmake .. makeSuccessful build will generate dynamic library file
libcalculator.soin this directory. Copy the file to the code package directory on all nodes, for example:/opt/mycode/java-invoke-cpp.Register C++ functions as openYuanrong functions
Use curl tool to call the Register Function API to register an openYuanrong function named
0-yr-mycpp, with corresponding function URNsn:cn:yrk:default:function:0-yr-mycpp:$latest.# Replace /opt/mycode/java-invoke-cpp with your code package directory META_SERVICE_ENDPOINT=<meta service component endpoint, default: http://{master node IP}:31182> curl -H "Content-type: application/json" -X POST -i ${META_SERVICE_ENDPOINT}/serverless/v1/functions -d '{"name":"0-yr-mycpp","runtime":"cpp11","kind":"yrlib","cpu":500,"memory":500,"timeout":60,"storageType":"local","codePath":"/opt/mycode/java-invoke-cpp"}'
Run Java main program for testing
In the
java-invoke-cpp/java-projectdirectory, execute the following command to build and package:mvn clean package
Successful build will generate
yrdemo-1.0.0.jarfile in thejava-invoke-cpp/java-project/targetdirectory. Execute the following command to run the main program.java -jar yrdemo-1.0.0.jar # Outputs 4 and 2