chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
{auto_gen_header}
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<groupId>io.ray</groupId>
|
||||
<artifactId>ray-superpom</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ray-runtime</artifactId>
|
||||
<name>ray runtime</name>
|
||||
<description>ray runtime implementation</description>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<output.directory>${basedir}/../../build/java</output.directory>
|
||||
</properties>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>release</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>release</name>
|
||||
<value>true</value>
|
||||
</property>
|
||||
<activeByDefault>false</activeByDefault>
|
||||
</activation>
|
||||
<properties>
|
||||
<output.directory>${basedir}</output.directory>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.ray</groupId>
|
||||
<artifactId>ray-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
{generated_bzl_deps}
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>native_dependencies</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-dependencies-to-build</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${basedir}/../../build/java</outputDirectory>
|
||||
<overWriteReleases>false</overWriteReleases>
|
||||
<overWriteSnapshots>false</overWriteSnapshots>
|
||||
<overWriteIfNewer>true</overWriteIfNewer>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.3.1</version>
|
||||
<configuration>
|
||||
<outputDirectory>${output.directory}</outputDirectory>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<minimizeJar>false</minimizeJar>
|
||||
<artifactSet>
|
||||
<includes>
|
||||
<include>com.google.guava</include>
|
||||
<include>com.google.protobuf</include>
|
||||
</includes>
|
||||
</artifactSet>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>com.google.common</pattern>
|
||||
<shadedPattern>io.ray.shaded.com.google.common</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.google.protobuf</pattern>
|
||||
<shadedPattern>io.ray.shaded.com.google.protobuf</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.google.thirdparty</pattern>
|
||||
<shadedPattern>io.ray.shaded.com.google.thirdparty</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,441 @@
|
||||
package io.ray.runtime;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import io.ray.api.ActorHandle;
|
||||
import io.ray.api.BaseActorHandle;
|
||||
import io.ray.api.CppActorHandle;
|
||||
import io.ray.api.ObjectRef;
|
||||
import io.ray.api.PyActorHandle;
|
||||
import io.ray.api.WaitResult;
|
||||
import io.ray.api.concurrencygroup.ConcurrencyGroup;
|
||||
import io.ray.api.exception.RuntimeEnvException;
|
||||
import io.ray.api.function.CppActorClass;
|
||||
import io.ray.api.function.CppActorMethod;
|
||||
import io.ray.api.function.CppFunction;
|
||||
import io.ray.api.function.PyActorClass;
|
||||
import io.ray.api.function.PyActorMethod;
|
||||
import io.ray.api.function.PyFunction;
|
||||
import io.ray.api.function.RayFunc;
|
||||
import io.ray.api.function.RayFuncR;
|
||||
import io.ray.api.id.ActorId;
|
||||
import io.ray.api.id.ObjectId;
|
||||
import io.ray.api.id.PlacementGroupId;
|
||||
import io.ray.api.options.ActorCreationOptions;
|
||||
import io.ray.api.options.CallOptions;
|
||||
import io.ray.api.options.PlacementGroupCreationOptions;
|
||||
import io.ray.api.parallelactor.ParallelActorContext;
|
||||
import io.ray.api.placementgroup.PlacementGroup;
|
||||
import io.ray.api.runtime.RayRuntime;
|
||||
import io.ray.api.runtimecontext.RuntimeContext;
|
||||
import io.ray.api.runtimeenv.RuntimeEnv;
|
||||
import io.ray.runtime.config.RayConfig;
|
||||
import io.ray.runtime.config.RunMode;
|
||||
import io.ray.runtime.context.RuntimeContextImpl;
|
||||
import io.ray.runtime.context.WorkerContext;
|
||||
import io.ray.runtime.functionmanager.CppFunctionDescriptor;
|
||||
import io.ray.runtime.functionmanager.FunctionDescriptor;
|
||||
import io.ray.runtime.functionmanager.FunctionManager;
|
||||
import io.ray.runtime.functionmanager.PyFunctionDescriptor;
|
||||
import io.ray.runtime.functionmanager.RayFunction;
|
||||
import io.ray.runtime.gcs.GcsClient;
|
||||
import io.ray.runtime.generated.Common.Language;
|
||||
import io.ray.runtime.object.ObjectRefImpl;
|
||||
import io.ray.runtime.object.ObjectStore;
|
||||
import io.ray.runtime.runtimeenv.RuntimeEnvImpl;
|
||||
import io.ray.runtime.task.ArgumentsBuilder;
|
||||
import io.ray.runtime.task.FunctionArg;
|
||||
import io.ray.runtime.task.TaskExecutor;
|
||||
import io.ray.runtime.task.TaskSubmitter;
|
||||
import io.ray.runtime.util.ConcurrencyGroupUtils;
|
||||
import io.ray.runtime.utils.parallelactor.ParallelActorContextImpl;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Core functionality to implement Ray APIs. */
|
||||
public abstract class AbstractRayRuntime implements RayRuntime {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRayRuntime.class);
|
||||
public static final String PYTHON_INIT_METHOD_NAME = "__init__";
|
||||
protected RayConfig rayConfig;
|
||||
protected TaskExecutor taskExecutor;
|
||||
protected FunctionManager functionManager;
|
||||
protected RuntimeContext runtimeContext;
|
||||
|
||||
protected ObjectStore objectStore;
|
||||
protected TaskSubmitter taskSubmitter;
|
||||
protected WorkerContext workerContext;
|
||||
|
||||
private static ParallelActorContextImpl parallelActorContextImpl = new ParallelActorContextImpl();
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
public AbstractRayRuntime(RayConfig rayConfig) {
|
||||
this.rayConfig = rayConfig;
|
||||
runtimeContext = new RuntimeContextImpl(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ObjectRef<T> put(T obj) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Putting Object in Task {}.", workerContext.getCurrentTaskId());
|
||||
}
|
||||
ObjectId objectId = objectStore.put(obj);
|
||||
return new ObjectRefImpl<T>(
|
||||
objectId,
|
||||
(Class<T>) (obj == null ? Object.class : obj.getClass()),
|
||||
/*skipAddingLocalRef=*/ true);
|
||||
}
|
||||
|
||||
public abstract GcsClient getGcsClient();
|
||||
|
||||
public abstract void start();
|
||||
|
||||
public abstract void run();
|
||||
|
||||
@Override
|
||||
public <T> T get(ObjectRef<T> objectRef) throws RuntimeException {
|
||||
return get(objectRef, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T get(ObjectRef<T> objectRef, long timeoutMs) throws RuntimeException {
|
||||
List<T> ret = get(ImmutableList.of(objectRef), timeoutMs);
|
||||
return ret.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> get(List<ObjectRef<T>> objectRefs) {
|
||||
return get(objectRefs, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> get(List<ObjectRef<T>> objectRefs, long timeoutMs) {
|
||||
List<ObjectId> objectIds = new ArrayList<>();
|
||||
Class<T> objectType = null;
|
||||
for (ObjectRef<T> o : objectRefs) {
|
||||
ObjectRefImpl<T> objectRefImpl = (ObjectRefImpl<T>) o;
|
||||
objectIds.add(objectRefImpl.getId());
|
||||
objectType = objectRefImpl.getType();
|
||||
}
|
||||
LOGGER.debug("Getting Objects {}.", objectIds);
|
||||
return objectStore.get(objectIds, objectType, timeoutMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free(List<ObjectRef<?>> objectRefs, boolean localOnly) {
|
||||
List<ObjectId> objectIds =
|
||||
objectRefs.stream()
|
||||
.map(ref -> ((ObjectRefImpl<?>) ref).getId())
|
||||
.collect(Collectors.toList());
|
||||
LOGGER.debug("Freeing Objects {}, localOnly = {}.", objectIds, localOnly);
|
||||
objectStore.delete(objectIds, localOnly);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> WaitResult<T> wait(
|
||||
List<ObjectRef<T>> waitList, int numReturns, int timeoutMs, boolean fetchLocal) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(
|
||||
"Waiting Objects {} with minimum number {} within {} ms.",
|
||||
waitList,
|
||||
numReturns,
|
||||
timeoutMs);
|
||||
}
|
||||
return objectStore.wait(waitList, numReturns, timeoutMs, fetchLocal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectRef call(RayFunc func, Object[] args, CallOptions options) {
|
||||
RayFunction rayFunction = functionManager.getFunction(func);
|
||||
FunctionDescriptor functionDescriptor = rayFunction.functionDescriptor;
|
||||
Optional<Class<?>> returnType = rayFunction.getReturnType();
|
||||
return callNormalFunction(functionDescriptor, args, returnType, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectRef call(PyFunction pyFunction, Object[] args, CallOptions options) {
|
||||
PyFunctionDescriptor functionDescriptor =
|
||||
new PyFunctionDescriptor(pyFunction.moduleName, "", pyFunction.functionName);
|
||||
return callNormalFunction(
|
||||
functionDescriptor, args, /*returnType=*/ Optional.of(pyFunction.returnType), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectRef call(CppFunction cppFunction, Object[] args, CallOptions options) {
|
||||
CppFunctionDescriptor functionDescriptor =
|
||||
new CppFunctionDescriptor(cppFunction.functionName, "JAVA", "");
|
||||
return callNormalFunction(
|
||||
functionDescriptor, args, /*returnType=*/ Optional.of(cppFunction.returnType), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectRef callActor(
|
||||
ActorHandle<?> actor, RayFunc func, Object[] args, CallOptions options) {
|
||||
RayFunction rayFunction = functionManager.getFunction(func);
|
||||
FunctionDescriptor functionDescriptor = rayFunction.functionDescriptor;
|
||||
Optional<Class<?>> returnType = rayFunction.getReturnType();
|
||||
return callActorFunction(actor, functionDescriptor, args, returnType, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectRef callActor(PyActorHandle pyActor, PyActorMethod pyActorMethod, Object... args) {
|
||||
PyFunctionDescriptor functionDescriptor =
|
||||
new PyFunctionDescriptor(
|
||||
pyActor.getModuleName(), pyActor.getClassName(), pyActorMethod.methodName);
|
||||
return callActorFunction(
|
||||
pyActor,
|
||||
functionDescriptor,
|
||||
args,
|
||||
/*returnType=*/ Optional.of(pyActorMethod.returnType),
|
||||
new CallOptions.Builder().build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectRef callActor(
|
||||
CppActorHandle cppActor, CppActorMethod cppActorMethod, Object[] args) {
|
||||
CppFunctionDescriptor functionDescriptor =
|
||||
new CppFunctionDescriptor(cppActorMethod.methodName, "JAVA", cppActor.getClassName());
|
||||
return callActorFunction(
|
||||
cppActor,
|
||||
functionDescriptor,
|
||||
args,
|
||||
/*returnType=*/ Optional.of(cppActorMethod.returnType),
|
||||
new CallOptions.Builder().build());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> ActorHandle<T> createActor(
|
||||
RayFunc actorFactoryFunc, Object[] args, ActorCreationOptions options) {
|
||||
FunctionDescriptor functionDescriptor =
|
||||
functionManager.getFunction(actorFactoryFunc).functionDescriptor;
|
||||
return (ActorHandle<T>) createActorImpl(functionDescriptor, args, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PyActorHandle createActor(
|
||||
PyActorClass pyActorClass, Object[] args, ActorCreationOptions options) {
|
||||
PyFunctionDescriptor functionDescriptor =
|
||||
new PyFunctionDescriptor(
|
||||
pyActorClass.moduleName, pyActorClass.className, PYTHON_INIT_METHOD_NAME);
|
||||
return (PyActorHandle) createActorImpl(functionDescriptor, args, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CppActorHandle createActor(
|
||||
CppActorClass cppActorClass, Object[] args, ActorCreationOptions options) {
|
||||
CppFunctionDescriptor functionDescriptor =
|
||||
new CppFunctionDescriptor(
|
||||
cppActorClass.createFunctionName, "JAVA", cppActorClass.className);
|
||||
return (CppActorHandle) createActorImpl(functionDescriptor, args, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlacementGroup createPlacementGroup(PlacementGroupCreationOptions creationOptions) {
|
||||
Preconditions.checkNotNull(
|
||||
creationOptions,
|
||||
"`PlacementGroupCreationOptions` must be specified when creating a new placement group.");
|
||||
return taskSubmitter.createPlacementGroup(creationOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePlacementGroup(PlacementGroupId id) {
|
||||
taskSubmitter.removePlacementGroup(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlacementGroup getPlacementGroup(PlacementGroupId id) {
|
||||
return getGcsClient().getPlacementGroupInfo(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlacementGroup getPlacementGroup(String name, String namespace) {
|
||||
return namespace == null
|
||||
? getGcsClient().getPlacementGroupInfo(name, runtimeContext.getNamespace())
|
||||
: getGcsClient().getPlacementGroupInfo(name, namespace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PlacementGroup> getAllPlacementGroups() {
|
||||
return getGcsClient().getAllPlacementGroupInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean waitPlacementGroupReady(PlacementGroupId id, int timeoutSeconds) {
|
||||
return taskSubmitter.waitPlacementGroupReady(id, timeoutSeconds);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T extends BaseActorHandle> T getActorHandle(ActorId actorId) {
|
||||
return (T) taskSubmitter.getActor(actorId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConcurrencyGroup createConcurrencyGroup(
|
||||
String name, int maxConcurrency, List<RayFunc> funcs) {
|
||||
return new ConcurrencyGroupImpl(name, maxConcurrency, funcs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConcurrencyGroup> extractConcurrencyGroups(RayFuncR<?> actorConstructorLambda) {
|
||||
return ConcurrencyGroupUtils.extractConcurrencyGroupsByAnnotations(actorConstructorLambda);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParallelActorContext getParallelActorContext() {
|
||||
return parallelActorContextImpl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeEnv createRuntimeEnv() {
|
||||
return new RuntimeEnvImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeEnv deserializeRuntimeEnv(String serializedRuntimeEnv) throws RuntimeEnvException {
|
||||
RuntimeEnvImpl runtimeEnv = new RuntimeEnvImpl();
|
||||
try {
|
||||
runtimeEnv.runtimeEnvs = (ObjectNode) MAPPER.readTree(serializedRuntimeEnv);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return runtimeEnv;
|
||||
}
|
||||
|
||||
private ObjectRef callNormalFunction(
|
||||
FunctionDescriptor functionDescriptor,
|
||||
Object[] args,
|
||||
Optional<Class<?>> returnType,
|
||||
CallOptions options) {
|
||||
int numReturns = returnType.isPresent() ? 1 : 0;
|
||||
List<FunctionArg> functionArgs = ArgumentsBuilder.wrap(args, functionDescriptor.getLanguage());
|
||||
if (options == null) {
|
||||
options = new CallOptions.Builder().build();
|
||||
}
|
||||
|
||||
ObjectRefImpl<?> impl = new ObjectRefImpl<>();
|
||||
/// Mapping the object id to the object ref.
|
||||
List<ObjectId> preparedReturnIds = getCurrentReturnIds(numReturns, ActorId.NIL);
|
||||
if (rayConfig.runMode == RunMode.CLUSTER && numReturns > 0) {
|
||||
ObjectRefImpl.registerObjectRefImpl(preparedReturnIds.get(0), impl);
|
||||
}
|
||||
|
||||
List<ObjectId> returnIds =
|
||||
taskSubmitter.submitTask(functionDescriptor, functionArgs, numReturns, options);
|
||||
Preconditions.checkState(returnIds.size() == numReturns);
|
||||
validatePreparedReturnIds(preparedReturnIds, returnIds);
|
||||
if (returnIds.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
impl.init(returnIds.get(0), returnType.get(), /*skipAddingLocalRef=*/ true);
|
||||
return impl;
|
||||
}
|
||||
}
|
||||
|
||||
private ObjectRef callActorFunction(
|
||||
BaseActorHandle rayActor,
|
||||
FunctionDescriptor functionDescriptor,
|
||||
Object[] args,
|
||||
Optional<Class<?>> returnType,
|
||||
CallOptions options) {
|
||||
int numReturns = returnType.isPresent() ? 1 : 0;
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Submitting Actor Task {}.", functionDescriptor);
|
||||
}
|
||||
List<FunctionArg> functionArgs = ArgumentsBuilder.wrap(args, functionDescriptor.getLanguage());
|
||||
|
||||
ObjectRefImpl<?> impl = new ObjectRefImpl<>();
|
||||
/// Mapping the object id to the object ref.
|
||||
List<ObjectId> preparedReturnIds = getCurrentReturnIds(numReturns, rayActor.getId());
|
||||
if (rayConfig.runMode == RunMode.CLUSTER && numReturns > 0) {
|
||||
ObjectRefImpl.registerObjectRefImpl(preparedReturnIds.get(0), impl);
|
||||
}
|
||||
List<ObjectId> returnIds =
|
||||
taskSubmitter.submitActorTask(
|
||||
rayActor, functionDescriptor, functionArgs, numReturns, options);
|
||||
Preconditions.checkState(returnIds.size() == numReturns);
|
||||
if (returnIds.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
validatePreparedReturnIds(preparedReturnIds, returnIds);
|
||||
impl.init(returnIds.get(0), returnType.get(), /*skipAddingLocalRef=*/ true);
|
||||
return impl;
|
||||
}
|
||||
}
|
||||
|
||||
private BaseActorHandle createActorImpl(
|
||||
FunctionDescriptor functionDescriptor, Object[] args, ActorCreationOptions options) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
if (options == null) {
|
||||
LOGGER.debug("Creating Actor {} with default options.", functionDescriptor);
|
||||
} else {
|
||||
LOGGER.debug(
|
||||
"Creating Actor {}, jvmOptions = {}.", functionDescriptor, options.getJvmOptions());
|
||||
}
|
||||
}
|
||||
if (rayConfig.runMode == RunMode.LOCAL && functionDescriptor.getLanguage() != Language.JAVA) {
|
||||
throw new IllegalArgumentException(
|
||||
"Ray doesn't support cross-language invocation in local mode.");
|
||||
}
|
||||
|
||||
List<FunctionArg> functionArgs = ArgumentsBuilder.wrap(args, functionDescriptor.getLanguage());
|
||||
if (functionDescriptor.getLanguage() != Language.JAVA && options != null) {
|
||||
Preconditions.checkState(
|
||||
options.getJvmOptions() == null || options.getJvmOptions().isEmpty());
|
||||
}
|
||||
|
||||
BaseActorHandle actor = taskSubmitter.createActor(functionDescriptor, functionArgs, options);
|
||||
return actor;
|
||||
}
|
||||
|
||||
abstract List<ObjectId> getCurrentReturnIds(int numReturns, ActorId actorId);
|
||||
|
||||
public WorkerContext getWorkerContext() {
|
||||
return workerContext;
|
||||
}
|
||||
|
||||
public ObjectStore getObjectStore() {
|
||||
return objectStore;
|
||||
}
|
||||
|
||||
public TaskExecutor getTaskExecutor() {
|
||||
return taskExecutor;
|
||||
}
|
||||
|
||||
public FunctionManager getFunctionManager() {
|
||||
return functionManager;
|
||||
}
|
||||
|
||||
public RayConfig getRayConfig() {
|
||||
return rayConfig;
|
||||
}
|
||||
|
||||
public RuntimeContext getRuntimeContext() {
|
||||
return runtimeContext;
|
||||
}
|
||||
|
||||
/// A helper to validate if the prepared return ids is as expected.
|
||||
void validatePreparedReturnIds(List<ObjectId> preparedReturnIds, List<ObjectId> realReturnIds) {
|
||||
if (rayConfig.runMode == RunMode.CLUSTER) {
|
||||
Preconditions.checkState(realReturnIds.size() == preparedReturnIds.size());
|
||||
for (int i = 0; i < preparedReturnIds.size(); ++i) {
|
||||
ObjectId prepared = preparedReturnIds.get(i);
|
||||
Object real = realReturnIds.get(i);
|
||||
Preconditions.checkState(
|
||||
prepared.equals(real),
|
||||
"The prepared object id {} is not equal to the real return id {}",
|
||||
prepared,
|
||||
real);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package io.ray.runtime;
|
||||
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.api.concurrencygroup.ConcurrencyGroup;
|
||||
import io.ray.api.function.RayFunc;
|
||||
import io.ray.runtime.functionmanager.FunctionDescriptor;
|
||||
import io.ray.runtime.functionmanager.JavaFunctionDescriptor;
|
||||
import io.ray.runtime.functionmanager.RayFunction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ConcurrencyGroupImpl implements ConcurrencyGroup {
|
||||
|
||||
private String name;
|
||||
|
||||
private int maxConcurrency;
|
||||
|
||||
private List<FunctionDescriptor> functionDescriptors = new ArrayList<>();
|
||||
|
||||
public ConcurrencyGroupImpl(String name, int maxConcurrency, List<RayFunc> funcs) {
|
||||
this.name = name;
|
||||
this.maxConcurrency = maxConcurrency;
|
||||
// Convert methods to function descriptors for actor method concurrency groups.
|
||||
funcs.forEach(
|
||||
func -> {
|
||||
RayFunction rayFunc =
|
||||
((AbstractRayRuntime) Ray.internal()).getFunctionManager().getFunction(func);
|
||||
functionDescriptors.add(rayFunc.getFunctionDescriptor());
|
||||
});
|
||||
}
|
||||
|
||||
public ConcurrencyGroupImpl(String name, int maxConcurrency) {
|
||||
this.name = name;
|
||||
this.maxConcurrency = maxConcurrency;
|
||||
}
|
||||
|
||||
public void addJavaFunctionDescriptor(JavaFunctionDescriptor javaFunctionDescriptor) {
|
||||
functionDescriptors.add(javaFunctionDescriptor);
|
||||
}
|
||||
|
||||
public int getMaxConcurrency() {
|
||||
return maxConcurrency;
|
||||
}
|
||||
|
||||
public List<FunctionDescriptor> getFunctionDescriptors() {
|
||||
return functionDescriptors;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package io.ray.runtime;
|
||||
|
||||
import io.ray.api.runtime.RayRuntime;
|
||||
import io.ray.api.runtime.RayRuntimeFactory;
|
||||
import io.ray.runtime.config.RayConfig;
|
||||
import io.ray.runtime.config.RunMode;
|
||||
import io.ray.runtime.generated.Common.WorkerType;
|
||||
import io.ray.runtime.util.LoggingUtil;
|
||||
import io.ray.runtime.util.SystemConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** The default Ray runtime factory. It produces an instance of RayRuntime. */
|
||||
public class DefaultRayRuntimeFactory implements RayRuntimeFactory {
|
||||
|
||||
@Override
|
||||
public RayRuntime createRayRuntime() {
|
||||
RayConfig rayConfig = RayConfig.create();
|
||||
LoggingUtil.setupLogging(rayConfig);
|
||||
SystemConfig.setup(rayConfig);
|
||||
Logger logger = LoggerFactory.getLogger(DefaultRayRuntimeFactory.class);
|
||||
|
||||
if (rayConfig.workerMode == WorkerType.WORKER) {
|
||||
// Handle the uncaught exceptions thrown from user-spawned threads.
|
||||
Thread.setDefaultUncaughtExceptionHandler(
|
||||
(Thread t, Throwable e) -> {
|
||||
logger.error(String.format("Uncaught worker exception in thread %s", t), e);
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
logger.debug("Initializing runtime with config: {}", rayConfig);
|
||||
AbstractRayRuntime runtime =
|
||||
rayConfig.runMode == RunMode.LOCAL
|
||||
? new RayDevRuntime(rayConfig)
|
||||
: new RayNativeRuntime(rayConfig);
|
||||
runtime.start();
|
||||
return runtime;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to initialize ray runtime, with config " + rayConfig, e);
|
||||
throw new RuntimeException("Failed to initialize ray runtime", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package io.ray.runtime;
|
||||
|
||||
import io.ray.api.BaseActorHandle;
|
||||
import io.ray.api.id.ActorId;
|
||||
import io.ray.api.id.JobId;
|
||||
import io.ray.api.id.ObjectId;
|
||||
import io.ray.api.id.PlacementGroupId;
|
||||
import io.ray.api.id.UniqueId;
|
||||
import io.ray.api.placementgroup.PlacementGroup;
|
||||
import io.ray.api.runtimecontext.ResourceValue;
|
||||
import io.ray.runtime.config.RayConfig;
|
||||
import io.ray.runtime.context.LocalModeWorkerContext;
|
||||
import io.ray.runtime.functionmanager.FunctionManager;
|
||||
import io.ray.runtime.gcs.GcsClient;
|
||||
import io.ray.runtime.object.LocalModeObjectStore;
|
||||
import io.ray.runtime.task.LocalModeTaskExecutor;
|
||||
import io.ray.runtime.task.LocalModeTaskSubmitter;
|
||||
import io.ray.runtime.util.BinaryFileUtil;
|
||||
import io.ray.runtime.util.JniUtils;
|
||||
import io.ray.runtime.util.SystemUtil;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class RayDevRuntime extends AbstractRayRuntime {
|
||||
|
||||
private AtomicInteger jobCounter = new AtomicInteger(0);
|
||||
|
||||
public RayDevRuntime(RayConfig rayConfig) {
|
||||
super(rayConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (rayConfig.getJobId().isNil()) {
|
||||
rayConfig.setJobId(nextJobId());
|
||||
}
|
||||
|
||||
updateSessionDir(rayConfig);
|
||||
JniUtils.loadLibrary(rayConfig.sessionDir, BinaryFileUtil.CORE_WORKER_JAVA_LIBRARY, true);
|
||||
|
||||
taskExecutor = new LocalModeTaskExecutor(this);
|
||||
workerContext = new LocalModeWorkerContext(rayConfig.getJobId());
|
||||
objectStore = new LocalModeObjectStore(workerContext);
|
||||
functionManager = new FunctionManager(rayConfig.codeSearchPath);
|
||||
taskSubmitter =
|
||||
new LocalModeTaskSubmitter(this, taskExecutor, (LocalModeObjectStore) objectStore);
|
||||
((LocalModeObjectStore) objectStore)
|
||||
.addObjectPutCallback(
|
||||
objectId -> {
|
||||
if (taskSubmitter != null) {
|
||||
((LocalModeTaskSubmitter) taskSubmitter).onObjectPut(objectId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
if (taskSubmitter != null) {
|
||||
((LocalModeTaskSubmitter) taskSubmitter).shutdown();
|
||||
taskSubmitter = null;
|
||||
}
|
||||
taskExecutor = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void killActor(BaseActorHandle actor, boolean noRestart) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T extends BaseActorHandle> Optional<T> getActor(String name, String namespace) {
|
||||
return (Optional<T>) ((LocalModeTaskSubmitter) taskSubmitter).getActor(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GcsClient getGcsClient() {
|
||||
throw new UnsupportedOperationException("Ray doesn't have gcs client in local mode.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<ResourceValue>> getAvailableResourceIds() {
|
||||
throw new UnsupportedOperationException("Ray doesn't support get resources ids in local mode.");
|
||||
}
|
||||
|
||||
@Override
|
||||
List<ObjectId> getCurrentReturnIds(int numReturns, ActorId actorId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlacementGroup getPlacementGroup(PlacementGroupId id) {
|
||||
// @TODO(clay4444): We need a LocalGcsClient before implements this.
|
||||
throw new UnsupportedOperationException(
|
||||
"Ray doesn't support placement group operations in local mode.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PlacementGroup> getAllPlacementGroups() {
|
||||
// @TODO(clay4444): We need a LocalGcsClient before implements this.
|
||||
throw new UnsupportedOperationException(
|
||||
"Ray doesn't support placement group operations in local mode.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UniqueId getCurrentNodeId() {
|
||||
throw new UnsupportedOperationException("Ray doesn't support it in local mode.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitActor() {}
|
||||
|
||||
private JobId nextJobId() {
|
||||
return JobId.fromInt(jobCounter.getAndIncrement());
|
||||
}
|
||||
|
||||
private static void updateSessionDir(RayConfig rayConfig) {
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd_hh-mm-ss-ms");
|
||||
Date date = new Date();
|
||||
String sessionDir =
|
||||
String.format("/tmp/ray/session_local_mode_%s_%d", format.format(date), SystemUtil.pid());
|
||||
rayConfig.setSessionDir(sessionDir);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package io.ray.runtime;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.api.BaseActorHandle;
|
||||
import io.ray.api.exception.RayIntentionalSystemExitException;
|
||||
import io.ray.api.id.ActorId;
|
||||
import io.ray.api.id.JobId;
|
||||
import io.ray.api.id.ObjectId;
|
||||
import io.ray.api.id.UniqueId;
|
||||
import io.ray.api.options.ActorLifetime;
|
||||
import io.ray.api.runtimecontext.ResourceValue;
|
||||
import io.ray.runtime.config.RayConfig;
|
||||
import io.ray.runtime.context.NativeWorkerContext;
|
||||
import io.ray.runtime.functionmanager.FunctionManager;
|
||||
import io.ray.runtime.gcs.GcsClient;
|
||||
import io.ray.runtime.gcs.GcsClientOptions;
|
||||
import io.ray.runtime.generated.Common.JobConfig;
|
||||
import io.ray.runtime.generated.Common.WorkerType;
|
||||
import io.ray.runtime.generated.Gcs.GcsNodeInfo;
|
||||
import io.ray.runtime.object.NativeObjectStore;
|
||||
import io.ray.runtime.runner.RunManager;
|
||||
import io.ray.runtime.task.NativeTaskExecutor;
|
||||
import io.ray.runtime.task.NativeTaskSubmitter;
|
||||
import io.ray.runtime.task.TaskExecutor;
|
||||
import io.ray.runtime.util.BinaryFileUtil;
|
||||
import io.ray.runtime.util.JniUtils;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Native runtime for cluster mode. */
|
||||
public final class RayNativeRuntime extends AbstractRayRuntime {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RayNativeRuntime.class);
|
||||
|
||||
private boolean startRayHead = false;
|
||||
|
||||
private GcsClient gcsClient;
|
||||
|
||||
/**
|
||||
* In Java, GC runs in a standalone thread, and we can't control the exact timing of garbage
|
||||
* collection. By using this lock, when {@link NativeObjectStore#nativeRemoveLocalReference} is
|
||||
* executing, the core worker will not be shut down, therefore it guarantees some kind of
|
||||
* thread-safety. Note that this guarantee only works for driver.
|
||||
*/
|
||||
private final ReadWriteLock shutdownLock = new ReentrantReadWriteLock();
|
||||
|
||||
public RayNativeRuntime(RayConfig rayConfig) {
|
||||
super(rayConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
try {
|
||||
if (rayConfig.workerMode == WorkerType.DRIVER && rayConfig.getBootstrapAddress() == null) {
|
||||
// Set it to true before `RunManager.startRayHead` so `Ray.shutdown()` can still kill
|
||||
// Ray processes even if `Ray.init()` failed.
|
||||
startRayHead = true;
|
||||
RunManager.startRayHead(rayConfig);
|
||||
}
|
||||
Preconditions.checkNotNull(rayConfig.getBootstrapAddress());
|
||||
|
||||
if (rayConfig.workerMode == WorkerType.DRIVER) {
|
||||
// In order to remove redis dependency in Java lang, we use a temp dir to load library
|
||||
// instead of getting session dir from redis.
|
||||
String tmpDir = "/tmp/ray/".concat(String.valueOf(System.currentTimeMillis()));
|
||||
JniUtils.loadLibrary(tmpDir, BinaryFileUtil.CORE_WORKER_JAVA_LIBRARY, true);
|
||||
|
||||
GcsNodeInfo nodeInfo = getGcsClient().getNodeToConnectForDriver(rayConfig.nodeIp);
|
||||
|
||||
// Update session dir.
|
||||
final String sessionDir = nodeInfo.getSessionDir();
|
||||
Preconditions.checkNotNull(sessionDir, "Session dir not found in node info");
|
||||
rayConfig.setSessionDir(sessionDir);
|
||||
Preconditions.checkNotNull(rayConfig.sessionDir);
|
||||
|
||||
rayConfig.rayletSocketName = nodeInfo.getRayletSocketName();
|
||||
rayConfig.objectStoreSocketName = nodeInfo.getObjectStoreSocketName();
|
||||
rayConfig.nodeManagerPort = nodeInfo.getNodeManagerPort();
|
||||
} else {
|
||||
// Expose ray ABI symbols which may be depended by other shared
|
||||
// libraries such as libstreaming_java.so.
|
||||
// See BUILD.bazel:libcore_worker_library_java.so
|
||||
Preconditions.checkNotNull(rayConfig.sessionDir);
|
||||
JniUtils.loadLibrary(rayConfig.sessionDir, BinaryFileUtil.CORE_WORKER_JAVA_LIBRARY, true);
|
||||
}
|
||||
|
||||
if (rayConfig.workerMode == WorkerType.DRIVER && rayConfig.getJobId() == JobId.NIL) {
|
||||
rayConfig.setJobId(getGcsClient().nextJobId());
|
||||
}
|
||||
// Make sure the job id has been set already.
|
||||
functionManager = new FunctionManager(rayConfig.codeSearchPath);
|
||||
|
||||
byte[] serializedJobConfig = null;
|
||||
if (rayConfig.workerMode == WorkerType.DRIVER) {
|
||||
JobConfig.Builder jobConfigBuilder =
|
||||
JobConfig.newBuilder()
|
||||
.addAllJvmOptions(rayConfig.jvmOptionsForJavaWorker)
|
||||
.addAllCodeSearchPath(rayConfig.codeSearchPath)
|
||||
.setRayNamespace(rayConfig.namespace);
|
||||
jobConfigBuilder.setRuntimeEnvInfo(rayConfig.runtimeEnvImpl.GenerateRuntimeEnvInfo());
|
||||
jobConfigBuilder.setDefaultActorLifetime(
|
||||
rayConfig.defaultActorLifetime == ActorLifetime.DETACHED
|
||||
? JobConfig.ActorLifetime.DETACHED
|
||||
: JobConfig.ActorLifetime.NON_DETACHED);
|
||||
serializedJobConfig = jobConfigBuilder.build().toByteArray();
|
||||
}
|
||||
|
||||
nativeInitialize(
|
||||
rayConfig.workerMode.getNumber(),
|
||||
rayConfig.nodeIp,
|
||||
rayConfig.getNodeManagerPort(),
|
||||
rayConfig.workerMode == WorkerType.DRIVER ? System.getProperty("user.dir") : "",
|
||||
rayConfig.objectStoreSocketName,
|
||||
rayConfig.rayletSocketName,
|
||||
(rayConfig.workerMode == WorkerType.DRIVER ? rayConfig.getJobId() : JobId.NIL).getBytes(),
|
||||
new GcsClientOptions(rayConfig),
|
||||
rayConfig.logDir,
|
||||
serializedJobConfig,
|
||||
rayConfig.getWorkerId().getBytes(),
|
||||
rayConfig.runtimeEnvHash);
|
||||
|
||||
taskExecutor = new NativeTaskExecutor(this);
|
||||
workerContext = new NativeWorkerContext();
|
||||
objectStore = new NativeObjectStore(workerContext, shutdownLock);
|
||||
taskSubmitter = new NativeTaskSubmitter();
|
||||
|
||||
LOGGER.debug(
|
||||
"RayNativeRuntime started with store {}, raylet {}",
|
||||
rayConfig.objectStoreSocketName,
|
||||
rayConfig.rayletSocketName);
|
||||
} catch (Exception e) {
|
||||
if (startRayHead) {
|
||||
try {
|
||||
RunManager.stopRay();
|
||||
} catch (Exception e2) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
// `shutdown` won't be called concurrently, but the lock is also used in `NativeObjectStore`.
|
||||
// When an object is garbage collected, the object will be unregistered from core worker.
|
||||
// Since GC runs in a separate thread, we need to make sure that core worker is available
|
||||
// when `NativeObjectStore` is accessing core worker in the GC thread.
|
||||
Lock writeLock = shutdownLock.writeLock();
|
||||
writeLock.lock();
|
||||
try {
|
||||
if (rayConfig.workerMode == WorkerType.DRIVER) {
|
||||
nativeShutdown();
|
||||
if (startRayHead) {
|
||||
startRayHead = false;
|
||||
RunManager.stopRay();
|
||||
}
|
||||
}
|
||||
if (null != gcsClient) {
|
||||
gcsClient.destroy();
|
||||
gcsClient = null;
|
||||
}
|
||||
LOGGER.debug("RayNativeRuntime shutdown");
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T extends BaseActorHandle> Optional<T> getActor(String name, String namespace) {
|
||||
if (name.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
byte[] actorIdBytes = nativeGetActorIdOfNamedActor(name, namespace);
|
||||
ActorId actorId = ActorId.fromBytes(actorIdBytes);
|
||||
if (actorId.isNil()) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of((T) getActorHandle(actorId));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void killActor(BaseActorHandle actor, boolean noRestart) {
|
||||
nativeKillActor(actor.getId().getBytes(), noRestart);
|
||||
}
|
||||
|
||||
@Override
|
||||
List<ObjectId> getCurrentReturnIds(int numReturns, ActorId actorId) {
|
||||
List<byte[]> ret = nativeGetCurrentReturnIds(numReturns, actorId.getBytes());
|
||||
return ret.stream().map(ObjectId::new).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitActor() {
|
||||
if (rayConfig.workerMode != WorkerType.WORKER || runtimeContext.getCurrentActorId().isNil()) {
|
||||
throw new RuntimeException("This shouldn't be called on a non-actor worker.");
|
||||
}
|
||||
LOGGER.info("Actor {} is exiting.", runtimeContext.getCurrentActorId());
|
||||
throw new RayIntentionalSystemExitException(
|
||||
String.format("Actor %s is exiting.", runtimeContext.getCurrentActorId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public GcsClient getGcsClient() {
|
||||
if (gcsClient == null) {
|
||||
synchronized (this) {
|
||||
if (gcsClient == null) {
|
||||
gcsClient =
|
||||
new GcsClient(
|
||||
rayConfig.getBootstrapAddress(),
|
||||
rayConfig.redisUsername,
|
||||
rayConfig.redisPassword);
|
||||
}
|
||||
}
|
||||
}
|
||||
return gcsClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Preconditions.checkState(rayConfig.workerMode == WorkerType.WORKER);
|
||||
nativeRunTaskExecutor(taskExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<ResourceValue>> getAvailableResourceIds() {
|
||||
return nativeGetResourceIds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return nativeGetNamespace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UniqueId getCurrentNodeId() {
|
||||
return UniqueId.fromBytes(nativeGetCurrentNodeId());
|
||||
}
|
||||
|
||||
private static native void nativeInitialize(
|
||||
int workerMode,
|
||||
String ndoeIpAddress,
|
||||
int nodeManagerPort,
|
||||
String driverName,
|
||||
String storeSocket,
|
||||
String rayletSocket,
|
||||
byte[] jobId,
|
||||
GcsClientOptions gcsClientOptions,
|
||||
String logDir,
|
||||
byte[] serializedJobConfig,
|
||||
byte[] workerId,
|
||||
int runtimeEnvHash);
|
||||
|
||||
private static native void nativeRunTaskExecutor(TaskExecutor taskExecutor);
|
||||
|
||||
private static native void nativeShutdown();
|
||||
|
||||
private static native void nativeKillActor(byte[] actorId, boolean noRestart);
|
||||
|
||||
private static native byte[] nativeGetActorIdOfNamedActor(String actorName, String namespace);
|
||||
|
||||
private static native Map<String, List<ResourceValue>> nativeGetResourceIds();
|
||||
|
||||
private static native String nativeGetNamespace();
|
||||
|
||||
private static native List<byte[]> nativeGetCurrentReturnIds(int numReturns, byte[] actorId);
|
||||
|
||||
private static native byte[] nativeGetCurrentNodeId();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package io.ray.runtime.actor;
|
||||
|
||||
import io.ray.api.ActorHandle;
|
||||
import io.ray.api.id.ActorId;
|
||||
import io.ray.api.id.ObjectId;
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/** Implementation of actor handle for local mode. */
|
||||
public class LocalModeActorHandle implements ActorHandle, Externalizable {
|
||||
|
||||
private ActorId actorId;
|
||||
|
||||
private AtomicReference<ObjectId> previousActorTaskDummyObjectId = new AtomicReference<>();
|
||||
|
||||
public LocalModeActorHandle(ActorId actorId, ObjectId previousActorTaskDummyObjectId) {
|
||||
this.actorId = actorId;
|
||||
this.previousActorTaskDummyObjectId.set(previousActorTaskDummyObjectId);
|
||||
}
|
||||
|
||||
/** Required by FST. */
|
||||
public LocalModeActorHandle() {}
|
||||
|
||||
@Override
|
||||
public ActorId getId() {
|
||||
return actorId;
|
||||
}
|
||||
|
||||
public ObjectId exchangePreviousActorTaskDummyObjectId(ObjectId previousActorTaskDummyObjectId) {
|
||||
return this.previousActorTaskDummyObjectId.getAndSet(previousActorTaskDummyObjectId);
|
||||
}
|
||||
|
||||
public LocalModeActorHandle copy() {
|
||||
return new LocalModeActorHandle(this.actorId, this.previousActorTaskDummyObjectId.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void writeExternal(ObjectOutput out) throws IOException {
|
||||
out.writeObject(actorId);
|
||||
out.writeObject(previousActorTaskDummyObjectId.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
actorId = (ActorId) in.readObject();
|
||||
previousActorTaskDummyObjectId.set((ObjectId) in.readObject());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package io.ray.runtime.actor;
|
||||
|
||||
import com.google.common.base.FinalizableReferenceQueue;
|
||||
import com.google.common.base.FinalizableWeakReference;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Sets;
|
||||
import io.ray.api.BaseActorHandle;
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.api.id.ActorId;
|
||||
import io.ray.api.id.ObjectId;
|
||||
import io.ray.runtime.AbstractRayRuntime;
|
||||
import io.ray.runtime.generated.Common.Language;
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.lang.ref.Reference;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Abstract and language-independent implementation of actor handle for cluster mode. This is a
|
||||
* wrapper class for C++ ActorHandle.
|
||||
*/
|
||||
public abstract class NativeActorHandle implements BaseActorHandle, Externalizable {
|
||||
|
||||
private static final FinalizableReferenceQueue REFERENCE_QUEUE = new FinalizableReferenceQueue();
|
||||
|
||||
private static final Set<Reference<NativeActorHandle>> REFERENCES = Sets.newConcurrentHashSet();
|
||||
|
||||
/** ID of the actor. */
|
||||
byte[] actorId;
|
||||
|
||||
/** ID of the actor handle. */
|
||||
byte[] actorHandleId = new byte[ObjectId.LENGTH];
|
||||
|
||||
private Language language;
|
||||
|
||||
NativeActorHandle(byte[] actorId, Language language) {
|
||||
Preconditions.checkState(!ActorId.fromBytes(actorId).isNil());
|
||||
this.actorId = actorId;
|
||||
this.language = language;
|
||||
new NativeActorHandleReference(this);
|
||||
}
|
||||
|
||||
/** Required by FST. */
|
||||
NativeActorHandle() {
|
||||
// Note there is no need to add local reference here since this is only used for FST.
|
||||
}
|
||||
|
||||
public ObjectId getActorHandleId() {
|
||||
return new ObjectId(actorHandleId);
|
||||
}
|
||||
|
||||
public static NativeActorHandle create(byte[] actorId) {
|
||||
Language language = Language.forNumber(nativeGetLanguage(actorId));
|
||||
Preconditions.checkState(language != null, "Language shouldn't be null");
|
||||
return create(actorId, language);
|
||||
}
|
||||
|
||||
public static NativeActorHandle create(byte[] actorId, Language language) {
|
||||
switch (language) {
|
||||
case JAVA:
|
||||
return new NativeJavaActorHandle(actorId);
|
||||
case PYTHON:
|
||||
return new NativePyActorHandle(actorId);
|
||||
case CPP:
|
||||
return new NativeCppActorHandle(actorId);
|
||||
default:
|
||||
throw new IllegalStateException("Unknown actor handle language: " + language);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorId getId() {
|
||||
return ActorId.fromBytes(actorId);
|
||||
}
|
||||
|
||||
public Language getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
out.writeObject(nativeSerialize(actorId, actorHandleId));
|
||||
out.writeObject(language);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
actorId = nativeDeserialize((byte[]) in.readObject());
|
||||
language = (Language) in.readObject();
|
||||
new NativeActorHandleReference(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize this actor handle to bytes.
|
||||
*
|
||||
* @return the bytes of the actor handle
|
||||
*/
|
||||
public byte[] toBytes() {
|
||||
return nativeSerialize(actorId, actorHandleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize an actor handle from bytes.
|
||||
*
|
||||
* @return the bytes of an actor handle
|
||||
*/
|
||||
public static NativeActorHandle fromBytes(byte[] bytes) {
|
||||
byte[] actorId = nativeDeserialize(bytes);
|
||||
Language language = Language.forNumber(nativeGetLanguage(actorId));
|
||||
Preconditions.checkNotNull(language);
|
||||
return create(actorId, language);
|
||||
}
|
||||
|
||||
private static final class NativeActorHandleReference
|
||||
extends FinalizableWeakReference<NativeActorHandle> {
|
||||
private final AtomicBoolean removed;
|
||||
private final byte[] actorId;
|
||||
|
||||
public NativeActorHandleReference(NativeActorHandle handle) {
|
||||
super(handle, REFERENCE_QUEUE);
|
||||
this.actorId = handle.actorId;
|
||||
AbstractRayRuntime runtime = (AbstractRayRuntime) Ray.internal();
|
||||
this.removed = new AtomicBoolean(false);
|
||||
REFERENCES.add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finalizeReferent() {
|
||||
if (!removed.getAndSet(true)) {
|
||||
REFERENCES.remove(this);
|
||||
// It's possible that GC is executed after the runtime is shutdown.
|
||||
if (Ray.isInitialized()) {
|
||||
nativeRemoveActorHandleReference(actorId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(chaokunyang) do we need to free the ActorHandle in core worker by using phantom reference?
|
||||
|
||||
private static native int nativeGetLanguage(byte[] actorId);
|
||||
|
||||
static native List<String> nativeGetActorCreationTaskFunctionDescriptor(byte[] actorId);
|
||||
|
||||
private static native byte[] nativeSerialize(byte[] actorId, byte[] actorHandleId);
|
||||
|
||||
private static native byte[] nativeDeserialize(byte[] data);
|
||||
|
||||
private static native void nativeRemoveActorHandleReference(byte[] actorId);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.ray.runtime.actor;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.nustaq.serialization.FSTBasicObjectSerializer;
|
||||
import org.nustaq.serialization.FSTClazzInfo;
|
||||
import org.nustaq.serialization.FSTClazzInfo.FSTFieldInfo;
|
||||
import org.nustaq.serialization.FSTObjectInput;
|
||||
import org.nustaq.serialization.FSTObjectOutput;
|
||||
|
||||
/** To deal with serialization about {@link NativeActorHandle}. */
|
||||
public class NativeActorHandleSerializer extends FSTBasicObjectSerializer {
|
||||
|
||||
@Override
|
||||
public void writeObject(
|
||||
FSTObjectOutput out,
|
||||
Object toWrite,
|
||||
FSTClazzInfo clzInfo,
|
||||
FSTClazzInfo.FSTFieldInfo referencedBy,
|
||||
int streamPosition)
|
||||
throws IOException {
|
||||
((NativeActorHandle) toWrite).writeExternal(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readObject(
|
||||
FSTObjectInput in, Object toRead, FSTClazzInfo clzInfo, FSTFieldInfo referencedBy)
|
||||
throws Exception {
|
||||
super.readObject(in, toRead, clzInfo, referencedBy);
|
||||
((NativeActorHandle) toRead).readExternal(in);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.ray.runtime.actor;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.api.CppActorHandle;
|
||||
import io.ray.runtime.generated.Common.Language;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
|
||||
/** Cpp actor handle implementation for cluster mode. */
|
||||
public class NativeCppActorHandle extends NativeActorHandle implements CppActorHandle {
|
||||
|
||||
NativeCppActorHandle(byte[] actorId) {
|
||||
super(actorId, Language.CPP);
|
||||
}
|
||||
|
||||
/** Required by FST. */
|
||||
public NativeCppActorHandle() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return nativeGetActorCreationTaskFunctionDescriptor(actorId).get(2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
super.readExternal(in);
|
||||
Preconditions.checkState(getLanguage() == Language.CPP);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.ray.runtime.actor;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.api.ActorHandle;
|
||||
import io.ray.runtime.generated.Common.Language;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
|
||||
/** Java implementation of actor handle for cluster mode. */
|
||||
public class NativeJavaActorHandle extends NativeActorHandle implements ActorHandle {
|
||||
|
||||
NativeJavaActorHandle(byte[] actorId) {
|
||||
super(actorId, Language.JAVA);
|
||||
}
|
||||
|
||||
/** Required by FST. */
|
||||
public NativeJavaActorHandle() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
super.readExternal(in);
|
||||
Preconditions.checkState(getLanguage() == Language.JAVA);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.ray.runtime.actor;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.api.PyActorHandle;
|
||||
import io.ray.runtime.generated.Common.Language;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
|
||||
/** Python actor handle implementation for cluster mode. */
|
||||
public class NativePyActorHandle extends NativeActorHandle implements PyActorHandle {
|
||||
|
||||
NativePyActorHandle(byte[] actorId) {
|
||||
super(actorId, Language.PYTHON);
|
||||
}
|
||||
|
||||
/** Required by FST. */
|
||||
public NativePyActorHandle() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModuleName() {
|
||||
return nativeGetActorCreationTaskFunctionDescriptor(actorId).get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return nativeGetActorCreationTaskFunctionDescriptor(actorId).get(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
super.readExternal(in);
|
||||
Preconditions.checkState(getLanguage() == Language.PYTHON);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
package io.ray.runtime.config;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Strings;
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigException;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import com.typesafe.config.ConfigRenderOptions;
|
||||
import io.ray.api.id.JobId;
|
||||
import io.ray.api.id.UniqueId;
|
||||
import io.ray.api.options.ActorLifetime;
|
||||
import io.ray.api.runtimeenv.RuntimeEnvConfig;
|
||||
import io.ray.api.runtimeenv.types.RuntimeEnvName;
|
||||
import io.ray.runtime.generated.Common.WorkerType;
|
||||
import io.ray.runtime.runtimeenv.RuntimeEnvImpl;
|
||||
import io.ray.runtime.util.NetworkUtil;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
/** Configurations of Ray runtime. See `ray.default.conf` for the meaning of each field. */
|
||||
public class RayConfig {
|
||||
|
||||
public static final String DEFAULT_CONFIG_FILE = "ray.default.conf";
|
||||
public static final String CUSTOM_CONFIG_FILE = "ray.conf";
|
||||
|
||||
private Config config;
|
||||
|
||||
/** IP of this node. if not provided, IP will be automatically detected. */
|
||||
public final String nodeIp;
|
||||
|
||||
public final WorkerType workerMode;
|
||||
public final RunMode runMode;
|
||||
private JobId jobId;
|
||||
public String sessionDir;
|
||||
public String logDir;
|
||||
|
||||
private String bootstrapAddress;
|
||||
public final String redisUsername;
|
||||
public final String redisPassword;
|
||||
|
||||
// RPC socket name of object store.
|
||||
public String objectStoreSocketName;
|
||||
|
||||
// RPC socket name of Raylet.
|
||||
public String rayletSocketName;
|
||||
// Listening port for node manager.
|
||||
public int nodeManagerPort;
|
||||
|
||||
// Worker ID assigned by raylet when starting the worker process.
|
||||
public UniqueId workerId;
|
||||
|
||||
public int runtimeEnvHash;
|
||||
|
||||
public RuntimeEnvImpl runtimeEnvImpl = null;
|
||||
|
||||
public final ActorLifetime defaultActorLifetime;
|
||||
|
||||
public static class LoggerConf {
|
||||
public final String loggerName;
|
||||
public final String fileName;
|
||||
public final String pattern;
|
||||
|
||||
public LoggerConf(String loggerName, String fileName, String pattern) {
|
||||
this.loggerName = loggerName;
|
||||
this.fileName = fileName;
|
||||
this.pattern = pattern;
|
||||
}
|
||||
}
|
||||
|
||||
public final List<LoggerConf> loggers;
|
||||
|
||||
public final List<String> codeSearchPath;
|
||||
|
||||
public final List<String> headArgs;
|
||||
|
||||
public final String namespace;
|
||||
|
||||
public final List<String> jvmOptionsForJavaWorker;
|
||||
|
||||
private void validate() {
|
||||
if (workerMode == WorkerType.WORKER) {
|
||||
Preconditions.checkArgument(
|
||||
bootstrapAddress != null, "Bootstrap address must be set in worker mode.");
|
||||
}
|
||||
}
|
||||
|
||||
private String removeTrailingSlash(String path) {
|
||||
if (path.endsWith("/")) {
|
||||
return path.substring(0, path.length() - 1);
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
public RayConfig(Config config) {
|
||||
this.config = config;
|
||||
// Worker mode.
|
||||
WorkerType localWorkerMode;
|
||||
try {
|
||||
localWorkerMode = config.getEnum(WorkerType.class, "ray.worker.mode");
|
||||
} catch (ConfigException.Missing e) {
|
||||
localWorkerMode = WorkerType.DRIVER;
|
||||
}
|
||||
workerMode = localWorkerMode;
|
||||
boolean isDriver = workerMode == WorkerType.DRIVER;
|
||||
// Run mode.
|
||||
if (config.hasPath("ray.local-mode")) {
|
||||
runMode = config.getBoolean("ray.local-mode") ? RunMode.LOCAL : RunMode.CLUSTER;
|
||||
} else {
|
||||
runMode = config.getEnum(RunMode.class, "ray.run-mode");
|
||||
}
|
||||
// Node ip.
|
||||
if (config.hasPath("ray.node-ip")) {
|
||||
nodeIp = config.getString("ray.node-ip");
|
||||
} else {
|
||||
if (SystemUtils.IS_OS_LINUX) {
|
||||
nodeIp = NetworkUtil.getIpAddress(null);
|
||||
} else {
|
||||
/// We use a localhost on MacOS or Windows to avid security popups.
|
||||
/// See the related issue https://github.com/ray-project/ray/issues/18730
|
||||
nodeIp = NetworkUtil.localhostIp();
|
||||
}
|
||||
}
|
||||
|
||||
// Job id.
|
||||
String jobId = config.getString("ray.job.id");
|
||||
if (!jobId.isEmpty()) {
|
||||
this.jobId = JobId.fromHexString(jobId);
|
||||
} else {
|
||||
this.jobId = JobId.NIL;
|
||||
}
|
||||
|
||||
// Namespace of this job.
|
||||
String localNamespace = config.getString("ray.job.namespace");
|
||||
if (workerMode == WorkerType.DRIVER) {
|
||||
namespace =
|
||||
StringUtils.isEmpty(localNamespace) ? UUID.randomUUID().toString() : localNamespace;
|
||||
} else {
|
||||
/// We shouldn't set it for worker.
|
||||
namespace = null;
|
||||
}
|
||||
|
||||
defaultActorLifetime = config.getEnum(ActorLifetime.class, "ray.job.default-actor-lifetime");
|
||||
Preconditions.checkState(defaultActorLifetime != null);
|
||||
|
||||
// jvm options for java workers of this job.
|
||||
jvmOptionsForJavaWorker = config.getStringList("ray.job.jvm-options");
|
||||
updateSessionDir(null);
|
||||
|
||||
// Object store socket name.
|
||||
if (config.hasPath("ray.object-store.socket-name")) {
|
||||
objectStoreSocketName = config.getString("ray.object-store.socket-name");
|
||||
}
|
||||
|
||||
// Raylet socket name.
|
||||
if (config.hasPath("ray.raylet.socket-name")) {
|
||||
rayletSocketName = config.getString("ray.raylet.socket-name");
|
||||
}
|
||||
|
||||
// Bootstrap configurations.
|
||||
String bootstrapAddress = config.getString("ray.address");
|
||||
if (StringUtils.isNotBlank(bootstrapAddress)) {
|
||||
setBootstrapAddress(bootstrapAddress);
|
||||
} else {
|
||||
// We need to start gcs using `RunManager` for local cluster
|
||||
this.bootstrapAddress = null;
|
||||
}
|
||||
|
||||
redisUsername = config.getString("ray.redis.username");
|
||||
redisPassword = config.getString("ray.redis.password");
|
||||
// Raylet node manager port.
|
||||
if (config.hasPath("ray.raylet.node-manager-port")) {
|
||||
nodeManagerPort = config.getInt("ray.raylet.node-manager-port");
|
||||
} else {
|
||||
Preconditions.checkState(
|
||||
workerMode != WorkerType.WORKER,
|
||||
"Worker started by raylet should accept the node manager port from raylet.");
|
||||
}
|
||||
|
||||
// Job code search path.
|
||||
String codeSearchPathString = null;
|
||||
if (config.hasPath("ray.job.code-search-path")) {
|
||||
codeSearchPathString = config.getString("ray.job.code-search-path");
|
||||
}
|
||||
if (StringUtils.isEmpty(codeSearchPathString)) {
|
||||
codeSearchPathString = System.getProperty("java.class.path");
|
||||
}
|
||||
codeSearchPath = Arrays.asList(codeSearchPathString.split(":"));
|
||||
|
||||
String workerIdHex = config.getString("ray.worker.id");
|
||||
workerId = workerIdHex.isEmpty() ? UniqueId.NIL : UniqueId.fromHexString(workerIdHex);
|
||||
|
||||
/// Driver needn't this config item.
|
||||
if (workerMode == WorkerType.WORKER && config.hasPath("ray.internal.runtime-env-hash")) {
|
||||
runtimeEnvHash = config.getInt("ray.internal.runtime-env-hash");
|
||||
}
|
||||
|
||||
{
|
||||
/// Runtime Env env-vars
|
||||
Map<String, String> envVars = new HashMap<>();
|
||||
List<String> jarUrls = null;
|
||||
final String envVarsPath = "ray.job.runtime-env.env-vars";
|
||||
if (config.hasPath(envVarsPath)) {
|
||||
Config envVarsConfig = config.getConfig(envVarsPath);
|
||||
envVarsConfig
|
||||
.entrySet()
|
||||
.forEach(
|
||||
(entry) -> {
|
||||
envVars.put(entry.getKey(), ((String) entry.getValue().unwrapped()));
|
||||
});
|
||||
}
|
||||
|
||||
/// Runtime env jars
|
||||
final String jarsPath = "ray.job.runtime-env.jars";
|
||||
if (config.hasPath(jarsPath)) {
|
||||
jarUrls = config.getStringList(jarsPath);
|
||||
}
|
||||
|
||||
/// Runtime env config
|
||||
RuntimeEnvConfig runtimeEnvConfig = null;
|
||||
final String timeoutPath = "ray.job.runtime-env.config.setup-timeout-seconds";
|
||||
if (config.hasPath(timeoutPath)) {
|
||||
runtimeEnvConfig = new RuntimeEnvConfig();
|
||||
runtimeEnvConfig.setSetupTimeoutSeconds(config.getInt(timeoutPath));
|
||||
}
|
||||
final String eagerInstallPath = "ray.job.runtime-env.config.eager-install";
|
||||
if (config.hasPath(eagerInstallPath)) {
|
||||
if (runtimeEnvConfig == null) {
|
||||
runtimeEnvConfig = new RuntimeEnvConfig();
|
||||
}
|
||||
runtimeEnvConfig.setEagerInstall(config.getBoolean(eagerInstallPath));
|
||||
}
|
||||
|
||||
runtimeEnvImpl = new RuntimeEnvImpl();
|
||||
if (!envVars.isEmpty()) {
|
||||
runtimeEnvImpl.set(RuntimeEnvName.ENV_VARS, envVars);
|
||||
}
|
||||
if (!jarUrls.isEmpty()) {
|
||||
runtimeEnvImpl.set(RuntimeEnvName.JARS, jarUrls);
|
||||
}
|
||||
if (runtimeEnvConfig != null) {
|
||||
runtimeEnvImpl.setConfig(runtimeEnvConfig);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
loggers = new ArrayList<>();
|
||||
List<Config> loggerConfigs = (List<Config>) config.getConfigList("ray.logging.loggers");
|
||||
for (Config loggerConfig : loggerConfigs) {
|
||||
Preconditions.checkState(loggerConfig.hasPath("name"));
|
||||
Preconditions.checkState(loggerConfig.hasPath("file-name"));
|
||||
final String name = loggerConfig.getString("name");
|
||||
final String fileName = loggerConfig.getString("file-name");
|
||||
final String pattern =
|
||||
loggerConfig.hasPath("pattern") ? loggerConfig.getString("pattern") : "";
|
||||
loggers.add(new LoggerConf(name, fileName, pattern));
|
||||
}
|
||||
}
|
||||
|
||||
headArgs = config.getStringList("ray.head-args");
|
||||
|
||||
// Validate config.
|
||||
validate();
|
||||
}
|
||||
|
||||
public void setBootstrapAddress(String bootstrapAddress) {
|
||||
Preconditions.checkNotNull(bootstrapAddress);
|
||||
Preconditions.checkState(this.bootstrapAddress == null, "Bootstrap address was already set");
|
||||
this.bootstrapAddress = bootstrapAddress;
|
||||
}
|
||||
|
||||
public String getBootstrapAddress() {
|
||||
return this.bootstrapAddress;
|
||||
}
|
||||
|
||||
public void setJobId(JobId jobId) {
|
||||
this.jobId = jobId;
|
||||
}
|
||||
|
||||
public JobId getJobId() {
|
||||
return this.jobId;
|
||||
}
|
||||
|
||||
public int getNodeManagerPort() {
|
||||
return nodeManagerPort;
|
||||
}
|
||||
|
||||
public UniqueId getWorkerId() {
|
||||
return workerId;
|
||||
}
|
||||
|
||||
public void setSessionDir(String sessionDir) {
|
||||
updateSessionDir(sessionDir);
|
||||
}
|
||||
|
||||
public Config getInternalConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
/** Renders the config value as a HOCON string. */
|
||||
@Override
|
||||
public String toString() {
|
||||
// These items might be dynamically generated or mutated at runtime.
|
||||
// Explicitly include them.
|
||||
Map<String, Object> dynamic = new HashMap<>();
|
||||
dynamic.put("ray.session-dir", sessionDir);
|
||||
dynamic.put("ray.raylet.socket-name", rayletSocketName);
|
||||
dynamic.put("ray.object-store.socket-name", objectStoreSocketName);
|
||||
dynamic.put("ray.raylet.node-manager-port", nodeManagerPort);
|
||||
dynamic.put("ray.address", bootstrapAddress);
|
||||
dynamic.put("ray.worker.id", workerId.isNil() ? "" : workerId.toString());
|
||||
Config toRender = ConfigFactory.parseMap(dynamic).withFallback(config);
|
||||
return toRender.root().render(ConfigRenderOptions.concise());
|
||||
}
|
||||
|
||||
private void updateSessionDir(String sessionDir) {
|
||||
// session dir
|
||||
if (config.hasPath("ray.session-dir")) {
|
||||
sessionDir = config.getString("ray.session-dir");
|
||||
}
|
||||
if (sessionDir != null) {
|
||||
sessionDir = removeTrailingSlash(sessionDir);
|
||||
}
|
||||
this.sessionDir = sessionDir;
|
||||
|
||||
// Log dir.
|
||||
String localLogDir = null;
|
||||
if (config.hasPath("ray.logging.dir")) {
|
||||
localLogDir = removeTrailingSlash(config.getString("ray.logging.dir"));
|
||||
}
|
||||
if (Strings.isNullOrEmpty(localLogDir)) {
|
||||
logDir = String.format("%s/logs", sessionDir);
|
||||
} else {
|
||||
logDir = localLogDir;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a RayConfig by reading configuration in the following order: 1. System properties. 2.
|
||||
* `ray.conf` file. 3. `ray.default.conf` file.
|
||||
*/
|
||||
public static RayConfig create() {
|
||||
ConfigFactory.invalidateCaches();
|
||||
Config config = ConfigFactory.systemProperties();
|
||||
String configPath = System.getProperty("ray.config-file");
|
||||
if (Strings.isNullOrEmpty(configPath)) {
|
||||
config = config.withFallback(ConfigFactory.load(CUSTOM_CONFIG_FILE));
|
||||
} else {
|
||||
config = config.withFallback(ConfigFactory.parseFile(new File(configPath)));
|
||||
}
|
||||
config = config.withFallback(ConfigFactory.load(DEFAULT_CONFIG_FILE));
|
||||
return new RayConfig(config.withOnlyPath("ray"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.ray.runtime.config;
|
||||
|
||||
public enum RunMode {
|
||||
|
||||
/**
|
||||
* Ray is running in one single Java process, without Raylet backend, object store, and GCS. It's
|
||||
* useful for debug.
|
||||
*/
|
||||
LOCAL,
|
||||
|
||||
/** Ray is running on one or more nodes, with multiple processes. */
|
||||
CLUSTER,
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package io.ray.runtime.context;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.ray.api.id.ActorId;
|
||||
import io.ray.api.id.JobId;
|
||||
import io.ray.api.id.TaskId;
|
||||
import io.ray.api.id.UniqueId;
|
||||
import io.ray.api.runtimeenv.RuntimeEnv;
|
||||
import io.ray.runtime.generated.Common.Address;
|
||||
import io.ray.runtime.generated.Common.TaskSpec;
|
||||
import io.ray.runtime.generated.Common.TaskType;
|
||||
import io.ray.runtime.task.LocalModeTaskSubmitter;
|
||||
import java.util.Random;
|
||||
|
||||
/** Worker context for local mode. */
|
||||
public class LocalModeWorkerContext implements WorkerContext {
|
||||
|
||||
private final JobId jobId;
|
||||
private ThreadLocal<TaskSpec> currentTask = new ThreadLocal<>();
|
||||
private final ThreadLocal<UniqueId> currentWorkerId = new ThreadLocal<>();
|
||||
|
||||
public LocalModeWorkerContext(JobId jobId) {
|
||||
this.jobId = jobId;
|
||||
|
||||
// Create a dummy driver task with a random task id, so that we can call
|
||||
// `getCurrentTaskId` from a driver.
|
||||
byte[] driverTaskId = new byte[TaskId.LENGTH];
|
||||
new Random().nextBytes(driverTaskId);
|
||||
TaskSpec dummyDriverTask =
|
||||
TaskSpec.newBuilder().setTaskId(ByteString.copyFrom(driverTaskId)).build();
|
||||
currentTask.set(dummyDriverTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UniqueId getCurrentWorkerId() {
|
||||
return currentWorkerId.get();
|
||||
}
|
||||
|
||||
public void setCurrentWorkerId(UniqueId workerId) {
|
||||
currentWorkerId.set(workerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JobId getCurrentJobId() {
|
||||
return jobId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorId getCurrentActorId() {
|
||||
TaskSpec taskSpec = currentTask.get();
|
||||
checkTaskSpecNotNull(taskSpec);
|
||||
return LocalModeTaskSubmitter.getActorId(taskSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskType getCurrentTaskType() {
|
||||
TaskSpec taskSpec = currentTask.get();
|
||||
checkTaskSpecNotNull(taskSpec);
|
||||
return taskSpec.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskId getCurrentTaskId() {
|
||||
TaskSpec taskSpec = currentTask.get();
|
||||
checkTaskSpecNotNull(taskSpec);
|
||||
return TaskId.fromBytes(taskSpec.getTaskId().toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getRpcAddress() {
|
||||
return Address.getDefaultInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeEnv getCurrentRuntimeEnv() {
|
||||
throw new RuntimeException("Not implemented.");
|
||||
}
|
||||
|
||||
public void setCurrentTask(TaskSpec taskSpec) {
|
||||
currentTask.set(taskSpec);
|
||||
}
|
||||
|
||||
private static void checkTaskSpecNotNull(TaskSpec taskSpec) {
|
||||
Preconditions.checkNotNull(
|
||||
taskSpec,
|
||||
"Current task is not set. Maybe you invoked this API in a user-created thread not managed by Ray. Invoking this API in a user-created thread is not supported yet in local mode. You can switch to cluster mode.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package io.ray.runtime.context;
|
||||
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import io.ray.api.id.ActorId;
|
||||
import io.ray.api.id.JobId;
|
||||
import io.ray.api.id.TaskId;
|
||||
import io.ray.api.id.UniqueId;
|
||||
import io.ray.api.runtimeenv.RuntimeEnv;
|
||||
import io.ray.runtime.generated.Common.Address;
|
||||
import io.ray.runtime.generated.Common.TaskType;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/** Worker context for cluster mode. This is a wrapper class for worker context of core worker. */
|
||||
public class NativeWorkerContext implements WorkerContext {
|
||||
|
||||
private ClassLoader currentClassLoader = null;
|
||||
|
||||
@Override
|
||||
public UniqueId getCurrentWorkerId() {
|
||||
return UniqueId.fromByteBuffer(nativeGetCurrentWorkerId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JobId getCurrentJobId() {
|
||||
return JobId.fromBytes(nativeGetCurrentJobId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorId getCurrentActorId() {
|
||||
return ActorId.fromByteBuffer(nativeGetCurrentActorId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskType getCurrentTaskType() {
|
||||
return TaskType.forNumber(nativeGetCurrentTaskType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskId getCurrentTaskId() {
|
||||
return TaskId.fromByteBuffer(nativeGetCurrentTaskId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getRpcAddress() {
|
||||
try {
|
||||
return Address.parseFrom(nativeGetRpcAddress());
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeEnv getCurrentRuntimeEnv() {
|
||||
String serialized_runtime_env = nativeGetSerializedRuntimeEnv();
|
||||
if (serialized_runtime_env == null) {
|
||||
return null;
|
||||
}
|
||||
return RuntimeEnv.deserialize(serialized_runtime_env);
|
||||
}
|
||||
|
||||
private static native int nativeGetCurrentTaskType();
|
||||
|
||||
private static native ByteBuffer nativeGetCurrentTaskId();
|
||||
|
||||
private static native byte[] nativeGetCurrentJobId();
|
||||
|
||||
private static native ByteBuffer nativeGetCurrentWorkerId();
|
||||
|
||||
private static native ByteBuffer nativeGetCurrentActorId();
|
||||
|
||||
private static native byte[] nativeGetRpcAddress();
|
||||
|
||||
private static native String nativeGetSerializedRuntimeEnv();
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package io.ray.runtime.context;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.api.BaseActorHandle;
|
||||
import io.ray.api.id.ActorId;
|
||||
import io.ray.api.id.JobId;
|
||||
import io.ray.api.id.TaskId;
|
||||
import io.ray.api.id.UniqueId;
|
||||
import io.ray.api.runtimecontext.ActorInfo;
|
||||
import io.ray.api.runtimecontext.ActorState;
|
||||
import io.ray.api.runtimecontext.NodeInfo;
|
||||
import io.ray.api.runtimecontext.ResourceValue;
|
||||
import io.ray.api.runtimecontext.RuntimeContext;
|
||||
import io.ray.api.runtimeenv.RuntimeEnv;
|
||||
import io.ray.runtime.AbstractRayRuntime;
|
||||
import io.ray.runtime.config.RunMode;
|
||||
import io.ray.runtime.util.ResourceUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class RuntimeContextImpl implements RuntimeContext {
|
||||
|
||||
private AbstractRayRuntime runtime;
|
||||
|
||||
public RuntimeContextImpl(AbstractRayRuntime runtime) {
|
||||
this.runtime = runtime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JobId getCurrentJobId() {
|
||||
return runtime.getWorkerContext().getCurrentJobId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorId getCurrentActorId() {
|
||||
ActorId actorId = runtime.getWorkerContext().getCurrentActorId();
|
||||
Preconditions.checkState(
|
||||
actorId != null && !actorId.isNil(), "This method should only be called from an actor.");
|
||||
return actorId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskId getCurrentTaskId() {
|
||||
return runtime.getWorkerContext().getCurrentTaskId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wasCurrentActorRestarted() {
|
||||
if (isLocalMode()) {
|
||||
return false;
|
||||
}
|
||||
return runtime.getGcsClient().wasCurrentActorRestarted(getCurrentActorId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocalMode() {
|
||||
return RunMode.LOCAL == runtime.getRayConfig().runMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NodeInfo> getAllNodeInfo() {
|
||||
return runtime.getGcsClient().getAllNodeInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ActorInfo> getAllActorInfo() {
|
||||
return runtime.getGcsClient().getAllActorInfo(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ActorInfo> getAllActorInfo(JobId jobId, ActorState actorState) {
|
||||
return runtime.getGcsClient().getAllActorInfo(jobId, actorState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends BaseActorHandle> T getCurrentActorHandle() {
|
||||
return runtime.getActorHandle(getCurrentActorId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getGpuIds() {
|
||||
Map<String, List<ResourceValue>> resourceIds = runtime.getAvailableResourceIds();
|
||||
Set<Long> assignedIds = new HashSet<>();
|
||||
for (Map.Entry<String, List<ResourceValue>> entry : resourceIds.entrySet()) {
|
||||
String pattern = "^GPU_group_[0-9A-Za-z]+$";
|
||||
if (entry.getKey().equals("GPU") || Pattern.matches(pattern, entry.getKey())) {
|
||||
assignedIds.addAll(
|
||||
entry.getValue().stream().map(x -> x.resourceId).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
List<Long> gpuIds;
|
||||
List<String> gpuOnThisNode = ResourceUtil.getCudaVisibleDevices();
|
||||
if (gpuOnThisNode != null) {
|
||||
gpuIds = new ArrayList<>();
|
||||
for (Long id : assignedIds) {
|
||||
gpuIds.add(Long.valueOf(gpuOnThisNode.get(id.intValue())));
|
||||
}
|
||||
} else {
|
||||
gpuIds = new ArrayList<>(assignedIds);
|
||||
}
|
||||
return gpuIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return runtime.getNamespace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UniqueId getCurrentNodeId() {
|
||||
return runtime.getCurrentNodeId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeEnv getCurrentRuntimeEnv() {
|
||||
return runtime.getWorkerContext().getCurrentRuntimeEnv();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package io.ray.runtime.context;
|
||||
|
||||
import io.ray.api.id.ActorId;
|
||||
import io.ray.api.id.JobId;
|
||||
import io.ray.api.id.TaskId;
|
||||
import io.ray.api.id.UniqueId;
|
||||
import io.ray.api.runtimeenv.RuntimeEnv;
|
||||
import io.ray.runtime.generated.Common.Address;
|
||||
import io.ray.runtime.generated.Common.TaskType;
|
||||
|
||||
/** The context of worker. */
|
||||
public interface WorkerContext {
|
||||
|
||||
/** ID of the current worker. */
|
||||
UniqueId getCurrentWorkerId();
|
||||
|
||||
/** ID of the current job. */
|
||||
JobId getCurrentJobId();
|
||||
|
||||
/** ID of the current actor. */
|
||||
ActorId getCurrentActorId();
|
||||
|
||||
/** Type of the current task. */
|
||||
TaskType getCurrentTaskType();
|
||||
|
||||
/** ID of the current task. */
|
||||
TaskId getCurrentTaskId();
|
||||
|
||||
Address getRpcAddress();
|
||||
|
||||
/** RuntimeEnv of the current worker or job(for driver). */
|
||||
RuntimeEnv getCurrentRuntimeEnv();
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package io.ray.runtime.functionmanager;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import io.ray.runtime.generated.Common.Language;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/** Represents metadata of a Cpp function. */
|
||||
public class CppFunctionDescriptor implements FunctionDescriptor {
|
||||
|
||||
public String createFunctionName;
|
||||
|
||||
public String className;
|
||||
|
||||
public String caller;
|
||||
|
||||
public CppFunctionDescriptor(String createFunctionName, String caller, String className) {
|
||||
this.createFunctionName = createFunctionName;
|
||||
this.caller = caller;
|
||||
this.className = className;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return createFunctionName + "." + caller + "." + className;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
CppFunctionDescriptor that = (CppFunctionDescriptor) o;
|
||||
return Objects.equal(createFunctionName, that.createFunctionName)
|
||||
&& Objects.equal(className, that.className)
|
||||
&& Objects.equal(caller, that.caller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(createFunctionName, caller, className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> toList() {
|
||||
return Arrays.asList(createFunctionName, caller, className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Language getLanguage() {
|
||||
return Language.CPP;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package io.ray.runtime.functionmanager;
|
||||
|
||||
import io.ray.runtime.generated.Common.Language;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Base interface of a Ray task's function descriptor.
|
||||
*
|
||||
* <p>A function descriptor is a list of strings that can uniquely describe a function. It's used to
|
||||
* load a function in workers.
|
||||
*/
|
||||
public interface FunctionDescriptor {
|
||||
|
||||
/** Returns A list of strings represents the functions. */
|
||||
List<String> toList();
|
||||
|
||||
/** Returns The language of the function. */
|
||||
Language getLanguage();
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
package io.ray.runtime.functionmanager;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import io.ray.api.function.RayFunc;
|
||||
import io.ray.runtime.util.LambdaUtils;
|
||||
import java.io.File;
|
||||
import java.lang.invoke.SerializedLambda;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.filefilter.DirectoryFileFilter;
|
||||
import org.apache.commons.io.filefilter.RegexFileFilter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.objectweb.asm.Type;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Manages functions in the current worker. */
|
||||
public class FunctionManager {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(FunctionManager.class);
|
||||
|
||||
static final String CONSTRUCTOR_NAME = "<init>";
|
||||
|
||||
/**
|
||||
* Cache from a RayFunc object to its corresponding JavaFunctionDescriptor. Because
|
||||
* `LambdaUtils.getSerializedLambda` is expensive.
|
||||
*/
|
||||
// If the cache is not thread local, we'll need a lock to protect it,
|
||||
// which means competition is highly possible.
|
||||
private static final ThreadLocal<WeakHashMap<Class<? extends RayFunc>, JavaFunctionDescriptor>>
|
||||
RAY_FUNC_CACHE = ThreadLocal.withInitial(WeakHashMap::new);
|
||||
|
||||
/** The table that manages functions. */
|
||||
private final JobFunctionTable jobFunctionTable;
|
||||
|
||||
/** The resource path which we can load the job's jar resources. */
|
||||
private final List<String> codeSearchPath;
|
||||
|
||||
/**
|
||||
* Construct a FunctionManager with the specified code search path.
|
||||
*
|
||||
* @param codeSearchPath The specified job resource that can store the job's resources.
|
||||
*/
|
||||
public FunctionManager(List<String> codeSearchPath) {
|
||||
this.codeSearchPath = codeSearchPath;
|
||||
jobFunctionTable = createJobFunctionTable();
|
||||
}
|
||||
|
||||
public ClassLoader getClassLoader() {
|
||||
return jobFunctionTable.classLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the RayFunction from a RayFunc instance (a lambda).
|
||||
*
|
||||
* @param func The lambda.
|
||||
* @return A RayFunction object.
|
||||
*/
|
||||
public RayFunction getFunction(RayFunc func) {
|
||||
JavaFunctionDescriptor functionDescriptor = RAY_FUNC_CACHE.get().get(func.getClass());
|
||||
if (functionDescriptor == null) {
|
||||
// It's OK to not lock here, because it's OK to have multiple JavaFunctionDescriptor instances
|
||||
// for the same RayFunc instance.
|
||||
SerializedLambda serializedLambda = LambdaUtils.getSerializedLambda(func);
|
||||
final String className = serializedLambda.getImplClass().replace('/', '.');
|
||||
final String methodName = serializedLambda.getImplMethodName();
|
||||
final String signature = serializedLambda.getImplMethodSignature();
|
||||
functionDescriptor = new JavaFunctionDescriptor(className, methodName, signature);
|
||||
RAY_FUNC_CACHE.get().put(func.getClass(), functionDescriptor);
|
||||
}
|
||||
return getFunction(functionDescriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the RayFunction from a function descriptor.
|
||||
*
|
||||
* @param functionDescriptor The function descriptor.
|
||||
* @return A RayFunction object.
|
||||
*/
|
||||
public RayFunction getFunction(JavaFunctionDescriptor functionDescriptor) {
|
||||
return jobFunctionTable.getFunction(functionDescriptor);
|
||||
}
|
||||
|
||||
/** A helper that creates function table. */
|
||||
private JobFunctionTable createJobFunctionTable() {
|
||||
ClassLoader classLoader;
|
||||
if (codeSearchPath == null || codeSearchPath.isEmpty()) {
|
||||
classLoader = getClass().getClassLoader();
|
||||
} else {
|
||||
URL[] urls =
|
||||
codeSearchPath.stream()
|
||||
.filter(p -> StringUtils.isNotBlank(p) && Files.exists(Paths.get(p)))
|
||||
.flatMap(
|
||||
p -> {
|
||||
try {
|
||||
if (!Files.isDirectory(Paths.get(p))) {
|
||||
if (!p.endsWith(".jar")) {
|
||||
return Stream.of(
|
||||
Paths.get(p).getParent().toAbsolutePath().toUri().toURL());
|
||||
} else {
|
||||
return Stream.of(Paths.get(p).toAbsolutePath().toUri().toURL());
|
||||
}
|
||||
} else {
|
||||
List<URL> subUrls = new ArrayList<>();
|
||||
subUrls.add(Paths.get(p).toAbsolutePath().toUri().toURL());
|
||||
Collection<File> jars =
|
||||
FileUtils.listFiles(
|
||||
new File(p),
|
||||
new RegexFileFilter(".*\\.jar"),
|
||||
DirectoryFileFilter.DIRECTORY);
|
||||
for (File jar : jars) {
|
||||
subUrls.add(jar.toPath().toUri().toURL());
|
||||
}
|
||||
return subUrls.stream();
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(String.format("Illegal %s resource path", p));
|
||||
}
|
||||
})
|
||||
.toArray(URL[]::new);
|
||||
classLoader = new URLClassLoader(urls);
|
||||
LOGGER.debug("Resource loaded from path {}.", (Object[]) (urls));
|
||||
}
|
||||
|
||||
return new JobFunctionTable(classLoader);
|
||||
}
|
||||
|
||||
/** Manages all functions that belong to one job. */
|
||||
static class JobFunctionTable {
|
||||
|
||||
/** The job's corresponding class loader. */
|
||||
final ClassLoader classLoader;
|
||||
/** Functions per class, per function name + type descriptor. */
|
||||
ConcurrentMap<String, Map<Pair<String, String>, Pair<RayFunction, Boolean>>> functions;
|
||||
|
||||
JobFunctionTable(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
this.functions = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
RayFunction getFunction(JavaFunctionDescriptor descriptor) {
|
||||
Map<Pair<String, String>, Pair<RayFunction, Boolean>> classFunctions =
|
||||
functions.get(descriptor.className);
|
||||
if (classFunctions == null) {
|
||||
synchronized (this) {
|
||||
classFunctions = functions.get(descriptor.className);
|
||||
if (classFunctions == null) {
|
||||
classFunctions = loadFunctionsForClass(descriptor.className);
|
||||
functions.put(descriptor.className, classFunctions);
|
||||
}
|
||||
}
|
||||
}
|
||||
final Pair<String, String> key = ImmutablePair.of(descriptor.name, descriptor.signature);
|
||||
RayFunction func = classFunctions.get(key).getLeft();
|
||||
if (func == null) {
|
||||
if (classFunctions.containsKey(key)) {
|
||||
throw new RuntimeException(
|
||||
String.format(
|
||||
"RayFunction %s is overloaded, the signature can't be empty.",
|
||||
descriptor.toString()));
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
String.format("RayFunction %s not found", descriptor.toString()));
|
||||
}
|
||||
}
|
||||
return func;
|
||||
}
|
||||
|
||||
/** Load all functions from a class. */
|
||||
Map<Pair<String, String>, Pair<RayFunction, Boolean>> loadFunctionsForClass(String className) {
|
||||
// If RayFunction is null, the function is overloaded.
|
||||
// The value of this map is a pair of <rayFunction, isDefault>.
|
||||
// The `isDefault` is used to mark if the method is a marked as default keyword.
|
||||
Map<Pair<String, String>, Pair<RayFunction, Boolean>> map = new HashMap<>();
|
||||
try {
|
||||
Class clazz = Class.forName(className, true, classLoader);
|
||||
List<Executable> executables = new ArrayList<>();
|
||||
executables.addAll(Arrays.asList(clazz.getDeclaredMethods()));
|
||||
executables.addAll(Arrays.asList(clazz.getDeclaredConstructors()));
|
||||
|
||||
Class clz = clazz;
|
||||
clz = clz.getSuperclass();
|
||||
while (clz != null && clz != Object.class) {
|
||||
executables.addAll(Arrays.asList(clz.getDeclaredMethods()));
|
||||
clz = clz.getSuperclass();
|
||||
}
|
||||
|
||||
// Put interface methods ahead, so that in can be override by subclass methods in `map.put`
|
||||
for (Class baseInterface : clazz.getInterfaces()) {
|
||||
for (Method method : baseInterface.getDeclaredMethods()) {
|
||||
if (method.isDefault()) {
|
||||
executables.add(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use reverse order so that child class methods can override super class methods.
|
||||
for (Executable e : Lists.reverse(executables)) {
|
||||
e.setAccessible(true);
|
||||
final String methodName = e instanceof Method ? e.getName() : CONSTRUCTOR_NAME;
|
||||
final Type type =
|
||||
e instanceof Method ? Type.getType((Method) e) : Type.getType((Constructor) e);
|
||||
final String signature = type.getDescriptor();
|
||||
RayFunction rayFunction =
|
||||
new RayFunction(
|
||||
e, classLoader, new JavaFunctionDescriptor(className, methodName, signature));
|
||||
final boolean isDefault = e instanceof Method && ((Method) e).isDefault();
|
||||
map.put(
|
||||
ImmutablePair.of(methodName, signature), ImmutablePair.of(rayFunction, isDefault));
|
||||
// For cross language call java function with signature "{length_of_arguments}" or just
|
||||
// empty "".
|
||||
// TODO: more robust signature type matching
|
||||
// https://github.com/ray-project/ray/issues/21380.
|
||||
String[] crossLangSignatures = {"", String.format("%s", type.getArgumentTypes().length)};
|
||||
for (String crossLangSignature : crossLangSignatures) {
|
||||
final Pair<String, String> crossLangDescriptor =
|
||||
ImmutablePair.of(methodName, crossLangSignature);
|
||||
/// default method is not overloaded, so we should filter it.
|
||||
if (map.containsKey(crossLangDescriptor) && !map.get(crossLangDescriptor).getRight()) {
|
||||
map.put(
|
||||
crossLangDescriptor,
|
||||
ImmutablePair.of(null, false)); // Mark this function as overloaded.
|
||||
} else {
|
||||
map.put(crossLangDescriptor, ImmutablePair.of(rayFunction, isDefault));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to load functions from class " + className, e);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package io.ray.runtime.functionmanager;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import io.ray.runtime.generated.Common.Language;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/** Represents metadata of Java function. */
|
||||
public final class JavaFunctionDescriptor implements FunctionDescriptor, Serializable {
|
||||
|
||||
private static final long serialVersionUID = -2137471820857197094L;
|
||||
|
||||
/** Function's class name. */
|
||||
public final String className;
|
||||
/** Function's name. */
|
||||
public final String name;
|
||||
/** Function's signature. */
|
||||
public final String signature;
|
||||
|
||||
public JavaFunctionDescriptor(String className, String name, String signature) {
|
||||
this.className = className;
|
||||
this.name = name;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return className + "." + name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
JavaFunctionDescriptor that = (JavaFunctionDescriptor) o;
|
||||
return Objects.equal(className, that.className)
|
||||
&& Objects.equal(name, that.name)
|
||||
&& Objects.equal(signature, that.signature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(className, name, signature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> toList() {
|
||||
return ImmutableList.of(className, name, signature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Language getLanguage() {
|
||||
return Language.JAVA;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package io.ray.runtime.functionmanager;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import io.ray.runtime.generated.Common.Language;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/** Represents metadata of a Python function. */
|
||||
public class PyFunctionDescriptor implements FunctionDescriptor {
|
||||
|
||||
public String moduleName;
|
||||
|
||||
public String className;
|
||||
|
||||
public String functionName;
|
||||
|
||||
public PyFunctionDescriptor(String moduleName, String className, String functionName) {
|
||||
this.moduleName = moduleName;
|
||||
this.className = className;
|
||||
this.functionName = functionName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return moduleName + "." + className + "." + functionName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
PyFunctionDescriptor that = (PyFunctionDescriptor) o;
|
||||
return Objects.equal(moduleName, that.moduleName)
|
||||
&& Objects.equal(className, that.className)
|
||||
&& Objects.equal(functionName, that.functionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(moduleName, className, functionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> toList() {
|
||||
return Arrays.asList(moduleName, className, functionName, "" /* function hash */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Language getLanguage() {
|
||||
return Language.PYTHON;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package io.ray.runtime.functionmanager;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Represents a Ray function (either a Method or a Constructor in Java) and its metadata. */
|
||||
public class RayFunction {
|
||||
|
||||
/** The executor object, can be either a Method or a Constructor. */
|
||||
public final Executable executable;
|
||||
|
||||
/** This function's class loader. */
|
||||
public final ClassLoader classLoader;
|
||||
|
||||
/** Function's metadata. */
|
||||
public final JavaFunctionDescriptor functionDescriptor;
|
||||
|
||||
public RayFunction(
|
||||
Executable executable, ClassLoader classLoader, JavaFunctionDescriptor functionDescriptor) {
|
||||
this.executable = executable;
|
||||
this.classLoader = classLoader;
|
||||
this.functionDescriptor = functionDescriptor;
|
||||
}
|
||||
|
||||
/** Returns True if it's a constructor, otherwise it's a method. */
|
||||
public boolean isConstructor() {
|
||||
return executable instanceof Constructor;
|
||||
}
|
||||
|
||||
/** Returns The underlying constructor object. */
|
||||
public Constructor<?> getConstructor() {
|
||||
return (Constructor<?>) executable;
|
||||
}
|
||||
|
||||
/** Returns The underlying method object. */
|
||||
public Method getMethod() {
|
||||
return (Method) executable;
|
||||
}
|
||||
|
||||
public JavaFunctionDescriptor getFunctionDescriptor() {
|
||||
return functionDescriptor;
|
||||
}
|
||||
|
||||
/** Returns Whether this function has a return value. */
|
||||
public boolean hasReturn() {
|
||||
if (isConstructor()) {
|
||||
return true;
|
||||
} else {
|
||||
return !getMethod().getReturnType().equals(void.class);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns Return type. */
|
||||
public Optional<Class<?>> getReturnType() {
|
||||
if (hasReturn()) {
|
||||
return Optional.of(((Method) executable).getReturnType());
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return executable.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package io.ray.runtime.gcs;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import io.ray.api.exception.RayException;
|
||||
import io.ray.api.id.ActorId;
|
||||
import io.ray.api.id.JobId;
|
||||
import io.ray.api.id.PlacementGroupId;
|
||||
import io.ray.api.id.UniqueId;
|
||||
import io.ray.api.placementgroup.PlacementGroup;
|
||||
import io.ray.api.runtimecontext.ActorInfo;
|
||||
import io.ray.api.runtimecontext.ActorState;
|
||||
import io.ray.api.runtimecontext.Address;
|
||||
import io.ray.api.runtimecontext.NodeInfo;
|
||||
import io.ray.runtime.generated.Gcs;
|
||||
import io.ray.runtime.generated.Gcs.GcsNodeInfo;
|
||||
import io.ray.runtime.placementgroup.PlacementGroupUtils;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** An implementation of GcsClient. */
|
||||
public class GcsClient {
|
||||
private static Logger LOGGER = LoggerFactory.getLogger(GcsClient.class);
|
||||
|
||||
private GlobalStateAccessor globalStateAccessor;
|
||||
|
||||
public GcsClient(String bootstrapAddress, String redisUsername, String redisPassword) {
|
||||
globalStateAccessor =
|
||||
GlobalStateAccessor.getInstance(bootstrapAddress, redisUsername, redisPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get placement group by {@link PlacementGroupId}.
|
||||
*
|
||||
* @param placementGroupId Id of placement group.
|
||||
* @return The placement group.
|
||||
*/
|
||||
public PlacementGroup getPlacementGroupInfo(PlacementGroupId placementGroupId) {
|
||||
byte[] result = globalStateAccessor.getPlacementGroupInfo(placementGroupId);
|
||||
return PlacementGroupUtils.generatePlacementGroupFromByteArray(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a placement group by name.
|
||||
*
|
||||
* @param name Name of the placement group.
|
||||
* @param namespace The namespace of the placement group.
|
||||
* @return The placement group.
|
||||
*/
|
||||
public PlacementGroup getPlacementGroupInfo(String name, String namespace) {
|
||||
byte[] result = globalStateAccessor.getPlacementGroupInfo(name, namespace);
|
||||
return result == null ? null : PlacementGroupUtils.generatePlacementGroupFromByteArray(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all placement groups in this cluster.
|
||||
*
|
||||
* @return All placement groups.
|
||||
*/
|
||||
public List<PlacementGroup> getAllPlacementGroupInfo() {
|
||||
List<byte[]> results = globalStateAccessor.getAllPlacementGroupInfo();
|
||||
|
||||
List<PlacementGroup> placementGroups = new ArrayList<>();
|
||||
for (byte[] result : results) {
|
||||
placementGroups.add(PlacementGroupUtils.generatePlacementGroupFromByteArray(result));
|
||||
}
|
||||
return placementGroups;
|
||||
}
|
||||
|
||||
public String getInternalKV(String ns, String key) {
|
||||
byte[] value = globalStateAccessor.getInternalKV(ns, key);
|
||||
return value == null ? null : new String(value);
|
||||
}
|
||||
|
||||
public List<NodeInfo> getAllNodeInfo() {
|
||||
List<byte[]> results = globalStateAccessor.getAllNodeInfo();
|
||||
|
||||
// This map is used for deduplication of node entries.
|
||||
Map<UniqueId, NodeInfo> nodes = new HashMap<>();
|
||||
for (byte[] result : results) {
|
||||
Preconditions.checkNotNull(result);
|
||||
GcsNodeInfo data = null;
|
||||
try {
|
||||
data = GcsNodeInfo.parseFrom(result);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new RuntimeException("Received invalid protobuf data from GCS.");
|
||||
}
|
||||
final UniqueId nodeId = UniqueId.fromByteBuffer(data.getNodeId().asReadOnlyByteBuffer());
|
||||
|
||||
// NOTE(lingxuan.zlx): we assume no duplicated node id in fetched node list
|
||||
// and it's only one final state for each node in recorded table.
|
||||
NodeInfo nodeInfo =
|
||||
new NodeInfo(
|
||||
nodeId,
|
||||
data.getNodeManagerAddress(),
|
||||
data.getNodeManagerHostname(),
|
||||
data.getNodeManagerPort(),
|
||||
data.getObjectStoreSocketName(),
|
||||
data.getRayletSocketName(),
|
||||
data.getState() == GcsNodeInfo.GcsNodeState.ALIVE,
|
||||
new HashMap<>(),
|
||||
data.getLabelsMap());
|
||||
if (nodeInfo.isAlive) {
|
||||
nodeInfo.resources.putAll(data.getResourcesTotalMap());
|
||||
}
|
||||
nodes.put(nodeId, nodeInfo);
|
||||
}
|
||||
|
||||
return new ArrayList<>(nodes.values());
|
||||
}
|
||||
|
||||
public List<ActorInfo> getAllActorInfo(JobId jobId, ActorState actorState) {
|
||||
List<ActorInfo> actorInfos = new ArrayList<>();
|
||||
List<byte[]> results = globalStateAccessor.getAllActorInfo(jobId, actorState);
|
||||
results.forEach(
|
||||
result -> {
|
||||
try {
|
||||
Gcs.ActorTableData info = Gcs.ActorTableData.parseFrom(result);
|
||||
UniqueId nodeId = UniqueId.NIL;
|
||||
if (!info.getAddress().getNodeId().isEmpty()) {
|
||||
nodeId =
|
||||
UniqueId.fromByteBuffer(
|
||||
ByteBuffer.wrap(info.getAddress().getNodeId().toByteArray()));
|
||||
}
|
||||
actorInfos.add(
|
||||
new ActorInfo(
|
||||
ActorId.fromBytes(info.getActorId().toByteArray()),
|
||||
ActorState.fromValue(info.getState().getNumber()),
|
||||
info.getNumRestarts(),
|
||||
new Address(
|
||||
nodeId, info.getAddress().getIpAddress(), info.getAddress().getPort()),
|
||||
info.getName()));
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new RayException("Failed to parse actor info.", e);
|
||||
}
|
||||
});
|
||||
|
||||
return actorInfos;
|
||||
}
|
||||
|
||||
/** If the actor exists in GCS. */
|
||||
public boolean actorExists(ActorId actorId) {
|
||||
byte[] result = globalStateAccessor.getActorInfo(actorId);
|
||||
return result != null;
|
||||
}
|
||||
|
||||
public boolean wasCurrentActorRestarted(ActorId actorId) {
|
||||
// TODO(ZhuSenlin): Get the actor table data from CoreWorker later.
|
||||
byte[] value = globalStateAccessor.getActorInfo(actorId);
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
Gcs.ActorTableData actorTableData = null;
|
||||
try {
|
||||
actorTableData = Gcs.ActorTableData.parseFrom(value);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new RuntimeException("Received invalid protobuf data from GCS.");
|
||||
}
|
||||
return actorTableData.getNumRestarts() != 0;
|
||||
}
|
||||
|
||||
public JobId nextJobId() {
|
||||
return JobId.fromBytes(globalStateAccessor.getNextJobID());
|
||||
}
|
||||
|
||||
public GcsNodeInfo getNodeToConnectForDriver(String nodeIpAddress) {
|
||||
byte[] value = globalStateAccessor.getNodeToConnectForDriver(nodeIpAddress);
|
||||
Preconditions.checkNotNull(value);
|
||||
GcsNodeInfo nodeInfo = null;
|
||||
try {
|
||||
nodeInfo = GcsNodeInfo.parseFrom(value);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new RuntimeException("Received invalid protobuf data from GCS.");
|
||||
}
|
||||
return nodeInfo;
|
||||
}
|
||||
|
||||
public byte[] getActorAddress(ActorId actorId) {
|
||||
byte[] serializedActorInfo = globalStateAccessor.getActorInfo(actorId);
|
||||
if (serializedActorInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
Gcs.ActorTableData actorTableData = Gcs.ActorTableData.parseFrom(serializedActorInfo);
|
||||
return actorTableData.getAddress().toByteArray();
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new RuntimeException("Received invalid protobuf data from GCS.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Destroy global state accessor when ray native runtime will be shutdown. */
|
||||
public void destroy() {
|
||||
// Only ray shutdown should call gcs client destroy.
|
||||
LOGGER.debug("Destroying global state accessor.");
|
||||
GlobalStateAccessor.destroyInstance();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.ray.runtime.gcs;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.runtime.config.RayConfig;
|
||||
|
||||
/** Options to create GCS Client. */
|
||||
public class GcsClientOptions {
|
||||
public String ip;
|
||||
public int port;
|
||||
public String username;
|
||||
public String password;
|
||||
|
||||
public GcsClientOptions(RayConfig rayConfig) {
|
||||
String[] ipAndPort = rayConfig.getBootstrapAddress().split(":");
|
||||
Preconditions.checkArgument(ipAndPort.length == 2, "Invalid bootstrap address.");
|
||||
ip = ipAndPort[0];
|
||||
port = Integer.parseInt(ipAndPort[1]);
|
||||
username = rayConfig.redisUsername;
|
||||
password = rayConfig.redisPassword;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package io.ray.runtime.gcs;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.api.id.ActorId;
|
||||
import io.ray.api.id.JobId;
|
||||
import io.ray.api.id.PlacementGroupId;
|
||||
import io.ray.api.runtimecontext.ActorState;
|
||||
import java.util.List;
|
||||
|
||||
/** `GlobalStateAccessor` is used for accessing information from GCS. */
|
||||
public class GlobalStateAccessor {
|
||||
// NOTE(lingxuan.zlx): this is a singleton, it can not be changed during a Ray session.
|
||||
// Native pointer to the C++ GcsStateAccessor.
|
||||
private Long globalStateAccessorNativePointer = 0L;
|
||||
private static GlobalStateAccessor globalStateAccessor;
|
||||
|
||||
public static synchronized GlobalStateAccessor getInstance(
|
||||
String bootstrapAddress, String redisUsername, String redisPassword) {
|
||||
if (null == globalStateAccessor) {
|
||||
globalStateAccessor = new GlobalStateAccessor(bootstrapAddress, redisUsername, redisPassword);
|
||||
}
|
||||
return globalStateAccessor;
|
||||
}
|
||||
|
||||
public static synchronized void destroyInstance() {
|
||||
if (null != globalStateAccessor) {
|
||||
globalStateAccessor.destroyGlobalStateAccessor();
|
||||
globalStateAccessor = null;
|
||||
}
|
||||
}
|
||||
|
||||
private GlobalStateAccessor(String bootstrapAddress, String redisUsername, String redisPassword) {
|
||||
globalStateAccessorNativePointer =
|
||||
nativeCreateGlobalStateAccessor(bootstrapAddress, redisUsername, redisPassword);
|
||||
validateGlobalStateAccessorPointer();
|
||||
connect();
|
||||
}
|
||||
|
||||
private boolean connect() {
|
||||
return this.nativeConnect(globalStateAccessorNativePointer);
|
||||
}
|
||||
|
||||
private void validateGlobalStateAccessorPointer() {
|
||||
Preconditions.checkState(
|
||||
globalStateAccessorNativePointer != 0,
|
||||
"Global state accessor native pointer must not be 0.");
|
||||
}
|
||||
|
||||
/** Returns A list of job info with JobInfo protobuf schema. */
|
||||
public List<byte[]> getAllJobInfo() {
|
||||
// Fetch a job list with protobuf bytes format from GCS.
|
||||
synchronized (GlobalStateAccessor.class) {
|
||||
validateGlobalStateAccessorPointer();
|
||||
return this.nativeGetAllJobInfo(globalStateAccessorNativePointer);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns next job id. */
|
||||
public byte[] getNextJobID() {
|
||||
// Get next job id from GCS.
|
||||
synchronized (GlobalStateAccessor.class) {
|
||||
validateGlobalStateAccessorPointer();
|
||||
return this.nativeGetNextJobID(globalStateAccessorNativePointer);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns A list of node info with GcsNodeInfo protobuf schema. */
|
||||
public List<byte[]> getAllNodeInfo() {
|
||||
// Fetch a node list with protobuf bytes format from GCS.
|
||||
synchronized (GlobalStateAccessor.class) {
|
||||
validateGlobalStateAccessorPointer();
|
||||
return this.nativeGetAllNodeInfo(globalStateAccessorNativePointer);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getPlacementGroupInfo(PlacementGroupId placementGroupId) {
|
||||
synchronized (GlobalStateAccessor.class) {
|
||||
validateGlobalStateAccessorPointer();
|
||||
return nativeGetPlacementGroupInfo(
|
||||
globalStateAccessorNativePointer, placementGroupId.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getPlacementGroupInfo(String name, String namespace) {
|
||||
synchronized (GlobalStateAccessor.class) {
|
||||
validateGlobalStateAccessorPointer();
|
||||
return nativeGetPlacementGroupInfoByName(globalStateAccessorNativePointer, name, namespace);
|
||||
}
|
||||
}
|
||||
|
||||
public List<byte[]> getAllPlacementGroupInfo() {
|
||||
synchronized (GlobalStateAccessor.class) {
|
||||
validateGlobalStateAccessorPointer();
|
||||
return this.nativeGetAllPlacementGroupInfo(globalStateAccessorNativePointer);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getInternalKV(String n, String k) {
|
||||
synchronized (GlobalStateAccessor.class) {
|
||||
validateGlobalStateAccessorPointer();
|
||||
return this.nativeGetInternalKV(globalStateAccessorNativePointer, n, k);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns A list of actor info with ActorInfo protobuf schema. */
|
||||
public List<byte[]> getAllActorInfo(JobId jobId, ActorState actorState) {
|
||||
// Fetch a actor list with protobuf bytes format from GCS.
|
||||
synchronized (GlobalStateAccessor.class) {
|
||||
validateGlobalStateAccessorPointer();
|
||||
byte[] jobIdBytes = null;
|
||||
String actorStateName = null;
|
||||
if (jobId != null) {
|
||||
jobIdBytes = jobId.getBytes();
|
||||
}
|
||||
if (actorState != null) {
|
||||
actorStateName = actorState.getName();
|
||||
}
|
||||
return this.nativeGetAllActorInfo(
|
||||
globalStateAccessorNativePointer, jobIdBytes, actorStateName);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns An actor info with ActorInfo protobuf schema. */
|
||||
public byte[] getActorInfo(ActorId actorId) {
|
||||
// Fetch an actor with protobuf bytes format from GCS.
|
||||
synchronized (GlobalStateAccessor.class) {
|
||||
validateGlobalStateAccessorPointer();
|
||||
return this.nativeGetActorInfo(globalStateAccessorNativePointer, actorId.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the node to connect for a Ray driver. */
|
||||
public byte[] getNodeToConnectForDriver(String nodeIpAddress) {
|
||||
// Fetch a node with protobuf bytes format from GCS.
|
||||
synchronized (GlobalStateAccessor.class) {
|
||||
validateGlobalStateAccessorPointer();
|
||||
return this.nativeGetNodeToConnectForDriver(globalStateAccessorNativePointer, nodeIpAddress);
|
||||
}
|
||||
}
|
||||
|
||||
private void destroyGlobalStateAccessor() {
|
||||
synchronized (GlobalStateAccessor.class) {
|
||||
if (0 == globalStateAccessorNativePointer) {
|
||||
return;
|
||||
}
|
||||
this.nativeDestroyGlobalStateAccessor(globalStateAccessorNativePointer);
|
||||
globalStateAccessorNativePointer = 0L;
|
||||
}
|
||||
}
|
||||
|
||||
private native long nativeCreateGlobalStateAccessor(
|
||||
String redisAddress, String redisUsername, String redisPassword);
|
||||
|
||||
private native void nativeDestroyGlobalStateAccessor(long nativePtr);
|
||||
|
||||
private native boolean nativeConnect(long nativePtr);
|
||||
|
||||
private native List<byte[]> nativeGetAllJobInfo(long nativePtr);
|
||||
|
||||
private native byte[] nativeGetNextJobID(long nativePtr);
|
||||
|
||||
private native List<byte[]> nativeGetAllNodeInfo(long nativePtr);
|
||||
|
||||
private native List<byte[]> nativeGetAllActorInfo(
|
||||
long nativePtr, byte[] jobId, String actorStateName);
|
||||
|
||||
private native byte[] nativeGetActorInfo(long nativePtr, byte[] actorId);
|
||||
|
||||
private native byte[] nativeGetPlacementGroupInfo(long nativePtr, byte[] placementGroupId);
|
||||
|
||||
private native byte[] nativeGetPlacementGroupInfoByName(
|
||||
long nativePtr, String name, String namespace);
|
||||
|
||||
private native List<byte[]> nativeGetAllPlacementGroupInfo(long nativePtr);
|
||||
|
||||
private native byte[] nativeGetInternalKV(long nativePtr, String n, String k);
|
||||
|
||||
private native byte[] nativeGetNodeToConnectForDriver(long nativePtr, String nodeIpAddress);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.ray.runtime.metric;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.DoubleAdder;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** Count measurement is mapped to count object in stats and counts the number. */
|
||||
public class Count extends Metric {
|
||||
|
||||
private DoubleAdder count;
|
||||
|
||||
@Deprecated
|
||||
public Count(String name, String description, String unit, Map<TagKey, String> tags) {
|
||||
super(name, tags);
|
||||
count = new DoubleAdder();
|
||||
metricNativePointer =
|
||||
NativeMetric.registerCountNative(
|
||||
name,
|
||||
description,
|
||||
unit,
|
||||
tags.keySet().stream().map(TagKey::getTagKey).collect(Collectors.toList()));
|
||||
Preconditions.checkState(metricNativePointer != 0, "Count native pointer must not be 0.");
|
||||
}
|
||||
|
||||
public Count(String name, String description, Map<String, String> tags) {
|
||||
this(name, description, "", TagKey.tagsFromMap(tags));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(double value) {
|
||||
count.add(value);
|
||||
this.value.addAndGet(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double getAndReset() {
|
||||
return count.sumThenReset();
|
||||
}
|
||||
|
||||
public double getCount() {
|
||||
return this.value.get();
|
||||
}
|
||||
|
||||
public void inc(double delta) {
|
||||
update(delta);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package io.ray.runtime.metric;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** Gauge measurement is mapped to gauge object in stats and is recording the last value. */
|
||||
public class Gauge extends Metric {
|
||||
|
||||
@Deprecated
|
||||
public Gauge(String name, String description, String unit, Map<TagKey, String> tags) {
|
||||
super(name, tags);
|
||||
metricNativePointer =
|
||||
NativeMetric.registerGaugeNative(
|
||||
name,
|
||||
description,
|
||||
unit,
|
||||
tags.keySet().stream().map(TagKey::getTagKey).collect(Collectors.toList()));
|
||||
Preconditions.checkState(metricNativePointer != 0, "Gauge native pointer must not be 0.");
|
||||
}
|
||||
|
||||
public Gauge(String name, String description, Map<String, String> tags) {
|
||||
this(name, description, "", TagKey.tagsFromMap(tags));
|
||||
}
|
||||
|
||||
public double getValue() {
|
||||
return value.doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double getAndReset() {
|
||||
return value.doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(double value) {
|
||||
this.value.set(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package io.ray.runtime.metric;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Histogram measurement is mapped to histogram object in stats. In order to reduce JNI calls
|
||||
* overhead, a memory historical window is used for storing transient value and we assume its max
|
||||
* size is 100.
|
||||
*/
|
||||
public class Histogram extends Metric {
|
||||
|
||||
private List<Double> histogramWindow;
|
||||
public static final int HISTOGRAM_WINDOW_SIZE = 100;
|
||||
|
||||
@Deprecated
|
||||
public Histogram(
|
||||
String name,
|
||||
String description,
|
||||
String unit,
|
||||
List<Double> boundaries,
|
||||
Map<TagKey, String> tags) {
|
||||
super(name, tags);
|
||||
metricNativePointer =
|
||||
NativeMetric.registerHistogramNative(
|
||||
name,
|
||||
description,
|
||||
unit,
|
||||
boundaries.stream().mapToDouble(Double::doubleValue).toArray(),
|
||||
tags.keySet().stream().map(TagKey::getTagKey).collect(Collectors.toList()));
|
||||
Preconditions.checkState(metricNativePointer != 0, "Histogram native pointer must not be 0.");
|
||||
histogramWindow = Collections.synchronizedList(new ArrayList<>());
|
||||
}
|
||||
|
||||
public Histogram(
|
||||
String name, String description, List<Double> boundaries, Map<String, String> tags) {
|
||||
this(name, description, "", boundaries, TagKey.tagsFromMap(tags));
|
||||
}
|
||||
|
||||
private void updateForWindow(double value) {
|
||||
if (histogramWindow.size() == HISTOGRAM_WINDOW_SIZE) {
|
||||
histogramWindow.remove(0);
|
||||
}
|
||||
histogramWindow.add(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(double value) {
|
||||
updateForWindow(value);
|
||||
this.value.set(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double getAndReset() {
|
||||
histogramWindow.clear();
|
||||
return value.doubleValue();
|
||||
}
|
||||
|
||||
public List<Double> getHistogramWindow() {
|
||||
return histogramWindow;
|
||||
}
|
||||
|
||||
public double getValue() {
|
||||
return value.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package io.ray.runtime.metric;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.util.concurrent.AtomicDouble;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Class metric is mapped to stats metric object in core worker. it must be in categories set
|
||||
* [Gague, Count, Sum, Histogram].
|
||||
*/
|
||||
public abstract class Metric {
|
||||
protected String name;
|
||||
|
||||
protected AtomicDouble value;
|
||||
|
||||
// Native pointer mapping to gauge object of stats.
|
||||
protected long metricNativePointer = 0L;
|
||||
|
||||
protected Map<TagKey, String> tags;
|
||||
|
||||
public Metric(String name, Map<TagKey, String> tags) {
|
||||
Preconditions.checkNotNull(tags, "Metric tags map must not be null.");
|
||||
Preconditions.checkNotNull(name, "Metric name must not be null.");
|
||||
this.name = name;
|
||||
this.tags = tags;
|
||||
this.value = new AtomicDouble();
|
||||
}
|
||||
|
||||
// Sync metric with core worker stats for registry.
|
||||
// Metric data will be flushed into stats view data inside core worker immediately after
|
||||
// record is called.
|
||||
|
||||
/** Flush records to stats in last aggregator. */
|
||||
public void record() {
|
||||
Preconditions.checkState(metricNativePointer != 0, "Metric native pointer must not be 0.");
|
||||
// Get tag key list from map;
|
||||
List<TagKey> nativeTagKeyList = new ArrayList<>();
|
||||
List<String> tagValues = new ArrayList<>();
|
||||
for (Map.Entry<TagKey, String> entry : tags.entrySet()) {
|
||||
nativeTagKeyList.add(entry.getKey());
|
||||
tagValues.add(entry.getValue());
|
||||
}
|
||||
// Get tag value list from map;
|
||||
NativeMetric.recordNative(
|
||||
metricNativePointer,
|
||||
getAndReset(),
|
||||
nativeTagKeyList.stream().map(TagKey::getTagKey).collect(Collectors.toList()),
|
||||
tagValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value to record and then reset.
|
||||
*
|
||||
* @return latest updating value.
|
||||
*/
|
||||
protected abstract double getAndReset();
|
||||
|
||||
/**
|
||||
* Update metric value without tags. Update metric info for user.
|
||||
*
|
||||
* @param value latest value for updating
|
||||
*/
|
||||
public abstract void update(double value);
|
||||
|
||||
/**
|
||||
* Update metric value with dynamic tag values.
|
||||
*
|
||||
* @param value latest value for updating
|
||||
* @param tags tag map
|
||||
*/
|
||||
public void update(double value, Map<TagKey, String> tags) {
|
||||
update(value);
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update metric value with dynamic tag values in pair string format.
|
||||
*
|
||||
* @param tags tag map
|
||||
* @param value latest value for updating
|
||||
*/
|
||||
public void update(Map<String, String> tags, double value) {
|
||||
update(value);
|
||||
this.tags = TagKey.tagsFromMap(tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert tagkey list to map in string format.
|
||||
*
|
||||
* @return tags.
|
||||
*/
|
||||
public Map<String, String> getTagMap() {
|
||||
Map<String, String> tagMap = new HashMap<>();
|
||||
for (Map.Entry<TagKey, String> entry : tags.entrySet()) {
|
||||
tagMap.put(entry.getKey().getTagKey(), entry.getValue());
|
||||
}
|
||||
return tagMap;
|
||||
}
|
||||
|
||||
/** Deallocate object from stats and reset native pointer in null. */
|
||||
public void unregister() {
|
||||
if (0 != metricNativePointer) {
|
||||
NativeMetric.unregisterMetricNative(metricNativePointer);
|
||||
}
|
||||
metricNativePointer = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package io.ray.runtime.metric;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
|
||||
/** Configurations of the metric. */
|
||||
public class MetricConfig {
|
||||
|
||||
private static final long DEFAULT_TIME_INTERVAL_MS = 5000L;
|
||||
private static final int DEFAULT_THREAD_POLL_SIZE = 1;
|
||||
private static final long DEFAULT_SHUTDOWN_WAIT_TIME_MS = 3000L;
|
||||
|
||||
public static final MetricConfig DEFAULT_CONFIG =
|
||||
new MetricConfig(
|
||||
DEFAULT_TIME_INTERVAL_MS, DEFAULT_THREAD_POLL_SIZE, DEFAULT_SHUTDOWN_WAIT_TIME_MS);
|
||||
|
||||
private final long timeIntervalMs;
|
||||
private final int threadPoolSize;
|
||||
private final long shutdownWaitTimeMs;
|
||||
|
||||
public MetricConfig(long timeIntervalMs, int threadPoolSize, long shutdownWaitTimeMs) {
|
||||
this.timeIntervalMs = timeIntervalMs;
|
||||
this.threadPoolSize = threadPoolSize;
|
||||
this.shutdownWaitTimeMs = shutdownWaitTimeMs;
|
||||
}
|
||||
|
||||
public long timeIntervalMs() {
|
||||
return timeIntervalMs;
|
||||
}
|
||||
|
||||
public int threadPoolSize() {
|
||||
return threadPoolSize;
|
||||
}
|
||||
|
||||
public long shutdownWaitTimeMs() {
|
||||
return shutdownWaitTimeMs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("timeIntervalMs", timeIntervalMs)
|
||||
.add("threadPoolSize", threadPoolSize)
|
||||
.add("shutdownWaitTimeMs", shutdownWaitTimeMs)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public static MetricConfigBuilder builder() {
|
||||
return new MetricConfigBuilder();
|
||||
}
|
||||
|
||||
public static class MetricConfigBuilder {
|
||||
private long timeIntervalMs = DEFAULT_TIME_INTERVAL_MS;
|
||||
private int threadPooSize = DEFAULT_THREAD_POLL_SIZE;
|
||||
private long shutdownWaitTimeMs = DEFAULT_SHUTDOWN_WAIT_TIME_MS;
|
||||
|
||||
public MetricConfig create() {
|
||||
return new MetricConfig(timeIntervalMs, threadPooSize, shutdownWaitTimeMs);
|
||||
}
|
||||
|
||||
public MetricConfigBuilder timeIntervalMs(long timeIntervalMs) {
|
||||
this.timeIntervalMs = timeIntervalMs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MetricConfigBuilder threadPoolSize(int threadPooSize) {
|
||||
this.threadPooSize = threadPooSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MetricConfigBuilder shutdownWaitTimeMs(long shutdownWaitTimeMs) {
|
||||
this.shutdownWaitTimeMs = shutdownWaitTimeMs;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package io.ray.runtime.metric;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* MetricId represents a metric with a given type, name and tags. If two metrics have the same type
|
||||
* and name but different tags(including key and value), they have a different MetricId. And in this
|
||||
* way, {@link MetricRegistry} can register two metrics with same name but different tags.
|
||||
*/
|
||||
public class MetricId {
|
||||
|
||||
private final MetricType type;
|
||||
private final String name;
|
||||
private final Map<TagKey, String> tags;
|
||||
|
||||
public MetricId(MetricType type, String name, Map<TagKey, String> tags) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof MetricId)) {
|
||||
return false;
|
||||
}
|
||||
MetricId metricId = (MetricId) o;
|
||||
return type == metricId.type
|
||||
&& Objects.equals(name, metricId.name)
|
||||
&& Objects.equals(tags, metricId.tags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(type, name, tags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("type", type)
|
||||
.add("name", name)
|
||||
.add("tags", tags)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public MetricType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Map<TagKey, String> getTags() {
|
||||
return tags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package io.ray.runtime.metric;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** MetricRegistry is a registry for metrics to be registered and updates metrics. */
|
||||
public class MetricRegistry {
|
||||
|
||||
public static final MetricRegistry DEFAULT_REGISTRY = new MetricRegistry();
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MetricRegistry.class);
|
||||
|
||||
private final Map<MetricId, Metric> registeredMetrics = new HashMap<>(64);
|
||||
|
||||
private MetricConfig metricConfig;
|
||||
private ScheduledExecutorService scheduledExecutorService;
|
||||
private volatile boolean isRunning = false;
|
||||
|
||||
public void startup() {
|
||||
startup(MetricConfig.DEFAULT_CONFIG);
|
||||
}
|
||||
|
||||
public void startup(MetricConfig metricConfig) {
|
||||
synchronized (this) {
|
||||
if (!isRunning) {
|
||||
this.metricConfig = metricConfig;
|
||||
scheduledExecutorService =
|
||||
new ScheduledThreadPoolExecutor(
|
||||
metricConfig.threadPoolSize(),
|
||||
new ThreadFactoryBuilder().setNameFormat("metric-registry-%d").build());
|
||||
scheduledExecutorService.scheduleAtFixedRate(
|
||||
this::update,
|
||||
metricConfig.timeIntervalMs(),
|
||||
metricConfig.timeIntervalMs(),
|
||||
TimeUnit.MILLISECONDS);
|
||||
isRunning = true;
|
||||
LOG.info("Finished startup metric registry, metricConfig is {}.", metricConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
synchronized (this) {
|
||||
if (isRunning && scheduledExecutorService != null) {
|
||||
try {
|
||||
scheduledExecutorService.shutdownNow();
|
||||
if (!scheduledExecutorService.awaitTermination(
|
||||
metricConfig.shutdownWaitTimeMs(), TimeUnit.MILLISECONDS)) {
|
||||
LOG.warn(
|
||||
"Metric registry did not shut down in {}ms time, so try to shut down again.",
|
||||
metricConfig.shutdownWaitTimeMs());
|
||||
scheduledExecutorService.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.warn(
|
||||
"Interrupted when shutting down metric registry, so try to shut down again.",
|
||||
e.getMessage(),
|
||||
e);
|
||||
scheduledExecutorService.shutdownNow();
|
||||
}
|
||||
if (scheduledExecutorService.isShutdown()) {
|
||||
isRunning = false;
|
||||
scheduledExecutorService = null;
|
||||
LOG.info("Metric registry has been shut down.");
|
||||
} else {
|
||||
LOG.warn("Failed to shut down metric registry service.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Metric register(Metric metric) {
|
||||
synchronized (this) {
|
||||
if (!isRunning) {
|
||||
LOG.warn("Failed to register a metric, because the metric registry is not running.");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
MetricId id = genMetricIdByMetric(metric);
|
||||
Metric ori = registeredMetrics.putIfAbsent(id, metric);
|
||||
if (ori == null) {
|
||||
return metric;
|
||||
} else {
|
||||
LOG.info("Metric {} has already registered, so use the previous one.", id);
|
||||
return ori;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Failed to register a metric: ", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void unregister(Metric metric) {
|
||||
synchronized (this) {
|
||||
if (!isRunning) {
|
||||
LOG.warn("Failed to unregister a metric, because the metric registry is not running.");
|
||||
}
|
||||
try {
|
||||
MetricId id = genMetricIdByMetric(metric);
|
||||
registeredMetrics.remove(id);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Failed to unregister a metric: ", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void update() {
|
||||
registeredMetrics.forEach(
|
||||
(id, metric) -> {
|
||||
metric.record();
|
||||
});
|
||||
}
|
||||
|
||||
private MetricType getMetricType(Metric metric) {
|
||||
if (metric instanceof Count) {
|
||||
return MetricType.COUNT;
|
||||
}
|
||||
if (metric instanceof Gauge) {
|
||||
return MetricType.GAUGE;
|
||||
}
|
||||
if (metric instanceof Sum) {
|
||||
return MetricType.SUM;
|
||||
}
|
||||
if (metric instanceof Histogram) {
|
||||
return MetricType.HISTOGRAM;
|
||||
}
|
||||
throw new RuntimeException(
|
||||
"Unknown metric type, the metric is " + metric.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
private MetricId genMetricIdByMetric(Metric metric) {
|
||||
return new MetricId(getMetricType(metric), metric.name, metric.tags);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.ray.runtime.metric;
|
||||
|
||||
/** Types of the metric. */
|
||||
public enum MetricType {
|
||||
COUNT,
|
||||
|
||||
GAUGE,
|
||||
|
||||
SUM,
|
||||
|
||||
HISTOGRAM
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package io.ray.runtime.metric;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** The entry of metrics for easy use. */
|
||||
public final class Metrics {
|
||||
|
||||
private static MetricRegistry metricRegistry;
|
||||
|
||||
public static MetricRegistry init(MetricConfig metricConfig) {
|
||||
synchronized (Metrics.class) {
|
||||
metricRegistry = new MetricRegistry();
|
||||
metricRegistry.startup(metricConfig);
|
||||
return metricRegistry;
|
||||
}
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
synchronized (Metrics.class) {
|
||||
if (metricRegistry != null) {
|
||||
metricRegistry.shutdown();
|
||||
metricRegistry = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static CountBuilder count() {
|
||||
return new CountBuilder();
|
||||
}
|
||||
|
||||
public static GaugeBuilder gauge() {
|
||||
return new GaugeBuilder();
|
||||
}
|
||||
|
||||
public static SumBuilder sum() {
|
||||
return new SumBuilder();
|
||||
}
|
||||
|
||||
public static HistogramBuilder histogram() {
|
||||
return new HistogramBuilder();
|
||||
}
|
||||
|
||||
public static class CountBuilder extends AbstractBuilder<CountBuilder, Count> {
|
||||
|
||||
@Override
|
||||
protected Count create() {
|
||||
return new Count(name, description, unit, generateTagKeysMap(tags));
|
||||
}
|
||||
}
|
||||
|
||||
public static class GaugeBuilder extends AbstractBuilder<GaugeBuilder, Gauge> {
|
||||
|
||||
@Override
|
||||
protected Gauge create() {
|
||||
return new Gauge(name, description, unit, generateTagKeysMap(tags));
|
||||
}
|
||||
}
|
||||
|
||||
public static class SumBuilder extends AbstractBuilder<SumBuilder, Sum> {
|
||||
|
||||
@Override
|
||||
protected Sum create() {
|
||||
return new Sum(name, description, unit, generateTagKeysMap(tags));
|
||||
}
|
||||
}
|
||||
|
||||
public static class HistogramBuilder extends AbstractBuilder<HistogramBuilder, Histogram> {
|
||||
|
||||
private List<Double> boundaries;
|
||||
|
||||
public HistogramBuilder boundaries(List<Double> boundaries) {
|
||||
this.boundaries = boundaries;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Histogram create() {
|
||||
return new Histogram(name, description, unit, boundaries, generateTagKeysMap(tags));
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class AbstractBuilder<B extends AbstractBuilder, M extends Metric> {
|
||||
protected String name;
|
||||
protected String description;
|
||||
protected String unit;
|
||||
protected Map<String, String> tags;
|
||||
|
||||
public B name(String name) {
|
||||
this.name = Preconditions.checkNotNull(name);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B description(String description) {
|
||||
this.description = Preconditions.checkNotNull(description);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B unit(String unit) {
|
||||
this.unit = Preconditions.checkNotNull(unit);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B tags(Map<String, String> tags) {
|
||||
this.tags = Preconditions.checkNotNull(tags);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a metric by sub-class.
|
||||
*
|
||||
* @return a metric
|
||||
*/
|
||||
protected abstract M create();
|
||||
|
||||
public M register() {
|
||||
M m = create();
|
||||
maybeInitRegistry();
|
||||
return (M) metricRegistry.register(m);
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<TagKey, String> generateTagKeysMap(Map<String, String> tags) {
|
||||
Map<TagKey, String> tagKeys = new HashMap<>(tags.size() * 2);
|
||||
tags.forEach(
|
||||
(key, value) -> {
|
||||
TagKey tagKey = new TagKey(key);
|
||||
tagKeys.put(tagKey, value);
|
||||
});
|
||||
return tagKeys;
|
||||
}
|
||||
|
||||
private static MetricRegistry maybeInitRegistry() {
|
||||
synchronized (Metrics.class) {
|
||||
if (metricRegistry != null) {
|
||||
return metricRegistry;
|
||||
} else {
|
||||
metricRegistry = MetricRegistry.DEFAULT_REGISTRY;
|
||||
metricRegistry.startup();
|
||||
return metricRegistry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.ray.runtime.metric;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Native metric provide a native interface to register tag or metric for current metric package.
|
||||
*/
|
||||
class NativeMetric {
|
||||
public static native void registerTagkeyNative(String tagKey);
|
||||
|
||||
public static native long registerCountNative(
|
||||
String name, String description, String unit, List<String> tagKeys);
|
||||
|
||||
public static native long registerGaugeNative(
|
||||
String name, String description, String unit, List<String> tagKeys);
|
||||
|
||||
public static native long registerHistogramNative(
|
||||
String name, String description, String unit, double[] boundaries, List<String> tagKeys);
|
||||
|
||||
public static native long registerSumNative(
|
||||
String name, String description, String unit, List<String> tagKeys);
|
||||
|
||||
public static native void recordNative(
|
||||
long metricNativePointer, double value, List tagKeys, List<String> tagValues);
|
||||
|
||||
public static native void unregisterMetricNative(long gaugePtr);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.ray.runtime.metric;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.DoubleAdder;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Sum measurement is mapped to sum object in stats. Property sum is used for storing transient sum
|
||||
* for registry aggregation.
|
||||
*/
|
||||
public class Sum extends Metric {
|
||||
|
||||
private DoubleAdder sum;
|
||||
|
||||
@Deprecated
|
||||
public Sum(String name, String description, String unit, Map<TagKey, String> tags) {
|
||||
super(name, tags);
|
||||
metricNativePointer =
|
||||
NativeMetric.registerSumNative(
|
||||
name,
|
||||
description,
|
||||
unit,
|
||||
tags.keySet().stream().map(TagKey::getTagKey).collect(Collectors.toList()));
|
||||
Preconditions.checkState(metricNativePointer != 0, "Count native pointer must not be 0.");
|
||||
this.sum = new DoubleAdder();
|
||||
}
|
||||
|
||||
public Sum(String name, String description, Map<String, String> tags) {
|
||||
this(name, description, "", TagKey.tagsFromMap(tags));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(double value) {
|
||||
sum.add(value);
|
||||
this.value.addAndGet(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double getAndReset() {
|
||||
return sum.sumThenReset();
|
||||
}
|
||||
|
||||
public double getSum() {
|
||||
return value.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package io.ray.runtime.metric;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Tagkey is mapping java object to stats tagkey object. */
|
||||
public class TagKey {
|
||||
|
||||
private String tagKey;
|
||||
|
||||
public TagKey(String key) {
|
||||
tagKey = key;
|
||||
NativeMetric.registerTagkeyNative(key);
|
||||
}
|
||||
|
||||
public String getTagKey() {
|
||||
return tagKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert pair of string and string map to tags map that can be recognized in native layer.
|
||||
*
|
||||
* @param tags metrics key value map.
|
||||
* @return
|
||||
*/
|
||||
public static Map<TagKey, String> tagsFromMap(Map<String, String> tags) {
|
||||
Map<TagKey, String> tagKeyMap = new HashMap<>();
|
||||
for (Map.Entry<String, String> entry : tags.entrySet()) {
|
||||
tagKeyMap.put(new TagKey(entry.getKey()), entry.getValue());
|
||||
}
|
||||
return tagKeyMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof TagKey)) {
|
||||
return false;
|
||||
}
|
||||
TagKey tagKey1 = (TagKey) o;
|
||||
return Objects.equals(tagKey, tagKey1.tagKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(tagKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TagKey{" + ", tagKey='" + tagKey + '\'' + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package io.ray.runtime.object;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.api.exception.RayTimeoutException;
|
||||
import io.ray.api.id.ObjectId;
|
||||
import io.ray.runtime.context.WorkerContext;
|
||||
import io.ray.runtime.generated.Common.Address;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Object store methods for local mode. */
|
||||
public class LocalModeObjectStore extends ObjectStore {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(LocalModeObjectStore.class);
|
||||
|
||||
private static final int GET_CHECK_INTERVAL_MS = 1;
|
||||
|
||||
private final Map<ObjectId, NativeRayObject> pool = new ConcurrentHashMap<>();
|
||||
private final List<Consumer<ObjectId>> objectPutCallbacks = new ArrayList<>();
|
||||
|
||||
public LocalModeObjectStore(WorkerContext workerContext) {
|
||||
super(workerContext);
|
||||
}
|
||||
|
||||
public void addObjectPutCallback(Consumer<ObjectId> callback) {
|
||||
this.objectPutCallbacks.add(callback);
|
||||
}
|
||||
|
||||
public boolean isObjectReady(ObjectId id) {
|
||||
return pool.containsKey(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectId putRaw(NativeRayObject obj) {
|
||||
ObjectId objectId = ObjectId.fromRandom();
|
||||
putRaw(obj, objectId);
|
||||
return objectId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putRaw(NativeRayObject obj, ObjectId objectId) {
|
||||
Preconditions.checkNotNull(obj);
|
||||
Preconditions.checkNotNull(objectId);
|
||||
pool.putIfAbsent(objectId, obj);
|
||||
for (Consumer<ObjectId> callback : objectPutCallbacks) {
|
||||
callback.accept(objectId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NativeRayObject> getRaw(List<ObjectId> objectIds, long timeoutMs) {
|
||||
waitInternal(objectIds, objectIds.size(), timeoutMs);
|
||||
if (timeoutMs >= 0 && objectIds.stream().filter(pool::containsKey).count() < objectIds.size()) {
|
||||
throw new RayTimeoutException("Get timed out: some object(s) not ready.");
|
||||
}
|
||||
return objectIds.stream().map(pool::get).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Boolean> wait(
|
||||
List<ObjectId> objectIds, int numObjects, long timeoutMs, boolean fetchLocal) {
|
||||
waitInternal(objectIds, numObjects, timeoutMs);
|
||||
return objectIds.stream().map(pool::containsKey).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void waitInternal(List<ObjectId> objectIds, int numObjects, long timeoutMs) {
|
||||
int ready = 0;
|
||||
long remainingTime = timeoutMs;
|
||||
boolean firstCheck = true;
|
||||
while (ready < numObjects && (timeoutMs < 0 || remainingTime > 0)) {
|
||||
if (!firstCheck) {
|
||||
long sleepTime =
|
||||
timeoutMs < 0 ? GET_CHECK_INTERVAL_MS : Math.min(remainingTime, GET_CHECK_INTERVAL_MS);
|
||||
try {
|
||||
Thread.sleep(sleepTime);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.warn("Got InterruptedException while sleeping.");
|
||||
}
|
||||
remainingTime -= sleepTime;
|
||||
}
|
||||
ready = 0;
|
||||
for (ObjectId objectId : objectIds) {
|
||||
if (pool.containsKey(objectId)) {
|
||||
ready += 1;
|
||||
}
|
||||
}
|
||||
firstCheck = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(List<ObjectId> objectIds, boolean localOnly) {
|
||||
for (ObjectId objectId : objectIds) {
|
||||
pool.remove(objectId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLocalReference(ObjectId objectId) {}
|
||||
|
||||
@Override
|
||||
public void removeLocalReference(ObjectId objectId) {}
|
||||
|
||||
@Override
|
||||
public Address getOwnerAddress(ObjectId id) {
|
||||
return Address.getDefaultInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getOwnershipInfo(ObjectId objectId) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerOwnershipInfoAndResolveFuture(
|
||||
ObjectId objectId, ObjectId outerObjectId, byte[] ownerAddress) {}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package io.ray.runtime.object;
|
||||
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import io.ray.api.id.BaseId;
|
||||
import io.ray.api.id.ObjectId;
|
||||
import io.ray.runtime.context.WorkerContext;
|
||||
import io.ray.runtime.generated.Common.Address;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Object store methods for cluster mode. This is a wrapper class for core worker object interface.
|
||||
*/
|
||||
public class NativeObjectStore extends ObjectStore {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(NativeObjectStore.class);
|
||||
|
||||
private final ReadWriteLock shutdownLock;
|
||||
|
||||
public NativeObjectStore(WorkerContext workerContext, ReadWriteLock shutdownLock) {
|
||||
super(workerContext);
|
||||
this.shutdownLock = shutdownLock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectId putRaw(NativeRayObject obj) {
|
||||
return new ObjectId(nativePut(obj));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putRaw(NativeRayObject obj, ObjectId objectId) {
|
||||
nativePut(objectId.getBytes(), obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NativeRayObject> getRaw(List<ObjectId> objectIds, long timeoutMs) {
|
||||
return nativeGet(toBinaryList(objectIds), timeoutMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Boolean> wait(
|
||||
List<ObjectId> objectIds, int numObjects, long timeoutMs, boolean fetchLocal) {
|
||||
return nativeWait(toBinaryList(objectIds), numObjects, timeoutMs, fetchLocal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(List<ObjectId> objectIds, boolean localOnly) {
|
||||
nativeDelete(toBinaryList(objectIds), localOnly);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLocalReference(ObjectId objectId) {
|
||||
nativeAddLocalReference(objectId.getBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeLocalReference(ObjectId objectId) {
|
||||
Lock readLock = shutdownLock.readLock();
|
||||
readLock.lock();
|
||||
try {
|
||||
nativeRemoveLocalReference(objectId.getBytes());
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getOwnershipInfo(ObjectId objectId) {
|
||||
return nativeGetOwnershipInfo(objectId.getBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerOwnershipInfoAndResolveFuture(
|
||||
ObjectId objectId, ObjectId outerObjectId, byte[] ownerAddress) {
|
||||
byte[] outer = null;
|
||||
if (outerObjectId != null) {
|
||||
outer = outerObjectId.getBytes();
|
||||
}
|
||||
nativeRegisterOwnershipInfoAndResolveFuture(objectId.getBytes(), outer, ownerAddress);
|
||||
}
|
||||
|
||||
public Map<ObjectId, long[]> getAllReferenceCounts() {
|
||||
Map<ObjectId, long[]> referenceCounts = new HashMap<>();
|
||||
for (Map.Entry<byte[], long[]> entry : nativeGetAllReferenceCounts().entrySet()) {
|
||||
referenceCounts.put(new ObjectId(entry.getKey()), entry.getValue());
|
||||
}
|
||||
return referenceCounts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getOwnerAddress(ObjectId id) {
|
||||
try {
|
||||
return Address.parseFrom(nativeGetOwnerAddress(id.getBytes()));
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<byte[]> toBinaryList(List<ObjectId> ids) {
|
||||
return ids.stream().map(BaseId::getBytes).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static native byte[] nativePut(NativeRayObject obj);
|
||||
|
||||
private static native void nativePut(byte[] objectId, NativeRayObject obj);
|
||||
|
||||
private static native List<NativeRayObject> nativeGet(List<byte[]> ids, long timeoutMs);
|
||||
|
||||
private static native List<Boolean> nativeWait(
|
||||
List<byte[]> objectIds, int numObjects, long timeoutMs, boolean fetchLocal);
|
||||
|
||||
private static native void nativeDelete(List<byte[]> objectIds, boolean localOnly);
|
||||
|
||||
private static native void nativeAddLocalReference(byte[] objectId);
|
||||
|
||||
private static native void nativeRemoveLocalReference(byte[] objectId);
|
||||
|
||||
private static native Map<byte[], long[]> nativeGetAllReferenceCounts();
|
||||
|
||||
private static native byte[] nativeGetOwnerAddress(byte[] objectId);
|
||||
|
||||
private static native byte[] nativeGetOwnershipInfo(byte[] objectId);
|
||||
|
||||
private static native void nativeRegisterOwnershipInfoAndResolveFuture(
|
||||
byte[] objectId, byte[] outerObjectId, byte[] ownerAddress);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.ray.runtime.object;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.api.id.BaseId;
|
||||
import io.ray.api.id.ObjectId;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** Binary representation of a ray object. See `RayObject` class in C++ for details. */
|
||||
public class NativeRayObject {
|
||||
|
||||
public byte[] data;
|
||||
public byte[] metadata;
|
||||
public List<byte[]> containedObjectIds;
|
||||
|
||||
public NativeRayObject(byte[] data, byte[] metadata) {
|
||||
Preconditions.checkState(bufferLength(data) > 0 || bufferLength(metadata) > 0);
|
||||
this.data = data;
|
||||
this.metadata = metadata;
|
||||
this.containedObjectIds = Collections.emptyList();
|
||||
}
|
||||
|
||||
public void setContainedObjectIds(List<ObjectId> containedObjectIds) {
|
||||
this.containedObjectIds = toBinaryList(containedObjectIds);
|
||||
}
|
||||
|
||||
private static int bufferLength(byte[] buffer) {
|
||||
if (buffer == null) {
|
||||
return 0;
|
||||
}
|
||||
return buffer.length;
|
||||
}
|
||||
|
||||
private static List<byte[]> toBinaryList(List<ObjectId> ids) {
|
||||
return ids.stream().map(BaseId::getBytes).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "<data>: " + bufferLength(data) + ", <metadata>: " + bufferLength(metadata);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package io.ray.runtime.object;
|
||||
|
||||
import com.google.common.base.FinalizableReferenceQueue;
|
||||
import com.google.common.base.FinalizableWeakReference;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Sets;
|
||||
import io.ray.api.ObjectRef;
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.api.id.ObjectId;
|
||||
import io.ray.runtime.AbstractRayRuntime;
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Implementation of {@link ObjectRef}. */
|
||||
public final class ObjectRefImpl<T> implements ObjectRef<T>, Externalizable {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ObjectRefImpl.class);
|
||||
|
||||
private static final FinalizableReferenceQueue REFERENCE_QUEUE = new FinalizableReferenceQueue();
|
||||
|
||||
private static final Set<Reference<ObjectRefImpl<?>>> REFERENCES = Sets.newConcurrentHashSet();
|
||||
|
||||
/// All the objects that are referenced by this worker.
|
||||
/// The key is the object ID in raw bytes, and the value is a weak reference to the ObjectRefImpl
|
||||
// object.
|
||||
private static ConcurrentHashMap<ObjectId, WeakReference<ObjectRefImpl<?>>> allObjects =
|
||||
new ConcurrentHashMap<>(1024);
|
||||
|
||||
private ObjectId id;
|
||||
|
||||
private Class<T> type;
|
||||
|
||||
// Raw data of this object.
|
||||
// This is currently used by only the memory store objects.
|
||||
// This byte array object is generated in CoreWorkerMemoryStore::Put.
|
||||
private byte[] rawData = null;
|
||||
|
||||
public ObjectRefImpl(ObjectId id, Class<T> type, boolean skipAddingLocalRef) {
|
||||
init(id, type, skipAddingLocalRef);
|
||||
}
|
||||
|
||||
public ObjectRefImpl(ObjectId id, Class<T> type) {
|
||||
this(id, type, /*skipAddingLocalRef=*/ false);
|
||||
}
|
||||
|
||||
public void init(ObjectId id, Class<?> type, boolean skipAddingLocalRef) {
|
||||
this.id = id;
|
||||
this.type = (Class<T>) type;
|
||||
AbstractRayRuntime runtime = (AbstractRayRuntime) Ray.internal();
|
||||
|
||||
if (!skipAddingLocalRef) {
|
||||
runtime.getObjectStore().addLocalReference(id);
|
||||
}
|
||||
// We still add the reference so that the local ref count will be properly
|
||||
// decremented once this object is GCed.
|
||||
new ObjectRefImplReference(this);
|
||||
}
|
||||
|
||||
private void setRawData(byte[] rawData) {
|
||||
Preconditions.checkState(this.rawData == null);
|
||||
this.rawData = rawData;
|
||||
}
|
||||
|
||||
public ObjectRefImpl() {}
|
||||
|
||||
@Override
|
||||
public synchronized T get() {
|
||||
return Ray.get(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized T get(long timeoutMs) {
|
||||
return Ray.get(this, timeoutMs);
|
||||
}
|
||||
|
||||
public ObjectId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Class<T> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ObjectRef(" + id.toString() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
out.writeObject(this.getId());
|
||||
out.writeObject(this.getType());
|
||||
AbstractRayRuntime runtime = (AbstractRayRuntime) Ray.internal();
|
||||
byte[] ownerAddress = runtime.getObjectStore().getOwnershipInfo(this.getId());
|
||||
out.writeInt(ownerAddress.length);
|
||||
out.write(ownerAddress);
|
||||
ObjectSerializer.addContainedObjectId(this.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
this.id = (ObjectId) in.readObject();
|
||||
this.type = (Class<T>) in.readObject();
|
||||
int len = in.readInt();
|
||||
byte[] ownerAddress = new byte[len];
|
||||
in.readFully(ownerAddress);
|
||||
|
||||
AbstractRayRuntime runtime = (AbstractRayRuntime) Ray.internal();
|
||||
runtime.getObjectStore().addLocalReference(id);
|
||||
new ObjectRefImplReference(this);
|
||||
|
||||
runtime
|
||||
.getObjectStore()
|
||||
.registerOwnershipInfoAndResolveFuture(
|
||||
this.id, ObjectSerializer.getOuterObjectId(), ownerAddress);
|
||||
}
|
||||
|
||||
private static final class ObjectRefImplReference
|
||||
extends FinalizableWeakReference<ObjectRefImpl<?>> {
|
||||
|
||||
private final ObjectId objectId;
|
||||
private final AtomicBoolean removed;
|
||||
|
||||
public ObjectRefImplReference(ObjectRefImpl<?> obj) {
|
||||
super(obj, REFERENCE_QUEUE);
|
||||
this.objectId = obj.id;
|
||||
this.removed = new AtomicBoolean(false);
|
||||
REFERENCES.add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finalizeReferent() {
|
||||
// This method may be invoked multiple times on the same instance (due to explicit invoking in
|
||||
// unit tests).
|
||||
if (!removed.getAndSet(true)) {
|
||||
REFERENCES.remove(this);
|
||||
// It's possible that GC is executed after the runtime is shutdown.
|
||||
if (Ray.isInitialized()) {
|
||||
((AbstractRayRuntime) (Ray.internal())).getObjectStore().removeLocalReference(objectId);
|
||||
allObjects.remove(objectId);
|
||||
LOG.debug("Object {} is finalized.", objectId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The callback that will be invoked once a Java object is allocated in memory store.
|
||||
private static void onMemoryStoreObjectAllocated(byte[] rawObjectId, byte[] data) {
|
||||
ObjectId objectId = new ObjectId(rawObjectId);
|
||||
Preconditions.checkState(rawObjectId != null);
|
||||
Preconditions.checkState(data != null);
|
||||
LOG.debug("onMemoryStoreObjectAllocated: {} , data.length is {}.", objectId, data.length);
|
||||
if (!allObjects.containsKey(objectId)) {
|
||||
LOG.info("The object {} doesn't exist in the weak reference pool", objectId);
|
||||
return;
|
||||
}
|
||||
WeakReference<ObjectRefImpl<?>> weakRef = allObjects.get(objectId);
|
||||
if (weakRef == null) {
|
||||
/// This will happen when a race condition occurs that at this moment,
|
||||
/// the item is being removed from map in another thread.
|
||||
LOG.info("The object {} has already been cleaned.", objectId);
|
||||
allObjects.remove(objectId);
|
||||
return;
|
||||
}
|
||||
ObjectRefImpl<?> objImpl = weakRef.get();
|
||||
if (objImpl == null) {
|
||||
LOG.info("The object {} has already been cleaned.", objectId);
|
||||
allObjects.remove(objectId);
|
||||
} else {
|
||||
// LOG.debug("The object {} set with a byte array data.", objectId);
|
||||
objImpl.setRawData(data);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void registerObjectRefImpl(ObjectId objectId, ObjectRefImpl<T> obj) {
|
||||
if (allObjects.containsKey(objectId)) {
|
||||
/// This is due to testLocalRefCounts() create 2 ObjectRefImpl objects for 1 id.
|
||||
LOG.warn("Duplicated object {}", objectId);
|
||||
return;
|
||||
}
|
||||
allObjects.put(objectId, new WeakReference<>(obj));
|
||||
LOG.debug("Putting object {} to weak reference pool.", objectId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package io.ray.runtime.object;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import io.ray.api.exception.RayActorException;
|
||||
import io.ray.api.exception.RayException;
|
||||
import io.ray.api.exception.RayTaskException;
|
||||
import io.ray.api.exception.RayWorkerException;
|
||||
import io.ray.api.exception.UnreconstructableException;
|
||||
import io.ray.api.id.ActorId;
|
||||
import io.ray.api.id.ObjectId;
|
||||
import io.ray.runtime.actor.NativeActorHandle;
|
||||
import io.ray.runtime.generated.Common.ErrorType;
|
||||
import io.ray.runtime.serializer.RayExceptionSerializer;
|
||||
import io.ray.runtime.serializer.Serializer;
|
||||
import io.ray.runtime.util.IdUtil;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
/**
|
||||
* Serialize to and deserialize from {@link NativeRayObject}. Metadata is generated during
|
||||
* serialization and respected during deserialization.
|
||||
*/
|
||||
public class ObjectSerializer {
|
||||
|
||||
private static final byte[] WORKER_EXCEPTION_META =
|
||||
String.valueOf(ErrorType.WORKER_DIED.getNumber()).getBytes();
|
||||
private static final byte[] ACTOR_EXCEPTION_META =
|
||||
String.valueOf(ErrorType.ACTOR_DIED.getNumber()).getBytes();
|
||||
private static final byte[] ACTOR_UNAVAILABLE_EXCEPTION_META =
|
||||
String.valueOf(ErrorType.ACTOR_UNAVAILABLE.getNumber()).getBytes();
|
||||
private static final byte[] UNRECONSTRUCTABLE_LINEAGE_EVICTED_EXCEPTION_META =
|
||||
String.valueOf(ErrorType.OBJECT_UNRECONSTRUCTABLE_LINEAGE_EVICTED.getNumber()).getBytes();
|
||||
private static final byte[] UNRECONSTRUCTABLE_MAX_ATTEMPTS_EXCEEDED_EXCEPTION_META =
|
||||
String.valueOf(ErrorType.OBJECT_UNRECONSTRUCTABLE_MAX_ATTEMPTS_EXCEEDED.getNumber())
|
||||
.getBytes();
|
||||
private static final byte[] UNRECONSTRUCTABLE_PUT_EXCEPTION_META =
|
||||
String.valueOf(ErrorType.OBJECT_UNRECONSTRUCTABLE_PUT.getNumber()).getBytes();
|
||||
private static final byte[] UNRECONSTRUCTABLE_RETRIES_DISABLED_EXCEPTION_META =
|
||||
String.valueOf(ErrorType.OBJECT_UNRECONSTRUCTABLE_RETRIES_DISABLED.getNumber()).getBytes();
|
||||
private static final byte[] UNRECONSTRUCTABLE_BORROWED_EXCEPTION_META =
|
||||
String.valueOf(ErrorType.OBJECT_UNRECONSTRUCTABLE_BORROWED.getNumber()).getBytes();
|
||||
private static final byte[] UNRECONSTRUCTABLE_REF_NOT_FOUND_EXCEPTION_META =
|
||||
String.valueOf(ErrorType.OBJECT_UNRECONSTRUCTABLE_REF_NOT_FOUND.getNumber()).getBytes();
|
||||
private static final byte[] UNRECONSTRUCTABLE_TASK_CANCELLED_EXCEPTION_META =
|
||||
String.valueOf(ErrorType.OBJECT_UNRECONSTRUCTABLE_TASK_CANCELLED.getNumber()).getBytes();
|
||||
private static final byte[] UNRECONSTRUCTABLE_LINEAGE_DISABLED_EXCEPTION_META =
|
||||
String.valueOf(ErrorType.OBJECT_UNRECONSTRUCTABLE_LINEAGE_DISABLED.getNumber()).getBytes();
|
||||
private static final byte[] OBJECT_LOST_META =
|
||||
String.valueOf(ErrorType.OBJECT_LOST.getNumber()).getBytes();
|
||||
private static final byte[] OWNER_DIED_META =
|
||||
String.valueOf(ErrorType.OWNER_DIED.getNumber()).getBytes();
|
||||
private static final byte[] OBJECT_DELETED_META =
|
||||
String.valueOf(ErrorType.OBJECT_DELETED.getNumber()).getBytes();
|
||||
private static final byte[] OBJECT_FREED_META =
|
||||
String.valueOf(ErrorType.OBJECT_FREED.getNumber()).getBytes();
|
||||
private static final byte[] TASK_EXECUTION_EXCEPTION_META =
|
||||
String.valueOf(ErrorType.TASK_EXECUTION_EXCEPTION.getNumber()).getBytes();
|
||||
|
||||
public static final byte[] OBJECT_METADATA_TYPE_CROSS_LANGUAGE = "XLANG".getBytes();
|
||||
public static final byte[] OBJECT_METADATA_TYPE_JAVA = "JAVA".getBytes();
|
||||
public static final byte[] OBJECT_METADATA_TYPE_PYTHON = "PYTHON".getBytes();
|
||||
public static final byte[] OBJECT_METADATA_TYPE_RAW = "RAW".getBytes();
|
||||
// A constant used as object metadata to indicate the object is an actor handle.
|
||||
// This value should be synchronized with the Python definition in ray_constants.py
|
||||
// TODO(fyrestone): Serialize the ActorHandle via the custom type feature of XLANG.
|
||||
public static final byte[] OBJECT_METADATA_TYPE_ACTOR_HANDLE = "ACTOR_HANDLE".getBytes();
|
||||
|
||||
// When an outer object is being serialized, the nested ObjectRefs are all
|
||||
// serialized and the writeExternal method of the nested ObjectRefs are
|
||||
// executed. So after the outer object is serialized, the containedObjectIds
|
||||
// field will contain all the nested object IDs.
|
||||
static ThreadLocal<Set<ObjectId>> containedObjectIds = ThreadLocal.withInitial(HashSet::new);
|
||||
|
||||
static ThreadLocal<ObjectId> outerObjectId = ThreadLocal.withInitial(() -> null);
|
||||
|
||||
/**
|
||||
* Deserialize an object from an {@link NativeRayObject} instance.
|
||||
*
|
||||
* @param nativeRayObject The object to deserialize.
|
||||
* @param objectId The associated object ID of the object.
|
||||
* @return The deserialized object.
|
||||
*/
|
||||
public static Object deserialize(
|
||||
NativeRayObject nativeRayObject, ObjectId objectId, Class<?> objectType) {
|
||||
byte[] meta = nativeRayObject.metadata;
|
||||
byte[] data = nativeRayObject.data;
|
||||
|
||||
if (meta != null && meta.length > 0) {
|
||||
// If meta is not null, deserialize the object from meta.
|
||||
if (Bytes.indexOf(meta, OBJECT_METADATA_TYPE_RAW) == 0) {
|
||||
if (objectType == ByteBuffer.class) {
|
||||
return ByteBuffer.wrap(data);
|
||||
}
|
||||
return data;
|
||||
} else if (Bytes.indexOf(meta, OBJECT_METADATA_TYPE_CROSS_LANGUAGE) == 0
|
||||
|| Bytes.indexOf(meta, OBJECT_METADATA_TYPE_JAVA) == 0) {
|
||||
return Serializer.decode(data, objectType);
|
||||
} else if (Bytes.indexOf(meta, WORKER_EXCEPTION_META) == 0) {
|
||||
return new RayWorkerException();
|
||||
} else if (Bytes.indexOf(meta, ACTOR_UNAVAILABLE_EXCEPTION_META) == 0) {
|
||||
// TODO(ryw): Add a new exception type ActorUnavailableException.
|
||||
// Also clean up the indexOf usage, should use equals.
|
||||
return new RayActorException();
|
||||
} else if (Bytes.indexOf(meta, UNRECONSTRUCTABLE_LINEAGE_EVICTED_EXCEPTION_META) == 0
|
||||
|| Bytes.indexOf(meta, UNRECONSTRUCTABLE_MAX_ATTEMPTS_EXCEEDED_EXCEPTION_META) == 0
|
||||
|| Bytes.indexOf(meta, UNRECONSTRUCTABLE_PUT_EXCEPTION_META) == 0
|
||||
|| Bytes.indexOf(meta, UNRECONSTRUCTABLE_RETRIES_DISABLED_EXCEPTION_META) == 0
|
||||
|| Bytes.indexOf(meta, UNRECONSTRUCTABLE_BORROWED_EXCEPTION_META) == 0
|
||||
|| Bytes.indexOf(meta, UNRECONSTRUCTABLE_REF_NOT_FOUND_EXCEPTION_META) == 0
|
||||
|| Bytes.indexOf(meta, UNRECONSTRUCTABLE_TASK_CANCELLED_EXCEPTION_META) == 0
|
||||
|| Bytes.indexOf(meta, UNRECONSTRUCTABLE_LINEAGE_DISABLED_EXCEPTION_META) == 0
|
||||
|| Bytes.indexOf(meta, OBJECT_LOST_META) == 0
|
||||
|| Bytes.indexOf(meta, OWNER_DIED_META) == 0
|
||||
|| Bytes.indexOf(meta, OBJECT_DELETED_META) == 0
|
||||
|| Bytes.indexOf(meta, OBJECT_FREED_META) == 0) {
|
||||
// TODO: Differentiate object errors.
|
||||
return new UnreconstructableException(objectId);
|
||||
} else if (Bytes.indexOf(meta, ACTOR_EXCEPTION_META) == 0) {
|
||||
ActorId actorId = IdUtil.getActorIdFromObjectId(objectId);
|
||||
if (data != null && data.length > 0) {
|
||||
RayException exception = deserializeActorException(data, actorId, objectId);
|
||||
if (exception != null) {
|
||||
return exception;
|
||||
}
|
||||
}
|
||||
return new RayActorException(actorId);
|
||||
} else if (Bytes.indexOf(meta, TASK_EXECUTION_EXCEPTION_META) == 0) {
|
||||
return deserializeRayException(data, objectId);
|
||||
} else if (Bytes.indexOf(meta, OBJECT_METADATA_TYPE_ACTOR_HANDLE) == 0) {
|
||||
byte[] serialized = Serializer.decode(data, byte[].class);
|
||||
return NativeActorHandle.fromBytes(serialized);
|
||||
} else if (Bytes.indexOf(meta, OBJECT_METADATA_TYPE_PYTHON) == 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Can't deserialize Python object: " + objectId.toString());
|
||||
}
|
||||
throw new IllegalArgumentException("Unrecognized metadata " + Arrays.toString(meta));
|
||||
} else {
|
||||
// If data is not null, deserialize the Java object.
|
||||
return Serializer.decode(data, objectType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize an Java object to an {@link NativeRayObject} instance.
|
||||
*
|
||||
* @param object The object to serialize.
|
||||
* @return The serialized object.
|
||||
*/
|
||||
public static NativeRayObject serialize(Object object) {
|
||||
if (object instanceof NativeRayObject) {
|
||||
return (NativeRayObject) object;
|
||||
} else if (object instanceof byte[]) {
|
||||
// If the object is a byte array, skip serializing it and use a special metadata to
|
||||
// indicate it's raw binary. So that this object can also be read by Python.
|
||||
return new NativeRayObject((byte[]) object, OBJECT_METADATA_TYPE_RAW);
|
||||
} else if (object instanceof ByteBuffer) {
|
||||
// Serialize ByteBuffer to raw bytes.
|
||||
ByteBuffer buffer = (ByteBuffer) object;
|
||||
byte[] bytes;
|
||||
if (buffer.hasArray()) {
|
||||
bytes = buffer.array();
|
||||
} else {
|
||||
bytes = new byte[buffer.remaining()];
|
||||
buffer.get(bytes);
|
||||
}
|
||||
return new NativeRayObject(bytes, OBJECT_METADATA_TYPE_RAW);
|
||||
} else if (object instanceof RayTaskException) {
|
||||
RayTaskException taskException = (RayTaskException) object;
|
||||
byte[] serializedBytes =
|
||||
Serializer.encode(RayExceptionSerializer.toBytes(taskException)).getLeft();
|
||||
// serializedBytes is MessagePack serialized bytes
|
||||
// taskException.toBytes() is protobuf serialized bytes
|
||||
// Only OBJECT_METADATA_TYPE_RAW is raw bytes,
|
||||
// any other type should be the MessagePack serialized bytes.
|
||||
return new NativeRayObject(serializedBytes, TASK_EXECUTION_EXCEPTION_META);
|
||||
} else if (object instanceof NativeActorHandle) {
|
||||
NativeActorHandle actorHandle = (NativeActorHandle) object;
|
||||
byte[] serializedBytes = Serializer.encode(actorHandle.toBytes()).getLeft();
|
||||
// serializedBytes is MessagePack serialized bytes
|
||||
// Only OBJECT_METADATA_TYPE_RAW is raw bytes,
|
||||
// any other type should be the MessagePack serialized bytes.
|
||||
NativeRayObject nativeRayObject =
|
||||
new NativeRayObject(serializedBytes, OBJECT_METADATA_TYPE_ACTOR_HANDLE);
|
||||
nativeRayObject.setContainedObjectIds(ImmutableList.of(actorHandle.getActorHandleId()));
|
||||
return nativeRayObject;
|
||||
} else {
|
||||
try {
|
||||
Pair<byte[], Boolean> serialized = Serializer.encode(object);
|
||||
NativeRayObject nativeRayObject =
|
||||
new NativeRayObject(
|
||||
serialized.getLeft(),
|
||||
serialized.getRight()
|
||||
? OBJECT_METADATA_TYPE_CROSS_LANGUAGE
|
||||
: OBJECT_METADATA_TYPE_JAVA);
|
||||
nativeRayObject.setContainedObjectIds(getAndClearContainedObjectIds());
|
||||
return nativeRayObject;
|
||||
} catch (Exception e) {
|
||||
// Clear `containedObjectIds`.
|
||||
getAndClearContainedObjectIds();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void addContainedObjectId(ObjectId objectId) {
|
||||
containedObjectIds.get().add(objectId);
|
||||
}
|
||||
|
||||
private static List<ObjectId> getAndClearContainedObjectIds() {
|
||||
List<ObjectId> ids = new ArrayList<>(containedObjectIds.get());
|
||||
containedObjectIds.get().clear();
|
||||
return ids;
|
||||
}
|
||||
|
||||
static void setOuterObjectId(ObjectId objectId) {
|
||||
outerObjectId.set(objectId);
|
||||
}
|
||||
|
||||
static ObjectId getOuterObjectId() {
|
||||
return outerObjectId.get();
|
||||
}
|
||||
|
||||
static void resetOuterObjectId() {
|
||||
outerObjectId.set(null);
|
||||
}
|
||||
|
||||
private static RayException deserializeRayException(byte[] msgPackData, ObjectId objectId) {
|
||||
// Serialization logic of task execution exception: an instance of
|
||||
// `io.ray.api.exception.RayTaskException`
|
||||
// -> a `RayException` protobuf message
|
||||
// -> protobuf-serialized bytes
|
||||
// -> MessagePack-serialized bytes.
|
||||
// So here the `data` variable is MessagePack-serialized bytes, and the `serialized`
|
||||
// variable is protobuf-serialized bytes. They are not the same.
|
||||
byte[] pbData = Serializer.decode(msgPackData, byte[].class);
|
||||
|
||||
if (pbData == null || pbData.length == 0) {
|
||||
// No RayException provided
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return RayExceptionSerializer.fromBytes(pbData);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Can't deserialize RayActorCreationTaskException object: " + objectId.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static RayException deserializeActorException(
|
||||
byte[] msgPackData, ActorId actorId, ObjectId objectId) {
|
||||
// Serialization logic of task execution exception: an instance of
|
||||
// `io.ray.api.exception.RayTaskException`
|
||||
// -> a `RayException` protobuf message
|
||||
// -> protobuf-serialized bytes
|
||||
// -> MessagePack-serialized bytes.
|
||||
// So here the `data` variable is MessagePack-serialized bytes, and the `serialized`
|
||||
// variable is protobuf-serialized bytes. They are not the same.
|
||||
byte[] pbData = Serializer.decode(msgPackData, byte[].class);
|
||||
|
||||
if (pbData == null || pbData.length == 0) {
|
||||
// No RayException provided
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
io.ray.runtime.generated.Common.RayErrorInfo rayErrorInfo =
|
||||
io.ray.runtime.generated.Common.RayErrorInfo.parseFrom(pbData);
|
||||
if (rayErrorInfo.getActorDiedError().hasCreationTaskFailureContext()) {
|
||||
return RayExceptionSerializer.fromRayExceptionPB(
|
||||
rayErrorInfo.getActorDiedError().getCreationTaskFailureContext());
|
||||
} else {
|
||||
// TODO(lixin) Generate friendly error message from RayErrorInfo.ActorDiedError's field
|
||||
// type.
|
||||
return new RayActorException(actorId);
|
||||
}
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Can't deserialize RayActorCreationTaskException object: " + objectId.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
package io.ray.runtime.object;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.api.ObjectRef;
|
||||
import io.ray.api.WaitResult;
|
||||
import io.ray.api.exception.RayException;
|
||||
import io.ray.api.id.ObjectId;
|
||||
import io.ray.runtime.context.WorkerContext;
|
||||
import io.ray.runtime.generated.Common.Address;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** A class that is used to put/get objects to/from the object store. */
|
||||
public abstract class ObjectStore {
|
||||
|
||||
private final WorkerContext workerContext;
|
||||
|
||||
public ObjectStore(WorkerContext workerContext) {
|
||||
this.workerContext = workerContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a raw object into object store.
|
||||
*
|
||||
* @param obj The ray object.
|
||||
* @return Generated ID of the object.
|
||||
*/
|
||||
public abstract ObjectId putRaw(NativeRayObject obj);
|
||||
|
||||
/**
|
||||
* Put a raw object with specified ID into object store.
|
||||
*
|
||||
* @param obj The ray object.
|
||||
* @param objectId Object ID specified by user.
|
||||
*/
|
||||
public abstract void putRaw(NativeRayObject obj, ObjectId objectId);
|
||||
|
||||
/**
|
||||
* Serialize and put an object to the object store.
|
||||
*
|
||||
* @param object The object to put.
|
||||
* @return Id of the object.
|
||||
*/
|
||||
public ObjectId put(Object object) {
|
||||
if (object instanceof NativeRayObject) {
|
||||
throw new IllegalArgumentException(
|
||||
"Trying to put a NativeRayObject. Please use putRaw instead.");
|
||||
}
|
||||
return putRaw(ObjectSerializer.serialize(object));
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize and put an object to the object store, with the given object id.
|
||||
*
|
||||
* <p>This method is only used for testing.
|
||||
*
|
||||
* @param object The object to put.
|
||||
* @param objectId Object id.
|
||||
*/
|
||||
public void put(Object object, ObjectId objectId) {
|
||||
if (object instanceof NativeRayObject) {
|
||||
throw new IllegalArgumentException(
|
||||
"Trying to put a NativeRayObject. Please use putRaw instead.");
|
||||
}
|
||||
putRaw(ObjectSerializer.serialize(object), objectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of raw objects from the object store.
|
||||
*
|
||||
* @param objectIds IDs of the objects to get.
|
||||
* @param timeoutMs Timeout in milliseconds, wait infinitely if it's negative.
|
||||
* @return Result list of objects data.
|
||||
* @throws RayTimeoutException If it's timeout to get the object.
|
||||
*/
|
||||
public abstract List<NativeRayObject> getRaw(List<ObjectId> objectIds, long timeoutMs);
|
||||
|
||||
/**
|
||||
* Get a list of objects from the object store.
|
||||
*
|
||||
* @param ids List of the object ids.
|
||||
* @param <T> Type of these objects.
|
||||
* @return A list of GetResult objects.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> List<T> get(List<ObjectId> ids, Class<?> elementType) {
|
||||
return get(ids, elementType, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of objects from the object store.
|
||||
*
|
||||
* @param ids List of the object ids.
|
||||
* @param <T> Type of these objects.
|
||||
* @param timeoutMs The maximum amount of time in seconds to wait before returning.
|
||||
* @return A list of GetResult objects.
|
||||
* @throws RayTimeoutException If it's timeout to get the object.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> List<T> get(List<ObjectId> ids, Class<?> elementType, long timeoutMs) {
|
||||
List<NativeRayObject> dataAndMetaList = getRaw(ids, timeoutMs);
|
||||
|
||||
List<T> results = new ArrayList<>();
|
||||
for (int i = 0; i < dataAndMetaList.size(); i++) {
|
||||
NativeRayObject dataAndMeta = dataAndMetaList.get(i);
|
||||
Object object = null;
|
||||
if (dataAndMeta != null) {
|
||||
try {
|
||||
ObjectSerializer.setOuterObjectId(ids.get(i));
|
||||
object = ObjectSerializer.deserialize(dataAndMeta, ids.get(i), elementType);
|
||||
} finally {
|
||||
ObjectSerializer.resetOuterObjectId();
|
||||
}
|
||||
}
|
||||
if (object instanceof RayException) {
|
||||
// If the object is a `RayException`, it means that an error occurred during task
|
||||
// execution.
|
||||
throw (RayException) object;
|
||||
}
|
||||
results.add((T) object);
|
||||
}
|
||||
// This check must be placed after the throw exception statement.
|
||||
// Because if there was any exception, The get operation would return early
|
||||
// and wouldn't wait until all objects exist.
|
||||
Preconditions.checkState(dataAndMetaList.stream().allMatch(Objects::nonNull));
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a list of RayObjects to be available, until specified number of objects are ready, or
|
||||
* specified timeout has passed.
|
||||
*
|
||||
* @param objectIds IDs of the objects to wait for.
|
||||
* @param numObjects Number of objects that should appear.
|
||||
* @param timeoutMs Timeout in milliseconds, wait infinitely if it's negative.
|
||||
* @param fetchLocal If true, wait for the object to be downloaded onto the local node before
|
||||
* returning it as ready. If false, ray.wait() will not trigger fetching of objects to the
|
||||
* local node and will return immediately once the object is available anywhere in the
|
||||
* cluster.
|
||||
* @return A list of booleans that indicates each object has appeared or not.
|
||||
*/
|
||||
public abstract List<Boolean> wait(
|
||||
List<ObjectId> objectIds, int numObjects, long timeoutMs, boolean fetchLocal);
|
||||
|
||||
/**
|
||||
* Wait for a list of RayObjects to be available, until specified number of objects are ready, or
|
||||
* specified timeout has passed.
|
||||
*
|
||||
* @param waitList A list of object references to wait for.
|
||||
* @param numReturns The number of objects that should be returned.
|
||||
* @param timeoutMs The maximum time in milliseconds to wait before returning.
|
||||
* @param fetchLocal If true, wait for the object to be downloaded onto the local node before
|
||||
* returning it as ready. If false, ray.wait() will not trigger fetching of objects to the
|
||||
* local node and will return immediately once the object is available anywhere in the
|
||||
* cluster.
|
||||
* @return Two lists, one containing locally available objects, one containing the rest.
|
||||
*/
|
||||
public <T> WaitResult<T> wait(
|
||||
List<ObjectRef<T>> waitList, int numReturns, int timeoutMs, boolean fetchLocal) {
|
||||
Preconditions.checkNotNull(waitList);
|
||||
if (waitList.isEmpty()) {
|
||||
return new WaitResult<>(Collections.emptyList(), Collections.emptyList());
|
||||
}
|
||||
|
||||
List<ObjectId> ids =
|
||||
waitList.stream().map(ref -> ((ObjectRefImpl<?>) ref).getId()).collect(Collectors.toList());
|
||||
|
||||
List<Boolean> ready = wait(ids, numReturns, timeoutMs, fetchLocal);
|
||||
List<ObjectRef<T>> readyList = new ArrayList<>();
|
||||
List<ObjectRef<T>> unreadyList = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < ready.size(); i++) {
|
||||
if (ready.get(i)) {
|
||||
readyList.add(waitList.get(i));
|
||||
} else {
|
||||
unreadyList.add(waitList.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
return new WaitResult<>(readyList, unreadyList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a list of objects from the object store.
|
||||
*
|
||||
* @param objectIds IDs of the objects to delete.
|
||||
* @param localOnly Whether only delete the objects in local node, or all nodes in the cluster.
|
||||
*/
|
||||
public abstract void delete(List<ObjectId> objectIds, boolean localOnly);
|
||||
|
||||
/**
|
||||
* Increase the local reference count for this object ID.
|
||||
*
|
||||
* @param objectId The object ID to increase the reference count for.
|
||||
*/
|
||||
public abstract void addLocalReference(ObjectId objectId);
|
||||
|
||||
/**
|
||||
* Decrease the reference count for this object ID.
|
||||
*
|
||||
* @param objectId The object ID to decrease the reference count for.
|
||||
*/
|
||||
public abstract void removeLocalReference(ObjectId objectId);
|
||||
|
||||
public abstract Address getOwnerAddress(ObjectId id);
|
||||
|
||||
/**
|
||||
* Get the ownership info.
|
||||
*
|
||||
* @param objectId The ID of the object to promote
|
||||
* @return the serialized ownership address
|
||||
*/
|
||||
public abstract byte[] getOwnershipInfo(ObjectId objectId);
|
||||
|
||||
/**
|
||||
* Add a reference to an ObjectID that will deserialized. This will also start the process to
|
||||
* resolve the future. Specifically, we will periodically contact the owner, until we learn that
|
||||
* the object has been created or the owner is no longer reachable. This will then unblock any
|
||||
* Gets or submissions of tasks dependent on the object.
|
||||
*
|
||||
* @param objectId The object ID to deserialize.
|
||||
* @param outerObjectId The object ID that contained objectId, if any. This may be nil if the
|
||||
* object ID was inlined directly in a task spec or if it was passed out-of-band by the
|
||||
* application (deserialized from a byte string).
|
||||
* @param ownerAddress The address of the object's owner.
|
||||
*/
|
||||
public abstract void registerOwnershipInfoAndResolveFuture(
|
||||
ObjectId objectId, ObjectId outerObjectId, byte[] ownerAddress);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package io.ray.runtime.placementgroup;
|
||||
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.api.id.PlacementGroupId;
|
||||
import io.ray.api.placementgroup.PlacementGroup;
|
||||
import io.ray.api.placementgroup.PlacementGroupState;
|
||||
import io.ray.api.placementgroup.PlacementStrategy;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** The default implementation of `PlacementGroup` interface. */
|
||||
public class PlacementGroupImpl implements PlacementGroup, Serializable {
|
||||
|
||||
private static final long serialVersionUID = -6616291240442716883L;
|
||||
|
||||
private final PlacementGroupId id;
|
||||
private final String name;
|
||||
private final List<Map<String, Double>> bundles;
|
||||
private final PlacementStrategy strategy;
|
||||
private final PlacementGroupState state;
|
||||
|
||||
private PlacementGroupImpl(
|
||||
PlacementGroupId id,
|
||||
String name,
|
||||
List<Map<String, Double>> bundles,
|
||||
PlacementStrategy strategy,
|
||||
PlacementGroupState state) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.bundles = bundles;
|
||||
this.strategy = strategy;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlacementGroupId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Double>> getBundles() {
|
||||
return bundles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlacementStrategy getStrategy() {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlacementGroupState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wait(int timeoutSeconds) {
|
||||
return Ray.internal().waitPlacementGroupReady(id, timeoutSeconds);
|
||||
}
|
||||
|
||||
/** A help class for create the placement group. */
|
||||
public static class Builder {
|
||||
private PlacementGroupId id;
|
||||
private String name;
|
||||
private List<Map<String, Double>> bundles;
|
||||
private PlacementStrategy strategy;
|
||||
private PlacementGroupState state;
|
||||
|
||||
/**
|
||||
* Set the Id of the placement group.
|
||||
*
|
||||
* @param id Id of the placement group.
|
||||
* @return self.
|
||||
*/
|
||||
public Builder setId(PlacementGroupId id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the placement group.
|
||||
*
|
||||
* @param name Name of the placement group.
|
||||
* @return self.
|
||||
*/
|
||||
public Builder setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bundles of the placement group.
|
||||
*
|
||||
* @param bundles the bundles of the placement group.
|
||||
* @return self.
|
||||
*/
|
||||
public Builder setBundles(List<Map<String, Double>> bundles) {
|
||||
this.bundles = bundles;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the placement strategy of the placement group.
|
||||
*
|
||||
* @param strategy the placement strategy of the placement group.
|
||||
* @return self.
|
||||
*/
|
||||
public Builder setStrategy(PlacementStrategy strategy) {
|
||||
this.strategy = strategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the placement state of the placement group.
|
||||
*
|
||||
* @param state the state of the placement group.
|
||||
* @return self.
|
||||
*/
|
||||
public Builder setState(PlacementGroupState state) {
|
||||
this.state = state;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PlacementGroupImpl build() {
|
||||
return new PlacementGroupImpl(id, name, bundles, strategy, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package io.ray.runtime.placementgroup;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import io.ray.api.id.PlacementGroupId;
|
||||
import io.ray.api.placementgroup.PlacementGroupState;
|
||||
import io.ray.api.placementgroup.PlacementStrategy;
|
||||
import io.ray.runtime.generated.Common;
|
||||
import io.ray.runtime.generated.Common.Bundle;
|
||||
import io.ray.runtime.generated.Gcs.PlacementGroupTableData;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** Utils for placement group. */
|
||||
public class PlacementGroupUtils {
|
||||
|
||||
private static List<Map<String, Double>> covertToUserSpecifiedBundles(List<Bundle> bundles) {
|
||||
List<Map<String, Double>> result = new ArrayList<>();
|
||||
|
||||
// NOTE(clay4444): We need to guarantee the order here.
|
||||
for (int i = 0; i < bundles.size(); i++) {
|
||||
Bundle bundle = bundles.get(i);
|
||||
result.add(bundle.getUnitResourcesMap());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static PlacementStrategy covertToUserSpecifiedStrategy(
|
||||
Common.PlacementStrategy strategy) {
|
||||
switch (strategy) {
|
||||
case PACK:
|
||||
return PlacementStrategy.PACK;
|
||||
case STRICT_PACK:
|
||||
return PlacementStrategy.STRICT_PACK;
|
||||
case SPREAD:
|
||||
return PlacementStrategy.SPREAD;
|
||||
case STRICT_SPREAD:
|
||||
return PlacementStrategy.STRICT_SPREAD;
|
||||
default:
|
||||
return PlacementStrategy.UNRECOGNIZED;
|
||||
}
|
||||
}
|
||||
|
||||
private static PlacementGroupState covertToUserSpecifiedState(
|
||||
PlacementGroupTableData.PlacementGroupState state) {
|
||||
switch (state) {
|
||||
case PENDING:
|
||||
return PlacementGroupState.PENDING;
|
||||
case CREATED:
|
||||
return PlacementGroupState.CREATED;
|
||||
case REMOVED:
|
||||
return PlacementGroupState.REMOVED;
|
||||
case RESCHEDULING:
|
||||
return PlacementGroupState.RESCHEDULING;
|
||||
default:
|
||||
return PlacementGroupState.UNRECOGNIZED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a PlacementGroupImpl from placementGroupTableData protobuf data.
|
||||
*
|
||||
* @param placementGroupTableData protobuf data.
|
||||
* @return placement group info {@link PlacementGroupImpl}
|
||||
*/
|
||||
private static PlacementGroupImpl generatePlacementGroupFromPbData(
|
||||
PlacementGroupTableData placementGroupTableData) {
|
||||
|
||||
PlacementGroupState state = covertToUserSpecifiedState(placementGroupTableData.getState());
|
||||
PlacementStrategy strategy =
|
||||
covertToUserSpecifiedStrategy(placementGroupTableData.getStrategy());
|
||||
|
||||
List<Map<String, Double>> bundles =
|
||||
covertToUserSpecifiedBundles(placementGroupTableData.getBundlesList());
|
||||
|
||||
PlacementGroupId placementGroupId =
|
||||
PlacementGroupId.fromByteBuffer(
|
||||
placementGroupTableData.getPlacementGroupId().asReadOnlyByteBuffer());
|
||||
|
||||
return new PlacementGroupImpl.Builder()
|
||||
.setId(placementGroupId)
|
||||
.setName(placementGroupTableData.getName())
|
||||
.setState(state)
|
||||
.setStrategy(strategy)
|
||||
.setBundles(bundles)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a PlacementGroupImpl from byte array.
|
||||
*
|
||||
* @param placementGroupByteArray bytes array from native method.
|
||||
* @return placement group info {@link PlacementGroupImpl}
|
||||
*/
|
||||
public static PlacementGroupImpl generatePlacementGroupFromByteArray(
|
||||
byte[] placementGroupByteArray) {
|
||||
Preconditions.checkNotNull(
|
||||
placementGroupByteArray, "Can't generate a placement group from empty byte array.");
|
||||
|
||||
PlacementGroupTableData placementGroupTableData;
|
||||
try {
|
||||
placementGroupTableData = PlacementGroupTableData.parseFrom(placementGroupByteArray);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new RuntimeException(
|
||||
"Received invalid placement group table protobuf data from GCS.", e);
|
||||
}
|
||||
|
||||
return generatePlacementGroupFromPbData(placementGroupTableData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package io.ray.runtime.runner;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import io.ray.runtime.config.RayConfig;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Ray service management on one box. */
|
||||
public class RunManager {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RunManager.class);
|
||||
|
||||
private static final Pattern pattern = Pattern.compile("--address='([^']+)'");
|
||||
|
||||
/** Start the head node. */
|
||||
public static void startRayHead(RayConfig rayConfig) {
|
||||
LOGGER.debug("Starting ray runtime @ {}.", rayConfig.nodeIp);
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("ray");
|
||||
command.add("start");
|
||||
command.add("--head");
|
||||
command.add("--redis-username");
|
||||
command.add(rayConfig.redisUsername);
|
||||
command.add("--redis-password");
|
||||
command.add(rayConfig.redisPassword);
|
||||
command.addAll(rayConfig.headArgs);
|
||||
|
||||
String output;
|
||||
try {
|
||||
output = runCommand(command);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to start Ray runtime.", e);
|
||||
}
|
||||
Matcher matcher = pattern.matcher(output);
|
||||
if (matcher.find()) {
|
||||
String bootstrapAddress = matcher.group(1);
|
||||
rayConfig.setBootstrapAddress(bootstrapAddress);
|
||||
} else {
|
||||
throw new RuntimeException("Redis address is not found. output: " + output);
|
||||
}
|
||||
LOGGER.info("Ray runtime started @ {}.", rayConfig.nodeIp);
|
||||
}
|
||||
|
||||
/** Stop ray. */
|
||||
public static void stopRay() {
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("ray");
|
||||
command.add("stop");
|
||||
command.add("--force");
|
||||
|
||||
try {
|
||||
runCommand(command);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to stop ray.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a process.
|
||||
*
|
||||
* @param command The command to start the process with.
|
||||
*/
|
||||
public static String runCommand(List<String> command) throws IOException, InterruptedException {
|
||||
return runCommand(command, 30, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public static String runCommand(List<String> command, long timeout, TimeUnit unit)
|
||||
throws IOException, InterruptedException {
|
||||
LOGGER.info("Starting process with command: {}", Joiner.on(" ").join(command));
|
||||
|
||||
ProcessBuilder builder = new ProcessBuilder(command).redirectErrorStream(true);
|
||||
Process p = builder.start();
|
||||
final boolean exited = p.waitFor(timeout, unit);
|
||||
if (!exited) {
|
||||
String output = IOUtils.toString(p.getInputStream(), Charset.defaultCharset());
|
||||
throw new RuntimeException("The process was not exited in time. output:\n" + output);
|
||||
}
|
||||
|
||||
String output = IOUtils.toString(p.getInputStream(), Charset.defaultCharset());
|
||||
if (p.exitValue() != 0) {
|
||||
String sb =
|
||||
"The exit value of the process is "
|
||||
+ p.exitValue()
|
||||
+ ". Command: "
|
||||
+ Joiner.on(" ").join(command)
|
||||
+ "\n"
|
||||
+ "output:\n"
|
||||
+ output;
|
||||
throw new RuntimeException(sb);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.ray.runtime.runner.worker;
|
||||
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.runtime.AbstractRayRuntime;
|
||||
|
||||
/** Default implementation of the worker process. */
|
||||
public class DefaultWorker {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Set run-mode to `CLUSTER` explicitly, to prevent the DefaultWorker to receive
|
||||
// a wrong run-mode parameter through jvm options.
|
||||
System.setProperty("ray.run-mode", "CLUSTER");
|
||||
System.setProperty("ray.worker.mode", "WORKER");
|
||||
Ray.init();
|
||||
((AbstractRayRuntime) Ray.internal()).run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package io.ray.runtime.runtimeenv;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.github.fge.jackson.JsonLoader;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import com.google.protobuf.util.JsonFormat;
|
||||
import io.ray.api.exception.RuntimeEnvException;
|
||||
import io.ray.api.runtimeenv.RuntimeEnv;
|
||||
import io.ray.api.runtimeenv.RuntimeEnvConfig;
|
||||
import io.ray.runtime.generated.RuntimeEnvironment;
|
||||
import java.io.IOException;
|
||||
|
||||
public class RuntimeEnvImpl implements RuntimeEnv {
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
public ObjectNode runtimeEnvs = MAPPER.createObjectNode();
|
||||
|
||||
private static final String CONFIG_FIELD_NAME = "config";
|
||||
|
||||
public RuntimeEnvImpl() {}
|
||||
|
||||
@Override
|
||||
public void set(String name, Object value) throws RuntimeEnvException {
|
||||
if (CONFIG_FIELD_NAME.equals(name) && value instanceof RuntimeEnvConfig == false) {
|
||||
throw new RuntimeEnvException(name + "must be instance of " + RuntimeEnvConfig.class);
|
||||
}
|
||||
JsonNode node = null;
|
||||
try {
|
||||
node = MAPPER.valueToTree(value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeEnvException("Failed to set field.", e);
|
||||
}
|
||||
runtimeEnvs.set(name, node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setJsonStr(String name, String jsonStr) throws RuntimeEnvException {
|
||||
JsonNode node = null;
|
||||
try {
|
||||
node = JsonLoader.fromString(jsonStr);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeEnvException("Failed to set json field.", e);
|
||||
}
|
||||
runtimeEnvs.set(name, node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T get(String name, Class<T> classOfT) throws RuntimeEnvException {
|
||||
JsonNode jsonNode = runtimeEnvs.get(name);
|
||||
if (jsonNode == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return MAPPER.treeToValue(jsonNode, classOfT);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeEnvException("Failed to get field.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJsonStr(String name) throws RuntimeEnvException {
|
||||
try {
|
||||
return MAPPER.writeValueAsString(runtimeEnvs.get(name));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeEnvException("Failed to get json field.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(String name) {
|
||||
return runtimeEnvs.has(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(String name) {
|
||||
if (contains(name)) {
|
||||
runtimeEnvs.remove(name);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String serialize() throws RuntimeEnvException {
|
||||
try {
|
||||
return MAPPER.writeValueAsString(runtimeEnvs);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeEnvException("Failed to serialize.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return runtimeEnvs.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String serializeToRuntimeEnvInfo() throws RuntimeEnvException {
|
||||
RuntimeEnvironment.RuntimeEnvInfo protoRuntimeEnvInfo = GenerateRuntimeEnvInfo();
|
||||
|
||||
JsonFormat.Printer printer = JsonFormat.printer();
|
||||
try {
|
||||
return printer.print(protoRuntimeEnvInfo);
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new RuntimeEnvException("Failed to serialize to runtime env info.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfig(RuntimeEnvConfig runtimeEnvConfig) {
|
||||
set(CONFIG_FIELD_NAME, runtimeEnvConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeEnvConfig getConfig() {
|
||||
if (!contains(CONFIG_FIELD_NAME)) {
|
||||
return null;
|
||||
}
|
||||
return get(CONFIG_FIELD_NAME, RuntimeEnvConfig.class);
|
||||
}
|
||||
|
||||
public RuntimeEnvironment.RuntimeEnvInfo GenerateRuntimeEnvInfo() throws RuntimeEnvException {
|
||||
String serializeRuntimeEnv = serialize();
|
||||
RuntimeEnvironment.RuntimeEnvInfo.Builder protoRuntimeEnvInfoBuilder =
|
||||
RuntimeEnvironment.RuntimeEnvInfo.newBuilder();
|
||||
protoRuntimeEnvInfoBuilder.setSerializedRuntimeEnv(serializeRuntimeEnv);
|
||||
RuntimeEnvConfig runtimeEnvConfig = getConfig();
|
||||
if (runtimeEnvConfig != null) {
|
||||
RuntimeEnvironment.RuntimeEnvConfig.Builder protoRuntimeEnvConfigBuilder =
|
||||
RuntimeEnvironment.RuntimeEnvConfig.newBuilder();
|
||||
protoRuntimeEnvConfigBuilder.setSetupTimeoutSeconds(
|
||||
runtimeEnvConfig.getSetupTimeoutSeconds());
|
||||
protoRuntimeEnvConfigBuilder.setEagerInstall(runtimeEnvConfig.getEagerInstall());
|
||||
protoRuntimeEnvInfoBuilder.setRuntimeEnvConfig(protoRuntimeEnvConfigBuilder.build());
|
||||
}
|
||||
|
||||
return protoRuntimeEnvInfoBuilder.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.ray.runtime.serializer;
|
||||
|
||||
import io.ray.runtime.actor.NativeActorHandle;
|
||||
import io.ray.runtime.actor.NativeActorHandleSerializer;
|
||||
import org.nustaq.serialization.FSTConfiguration;
|
||||
|
||||
/** Java object serialization TODO: use others (e.g. Arrow) for higher performance */
|
||||
public class FstSerializer {
|
||||
|
||||
private static final ThreadLocal<FSTConfiguration> conf =
|
||||
ThreadLocal.withInitial(
|
||||
() -> {
|
||||
FSTConfiguration conf = FSTConfiguration.createDefaultConfiguration();
|
||||
conf.registerSerializer(
|
||||
NativeActorHandle.class, new NativeActorHandleSerializer(), true);
|
||||
return conf;
|
||||
});
|
||||
|
||||
public static byte[] encode(Object obj) {
|
||||
FSTConfiguration current = conf.get();
|
||||
current.setClassLoader(Thread.currentThread().getContextClassLoader());
|
||||
return current.asByteArray(obj);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T decode(byte[] bs) {
|
||||
FSTConfiguration current = conf.get();
|
||||
current.setClassLoader(Thread.currentThread().getContextClassLoader());
|
||||
return (T) current.asObject(bs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
package io.ray.runtime.serializer;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.math.BigInteger;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.mutable.MutableBoolean;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.msgpack.core.MessageBufferPacker;
|
||||
import org.msgpack.core.MessagePack;
|
||||
import org.msgpack.core.MessagePacker;
|
||||
import org.msgpack.core.MessageUnpacker;
|
||||
import org.msgpack.value.ArrayValue;
|
||||
import org.msgpack.value.ExtensionValue;
|
||||
import org.msgpack.value.IntegerValue;
|
||||
import org.msgpack.value.Value;
|
||||
import org.msgpack.value.ValueType;
|
||||
|
||||
// We can't pack List / Map by MessagePack, because we don't know the type class when unpacking.
|
||||
public class MessagePackSerializer {
|
||||
|
||||
private static final byte LANGUAGE_SPECIFIC_TYPE_EXTENSION_ID = 101;
|
||||
// MessagePack length is an int takes up to 9 bytes.
|
||||
// https://github.com/msgpack/msgpack/blob/master/spec.md#int-format-family
|
||||
private static final int MESSAGE_PACK_OFFSET = 9;
|
||||
|
||||
// Pakcers indexed by its corresponding Java class object.
|
||||
private static Map<Class<?>, TypePacker> packers = new HashMap<>();
|
||||
// Unpackers indexed by its corresponding MessagePack ValueType.
|
||||
private static Map<ValueType, TypeUnpacker> unpackers = new HashMap<>();
|
||||
// Null and array don't have a corresponding class, so define them separately.
|
||||
private static final TypePacker NULL_PACKER;
|
||||
private static final TypePacker ARRAY_PACKER;
|
||||
private static final TypePacker EXTENSION_PACKER;
|
||||
|
||||
static {
|
||||
// ===== Initialize packers =====
|
||||
// Null packer.
|
||||
NULL_PACKER = (object, packer, javaSerializer) -> packer.packNil();
|
||||
|
||||
// Array packer.
|
||||
ARRAY_PACKER =
|
||||
((object, packer, javaSerializer) -> {
|
||||
int length = Array.getLength(object);
|
||||
packer.packArrayHeader(length);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
pack(Array.get(object, i), packer, javaSerializer);
|
||||
}
|
||||
});
|
||||
|
||||
// Extension packer.
|
||||
EXTENSION_PACKER =
|
||||
((object, packer, javaSerializer) -> {
|
||||
javaSerializer.serialize(object, packer);
|
||||
});
|
||||
|
||||
packers.put(
|
||||
Boolean.class, ((object, packer, javaSerializer) -> packer.packBoolean((Boolean) object)));
|
||||
packers.put(Byte.class, ((object, packer, javaSerializer) -> packer.packByte((Byte) object)));
|
||||
packers.put(
|
||||
Short.class, ((object, packer, javaSerializer) -> packer.packShort((Short) object)));
|
||||
packers.put(
|
||||
Integer.class, ((object, packer, javaSerializer) -> packer.packInt((Integer) object)));
|
||||
packers.put(Long.class, ((object, packer, javaSerializer) -> packer.packLong((Long) object)));
|
||||
packers.put(
|
||||
BigInteger.class,
|
||||
((object, packer, javaSerializer) -> packer.packBigInteger((BigInteger) object)));
|
||||
packers.put(
|
||||
Float.class, ((object, packer, javaSerializer) -> packer.packFloat((Float) object)));
|
||||
packers.put(
|
||||
Double.class, ((object, packer, javaSerializer) -> packer.packDouble((Double) object)));
|
||||
packers.put(
|
||||
String.class, ((object, packer, javaSerializer) -> packer.packString((String) object)));
|
||||
packers.put(
|
||||
byte[].class,
|
||||
((object, packer, javaSerializer) -> {
|
||||
byte[] bytes = (byte[]) object;
|
||||
packer.packBinaryHeader(bytes.length);
|
||||
packer.writePayload(bytes);
|
||||
}));
|
||||
|
||||
// ===== Initialize unpackers =====
|
||||
List<Class<?>> booleanClasses = ImmutableList.of(Boolean.class, boolean.class);
|
||||
List<Class<?>> byteClasses = ImmutableList.of(Byte.class, byte.class);
|
||||
List<Class<?>> shortClasses = ImmutableList.of(Short.class, short.class);
|
||||
List<Class<?>> intClasses = ImmutableList.of(Integer.class, int.class);
|
||||
List<Class<?>> longClasses = ImmutableList.of(Long.class, long.class);
|
||||
List<Class<?>> bigIntClasses = ImmutableList.of(BigInteger.class);
|
||||
List<Class<?>> floatClasses = ImmutableList.of(Float.class, float.class);
|
||||
List<Class<?>> doubleClasses = ImmutableList.of(Double.class, double.class);
|
||||
List<Class<?>> stringClasses = ImmutableList.of(String.class);
|
||||
List<Class<?>> binaryClasses = ImmutableList.of(byte[].class);
|
||||
|
||||
// Null unpacker.
|
||||
unpackers.put(ValueType.NIL, (value, targetClass, javaDeserializer) -> null);
|
||||
// Boolean unpacker.
|
||||
unpackers.put(
|
||||
ValueType.BOOLEAN,
|
||||
(value, targetClass, javaDeserializer) -> {
|
||||
Preconditions.checkArgument(
|
||||
checkTypeCompatible(booleanClasses, targetClass),
|
||||
"Boolean can't be deserialized as {}.",
|
||||
targetClass);
|
||||
return value.asBooleanValue().getBoolean();
|
||||
});
|
||||
// Integer unpacker.
|
||||
unpackers.put(
|
||||
ValueType.INTEGER,
|
||||
((value, targetClass, javaDeserializer) -> {
|
||||
IntegerValue iv = value.asIntegerValue();
|
||||
if (iv.isInByteRange() && checkTypeCompatible(byteClasses, targetClass)) {
|
||||
return iv.asByte();
|
||||
} else if (iv.isInShortRange() && checkTypeCompatible(shortClasses, targetClass)) {
|
||||
return iv.asShort();
|
||||
} else if (iv.isInIntRange() && checkTypeCompatible(intClasses, targetClass)) {
|
||||
return iv.asInt();
|
||||
} else if (iv.isInLongRange() && checkTypeCompatible(longClasses, targetClass)) {
|
||||
return iv.asLong();
|
||||
} else if (checkTypeCompatible(bigIntClasses, targetClass)) {
|
||||
return iv.asBigInteger();
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"Integer can't be deserialized as " + targetClass + ".");
|
||||
}));
|
||||
// Float unpacker.
|
||||
unpackers.put(
|
||||
ValueType.FLOAT,
|
||||
((value, targetClass, javaDeserializer) -> {
|
||||
if (checkTypeCompatible(doubleClasses, targetClass)) {
|
||||
return value.asFloatValue().toDouble();
|
||||
} else if (checkTypeCompatible(floatClasses, targetClass)) {
|
||||
return value.asFloatValue().toFloat();
|
||||
}
|
||||
throw new IllegalArgumentException("Float can't be deserialized as " + targetClass + ".");
|
||||
}));
|
||||
// String unpacker.
|
||||
unpackers.put(
|
||||
ValueType.STRING,
|
||||
((value, targetClass, javaDeserializer) -> {
|
||||
Preconditions.checkArgument(
|
||||
checkTypeCompatible(stringClasses, targetClass),
|
||||
"String can't be deserialized as {}.",
|
||||
targetClass);
|
||||
return value.asStringValue().asString();
|
||||
}));
|
||||
// Binary unpacker.
|
||||
unpackers.put(
|
||||
ValueType.BINARY,
|
||||
((value, targetClass, javaDeserializer) -> {
|
||||
Preconditions.checkArgument(
|
||||
checkTypeCompatible(binaryClasses, targetClass),
|
||||
"Binary can't be deserialized as {}.",
|
||||
targetClass);
|
||||
return value.asBinaryValue().asByteArray();
|
||||
}));
|
||||
// Array unpacker.
|
||||
unpackers.put(
|
||||
ValueType.ARRAY,
|
||||
((value, targetClass, javaDeserializer) -> {
|
||||
ArrayValue av = value.asArrayValue();
|
||||
Class<?> componentType =
|
||||
targetClass.isArray() ? targetClass.getComponentType() : Object.class;
|
||||
Object array = Array.newInstance(componentType, av.size());
|
||||
for (int i = 0; i < av.size(); ++i) {
|
||||
Array.set(array, i, unpack(av.get(i), componentType, javaDeserializer));
|
||||
}
|
||||
return array;
|
||||
}));
|
||||
// Extension unpacker.
|
||||
unpackers.put(
|
||||
ValueType.EXTENSION,
|
||||
((value, targetClass, javaDeserializer) -> {
|
||||
ExtensionValue ev = value.asExtensionValue();
|
||||
byte extType = ev.getType();
|
||||
if (extType == LANGUAGE_SPECIFIC_TYPE_EXTENSION_ID) {
|
||||
return javaDeserializer.deserialize(ev);
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown extension type id " + ev.getType() + ".");
|
||||
}));
|
||||
}
|
||||
|
||||
interface JavaSerializer {
|
||||
|
||||
void serialize(Object object, MessagePacker packer) throws IOException;
|
||||
}
|
||||
|
||||
interface JavaDeserializer {
|
||||
|
||||
Object deserialize(ExtensionValue v);
|
||||
}
|
||||
|
||||
interface TypePacker {
|
||||
|
||||
void pack(Object object, MessagePacker packer, JavaSerializer javaSerializer)
|
||||
throws IOException;
|
||||
}
|
||||
|
||||
interface TypeUnpacker {
|
||||
|
||||
Object unpack(Value value, Class<?> targetClass, JavaDeserializer javaDeserializer);
|
||||
}
|
||||
|
||||
private static boolean checkTypeCompatible(List<Class<?>> expected, Class<?> actual) {
|
||||
for (Class<?> expectedClass : expected) {
|
||||
if (actual.isAssignableFrom(expectedClass)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void pack(Object object, MessagePacker packer, JavaSerializer javaSerializer)
|
||||
throws IOException {
|
||||
TypePacker typePacker;
|
||||
if (object == null) {
|
||||
typePacker = NULL_PACKER;
|
||||
} else {
|
||||
Class<?> type = object.getClass();
|
||||
typePacker = packers.get(type);
|
||||
if (typePacker == null) {
|
||||
if (type.isArray()) {
|
||||
typePacker = ARRAY_PACKER;
|
||||
} else {
|
||||
typePacker = EXTENSION_PACKER;
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
typePacker.pack(object, packer, javaSerializer);
|
||||
} catch (Exception e) {
|
||||
if (typePacker != EXTENSION_PACKER) {
|
||||
EXTENSION_PACKER.pack(object, packer, javaSerializer);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Object unpack(Value v, Class<?> type, JavaDeserializer javaDeserializer) {
|
||||
return unpackers.get(v.getValueType()).unpack(v, type, javaDeserializer);
|
||||
}
|
||||
|
||||
public static Pair<byte[], Boolean> encode(Object obj) {
|
||||
MessageBufferPacker packer = MessagePack.newDefaultBufferPacker();
|
||||
try {
|
||||
// Reserve MESSAGE_PACK_OFFSET bytes for MessagePack bytes length.
|
||||
packer.writePayload(new byte[MESSAGE_PACK_OFFSET]);
|
||||
// Serialize input object by MessagePack.
|
||||
MutableBoolean isCrossLanguage = new MutableBoolean(true);
|
||||
pack(
|
||||
obj,
|
||||
packer,
|
||||
((object, packer1) -> {
|
||||
byte[] payload = FstSerializer.encode(object);
|
||||
packer1.packExtensionTypeHeader(LANGUAGE_SPECIFIC_TYPE_EXTENSION_ID, payload.length);
|
||||
packer1.addPayload(payload);
|
||||
isCrossLanguage.setFalse();
|
||||
}));
|
||||
byte[] msgpackBytes = packer.toByteArray();
|
||||
// Serialize MessagePack bytes length.
|
||||
MessageBufferPacker headerPacker = MessagePack.newDefaultBufferPacker();
|
||||
Preconditions.checkState(msgpackBytes.length >= MESSAGE_PACK_OFFSET);
|
||||
headerPacker.packLong(msgpackBytes.length - MESSAGE_PACK_OFFSET);
|
||||
byte[] msgpackBytesLength = headerPacker.toByteArray();
|
||||
// Check serialized MessagePack bytes length is valid.
|
||||
Preconditions.checkState(msgpackBytesLength.length <= MESSAGE_PACK_OFFSET);
|
||||
// Write MessagePack bytes length to reserved buffer.
|
||||
System.arraycopy(msgpackBytesLength, 0, msgpackBytes, 0, msgpackBytesLength.length);
|
||||
return ImmutablePair.of(msgpackBytes, isCrossLanguage.getValue());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T decode(byte[] bs, Class<?> type) {
|
||||
try {
|
||||
// Read MessagePack bytes length.
|
||||
MessageUnpacker headerUnpacker = MessagePack.newDefaultUnpacker(bs, 0, MESSAGE_PACK_OFFSET);
|
||||
long msgpackBytesLength = headerUnpacker.unpackLong();
|
||||
headerUnpacker.close();
|
||||
// Check MessagePack bytes length is valid.
|
||||
Preconditions.checkState(MESSAGE_PACK_OFFSET + msgpackBytesLength <= bs.length);
|
||||
// Deserialize MessagePack bytes from MESSAGE_PACK_OFFSET.
|
||||
MessageUnpacker unpacker =
|
||||
MessagePack.newDefaultUnpacker(bs, MESSAGE_PACK_OFFSET, (int) msgpackBytesLength);
|
||||
Value v = unpacker.unpackValue();
|
||||
if (type == null) {
|
||||
type = Object.class;
|
||||
}
|
||||
return (T) unpack(v, type, ((ExtensionValue ev) -> FstSerializer.decode(ev.getData())));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.ray.runtime.serializer;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import io.ray.api.exception.CrossLanguageException;
|
||||
import io.ray.api.exception.RayException;
|
||||
import io.ray.runtime.generated.Common.Language;
|
||||
|
||||
public class RayExceptionSerializer {
|
||||
|
||||
public static byte[] toBytes(RayException exception) {
|
||||
String formattedException =
|
||||
org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace(exception);
|
||||
io.ray.runtime.generated.Common.RayException.Builder builder =
|
||||
io.ray.runtime.generated.Common.RayException.newBuilder();
|
||||
builder.setLanguage(Language.JAVA);
|
||||
builder.setFormattedExceptionString(formattedException);
|
||||
builder.setSerializedException(ByteString.copyFrom(Serializer.encode(exception).getLeft()));
|
||||
return builder.build().toByteArray();
|
||||
}
|
||||
|
||||
public static RayException fromBytes(byte[] serialized) throws InvalidProtocolBufferException {
|
||||
io.ray.runtime.generated.Common.RayException exception =
|
||||
io.ray.runtime.generated.Common.RayException.parseFrom(serialized);
|
||||
if (exception.getLanguage() == Language.JAVA) {
|
||||
return Serializer.decode(
|
||||
exception.getSerializedException().toByteArray(), RayException.class);
|
||||
} else {
|
||||
return new CrossLanguageException(
|
||||
String.format(
|
||||
"An exception raised from %s:\n%s",
|
||||
exception.getLanguage().name(), exception.getFormattedExceptionString()));
|
||||
}
|
||||
}
|
||||
|
||||
public static RayException fromRayExceptionPB(
|
||||
io.ray.runtime.generated.Common.RayException rayExceptionPB) {
|
||||
if (rayExceptionPB.getLanguage() == Language.JAVA) {
|
||||
return Serializer.decode(
|
||||
rayExceptionPB.getSerializedException().toByteArray(), RayException.class);
|
||||
} else {
|
||||
return new CrossLanguageException(
|
||||
String.format(
|
||||
"An exception raised from %s:\n%s",
|
||||
rayExceptionPB.getLanguage().name(), rayExceptionPB.getFormattedExceptionString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.ray.runtime.serializer;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
public class Serializer {
|
||||
|
||||
public static Pair<byte[], Boolean> encode(Object obj) {
|
||||
return MessagePackSerializer.encode(obj);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T decode(byte[] bs, Class<?> type) {
|
||||
return MessagePackSerializer.decode(bs, type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package io.ray.runtime.task;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import io.ray.api.ObjectRef;
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.api.id.ObjectId;
|
||||
import io.ray.runtime.AbstractRayRuntime;
|
||||
import io.ray.runtime.generated.Common.Address;
|
||||
import io.ray.runtime.generated.Common.Language;
|
||||
import io.ray.runtime.object.NativeRayObject;
|
||||
import io.ray.runtime.object.ObjectRefImpl;
|
||||
import io.ray.runtime.object.ObjectSerializer;
|
||||
import io.ray.runtime.util.SystemConfig;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Helper methods to convert arguments from/to objects. */
|
||||
public class ArgumentsBuilder {
|
||||
|
||||
/** This dummy type is also defined in signature.py. Please keep it synced. */
|
||||
private static final NativeRayObject PYTHON_DUMMY_TYPE =
|
||||
ObjectSerializer.serialize("__RAY_DUMMY__".getBytes());
|
||||
|
||||
/** Convert real function arguments to task spec arguments. */
|
||||
public static List<FunctionArg> wrap(Object[] args, Language language) {
|
||||
List<FunctionArg> ret = new ArrayList<>();
|
||||
for (Object arg : args) {
|
||||
ObjectId id = null;
|
||||
Address address = null;
|
||||
NativeRayObject value = null;
|
||||
if (arg instanceof ObjectRef) {
|
||||
Preconditions.checkState(arg instanceof ObjectRefImpl);
|
||||
id = ((ObjectRefImpl<?>) arg).getId();
|
||||
address = ((AbstractRayRuntime) Ray.internal()).getObjectStore().getOwnerAddress(id);
|
||||
} else {
|
||||
value = ObjectSerializer.serialize(arg);
|
||||
if (language != Language.JAVA) {
|
||||
boolean isCrossData =
|
||||
Bytes.indexOf(value.metadata, ObjectSerializer.OBJECT_METADATA_TYPE_CROSS_LANGUAGE)
|
||||
== 0
|
||||
|| Bytes.indexOf(value.metadata, ObjectSerializer.OBJECT_METADATA_TYPE_RAW) == 0
|
||||
|| Bytes.indexOf(
|
||||
value.metadata, ObjectSerializer.OBJECT_METADATA_TYPE_ACTOR_HANDLE)
|
||||
== 0;
|
||||
if (!isCrossData) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
"Can't transfer %s data to %s",
|
||||
new String(value.metadata), language.getValueDescriptor().getName()));
|
||||
}
|
||||
}
|
||||
if (value.data.length > SystemConfig.getLargestSizePassedByValue()) {
|
||||
id = ((AbstractRayRuntime) Ray.internal()).getObjectStore().putRaw(value);
|
||||
address = ((AbstractRayRuntime) Ray.internal()).getWorkerContext().getRpcAddress();
|
||||
value = null;
|
||||
}
|
||||
}
|
||||
if (language == Language.PYTHON) {
|
||||
ret.add(FunctionArg.passByValue(PYTHON_DUMMY_TYPE));
|
||||
}
|
||||
if (id != null) {
|
||||
ret.add(FunctionArg.passByReference(id, address));
|
||||
} else {
|
||||
ret.add(FunctionArg.passByValue(value));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Convert list of NativeRayObject/ByteBuffer to real function arguments. */
|
||||
public static Object[] unwrap(List<Object> args, Class<?>[] types) {
|
||||
Object[] realArgs = new Object[args.size()];
|
||||
for (int i = 0; i < args.size(); i++) {
|
||||
Object arg = args.get(i);
|
||||
Preconditions.checkState(arg instanceof ByteBuffer || arg instanceof NativeRayObject);
|
||||
if (arg instanceof ByteBuffer) {
|
||||
Preconditions.checkState(types[i] == ByteBuffer.class);
|
||||
realArgs[i] = arg;
|
||||
} else {
|
||||
realArgs[i] = ObjectSerializer.deserialize((NativeRayObject) arg, null, types[i]);
|
||||
}
|
||||
}
|
||||
return realArgs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package io.ray.runtime.task;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.api.id.ObjectId;
|
||||
import io.ray.runtime.generated.Common.Address;
|
||||
import io.ray.runtime.object.NativeRayObject;
|
||||
|
||||
/**
|
||||
* Represents a function argument in task spec. Either `id` or `data` should be null, when id is not
|
||||
* null, this argument will be passed by reference, otherwise it will be passed by value.
|
||||
*/
|
||||
public class FunctionArg {
|
||||
|
||||
/** The id of this argument (passed by reference). */
|
||||
public final ObjectId id;
|
||||
|
||||
/** The owner address of this argument (passed by reference). */
|
||||
public final Address ownerAddress;
|
||||
|
||||
/** Serialized data of this argument (passed by value). */
|
||||
public final NativeRayObject value;
|
||||
|
||||
private FunctionArg(ObjectId id, Address ownerAddress) {
|
||||
Preconditions.checkNotNull(id);
|
||||
Preconditions.checkNotNull(ownerAddress);
|
||||
this.id = id;
|
||||
this.ownerAddress = ownerAddress;
|
||||
this.value = null;
|
||||
}
|
||||
|
||||
private FunctionArg(NativeRayObject nativeRayObject) {
|
||||
Preconditions.checkNotNull(nativeRayObject);
|
||||
this.id = null;
|
||||
this.ownerAddress = null;
|
||||
this.value = nativeRayObject;
|
||||
}
|
||||
|
||||
/** Create a FunctionArg that will be passed by reference. */
|
||||
public static FunctionArg passByReference(ObjectId id, Address ownerAddress) {
|
||||
return new FunctionArg(id, ownerAddress);
|
||||
}
|
||||
|
||||
/** Create a FunctionArg that will be passed by value. */
|
||||
public static FunctionArg passByValue(NativeRayObject value) {
|
||||
return new FunctionArg(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (id != null) {
|
||||
return "<id>: " + id.toString();
|
||||
} else {
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.ray.runtime.task;
|
||||
|
||||
import io.ray.api.id.UniqueId;
|
||||
import io.ray.runtime.AbstractRayRuntime;
|
||||
|
||||
/**
|
||||
* Task executor for local mode.
|
||||
*
|
||||
* <p>In local mode, multiple actors may share the same {@code TaskExecutor} instance but run on
|
||||
* different threads (each actor is dispatched to its own executor service thread). A {@code
|
||||
* ThreadLocal} is used to isolate each actor's context per thread, preventing cross-actor
|
||||
* interference.
|
||||
*/
|
||||
public class LocalModeTaskExecutor extends TaskExecutor<LocalModeTaskExecutor.LocalActorContext> {
|
||||
|
||||
static class LocalActorContext extends TaskExecutor.ActorContext {
|
||||
|
||||
/** The worker ID of the actor. */
|
||||
private final UniqueId workerId;
|
||||
|
||||
public LocalActorContext(UniqueId workerId) {
|
||||
this.workerId = workerId;
|
||||
}
|
||||
|
||||
public UniqueId getWorkerId() {
|
||||
return workerId;
|
||||
}
|
||||
}
|
||||
|
||||
/** Thread-local storage: each actor thread holds its own context. */
|
||||
private final ThreadLocal<LocalActorContext> actorContext = new ThreadLocal<>();
|
||||
|
||||
public LocalModeTaskExecutor(AbstractRayRuntime runtime) {
|
||||
super(runtime);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocalActorContext createActorContext() {
|
||||
return new LocalActorContext(runtime.getWorkerContext().getCurrentWorkerId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocalActorContext getActorContext() {
|
||||
return actorContext.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setActorContext(UniqueId workerId, LocalActorContext actorContext) {
|
||||
if (actorContext == null) {
|
||||
this.actorContext.remove();
|
||||
} else {
|
||||
this.actorContext.set(actorContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,627 @@
|
||||
package io.ray.runtime.task;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.protobuf.ByteString;
|
||||
import io.ray.api.ActorHandle;
|
||||
import io.ray.api.BaseActorHandle;
|
||||
import io.ray.api.id.ActorId;
|
||||
import io.ray.api.id.ObjectId;
|
||||
import io.ray.api.id.PlacementGroupId;
|
||||
import io.ray.api.id.TaskId;
|
||||
import io.ray.api.id.UniqueId;
|
||||
import io.ray.api.options.ActorCreationOptions;
|
||||
import io.ray.api.options.CallOptions;
|
||||
import io.ray.api.options.PlacementGroupCreationOptions;
|
||||
import io.ray.api.placementgroup.PlacementGroup;
|
||||
import io.ray.runtime.AbstractRayRuntime;
|
||||
import io.ray.runtime.ConcurrencyGroupImpl;
|
||||
import io.ray.runtime.actor.LocalModeActorHandle;
|
||||
import io.ray.runtime.context.LocalModeWorkerContext;
|
||||
import io.ray.runtime.functionmanager.FunctionDescriptor;
|
||||
import io.ray.runtime.functionmanager.JavaFunctionDescriptor;
|
||||
import io.ray.runtime.generated.Common;
|
||||
import io.ray.runtime.generated.Common.ActorCreationTaskSpec;
|
||||
import io.ray.runtime.generated.Common.ActorTaskSpec;
|
||||
import io.ray.runtime.generated.Common.Address;
|
||||
import io.ray.runtime.generated.Common.Language;
|
||||
import io.ray.runtime.generated.Common.ObjectReference;
|
||||
import io.ray.runtime.generated.Common.TaskArg;
|
||||
import io.ray.runtime.generated.Common.TaskSpec;
|
||||
import io.ray.runtime.generated.Common.TaskType;
|
||||
import io.ray.runtime.object.LocalModeObjectStore;
|
||||
import io.ray.runtime.object.NativeRayObject;
|
||||
import io.ray.runtime.placementgroup.PlacementGroupImpl;
|
||||
import io.ray.runtime.util.IdUtil;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Task submitter for local mode. */
|
||||
public class LocalModeTaskSubmitter implements TaskSubmitter {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(LocalModeTaskSubmitter.class);
|
||||
|
||||
private final Map<ObjectId, Set<TaskSpec>> waitingTasks = new HashMap<>();
|
||||
private final Object taskAndObjectLock = new Object();
|
||||
private final AbstractRayRuntime runtime;
|
||||
private final TaskExecutor taskExecutor;
|
||||
private final LocalModeObjectStore objectStore;
|
||||
|
||||
private final Map<ActorId, Integer> actorMaxConcurrency = new ConcurrentHashMap<>();
|
||||
|
||||
/// The thread pool to execute normal tasks.
|
||||
private final ExecutorService normalTaskExecutorService;
|
||||
|
||||
private final Map<ActorId, LocalModeActorHandle> actorHandles = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, ActorHandle> namedActors = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<ActorId, TaskExecutor.ActorContext> actorContexts = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<PlacementGroupId, PlacementGroup> placementGroups = new ConcurrentHashMap<>();
|
||||
|
||||
private static final String DEFAULT_CONCURRENCY_GROUP_NAME = "DEFAULT_CONCURRENCY_GROUP_NAME";
|
||||
|
||||
private final ActorConcurrencyGroupManager actorConcurrencyGroupManager;
|
||||
|
||||
private static final class ActorExecutorService {
|
||||
|
||||
private Map<String, ExecutorService> services = new ConcurrentHashMap<>();
|
||||
|
||||
/// A map that index the actor functions to its concurrency group.
|
||||
private Map<JavaFunctionDescriptor, String> indexFunctionToConcurrencyGroupName =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
public ActorExecutorService(TaskSpec taskSpec) {
|
||||
ActorCreationTaskSpec actorCreationTaskSpec = taskSpec.getActorCreationTaskSpec();
|
||||
Preconditions.checkNotNull(actorCreationTaskSpec);
|
||||
final List<Common.ConcurrencyGroup> concurrencyGroups =
|
||||
actorCreationTaskSpec.getConcurrencyGroupsList();
|
||||
concurrencyGroups.forEach(
|
||||
(concurrencyGroup) -> {
|
||||
ExecutorService executorService =
|
||||
Executors.newFixedThreadPool(concurrencyGroup.getMaxConcurrency());
|
||||
Preconditions.checkState(!services.containsKey(concurrencyGroup.getName()));
|
||||
services.put(concurrencyGroup.getName(), executorService);
|
||||
concurrencyGroup
|
||||
.getFunctionDescriptorsList()
|
||||
.forEach(
|
||||
(fd) -> {
|
||||
indexFunctionToConcurrencyGroupName.put(
|
||||
protoFunctionDescriptorToJava(fd), concurrencyGroup.getName());
|
||||
});
|
||||
});
|
||||
|
||||
/// Put the default concurrency group.
|
||||
services.put(
|
||||
/*defaultConcurrencyGroupName=*/ DEFAULT_CONCURRENCY_GROUP_NAME,
|
||||
Executors.newFixedThreadPool(actorCreationTaskSpec.getMaxConcurrency()));
|
||||
}
|
||||
|
||||
public synchronized ExecutorService getExecutorService(TaskSpec taskSpec) {
|
||||
String concurrencyGroupName = taskSpec.getConcurrencyGroupName();
|
||||
Preconditions.checkNotNull(concurrencyGroupName);
|
||||
/// First look up it by the given concurrency group name.
|
||||
if (!concurrencyGroupName.isEmpty()) {
|
||||
Preconditions.checkState(services.containsKey(concurrencyGroupName));
|
||||
return services.get(concurrencyGroupName);
|
||||
}
|
||||
/// The concurrency group is not specified, then we look up it by the function name.
|
||||
JavaFunctionDescriptor javaFunctionDescriptor =
|
||||
protoFunctionDescriptorToJava(taskSpec.getFunctionDescriptor());
|
||||
if (indexFunctionToConcurrencyGroupName.containsKey(javaFunctionDescriptor)) {
|
||||
concurrencyGroupName = indexFunctionToConcurrencyGroupName.get(javaFunctionDescriptor);
|
||||
Preconditions.checkState(services.containsKey(concurrencyGroupName));
|
||||
return services.get(concurrencyGroupName);
|
||||
} else {
|
||||
/// This function is not specified any concurrency group both in creating actor and
|
||||
// submitting task.
|
||||
return services.get(DEFAULT_CONCURRENCY_GROUP_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void shutdown() {
|
||||
services.forEach((key, service) -> service.shutdown());
|
||||
services.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ActorConcurrencyGroupManager {
|
||||
|
||||
private Map<ActorId, ActorExecutorService> actorExecutorServices = new ConcurrentHashMap<>();
|
||||
|
||||
public synchronized void registerActor(ActorId actorId, TaskSpec taskSpec) {
|
||||
Preconditions.checkState(!actorExecutorServices.containsKey(actorId));
|
||||
ActorExecutorService actorExecutorService = new ActorExecutorService(taskSpec);
|
||||
actorExecutorServices.put(actorId, actorExecutorService);
|
||||
}
|
||||
|
||||
public synchronized ExecutorService getExecutorServiceForConcurrencyGroup(TaskSpec taskSpec) {
|
||||
final ActorId actorId = getActorId(taskSpec);
|
||||
Preconditions.checkState(actorExecutorServices.containsKey(actorId));
|
||||
ActorExecutorService actorExecutorService = actorExecutorServices.get(actorId);
|
||||
return actorExecutorService.getExecutorService(taskSpec);
|
||||
}
|
||||
|
||||
public synchronized void shutdown() {
|
||||
actorExecutorServices.forEach(
|
||||
(actorId, actorExecutorService) -> {
|
||||
actorExecutorService.shutdown();
|
||||
});
|
||||
actorExecutorServices.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public LocalModeTaskSubmitter(
|
||||
AbstractRayRuntime runtime, TaskExecutor taskExecutor, LocalModeObjectStore objectStore) {
|
||||
this.runtime = runtime;
|
||||
this.taskExecutor = taskExecutor;
|
||||
this.objectStore = objectStore;
|
||||
// The thread pool that executes normal tasks in parallel.
|
||||
normalTaskExecutorService = Executors.newCachedThreadPool();
|
||||
actorConcurrencyGroupManager = new ActorConcurrencyGroupManager();
|
||||
}
|
||||
|
||||
public void onObjectPut(ObjectId id) {
|
||||
Set<TaskSpec> tasks;
|
||||
synchronized (taskAndObjectLock) {
|
||||
tasks = waitingTasks.remove(id);
|
||||
if (tasks != null) {
|
||||
for (TaskSpec task : tasks) {
|
||||
Set<ObjectId> unreadyObjects = getUnreadyObjects(task);
|
||||
if (unreadyObjects.isEmpty()) {
|
||||
submitTaskSpec(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<ObjectId> getUnreadyObjects(TaskSpec taskSpec) {
|
||||
Set<ObjectId> unreadyObjects = new HashSet<>();
|
||||
// Check whether task arguments are ready.
|
||||
for (TaskArg arg : taskSpec.getArgsList()) {
|
||||
ByteString idByteString = arg.getObjectRef().getObjectId();
|
||||
if (idByteString != ByteString.EMPTY) {
|
||||
ObjectId id = new ObjectId(idByteString.toByteArray());
|
||||
if (!objectStore.isObjectReady(id)) {
|
||||
unreadyObjects.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (taskSpec.getType() == TaskType.ACTOR_TASK) {
|
||||
// Code path of concurrent actors.
|
||||
// For concurrent actors, we should make sure the actor created
|
||||
// before we submit the following actor tasks.
|
||||
ActorId actorId = ActorId.fromBytes(taskSpec.getActorTaskSpec().getActorId().toByteArray());
|
||||
ObjectId dummyActorCreationObjectId = IdUtil.getActorCreationDummyObjectId(actorId);
|
||||
if (!objectStore.isObjectReady(dummyActorCreationObjectId)) {
|
||||
unreadyObjects.add(dummyActorCreationObjectId);
|
||||
}
|
||||
}
|
||||
return unreadyObjects;
|
||||
}
|
||||
|
||||
private TaskSpec.Builder getTaskSpecBuilder(
|
||||
TaskType taskType, FunctionDescriptor functionDescriptor, List<FunctionArg> args) {
|
||||
byte[] taskIdBytes = new byte[TaskId.LENGTH];
|
||||
new Random().nextBytes(taskIdBytes);
|
||||
List<String> functionDescriptorList = functionDescriptor.toList();
|
||||
Preconditions.checkState(functionDescriptorList.size() >= 3);
|
||||
return TaskSpec.newBuilder()
|
||||
.setType(taskType)
|
||||
.setLanguage(Language.JAVA)
|
||||
.setJobId(ByteString.copyFrom(runtime.getRayConfig().getJobId().getBytes()))
|
||||
.setTaskId(ByteString.copyFrom(taskIdBytes))
|
||||
.setFunctionDescriptor(
|
||||
Common.FunctionDescriptor.newBuilder()
|
||||
.setJavaFunctionDescriptor(
|
||||
Common.JavaFunctionDescriptor.newBuilder()
|
||||
.setClassName(functionDescriptorList.get(0))
|
||||
.setFunctionName(functionDescriptorList.get(1))
|
||||
.setSignature(functionDescriptorList.get(2))))
|
||||
.addAllArgs(
|
||||
args.stream()
|
||||
.map(
|
||||
arg ->
|
||||
arg.id != null
|
||||
? TaskArg.newBuilder()
|
||||
.setObjectRef(
|
||||
ObjectReference.newBuilder()
|
||||
.setObjectId(ByteString.copyFrom(arg.id.getBytes())))
|
||||
.build()
|
||||
: TaskArg.newBuilder()
|
||||
.setData(ByteString.copyFrom(arg.value.data))
|
||||
.setMetadata(
|
||||
arg.value.metadata != null
|
||||
? ByteString.copyFrom(arg.value.metadata)
|
||||
: ByteString.EMPTY)
|
||||
.build())
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ObjectId> submitTask(
|
||||
FunctionDescriptor functionDescriptor,
|
||||
List<FunctionArg> args,
|
||||
int numReturns,
|
||||
CallOptions options) {
|
||||
Preconditions.checkState(numReturns <= 1);
|
||||
TaskSpec taskSpec =
|
||||
getTaskSpecBuilder(TaskType.NORMAL_TASK, functionDescriptor, args)
|
||||
.setNumReturns(numReturns)
|
||||
.build();
|
||||
submitTaskSpec(taskSpec);
|
||||
return getReturnIds(taskSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseActorHandle createActor(
|
||||
FunctionDescriptor functionDescriptor, List<FunctionArg> args, ActorCreationOptions options)
|
||||
throws IllegalArgumentException {
|
||||
if (options != null) {
|
||||
if (options.getGroup() != null) {
|
||||
PlacementGroupImpl group = (PlacementGroupImpl) options.getGroup();
|
||||
// bundleIndex == -1 indicates using any available bundle.
|
||||
Preconditions.checkArgument(
|
||||
options.getBundleIndex() == -1
|
||||
|| options.getBundleIndex() >= 0
|
||||
&& options.getBundleIndex() < group.getBundles().size(),
|
||||
String.format(
|
||||
"Bundle index %s is invalid, the correct bundle index should be "
|
||||
+ "either in the range of 0 to the number of bundles "
|
||||
+ "or -1 which means put the task to any available bundles.",
|
||||
options.getBundleIndex()));
|
||||
}
|
||||
}
|
||||
|
||||
ActorId actorId = ActorId.fromRandom();
|
||||
ActorCreationTaskSpec.Builder actorCreationTaskSpecBuilder =
|
||||
ActorCreationTaskSpec.newBuilder()
|
||||
.setActorId(ByteString.copyFrom(actorId.toByteBuffer()))
|
||||
.setMaxConcurrency(options.getMaxConcurrency())
|
||||
.setMaxPendingCalls(options.getMaxPendingCalls());
|
||||
appendConcurrencyGroupsBuilder(actorCreationTaskSpecBuilder, options);
|
||||
TaskSpec taskSpec =
|
||||
getTaskSpecBuilder(TaskType.ACTOR_CREATION_TASK, functionDescriptor, args)
|
||||
.setNumReturns(1)
|
||||
.setActorCreationTaskSpec(actorCreationTaskSpecBuilder.build())
|
||||
.build();
|
||||
submitTaskSpec(taskSpec);
|
||||
final LocalModeActorHandle actorHandle =
|
||||
new LocalModeActorHandle(actorId, getReturnIds(taskSpec).get(0));
|
||||
actorHandles.put(actorId, actorHandle.copy());
|
||||
if (StringUtils.isNotBlank(options.getName())) {
|
||||
Preconditions.checkArgument(
|
||||
!namedActors.containsKey(options.getName()),
|
||||
String.format("Actor of name %s exists", options.getName()));
|
||||
namedActors.put(options.getName(), actorHandle);
|
||||
}
|
||||
return actorHandle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ObjectId> submitActorTask(
|
||||
BaseActorHandle actor,
|
||||
FunctionDescriptor functionDescriptor,
|
||||
List<FunctionArg> args,
|
||||
int numReturns,
|
||||
CallOptions options) {
|
||||
Preconditions.checkState(numReturns <= 1);
|
||||
TaskSpec.Builder builder = getTaskSpecBuilder(TaskType.ACTOR_TASK, functionDescriptor, args);
|
||||
List<ObjectId> returnIds =
|
||||
getReturnIds(TaskId.fromBytes(builder.getTaskId().toByteArray()), numReturns);
|
||||
TaskSpec taskSpec =
|
||||
builder
|
||||
.setNumReturns(numReturns)
|
||||
.setActorTaskSpec(
|
||||
ActorTaskSpec.newBuilder()
|
||||
.setActorId(ByteString.copyFrom(actor.getId().getBytes()))
|
||||
.build())
|
||||
.setConcurrencyGroupName(options.getConcurrencyGroupName())
|
||||
.build();
|
||||
submitTaskSpec(taskSpec);
|
||||
if (numReturns == 0) {
|
||||
return ImmutableList.of();
|
||||
} else {
|
||||
return ImmutableList.of(returnIds.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlacementGroup createPlacementGroup(PlacementGroupCreationOptions creationOptions) {
|
||||
PlacementGroupImpl placementGroup =
|
||||
new PlacementGroupImpl.Builder()
|
||||
.setId(PlacementGroupId.fromRandom())
|
||||
.setName(creationOptions.getName())
|
||||
.setBundles(creationOptions.getBundles())
|
||||
.setStrategy(creationOptions.getStrategy())
|
||||
.build();
|
||||
placementGroups.put(placementGroup.getId(), placementGroup);
|
||||
return placementGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePlacementGroup(PlacementGroupId id) {
|
||||
placementGroups.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean waitPlacementGroupReady(PlacementGroupId id, int timeoutSeconds) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseActorHandle getActor(ActorId actorId) {
|
||||
return actorHandles.get(actorId).copy();
|
||||
}
|
||||
|
||||
public Optional<BaseActorHandle> getActor(String name) {
|
||||
ActorHandle actorHandle = namedActors.get(name);
|
||||
if (null == actorHandle) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(actorHandle);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
// Shutdown actor concurrency group manager.
|
||||
actorConcurrencyGroupManager.shutdown();
|
||||
// Shutdown normal task executor service.
|
||||
normalTaskExecutorService.shutdown();
|
||||
}
|
||||
|
||||
public static ActorId getActorId(TaskSpec taskSpec) {
|
||||
ByteString actorId = null;
|
||||
if (taskSpec.getType() == TaskType.ACTOR_CREATION_TASK) {
|
||||
actorId = taskSpec.getActorCreationTaskSpec().getActorId();
|
||||
} else if (taskSpec.getType() == TaskType.ACTOR_TASK) {
|
||||
actorId = taskSpec.getActorTaskSpec().getActorId();
|
||||
}
|
||||
if (actorId == null) {
|
||||
return null;
|
||||
}
|
||||
return ActorId.fromBytes(actorId.toByteArray());
|
||||
}
|
||||
|
||||
private void submitTaskSpec(TaskSpec taskSpec) {
|
||||
LOGGER.debug("Submitting task: {}.", taskSpec);
|
||||
synchronized (taskAndObjectLock) {
|
||||
Set<ObjectId> unreadyObjects = getUnreadyObjects(taskSpec);
|
||||
|
||||
final Runnable runnable =
|
||||
() -> {
|
||||
try {
|
||||
executeTask(taskSpec);
|
||||
if (taskSpec.getType() == TaskType.ACTOR_CREATION_TASK) {
|
||||
// Construct a dummy object id for actor creation task so that the following
|
||||
// actor task can touch if this actor is created.
|
||||
ObjectId dummy =
|
||||
IdUtil.getActorCreationDummyObjectId(
|
||||
ActorId.fromBytes(
|
||||
taskSpec.getActorCreationTaskSpec().getActorId().toByteArray()));
|
||||
objectStore.put(new Object(), dummy);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOGGER.error("Unexpected exception when executing a task.", ex);
|
||||
System.exit(-1);
|
||||
}
|
||||
};
|
||||
|
||||
if (taskSpec.getType() == TaskType.ACTOR_CREATION_TASK) {
|
||||
actorMaxConcurrency.put(
|
||||
getActorId(taskSpec), taskSpec.getActorCreationTaskSpec().getMaxConcurrency());
|
||||
}
|
||||
|
||||
if (unreadyObjects.isEmpty()) {
|
||||
// If all dependencies are ready, execute this task.
|
||||
ExecutorService executorService;
|
||||
if (taskSpec.getType() == TaskType.ACTOR_CREATION_TASK) {
|
||||
synchronized (actorConcurrencyGroupManager) {
|
||||
actorConcurrencyGroupManager.registerActor(getActorId(taskSpec), taskSpec);
|
||||
}
|
||||
executorService = normalTaskExecutorService;
|
||||
} else if (taskSpec.getType() == TaskType.ACTOR_TASK) {
|
||||
synchronized (actorConcurrencyGroupManager) {
|
||||
executorService =
|
||||
actorConcurrencyGroupManager.getExecutorServiceForConcurrencyGroup(taskSpec);
|
||||
}
|
||||
} else {
|
||||
// Normal task.
|
||||
executorService = normalTaskExecutorService;
|
||||
}
|
||||
try {
|
||||
executorService.submit(runnable);
|
||||
} catch (RejectedExecutionException e) {
|
||||
if (executorService.isShutdown()) {
|
||||
LOGGER.warn(
|
||||
"Ignore task submission due to the ExecutorService is shutdown. Task: {}",
|
||||
taskSpec);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If some dependencies aren't ready yet, put this task in waiting list.
|
||||
for (ObjectId id : unreadyObjects) {
|
||||
waitingTasks.computeIfAbsent(id, k -> new HashSet<>()).add(taskSpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void executeTask(TaskSpec taskSpec) {
|
||||
TaskExecutor.ActorContext actorContext = null;
|
||||
UniqueId workerId;
|
||||
if (taskSpec.getType() == TaskType.ACTOR_TASK) {
|
||||
actorContext = actorContexts.get(getActorId(taskSpec));
|
||||
Preconditions.checkNotNull(actorContext);
|
||||
workerId = ((LocalModeTaskExecutor.LocalActorContext) actorContext).getWorkerId();
|
||||
} else {
|
||||
// Actor creation task and normal task will use a new random worker id.
|
||||
workerId = UniqueId.randomId();
|
||||
}
|
||||
taskExecutor.setActorContext(workerId, actorContext);
|
||||
List<NativeRayObject> args =
|
||||
getFunctionArgs(taskSpec).stream()
|
||||
.map(
|
||||
arg ->
|
||||
arg.id != null
|
||||
? objectStore.getRaw(Collections.singletonList(arg.id), -1).get(0)
|
||||
: arg.value)
|
||||
.collect(Collectors.toList());
|
||||
((LocalModeWorkerContext) runtime.getWorkerContext()).setCurrentTask(taskSpec);
|
||||
|
||||
((LocalModeWorkerContext) runtime.getWorkerContext()).setCurrentWorkerId(workerId);
|
||||
List<String> rayFunctionInfo = getJavaFunctionDescriptor(taskSpec).toList();
|
||||
taskExecutor.checkByteBufferArguments(rayFunctionInfo);
|
||||
List<NativeRayObject> returnObjects = taskExecutor.execute(rayFunctionInfo, args);
|
||||
if (taskSpec.getType() == TaskType.ACTOR_CREATION_TASK) {
|
||||
// Update actor context map ASAP in case objectStore.putRaw triggered the next actor task
|
||||
// on this actor.
|
||||
final TaskExecutor.ActorContext ac = taskExecutor.getActorContext();
|
||||
Preconditions.checkNotNull(ac);
|
||||
actorContexts.put(getActorId(taskSpec), ac);
|
||||
}
|
||||
// Set this flag to true is necessary because at the end of `taskExecutor.execute()`,
|
||||
// this flag will be set to false. And `runtime.getWorkerContext()` requires it to be
|
||||
// true.
|
||||
((LocalModeWorkerContext) runtime.getWorkerContext()).setCurrentTask(null);
|
||||
List<ObjectId> returnIds = getReturnIds(taskSpec);
|
||||
for (int i = 0; i < returnIds.size(); i++) {
|
||||
NativeRayObject putObject;
|
||||
if (i >= returnObjects.size()) {
|
||||
// If the task is an actor task or an actor creation task,
|
||||
// put the dummy object in object store, so those tasks which depends on it
|
||||
// can be executed.
|
||||
putObject = new NativeRayObject(new byte[] {1}, null);
|
||||
} else {
|
||||
putObject = returnObjects.get(i);
|
||||
}
|
||||
objectStore.putRaw(putObject, returnIds.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
private static JavaFunctionDescriptor getJavaFunctionDescriptor(TaskSpec taskSpec) {
|
||||
Common.FunctionDescriptor functionDescriptor = taskSpec.getFunctionDescriptor();
|
||||
if (functionDescriptor.getFunctionDescriptorCase()
|
||||
== Common.FunctionDescriptor.FunctionDescriptorCase.JAVA_FUNCTION_DESCRIPTOR) {
|
||||
return new JavaFunctionDescriptor(
|
||||
functionDescriptor.getJavaFunctionDescriptor().getClassName(),
|
||||
functionDescriptor.getJavaFunctionDescriptor().getFunctionName(),
|
||||
functionDescriptor.getJavaFunctionDescriptor().getSignature());
|
||||
} else {
|
||||
throw new RuntimeException("Can't build non java function descriptor");
|
||||
}
|
||||
}
|
||||
|
||||
private static List<FunctionArg> getFunctionArgs(TaskSpec taskSpec) {
|
||||
List<FunctionArg> functionArgs = new ArrayList<>();
|
||||
for (int i = 0; i < taskSpec.getArgsCount(); i++) {
|
||||
TaskArg arg = taskSpec.getArgs(i);
|
||||
if (arg.getObjectRef().getObjectId() != ByteString.EMPTY) {
|
||||
functionArgs.add(
|
||||
FunctionArg.passByReference(
|
||||
new ObjectId(arg.getObjectRef().getObjectId().toByteArray()),
|
||||
Address.getDefaultInstance()));
|
||||
} else {
|
||||
functionArgs.add(
|
||||
FunctionArg.passByValue(
|
||||
new NativeRayObject(arg.getData().toByteArray(), arg.getMetadata().toByteArray())));
|
||||
}
|
||||
}
|
||||
return functionArgs;
|
||||
}
|
||||
|
||||
private static List<ObjectId> getReturnIds(TaskSpec taskSpec) {
|
||||
return getReturnIds(
|
||||
TaskId.fromBytes(taskSpec.getTaskId().toByteArray()), taskSpec.getNumReturns());
|
||||
}
|
||||
|
||||
private static List<ObjectId> getReturnIds(TaskId taskId, long numReturns) {
|
||||
List<ObjectId> returnIds = new ArrayList<>();
|
||||
for (int i = 0; i < numReturns; i++) {
|
||||
returnIds.add(
|
||||
ObjectId.fromByteBuffer(
|
||||
(ByteBuffer)
|
||||
ByteBuffer.allocate(ObjectId.LENGTH)
|
||||
.put(taskId.getBytes())
|
||||
.putInt(TaskId.LENGTH, i + 1)
|
||||
.position(0)));
|
||||
}
|
||||
return returnIds;
|
||||
}
|
||||
|
||||
/** Whether this is a concurrent actor. */
|
||||
private boolean isConcurrentActor(TaskSpec taskSpec) {
|
||||
final ActorId actorId = getActorId(taskSpec);
|
||||
Preconditions.checkNotNull(actorId);
|
||||
return actorMaxConcurrency.containsKey(actorId) && actorMaxConcurrency.get(actorId) > 1;
|
||||
}
|
||||
|
||||
private static void appendConcurrencyGroupsBuilder(
|
||||
ActorCreationTaskSpec.Builder actorCreationTaskSpecBuilder, ActorCreationOptions options) {
|
||||
Preconditions.checkNotNull(actorCreationTaskSpecBuilder);
|
||||
if (options == null
|
||||
|| options.getConcurrencyGroups() == null
|
||||
|| options.getConcurrencyGroups().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
options
|
||||
.getConcurrencyGroups()
|
||||
.forEach(
|
||||
(concurrencyGroup) -> {
|
||||
Common.ConcurrencyGroup.Builder concurrencyGroupBuilder =
|
||||
Common.ConcurrencyGroup.newBuilder();
|
||||
ConcurrencyGroupImpl impl = (ConcurrencyGroupImpl) concurrencyGroup;
|
||||
concurrencyGroupBuilder
|
||||
.setMaxConcurrency(impl.getMaxConcurrency())
|
||||
.setName(impl.getName());
|
||||
appendFunctionDescriptors(concurrencyGroupBuilder, impl.getFunctionDescriptors());
|
||||
actorCreationTaskSpecBuilder.addConcurrencyGroups(concurrencyGroupBuilder);
|
||||
});
|
||||
}
|
||||
|
||||
private static void appendFunctionDescriptors(
|
||||
Common.ConcurrencyGroup.Builder builder, List<FunctionDescriptor> functionDescriptors) {
|
||||
Preconditions.checkNotNull(functionDescriptors);
|
||||
Preconditions.checkState(!functionDescriptors.isEmpty());
|
||||
functionDescriptors.stream()
|
||||
.map(functionDescriptor -> (JavaFunctionDescriptor) functionDescriptor)
|
||||
.map(
|
||||
javaFunctionDescriptor ->
|
||||
Common.FunctionDescriptor.newBuilder()
|
||||
.setJavaFunctionDescriptor(
|
||||
Common.JavaFunctionDescriptor.newBuilder()
|
||||
.setClassName(javaFunctionDescriptor.className)
|
||||
.setFunctionName(javaFunctionDescriptor.name)
|
||||
.setSignature(javaFunctionDescriptor.signature)))
|
||||
.forEach(builder::addFunctionDescriptors);
|
||||
}
|
||||
|
||||
private static JavaFunctionDescriptor protoFunctionDescriptorToJava(
|
||||
Common.FunctionDescriptor protoFunctionDescriptor) {
|
||||
Preconditions.checkNotNull(protoFunctionDescriptor);
|
||||
Common.JavaFunctionDescriptor protoJavaFunctionDescriptor =
|
||||
protoFunctionDescriptor.getJavaFunctionDescriptor();
|
||||
return new JavaFunctionDescriptor(
|
||||
protoJavaFunctionDescriptor.getClassName(),
|
||||
protoJavaFunctionDescriptor.getFunctionName(),
|
||||
protoJavaFunctionDescriptor.getSignature());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.ray.runtime.task;
|
||||
|
||||
import io.ray.api.id.UniqueId;
|
||||
import io.ray.runtime.AbstractRayRuntime;
|
||||
|
||||
/**
|
||||
* Task executor for cluster mode.
|
||||
*
|
||||
* <p>In cluster mode, each worker process serves exactly one actor. The C++ core worker runs {@code
|
||||
* ACTOR_CREATION_TASK} on the main event-loop thread and dispatches subsequent {@code ACTOR_TASK}s
|
||||
* to a BoundedExecutor thread pool — potentially a different thread. Therefore the actor context is
|
||||
* stored as a shared volatile field so that it is visible across all threads within the same worker
|
||||
* process.
|
||||
*/
|
||||
public class NativeTaskExecutor extends TaskExecutor<NativeTaskExecutor.NativeActorContext> {
|
||||
|
||||
static class NativeActorContext extends TaskExecutor.ActorContext {}
|
||||
|
||||
/** Shared across threads; safe because one worker process serves exactly one actor. */
|
||||
private volatile NativeActorContext actorContext;
|
||||
|
||||
public NativeTaskExecutor(AbstractRayRuntime runtime) {
|
||||
super(runtime);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NativeActorContext createActorContext() {
|
||||
return new NativeActorContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NativeActorContext getActorContext() {
|
||||
return actorContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setActorContext(UniqueId workerId, NativeActorContext actorContext) {
|
||||
this.actorContext = actorContext;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package io.ray.runtime.task;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import io.ray.api.BaseActorHandle;
|
||||
import io.ray.api.PlacementGroups;
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.api.id.ActorId;
|
||||
import io.ray.api.id.ObjectId;
|
||||
import io.ray.api.id.PlacementGroupId;
|
||||
import io.ray.api.options.ActorCreationOptions;
|
||||
import io.ray.api.options.CallOptions;
|
||||
import io.ray.api.options.PlacementGroupCreationOptions;
|
||||
import io.ray.api.placementgroup.PlacementGroup;
|
||||
import io.ray.runtime.actor.NativeActorHandle;
|
||||
import io.ray.runtime.functionmanager.FunctionDescriptor;
|
||||
import io.ray.runtime.placementgroup.PlacementGroupImpl;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/** Task submitter for cluster mode. This is a wrapper class for core worker task interface. */
|
||||
public class NativeTaskSubmitter implements TaskSubmitter {
|
||||
|
||||
@Override
|
||||
public List<ObjectId> submitTask(
|
||||
FunctionDescriptor functionDescriptor,
|
||||
List<FunctionArg> args,
|
||||
int numReturns,
|
||||
CallOptions options) {
|
||||
List<byte[]> returnIds =
|
||||
nativeSubmitTask(
|
||||
functionDescriptor, functionDescriptor.hashCode(), args, numReturns, options);
|
||||
if (returnIds == null) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
return returnIds.stream().map(ObjectId::new).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseActorHandle createActor(
|
||||
FunctionDescriptor functionDescriptor, List<FunctionArg> args, ActorCreationOptions options)
|
||||
throws IllegalArgumentException {
|
||||
if (options != null) {
|
||||
if (options.getGroup() != null) {
|
||||
PlacementGroupImpl group = (PlacementGroupImpl) options.getGroup();
|
||||
// bundleIndex == -1 indicates using any available bundle.
|
||||
Preconditions.checkArgument(
|
||||
options.getBundleIndex() == -1
|
||||
|| options.getBundleIndex() >= 0
|
||||
&& options.getBundleIndex() < group.getBundles().size(),
|
||||
String.format(
|
||||
"Bundle index %s is invalid, the correct bundle index should be "
|
||||
+ "either in the range of 0 to the number of bundles "
|
||||
+ "or -1 which means put the task to any available bundles.",
|
||||
options.getBundleIndex()));
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(options.getName())) {
|
||||
Optional<BaseActorHandle> actor = Ray.getActor(options.getName());
|
||||
Preconditions.checkArgument(
|
||||
!actor.isPresent(), String.format("Actor of name %s exists", options.getName()));
|
||||
}
|
||||
}
|
||||
byte[] actorId =
|
||||
nativeCreateActor(functionDescriptor, functionDescriptor.hashCode(), args, options);
|
||||
return NativeActorHandle.create(actorId, functionDescriptor.getLanguage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseActorHandle getActor(ActorId actorId) {
|
||||
return NativeActorHandle.create(actorId.getBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ObjectId> submitActorTask(
|
||||
BaseActorHandle actor,
|
||||
FunctionDescriptor functionDescriptor,
|
||||
List<FunctionArg> args,
|
||||
int numReturns,
|
||||
CallOptions options) {
|
||||
Preconditions.checkState(actor instanceof NativeActorHandle);
|
||||
// TODO: Ray Java does not support per-method MaxRetries. It only supports
|
||||
// setting Actor-level MaxTaskRetries for any method calls.
|
||||
// See: src/ray/core_worker/lib/java/io_ray_runtime_task_NativeTaskSubmitter.cc
|
||||
List<byte[]> returnIds =
|
||||
nativeSubmitActorTask(
|
||||
actor.getId().getBytes(),
|
||||
functionDescriptor,
|
||||
functionDescriptor.hashCode(),
|
||||
args,
|
||||
numReturns,
|
||||
options);
|
||||
if (returnIds == null) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
return returnIds.stream().map(ObjectId::new).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlacementGroup createPlacementGroup(PlacementGroupCreationOptions creationOptions) {
|
||||
if (StringUtils.isNotBlank(creationOptions.getName())) {
|
||||
PlacementGroup placementGroup = PlacementGroups.getPlacementGroup(creationOptions.getName());
|
||||
Preconditions.checkArgument(
|
||||
placementGroup == null,
|
||||
String.format("Placement group with name %s exists!", creationOptions.getName()));
|
||||
}
|
||||
byte[] bytes = nativeCreatePlacementGroup(creationOptions);
|
||||
return new PlacementGroupImpl.Builder()
|
||||
.setId(PlacementGroupId.fromBytes(bytes))
|
||||
.setName(creationOptions.getName())
|
||||
.setBundles(creationOptions.getBundles())
|
||||
.setStrategy(creationOptions.getStrategy())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePlacementGroup(PlacementGroupId id) {
|
||||
nativeRemovePlacementGroup(id.getBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean waitPlacementGroupReady(PlacementGroupId id, int timeoutSeconds) {
|
||||
return nativeWaitPlacementGroupReady(id.getBytes(), timeoutSeconds);
|
||||
}
|
||||
|
||||
private static native List<byte[]> nativeSubmitTask(
|
||||
FunctionDescriptor functionDescriptor,
|
||||
int functionDescriptorHash,
|
||||
List<FunctionArg> args,
|
||||
int numReturns,
|
||||
CallOptions callOptions);
|
||||
|
||||
private static native byte[] nativeCreateActor(
|
||||
FunctionDescriptor functionDescriptor,
|
||||
int functionDescriptorHash,
|
||||
List<FunctionArg> args,
|
||||
ActorCreationOptions actorCreationOptions);
|
||||
|
||||
private static native List<byte[]> nativeSubmitActorTask(
|
||||
byte[] actorId,
|
||||
FunctionDescriptor functionDescriptor,
|
||||
int functionDescriptorHash,
|
||||
List<FunctionArg> args,
|
||||
int numReturns,
|
||||
CallOptions callOptions);
|
||||
|
||||
private static native byte[] nativeCreatePlacementGroup(
|
||||
PlacementGroupCreationOptions creationOptions);
|
||||
|
||||
private static native void nativeRemovePlacementGroup(byte[] placementGroupId);
|
||||
|
||||
private static native boolean nativeWaitPlacementGroupReady(
|
||||
byte[] placementGroupId, int timeoutSeconds);
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package io.ray.runtime.task;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.api.exception.RayActorException;
|
||||
import io.ray.api.exception.RayException;
|
||||
import io.ray.api.exception.RayIntentionalSystemExitException;
|
||||
import io.ray.api.exception.RayTaskException;
|
||||
import io.ray.api.id.JobId;
|
||||
import io.ray.api.id.TaskId;
|
||||
import io.ray.api.id.UniqueId;
|
||||
import io.ray.runtime.AbstractRayRuntime;
|
||||
import io.ray.runtime.functionmanager.JavaFunctionDescriptor;
|
||||
import io.ray.runtime.functionmanager.RayFunction;
|
||||
import io.ray.runtime.generated.Common.TaskType;
|
||||
import io.ray.runtime.object.NativeRayObject;
|
||||
import io.ray.runtime.object.ObjectSerializer;
|
||||
import io.ray.runtime.util.NetworkUtil;
|
||||
import io.ray.runtime.util.SystemUtil;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** The task executor, which executes tasks assigned by raylet continuously. */
|
||||
public abstract class TaskExecutor<T extends TaskExecutor.ActorContext> {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TaskExecutor.class);
|
||||
|
||||
protected final AbstractRayRuntime runtime;
|
||||
|
||||
private final ThreadLocal<RayFunction> localRayFunction = new ThreadLocal<>();
|
||||
|
||||
static class ActorContext {
|
||||
/** The current actor object, if this worker is an actor, otherwise null. */
|
||||
Object currentActor = null;
|
||||
}
|
||||
|
||||
TaskExecutor(AbstractRayRuntime runtime) {
|
||||
this.runtime = runtime;
|
||||
}
|
||||
|
||||
protected abstract T createActorContext();
|
||||
|
||||
/**
|
||||
* Retrieve the actor context for the current execution.
|
||||
*
|
||||
* <p>Subclasses implement this to match their threading model:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Cluster mode: a single actor per worker process, context is shared across threads.
|
||||
* <li>Local mode: multiple actors may share one TaskExecutor, context is thread-isolated.
|
||||
* </ul>
|
||||
*/
|
||||
protected abstract T getActorContext();
|
||||
|
||||
/**
|
||||
* Store the actor context after creation.
|
||||
*
|
||||
* @param workerId the worker that owns this actor
|
||||
* @param actorContext the context to store, or {@code null} to clear
|
||||
*/
|
||||
protected abstract void setActorContext(UniqueId workerId, T actorContext);
|
||||
|
||||
private RayFunction getRayFunction(List<String> rayFunctionInfo) {
|
||||
JobId jobId = runtime.getWorkerContext().getCurrentJobId();
|
||||
JavaFunctionDescriptor functionDescriptor = parseFunctionDescriptor(rayFunctionInfo);
|
||||
return runtime.getFunctionManager().getFunction(functionDescriptor);
|
||||
}
|
||||
|
||||
/** The return value indicates which parameters are ByteBuffer. */
|
||||
protected boolean[] checkByteBufferArguments(List<String> rayFunctionInfo) {
|
||||
localRayFunction.set(null);
|
||||
try {
|
||||
localRayFunction.set(getRayFunction(rayFunctionInfo));
|
||||
} catch (Throwable e) {
|
||||
// Ignore the exception.
|
||||
return null;
|
||||
}
|
||||
Class<?>[] types = localRayFunction.get().executable.getParameterTypes();
|
||||
boolean[] results = new boolean[types.length];
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
results[i] = types[i] == ByteBuffer.class;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private void throwIfDependencyFailed(Object arg) {
|
||||
if (arg instanceof RayException) {
|
||||
throw (RayException) arg;
|
||||
}
|
||||
}
|
||||
|
||||
protected List<NativeRayObject> execute(List<String> rayFunctionInfo, List<Object> argsBytes) {
|
||||
TaskType taskType = runtime.getWorkerContext().getCurrentTaskType();
|
||||
TaskId taskId = runtime.getWorkerContext().getCurrentTaskId();
|
||||
LOGGER.debug("Executing task {} {}", taskId, rayFunctionInfo);
|
||||
|
||||
T actorContext = null;
|
||||
if (taskType == TaskType.ACTOR_CREATION_TASK) {
|
||||
actorContext = createActorContext();
|
||||
setActorContext(runtime.getWorkerContext().getCurrentWorkerId(), actorContext);
|
||||
} else if (taskType == TaskType.ACTOR_TASK) {
|
||||
actorContext = getActorContext();
|
||||
Preconditions.checkNotNull(actorContext);
|
||||
}
|
||||
|
||||
List<NativeRayObject> returnObjects = new ArrayList<>();
|
||||
// Find the executable object.
|
||||
|
||||
RayFunction rayFunction = localRayFunction.get();
|
||||
Object[] args = null;
|
||||
try {
|
||||
// Find the executable object.
|
||||
if (rayFunction == null) {
|
||||
// Failed to get RayFunction in checkByteBufferArguments. Redo here to throw
|
||||
// the exception again.
|
||||
rayFunction = getRayFunction(rayFunctionInfo);
|
||||
}
|
||||
Thread.currentThread().setContextClassLoader(rayFunction.classLoader);
|
||||
|
||||
// Get local actor object and arguments.
|
||||
Object actor = null;
|
||||
if (taskType == TaskType.ACTOR_TASK) {
|
||||
actor = actorContext.currentActor;
|
||||
}
|
||||
args = ArgumentsBuilder.unwrap(argsBytes, rayFunction.executable.getParameterTypes());
|
||||
for (Object arg : args) {
|
||||
throwIfDependencyFailed(arg);
|
||||
}
|
||||
|
||||
// Execute the task.
|
||||
Object result;
|
||||
try {
|
||||
if (!rayFunction.isConstructor()) {
|
||||
result = rayFunction.getMethod().invoke(actor, args);
|
||||
} else {
|
||||
result = rayFunction.getConstructor().newInstance(args);
|
||||
}
|
||||
} catch (InvocationTargetException e) {
|
||||
if (e.getCause() != null) {
|
||||
throw e.getCause();
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// Set result
|
||||
if (taskType != TaskType.ACTOR_CREATION_TASK) {
|
||||
if (rayFunction.hasReturn()) {
|
||||
returnObjects.add(ObjectSerializer.serialize(result));
|
||||
}
|
||||
} else {
|
||||
actorContext.currentActor = result;
|
||||
}
|
||||
LOGGER.debug("Finished executing task {}", taskId);
|
||||
} catch (Throwable e) {
|
||||
if (e instanceof RayIntentionalSystemExitException) {
|
||||
// We don't need to fill the `returnObjects` with an exception metadata
|
||||
// because the node manager or the direct actor task submitter will fill
|
||||
// the return object with the ACTOR_DIED metadata.
|
||||
throw (RayIntentionalSystemExitException) e;
|
||||
}
|
||||
|
||||
final List<Class<?>> argTypes =
|
||||
args == null
|
||||
? null
|
||||
: Arrays.stream(args)
|
||||
.map(arg -> arg == null ? null : arg.getClass())
|
||||
.collect(Collectors.toList());
|
||||
LOGGER.error(
|
||||
"Failed to execute task {} . rayFunction is {} , argument types are {}",
|
||||
taskId,
|
||||
rayFunction,
|
||||
argTypes,
|
||||
e);
|
||||
|
||||
if (taskType != TaskType.ACTOR_CREATION_TASK) {
|
||||
boolean hasReturn = rayFunction != null && rayFunction.hasReturn();
|
||||
boolean isCrossLanguage = parseFunctionDescriptor(rayFunctionInfo).signature.equals("");
|
||||
if (hasReturn || isCrossLanguage) {
|
||||
NativeRayObject serializedException;
|
||||
try {
|
||||
serializedException =
|
||||
ObjectSerializer.serialize(
|
||||
new RayTaskException(
|
||||
SystemUtil.pid(),
|
||||
NetworkUtil.getIpAddress(null),
|
||||
"Error executing task " + taskId,
|
||||
e));
|
||||
} catch (Exception unserializable) {
|
||||
// We should try-catch `ObjectSerializer.serialize` here. Because otherwise if the
|
||||
// application-level exception is not serializable. `ObjectSerializer.serialize`
|
||||
// will throw an exception and crash the worker.
|
||||
// Refer to the case `TaskExceptionTest.java` for more details.
|
||||
LOGGER.warn("Failed to serialize the exception to a RayObject.", unserializable);
|
||||
serializedException =
|
||||
ObjectSerializer.serialize(
|
||||
new RayTaskException(
|
||||
String.format(
|
||||
"Error executing task %s with the exception: %s",
|
||||
taskId, ExceptionUtils.getStackTrace(e))));
|
||||
}
|
||||
Preconditions.checkNotNull(serializedException);
|
||||
returnObjects.add(serializedException);
|
||||
} else {
|
||||
returnObjects.add(
|
||||
ObjectSerializer.serialize(
|
||||
new RayTaskException(
|
||||
SystemUtil.pid(),
|
||||
NetworkUtil.getIpAddress(null),
|
||||
String.format(
|
||||
"Function %s of task %s doesn't exist",
|
||||
String.join(".", rayFunctionInfo), taskId),
|
||||
e)));
|
||||
}
|
||||
} else {
|
||||
throw new RayActorException(SystemUtil.pid(), NetworkUtil.getIpAddress(null), e);
|
||||
}
|
||||
}
|
||||
return returnObjects;
|
||||
}
|
||||
|
||||
private JavaFunctionDescriptor parseFunctionDescriptor(List<String> rayFunctionInfo) {
|
||||
Preconditions.checkState(rayFunctionInfo != null && rayFunctionInfo.size() == 3);
|
||||
return new JavaFunctionDescriptor(
|
||||
rayFunctionInfo.get(0), rayFunctionInfo.get(1), rayFunctionInfo.get(2));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package io.ray.runtime.task;
|
||||
|
||||
import io.ray.api.BaseActorHandle;
|
||||
import io.ray.api.id.ActorId;
|
||||
import io.ray.api.id.ObjectId;
|
||||
import io.ray.api.id.PlacementGroupId;
|
||||
import io.ray.api.options.ActorCreationOptions;
|
||||
import io.ray.api.options.CallOptions;
|
||||
import io.ray.api.options.PlacementGroupCreationOptions;
|
||||
import io.ray.api.placementgroup.PlacementGroup;
|
||||
import io.ray.runtime.functionmanager.FunctionDescriptor;
|
||||
import java.util.List;
|
||||
|
||||
/** A set of methods to submit tasks and create actors. */
|
||||
public interface TaskSubmitter {
|
||||
|
||||
/**
|
||||
* Submit a normal task.
|
||||
*
|
||||
* @param functionDescriptor The remote function to execute.
|
||||
* @param args Arguments of this task.
|
||||
* @param numReturns Return object count.
|
||||
* @param options Options for this task.
|
||||
* @return Ids of the return objects.
|
||||
*/
|
||||
List<ObjectId> submitTask(
|
||||
FunctionDescriptor functionDescriptor,
|
||||
List<FunctionArg> args,
|
||||
int numReturns,
|
||||
CallOptions options);
|
||||
|
||||
/**
|
||||
* Create an actor.
|
||||
*
|
||||
* @param functionDescriptor The remote function that generates the actor object.
|
||||
* @param args Arguments of this task.
|
||||
* @param options Options for this actor creation task.
|
||||
* @return Handle to the actor.
|
||||
* @throws IllegalArgumentException if actor of specified name exists
|
||||
*/
|
||||
BaseActorHandle createActor(
|
||||
FunctionDescriptor functionDescriptor, List<FunctionArg> args, ActorCreationOptions options)
|
||||
throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Submit an actor task.
|
||||
*
|
||||
* @param actor Handle to the actor.
|
||||
* @param functionDescriptor The remote function to execute.
|
||||
* @param args Arguments of this task.
|
||||
* @param numReturns Return object count.
|
||||
* @param options Options for this task.
|
||||
* @return Ids of the return objects.
|
||||
*/
|
||||
List<ObjectId> submitActorTask(
|
||||
BaseActorHandle actor,
|
||||
FunctionDescriptor functionDescriptor,
|
||||
List<FunctionArg> args,
|
||||
int numReturns,
|
||||
CallOptions options);
|
||||
|
||||
/**
|
||||
* Create a placement group.
|
||||
*
|
||||
* @param creationOptions Creation options of the placement group.
|
||||
* @return A handle to the created placement group.
|
||||
*/
|
||||
PlacementGroup createPlacementGroup(PlacementGroupCreationOptions creationOptions);
|
||||
|
||||
/**
|
||||
* Remove a placement group by id.
|
||||
*
|
||||
* @param id Id of the placement group.
|
||||
*/
|
||||
void removePlacementGroup(PlacementGroupId id);
|
||||
|
||||
/**
|
||||
* Wait for the placement group to be ready within the specified time.
|
||||
*
|
||||
* @param id Id of placement group.
|
||||
* @param timeoutSeconds Timeout in seconds.
|
||||
* @return True if the placement group is created. False otherwise.
|
||||
*/
|
||||
boolean waitPlacementGroupReady(PlacementGroupId id, int timeoutSeconds);
|
||||
|
||||
BaseActorHandle getActor(ActorId actorId);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package io.ray.runtime.util;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
public class BinaryFileUtil {
|
||||
|
||||
public static final String CORE_WORKER_JAVA_LIBRARY = "core_worker_library_java";
|
||||
|
||||
/**
|
||||
* Extract a platform-native resource file to <code>destDir</code>. Note that this a process-safe
|
||||
* operation. If multi processes extract the file to same directory concurrently, this operation
|
||||
* will be protected by a file lock.
|
||||
*
|
||||
* @param destDir a directory to extract resource file to
|
||||
* @param fileName resource file name
|
||||
* @return extracted resource file
|
||||
*/
|
||||
public static File getNativeFile(String destDir, String fileName) {
|
||||
final File dir = new File(destDir);
|
||||
if (!dir.exists()) {
|
||||
try {
|
||||
FileUtils.forceMkdir(dir);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Couldn't make directory: " + dir.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
String lockFilePath = destDir + File.separator + "file_lock";
|
||||
try (FileLock ignored = new RandomAccessFile(lockFilePath, "rw").getChannel().lock()) {
|
||||
String resourceDir;
|
||||
if (SystemUtils.IS_OS_MAC) {
|
||||
resourceDir = "native/darwin/";
|
||||
} else if (SystemUtils.IS_OS_LINUX) {
|
||||
resourceDir = "native/linux/";
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Unsupported os " + SystemUtils.OS_NAME);
|
||||
}
|
||||
/// File doesn't exist. Create a temp file and then rename it.
|
||||
final String tempFilePath = String.format("%s/%s.tmp", destDir, fileName);
|
||||
// Adding a temporary file here is used to fix the issue that when
|
||||
// a java worker crashes during extracting dynamic library file, next
|
||||
// java worker will use an incomplete file. The issue link is:
|
||||
//
|
||||
// https://github.com/ray-project/ray/issues/19341
|
||||
File tempFile = new File(tempFilePath);
|
||||
|
||||
String resourcePath = resourceDir + fileName;
|
||||
File destFile = new File(String.format("%s/%s", destDir, fileName));
|
||||
if (destFile.exists()) {
|
||||
return destFile;
|
||||
}
|
||||
|
||||
// File does not exist.
|
||||
try (InputStream is = BinaryFileUtil.class.getResourceAsStream("/" + resourcePath)) {
|
||||
Preconditions.checkNotNull(is, "{} doesn't exist.", resourcePath);
|
||||
Files.copy(is, Paths.get(tempFile.getCanonicalPath()), StandardCopyOption.REPLACE_EXISTING);
|
||||
if (!tempFile.renameTo(destFile)) {
|
||||
throw new RuntimeException(
|
||||
String.format(
|
||||
"Couldn't rename temp file(%s) to %s",
|
||||
tempFile.getAbsolutePath(), destFile.getAbsolutePath()));
|
||||
}
|
||||
return destFile;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Couldn't get temp file from resource " + resourcePath, e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package io.ray.runtime.util;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.api.concurrencygroup.ConcurrencyGroup;
|
||||
import io.ray.api.concurrencygroup.annotations.DefConcurrencyGroup;
|
||||
import io.ray.api.concurrencygroup.annotations.DefConcurrencyGroups;
|
||||
import io.ray.api.concurrencygroup.annotations.UseConcurrencyGroup;
|
||||
import io.ray.api.function.RayFuncR;
|
||||
import io.ray.runtime.ConcurrencyGroupImpl;
|
||||
import io.ray.runtime.functionmanager.JavaFunctionDescriptor;
|
||||
import java.lang.invoke.SerializedLambda;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/// TODO(qwang): cache this.
|
||||
public final class ConcurrencyGroupUtils {
|
||||
|
||||
public static List<ConcurrencyGroup> extractConcurrencyGroupsByAnnotations(
|
||||
RayFuncR<?> actorConstructorLambda) {
|
||||
SerializedLambda serializedLambda = LambdaUtils.getSerializedLambda(actorConstructorLambda);
|
||||
Class<?> actorClz =
|
||||
MethodUtils.getReturnTypeFromSignature(serializedLambda.getInstantiatedMethodType());
|
||||
|
||||
/// Extract the concurrency groups definition.
|
||||
ArrayList<ConcurrencyGroup> ret = new ArrayList<ConcurrencyGroup>();
|
||||
Map<String, ConcurrencyGroupImpl> allConcurrencyGroupsMap =
|
||||
extractConcurrencyGroupsFromClassAnnotation(actorClz);
|
||||
Class<?>[] interfaces = actorClz.getInterfaces();
|
||||
for (Class<?> interfaceClz : interfaces) {
|
||||
Preconditions.checkState(interfaceClz != null);
|
||||
Map<String, ConcurrencyGroupImpl> currConcurrencyGroups =
|
||||
extractConcurrencyGroupsFromClassAnnotation(interfaceClz);
|
||||
allConcurrencyGroupsMap.putAll(currConcurrencyGroups);
|
||||
}
|
||||
|
||||
/// Extract the using of concurrency groups which annotated on the actor methods.
|
||||
Method[] methods = actorClz.getMethods();
|
||||
for (Method method : methods) {
|
||||
UseConcurrencyGroup useConcurrencyGroupAnnotation =
|
||||
method.getAnnotation(UseConcurrencyGroup.class);
|
||||
if (useConcurrencyGroupAnnotation != null) {
|
||||
String concurrencyGroupName = useConcurrencyGroupAnnotation.name();
|
||||
Preconditions.checkState(allConcurrencyGroupsMap.containsKey(concurrencyGroupName));
|
||||
allConcurrencyGroupsMap
|
||||
.get(concurrencyGroupName)
|
||||
.addJavaFunctionDescriptor(
|
||||
new JavaFunctionDescriptor(
|
||||
method.getDeclaringClass().getName(),
|
||||
method.getName(),
|
||||
MethodUtils.getSignature(method)));
|
||||
}
|
||||
}
|
||||
|
||||
allConcurrencyGroupsMap.forEach(
|
||||
(key, value) -> {
|
||||
ret.add(value);
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Extract the concurrency groups from the class annotation.
|
||||
/// Both work for class and interface.
|
||||
private static Map<String, ConcurrencyGroupImpl> extractConcurrencyGroupsFromClassAnnotation(
|
||||
Class<?> clz) {
|
||||
Map<String, ConcurrencyGroupImpl> ret = new HashMap<>();
|
||||
DefConcurrencyGroups concurrencyGroupsDefinitionAnnotation =
|
||||
clz.getAnnotation(DefConcurrencyGroups.class);
|
||||
if (concurrencyGroupsDefinitionAnnotation != null) {
|
||||
DefConcurrencyGroup[] defAnnotations = concurrencyGroupsDefinitionAnnotation.value();
|
||||
if (defAnnotations.length == 0) {
|
||||
throw new IllegalArgumentException("TODO");
|
||||
}
|
||||
for (DefConcurrencyGroup def : defAnnotations) {
|
||||
ret.put(def.name(), new ConcurrencyGroupImpl(def.name(), def.maxConcurrency()));
|
||||
}
|
||||
} else {
|
||||
/// Code path of that no annotation or 1 annotation definition.
|
||||
DefConcurrencyGroup defConcurrencyGroup = clz.getAnnotation(DefConcurrencyGroup.class);
|
||||
if (defConcurrencyGroup != null) {
|
||||
ret.put(
|
||||
defConcurrencyGroup.name(),
|
||||
new ConcurrencyGroupImpl(
|
||||
defConcurrencyGroup.name(), defConcurrencyGroup.maxConcurrency()));
|
||||
} else {
|
||||
/// This actor is not defined with concurrency groups annotation.
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.ray.runtime.util;
|
||||
|
||||
import io.ray.api.id.ActorId;
|
||||
import io.ray.api.id.ObjectId;
|
||||
import io.ray.api.id.TaskId;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Helper method for different Ids. Note: any changes to these methods must be synced with C++
|
||||
* helper functions in src/ray/common/id.h
|
||||
*/
|
||||
public class IdUtil {
|
||||
|
||||
/**
|
||||
* Compute the actor ID of the task which created this object.
|
||||
*
|
||||
* @return The actor ID of the task which created this object.
|
||||
*/
|
||||
public static ActorId getActorIdFromObjectId(ObjectId objectId) {
|
||||
byte[] taskIdBytes = new byte[TaskId.LENGTH];
|
||||
System.arraycopy(objectId.getBytes(), 0, taskIdBytes, 0, TaskId.LENGTH);
|
||||
TaskId taskId = TaskId.fromBytes(taskIdBytes);
|
||||
byte[] actorIdBytes = new byte[ActorId.LENGTH];
|
||||
System.arraycopy(
|
||||
taskId.getBytes(), TaskId.UNIQUE_BYTES_LENGTH, actorIdBytes, 0, ActorId.LENGTH);
|
||||
return ActorId.fromBytes(actorIdBytes);
|
||||
}
|
||||
|
||||
/** Compute the dummy object id for actor creation task. */
|
||||
public static ObjectId getActorCreationDummyObjectId(ActorId actorId) {
|
||||
byte[] objectIdBytes = new byte[ObjectId.LENGTH];
|
||||
Arrays.fill(objectIdBytes, (byte) 0xFF);
|
||||
byte[] actorIdBytes = actorId.getBytes();
|
||||
System.arraycopy(actorIdBytes, 0, objectIdBytes, 0, ActorId.LENGTH);
|
||||
return new ObjectId(objectIdBytes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.ray.runtime.util;
|
||||
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
// Required by JNI macro RAY_CHECK_JAVA_EXCEPTION
|
||||
public final class JniExceptionUtil {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(JniExceptionUtil.class);
|
||||
|
||||
public static String getStackTrace(
|
||||
String fileName, int lineNumber, String function, Throwable throwable) {
|
||||
LOGGER.error(
|
||||
"An unexpected exception occurred while executing Java code from JNI ({}:{} {}).",
|
||||
fileName,
|
||||
lineNumber,
|
||||
function,
|
||||
throwable);
|
||||
// Return the exception in string form to JNI.
|
||||
return ExceptionUtils.getStackTrace(throwable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package io.ray.runtime.util;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.sun.jna.NativeLibrary;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Set;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class JniUtils {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(JniUtils.class);
|
||||
private static Set<String> loadedLibs = Sets.newHashSet();
|
||||
private static String defaultDestDir;
|
||||
|
||||
/**
|
||||
* Loads the native library specified by the <code>libraryName</code> argument. The <code>
|
||||
* libraryName</code> argument must not contain any platform specific prefix, file extension or
|
||||
* path.
|
||||
*
|
||||
* @param libraryName the name of the library.
|
||||
*/
|
||||
public static synchronized void loadLibrary(String libraryName) {
|
||||
loadLibrary(getDefaultDestDir(), libraryName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the native library specified by the <code>libraryName</code> argument. The <code>
|
||||
* libraryName</code> argument must not contain any platform specific prefix, file extension or
|
||||
* path.
|
||||
*
|
||||
* @param libraryName the name of the library.
|
||||
* @param exportSymbols export symbols of library so that it can be used by other libs.
|
||||
*/
|
||||
public static synchronized void loadLibrary(String libraryName, boolean exportSymbols) {
|
||||
loadLibrary(getDefaultDestDir(), libraryName, exportSymbols);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the native library specified by the <code>libraryName</code> argument. The <code>
|
||||
* libraryName</code> argument must not contain any platform specific prefix, file extension or
|
||||
* path.
|
||||
*
|
||||
* @param destDir The destination dir the library to be extracted.
|
||||
* @param libraryName the name of the library.
|
||||
*/
|
||||
public static synchronized void loadLibrary(String destDir, String libraryName) {
|
||||
loadLibrary(destDir, libraryName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the native library specified by the <code>libraryName</code> argument. The <code>
|
||||
* libraryName</code> argument must not contain any platform specific prefix, file extension or
|
||||
* path.
|
||||
*
|
||||
* @param destDir The destination dir the library to be extracted.
|
||||
* @param libraryName the name of the library.
|
||||
* @param exportSymbols export symbols of library so that it can be used by other libs.
|
||||
*/
|
||||
public static synchronized void loadLibrary(
|
||||
String destDir, String libraryName, boolean exportSymbols) {
|
||||
if (!loadedLibs.contains(libraryName)) {
|
||||
LOGGER.debug("Loading native library {} in {}.", libraryName, destDir);
|
||||
// Load native library.
|
||||
String fileName = System.mapLibraryName(libraryName);
|
||||
final File file = BinaryFileUtil.getNativeFile(destDir, fileName);
|
||||
|
||||
if (exportSymbols) {
|
||||
// Expose library symbols using RTLD_GLOBAL which may be depended by other shared
|
||||
// libraries.
|
||||
NativeLibrary.getInstance(file.getAbsolutePath());
|
||||
}
|
||||
System.load(file.getAbsolutePath());
|
||||
LOGGER.debug("Native library loaded.");
|
||||
loadedLibs.add(libraryName);
|
||||
}
|
||||
}
|
||||
|
||||
/** Cache the result so that multiple calls return the same dest dir. */
|
||||
private static synchronized String getDefaultDestDir() {
|
||||
if (defaultDestDir == null) {
|
||||
try {
|
||||
defaultDestDir = Files.createTempDirectory("native_libs").toString();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return defaultDestDir;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package io.ray.runtime.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.invoke.SerializedLambda;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/** see http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html. */
|
||||
public final class LambdaUtils {
|
||||
|
||||
private LambdaUtils() {}
|
||||
|
||||
public static SerializedLambda getSerializedLambda(Serializable lambda) {
|
||||
// Note.
|
||||
// the class of lambda which isAssignableFrom Serializable
|
||||
// has an privte method:writeReplace
|
||||
// This mechanism may be changed in the future
|
||||
try {
|
||||
Method m = lambda.getClass().getDeclaredMethod("writeReplace");
|
||||
m.setAccessible(true);
|
||||
return (SerializedLambda) m.invoke(lambda);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("failed to getSerializedLambda:" + lambda.getClass().getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package io.ray.runtime.util;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import io.ray.runtime.config.RayConfig;
|
||||
import io.ray.runtime.generated.Common.WorkerType;
|
||||
import java.io.FileWriter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.core.LoggerContext;
|
||||
import org.apache.logging.log4j.core.appender.ConsoleAppender;
|
||||
import org.apache.logging.log4j.core.config.Configurator;
|
||||
import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
|
||||
import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder;
|
||||
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
|
||||
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
|
||||
import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder;
|
||||
import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder;
|
||||
import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder;
|
||||
import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
|
||||
|
||||
public class LoggingUtil {
|
||||
private static boolean setup = false;
|
||||
|
||||
public static synchronized void setupLogging(RayConfig rayConfig) {
|
||||
if (setup) {
|
||||
return;
|
||||
}
|
||||
setup = true;
|
||||
|
||||
LoggerContext.getContext().reconfigure();
|
||||
Config config = rayConfig.getInternalConfig();
|
||||
|
||||
if (rayConfig.workerMode == WorkerType.DRIVER) {
|
||||
// Logs of drivers are printed to console.
|
||||
ConfigurationBuilder<BuiltConfiguration> builder =
|
||||
ConfigurationBuilderFactory.newConfigurationBuilder();
|
||||
|
||||
builder.setStatusLevel(Level.INFO);
|
||||
builder.setConfigurationName("DefaultLogger");
|
||||
|
||||
// create a console appender
|
||||
AppenderComponentBuilder appenderBuilder =
|
||||
builder
|
||||
.newAppender("Console", "CONSOLE")
|
||||
.addAttribute("target", ConsoleAppender.Target.SYSTEM_OUT);
|
||||
appenderBuilder.add(
|
||||
builder
|
||||
.newLayout("PatternLayout")
|
||||
.addAttribute("pattern", config.getString("ray.logging.pattern")));
|
||||
RootLoggerComponentBuilder rootLogger = builder.newRootLogger(Level.INFO);
|
||||
rootLogger.add(builder.newAppenderRef("Console"));
|
||||
|
||||
builder.add(appenderBuilder);
|
||||
builder.add(rootLogger);
|
||||
Configurator.reconfigure(builder.build());
|
||||
|
||||
} else {
|
||||
// Logs of workers are printed to files.
|
||||
String jobIdHex = System.getenv("RAY_JOB_ID");
|
||||
String maxFileSize = System.getenv("RAY_ROTATION_MAX_BYTES");
|
||||
if (StringUtils.isEmpty(maxFileSize)) {
|
||||
maxFileSize = rayConfig.getInternalConfig().getString("ray.logging.max-file-size");
|
||||
}
|
||||
String maxBackupFiles = System.getenv("RAY_ROTATION_BACKUP_COUNT");
|
||||
if (StringUtils.isEmpty(maxBackupFiles)) {
|
||||
maxBackupFiles = rayConfig.getInternalConfig().getString("ray.logging.max-backup-files");
|
||||
}
|
||||
|
||||
ConfigurationBuilder<BuiltConfiguration> globalConfigBuilder =
|
||||
ConfigurationBuilderFactory.newConfigurationBuilder();
|
||||
|
||||
// TODO(qwang): We can use rayConfig.logLevel instead.
|
||||
Level level = Level.toLevel(config.getString("ray.logging.level"));
|
||||
|
||||
globalConfigBuilder.setStatusLevel(Level.INFO);
|
||||
globalConfigBuilder.setConfigurationName("DefaultLogger");
|
||||
|
||||
/// Setup root logger for Java worker.
|
||||
RootLoggerComponentBuilder rootLoggerBuilder = globalConfigBuilder.newAsyncRootLogger(level);
|
||||
rootLoggerBuilder.addAttribute("RingBufferSize", "1048576");
|
||||
final String javaWorkerLogName = "JavaWorkerLogToRollingFile";
|
||||
String logFileName =
|
||||
rayConfig.getInternalConfig().getString("ray.logging.file-prefix")
|
||||
+ "-"
|
||||
+ jobIdHex
|
||||
+ "-"
|
||||
+ SystemUtil.pid();
|
||||
setupLogger(
|
||||
globalConfigBuilder,
|
||||
rayConfig.logDir,
|
||||
new RayConfig.LoggerConf(
|
||||
javaWorkerLogName, logFileName, config.getString("ray.logging.pattern")),
|
||||
maxFileSize,
|
||||
maxBackupFiles,
|
||||
null);
|
||||
rootLoggerBuilder.add(globalConfigBuilder.newAppenderRef(javaWorkerLogName));
|
||||
globalConfigBuilder.add(rootLoggerBuilder);
|
||||
// write `:job_id:<job_id>` to the beginning of log file to conform
|
||||
// to PR #31772
|
||||
writeJobId(rayConfig.logDir + "/" + logFileName + ".log", jobIdHex);
|
||||
/// Setup user loggers.
|
||||
for (RayConfig.LoggerConf conf : rayConfig.loggers) {
|
||||
final String logPattern =
|
||||
StringUtils.isEmpty(conf.pattern)
|
||||
? config.getString("ray.logging.pattern")
|
||||
: conf.pattern;
|
||||
setupUserLogger(
|
||||
globalConfigBuilder,
|
||||
rayConfig.logDir,
|
||||
new RayConfig.LoggerConf(conf.loggerName, conf.fileName, logPattern),
|
||||
maxFileSize,
|
||||
maxBackupFiles,
|
||||
jobIdHex);
|
||||
}
|
||||
Configurator.reconfigure(globalConfigBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeJobId(String logFilePath, String jobIdHex) {
|
||||
try (FileWriter writer = new FileWriter(logFilePath)) {
|
||||
writer.write(":job_id:" + jobIdHex + "\n");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(
|
||||
"Failed to write job id, " + jobIdHex + ", to log file, " + logFilePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setupUserLogger(
|
||||
ConfigurationBuilder<BuiltConfiguration> globalConfigBuilder,
|
||||
String logDir,
|
||||
RayConfig.LoggerConf userLoggerConf,
|
||||
String maxFileSize,
|
||||
String maxBackupFiles,
|
||||
String jobIdHex) {
|
||||
LoggerComponentBuilder userLoggerBuilder =
|
||||
globalConfigBuilder.newAsyncLogger(userLoggerConf.loggerName);
|
||||
setupLogger(globalConfigBuilder, logDir, userLoggerConf, maxFileSize, maxBackupFiles, jobIdHex);
|
||||
userLoggerBuilder
|
||||
.add(globalConfigBuilder.newAppenderRef(userLoggerConf.loggerName))
|
||||
.addAttribute("additivity", false);
|
||||
globalConfigBuilder.add(userLoggerBuilder);
|
||||
}
|
||||
|
||||
private static void setupLogger(
|
||||
ConfigurationBuilder<BuiltConfiguration> globalConfigBuilder,
|
||||
String logDir,
|
||||
RayConfig.LoggerConf userLoggerConf,
|
||||
String maxFileSize,
|
||||
String maxBackupFiles,
|
||||
String jobIdHex) {
|
||||
LayoutComponentBuilder layoutBuilder =
|
||||
globalConfigBuilder
|
||||
.newLayout("PatternLayout")
|
||||
.addAttribute("pattern", userLoggerConf.pattern);
|
||||
ComponentBuilder userLoggerTriggeringPolicy =
|
||||
globalConfigBuilder
|
||||
.newComponent("Policies")
|
||||
.addComponent(
|
||||
globalConfigBuilder
|
||||
.newComponent("SizeBasedTriggeringPolicy")
|
||||
.addAttribute("size", maxFileSize));
|
||||
ComponentBuilder userLoggerRolloverStrategy =
|
||||
globalConfigBuilder
|
||||
.newComponent("DefaultRolloverStrategy")
|
||||
.addAttribute("max", maxBackupFiles);
|
||||
String logFileName = userLoggerConf.fileName.replace("%p", String.valueOf(SystemUtil.pid()));
|
||||
if (jobIdHex != null) {
|
||||
logFileName = logFileName.replace("%j", jobIdHex);
|
||||
}
|
||||
final String logPath = logDir + "/" + logFileName + ".log";
|
||||
final String rotatedLogPath = logDir + "/" + logFileName + ".%i.log";
|
||||
AppenderComponentBuilder userLoggerAppenderBuilder =
|
||||
globalConfigBuilder
|
||||
.newAppender(userLoggerConf.loggerName, "RollingFile")
|
||||
.addAttribute("filePattern", rotatedLogPath)
|
||||
.add(layoutBuilder)
|
||||
.addComponent(userLoggerTriggeringPolicy)
|
||||
.addComponent(userLoggerRolloverStrategy)
|
||||
.addAttribute("fileName", logPath);
|
||||
globalConfigBuilder.add(userLoggerAppenderBuilder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package io.ray.runtime.util;
|
||||
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.runtime.AbstractRayRuntime;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public final class MethodUtils {
|
||||
|
||||
public static String getSignature(Method m) {
|
||||
String sig;
|
||||
try {
|
||||
Field signatureField = Method.class.getDeclaredField("signature");
|
||||
signatureField.setAccessible(true);
|
||||
sig = (String) signatureField.get(m);
|
||||
if (sig != null) {
|
||||
return sig;
|
||||
}
|
||||
} catch (IllegalAccessException | NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder("(");
|
||||
for (Class<?> c : m.getParameterTypes()) {
|
||||
sb.append((sig = Array.newInstance(c, 0).toString()).substring(1, sig.indexOf('@')));
|
||||
}
|
||||
|
||||
return sb.append(')')
|
||||
.append(
|
||||
m.getReturnType() == void.class
|
||||
? "V"
|
||||
: (sig = Array.newInstance(m.getReturnType(), 0).toString())
|
||||
.substring(1, sig.indexOf('@')))
|
||||
.toString();
|
||||
}
|
||||
|
||||
public static Class<?> getReturnTypeFromSignature(String signature) {
|
||||
final int startIndex = signature.indexOf(')');
|
||||
final int endIndex = signature.lastIndexOf(';');
|
||||
final String className = signature.substring(startIndex + 2, endIndex).replace('/', '.');
|
||||
Class<?> actorClz;
|
||||
try {
|
||||
try {
|
||||
actorClz = Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
/// This code path indicates that here might be in another thread of a worker.
|
||||
/// So try to load the class from URLClassLoader of this worker.
|
||||
ClassLoader cl =
|
||||
((AbstractRayRuntime) Ray.internal()).getFunctionManager().getClassLoader();
|
||||
actorClz = Class.forName(className, true, cl);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return actorClz;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package io.ray.runtime.util;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Enumeration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class NetworkUtil {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(NetworkUtil.class);
|
||||
|
||||
public static String getIpAddress(String interfaceName) {
|
||||
try {
|
||||
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
while (interfaces.hasMoreElements()) {
|
||||
NetworkInterface current = interfaces.nextElement();
|
||||
if (!current.isUp() || current.isLoopback() || current.isVirtual()) {
|
||||
continue;
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(interfaceName)
|
||||
&& !interfaceName.equals(current.getDisplayName())) {
|
||||
continue;
|
||||
}
|
||||
Enumeration<InetAddress> addresses = current.getInetAddresses();
|
||||
while (addresses.hasMoreElements()) {
|
||||
InetAddress addr = addresses.nextElement();
|
||||
if (addr.isLoopbackAddress()) {
|
||||
continue;
|
||||
}
|
||||
if (addr instanceof Inet6Address) {
|
||||
continue;
|
||||
}
|
||||
return addr.getHostAddress();
|
||||
}
|
||||
}
|
||||
LOGGER.warn("You need to correctly specify [ray.java] net_interface in config.");
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Can't get ip address, use 127.0.0.1 as default.", e);
|
||||
}
|
||||
|
||||
return "127.0.0.1";
|
||||
}
|
||||
|
||||
public static String localhostIp() {
|
||||
return "127.0.0.1";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.ray.runtime.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ResourceUtil {
|
||||
|
||||
/**
|
||||
* Get the device IDs in the CUDA_VISIBLE_DEVICES environment variable. The local mode is not
|
||||
* support.
|
||||
*
|
||||
* @return devices (List[String]): If CUDA_VISIBLE_DEVICES is set, returns a list of strings
|
||||
* representing the IDs of the visible GPUs. If it is not set or is set to NoDevFiles, returns
|
||||
* empty list.
|
||||
*/
|
||||
public static List<String> getCudaVisibleDevices() {
|
||||
List<String> gpuDevices = new ArrayList<>();
|
||||
String gpuIdsStr = System.getenv("CUDA_VISIBLE_DEVICES");
|
||||
if (gpuIdsStr == null) {
|
||||
return null;
|
||||
} else if (gpuIdsStr.isEmpty()) {
|
||||
return gpuDevices;
|
||||
} else if ("NoDevFiles".equals(gpuIdsStr)) {
|
||||
return gpuDevices;
|
||||
}
|
||||
gpuDevices = Arrays.stream(gpuIdsStr.split(",")).collect(Collectors.toList());
|
||||
return gpuDevices;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package io.ray.runtime.util;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.gson.Gson;
|
||||
import io.ray.runtime.config.RayConfig;
|
||||
import io.ray.runtime.config.RunMode;
|
||||
import java.util.HashMap;
|
||||
|
||||
/** The utility class to read system config from native code. */
|
||||
public class SystemConfig {
|
||||
|
||||
private static RayConfig rayConfig = null;
|
||||
|
||||
private static Gson gson = new Gson();
|
||||
|
||||
/// A cache to avoid duplicated reading via JNI.
|
||||
private static HashMap<String, Object> cachedConfigs = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The string key to use for getting the largest size passed by value. If the size of an
|
||||
* argument's serialized data is smaller than this number, the argument will be passed by value.
|
||||
* Otherwise it'll be passed by reference.
|
||||
*/
|
||||
public static final String KEY_TO_LARGEST_SIZE_PASS_BY_VALUE = "max_direct_call_object_size";
|
||||
|
||||
public static synchronized void setup(RayConfig config) {
|
||||
rayConfig = config;
|
||||
}
|
||||
|
||||
public static synchronized Object get(String key) {
|
||||
Preconditions.checkNotNull(rayConfig);
|
||||
if (rayConfig.runMode == RunMode.LOCAL) {
|
||||
// Code path of local mode.
|
||||
return getInLocalMode(key);
|
||||
}
|
||||
// Code path of cluster mode.
|
||||
if (cachedConfigs.containsKey(key)) {
|
||||
return cachedConfigs.get(key);
|
||||
}
|
||||
|
||||
Object val = gson.fromJson(nativeGetSystemConfig(key), Object.class);
|
||||
Preconditions.checkNotNull(val);
|
||||
cachedConfigs.put(key, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
public static synchronized long getLargestSizePassedByValue() {
|
||||
return ((Double) SystemConfig.get(KEY_TO_LARGEST_SIZE_PASS_BY_VALUE)).longValue();
|
||||
}
|
||||
|
||||
private static Object getInLocalMode(String key) {
|
||||
if (KEY_TO_LARGEST_SIZE_PASS_BY_VALUE.equals(key)) {
|
||||
// Hard code 10K in local mode.
|
||||
return 100.0 * 1024;
|
||||
}
|
||||
throw new RuntimeException(String.format("Unsupported key: %s", key));
|
||||
}
|
||||
|
||||
private static native String nativeGetSystemConfig(String key);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package io.ray.runtime.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.RuntimeMXBean;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/** some utilities for system process. */
|
||||
public class SystemUtil {
|
||||
|
||||
static final ReentrantLock pidlock = new ReentrantLock();
|
||||
static Integer pid;
|
||||
|
||||
public static int pid() {
|
||||
if (pid == null) {
|
||||
pidlock.lock();
|
||||
try {
|
||||
if (pid == null) {
|
||||
RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
|
||||
String name = runtime.getName();
|
||||
int index = name.indexOf("@");
|
||||
if (index != -1) {
|
||||
pid = Integer.parseInt(name.substring(0, index));
|
||||
} else {
|
||||
throw new RuntimeException("parse pid error:" + name);
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
pidlock.unlock();
|
||||
}
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
public static boolean isProcessAlive(int pid) {
|
||||
Process process;
|
||||
try {
|
||||
process = Runtime.getRuntime().exec(new String[] {"kill", "-0", String.valueOf(pid)});
|
||||
process.waitFor();
|
||||
} catch (InterruptedException | IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return process.exitValue() == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.ray.runtime.util.generator;
|
||||
|
||||
public abstract class BaseGenerator {
|
||||
|
||||
protected static final int MAX_PARAMETERS = 6;
|
||||
|
||||
protected StringBuilder sb;
|
||||
|
||||
protected void newLine(String line) {
|
||||
sb.append(line).append("\n");
|
||||
}
|
||||
|
||||
protected void newLine(int numIndents, String line) {
|
||||
indents(numIndents);
|
||||
newLine(line);
|
||||
}
|
||||
|
||||
protected void indents(int numIndents) {
|
||||
for (int i = 0; i < numIndents; i++) {
|
||||
sb.append(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package io.ray.runtime.util.generator;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
public class ConcurrencyGroupBuilderGenerator extends BaseGenerator {
|
||||
|
||||
/**
|
||||
* Build `BaseConcurrencyGroupBuilder::addMethod()` methods with the given number of parameters.
|
||||
*
|
||||
* @param numParameters the number of parameters.
|
||||
* @param hasReturn if true, Build api for functions with return.
|
||||
*/
|
||||
private void buildConcurrencyGroupMethods(int numParameters, boolean hasReturn) {
|
||||
// 1) Construct the `genericTypes` part, e.g. `<T0, T1, T2, R>`.
|
||||
StringBuilder genericTypes = new StringBuilder();
|
||||
for (int i = 0; i < numParameters; i++) {
|
||||
genericTypes.append("T").append(i).append(", ");
|
||||
}
|
||||
// Return generic type.
|
||||
if (hasReturn) {
|
||||
genericTypes.append("R, ");
|
||||
}
|
||||
if (genericTypes.length() > 0) {
|
||||
// Trim trailing ", ";
|
||||
genericTypes = new StringBuilder(genericTypes.substring(0, genericTypes.length() - 2));
|
||||
genericTypes = new StringBuilder("<" + genericTypes + ">");
|
||||
}
|
||||
// 2) Construct the `returnType` part.
|
||||
final String returnType =
|
||||
hasReturn ? "ConcurrencyGroupBuilder<A>" : "ConcurrencyGroupBuilder<A>";
|
||||
// 3) Construct the `argsDeclaration` part.
|
||||
String rayFuncGenericTypes = genericTypes.toString();
|
||||
if (rayFuncGenericTypes.isEmpty()) {
|
||||
rayFuncGenericTypes = "<A>";
|
||||
} else {
|
||||
rayFuncGenericTypes = rayFuncGenericTypes.replace("<", "<A, ");
|
||||
}
|
||||
String argsDeclarationPrefix =
|
||||
String.format(
|
||||
"RayFunc%s%d%s f, ", hasReturn ? "" : "Void", numParameters + 1, rayFuncGenericTypes);
|
||||
// Enumerate all combinations of the parameters.
|
||||
for (String param : generateParameters(numParameters)) {
|
||||
String argsDeclaration = argsDeclarationPrefix + param;
|
||||
// Trim trailing ", ";
|
||||
argsDeclaration = argsDeclaration.substring(0, argsDeclaration.length() - 2);
|
||||
// Print the first line (method signature).
|
||||
final String modifiers = "public";
|
||||
final String callFunc = "addMethod";
|
||||
newLine(
|
||||
1,
|
||||
String.format(
|
||||
"%s%s %s %s(%s) {",
|
||||
modifiers,
|
||||
(genericTypes.length() == 0) ? "" : " " + genericTypes,
|
||||
returnType,
|
||||
callFunc,
|
||||
argsDeclaration));
|
||||
// 5) Construct the third line.
|
||||
newLine(2, "return internalAddMethod(f);");
|
||||
newLine(1, "}");
|
||||
newLine("");
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns Whole file content of `BaseConcurrencyGroupBuilderGenerator.java`. */
|
||||
private String generateActorCallDotJava() {
|
||||
sb = new StringBuilder();
|
||||
newLine("// Generated by `BaseConcurrencyGroupBuilderGenerator.java`. DO NOT EDIT.");
|
||||
newLine("");
|
||||
newLine("package io.ray.api.concurrencygroup;");
|
||||
newLine("");
|
||||
newLine("import io.ray.api.function.RayFunc;");
|
||||
for (int i = 1; i <= MAX_PARAMETERS; i++) {
|
||||
newLine("import io.ray.api.function.RayFunc" + i + ";");
|
||||
}
|
||||
for (int i = 1; i <= MAX_PARAMETERS; i++) {
|
||||
newLine("import io.ray.api.function.RayFuncVoid" + i + ";");
|
||||
}
|
||||
newLine("");
|
||||
newLine("/**");
|
||||
newLine(" * This class provides type-safe interfaces for concurrency groups.");
|
||||
newLine(" **/");
|
||||
newLine("public abstract class BaseConcurrencyGroupBuilder<A> {");
|
||||
newLine("");
|
||||
newLine(
|
||||
" protected abstract " + "ConcurrencyGroupBuilder<A> internalAddMethod(RayFunc func);");
|
||||
newLine("");
|
||||
for (int i = 0; i <= MAX_PARAMETERS - 1; i++) {
|
||||
buildConcurrencyGroupMethods(i, true);
|
||||
buildConcurrencyGroupMethods(i, false);
|
||||
}
|
||||
newLine("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private List<String> generateParameters(int numParams) {
|
||||
List<String> res = new ArrayList<>();
|
||||
dfs(0, numParams, "", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
private void dfs(int pos, int numParams, String cur, List<String> res) {
|
||||
if (pos >= numParams) {
|
||||
res.add(cur);
|
||||
return;
|
||||
}
|
||||
String nextParameter = "";
|
||||
dfs(pos + 1, numParams, cur + nextParameter, res);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
String path =
|
||||
System.getProperty("user.dir")
|
||||
+ "/api/src/main/java/io/ray/api/"
|
||||
+ "concurrencyGroup/BaseConcurrencyGroupBuilder.java";
|
||||
FileUtils.write(
|
||||
new File(path),
|
||||
new ConcurrencyGroupBuilderGenerator().generateActorCallDotJava(),
|
||||
Charset.defaultCharset());
|
||||
}
|
||||
}
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
package io.ray.runtime.util.generator;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
/**
|
||||
* A util class that generates `RayCall.java` and `ActorCall.java`, which provide type-safe
|
||||
* interfaces for `Ray.call`, `Ray.createActor` and `actor.call`.
|
||||
*/
|
||||
public class ParallelActorCallGenerator extends BaseGenerator {
|
||||
|
||||
/** Returns Whole file content of `RayCall.java`. */
|
||||
private String generateRayCallDotJava() {
|
||||
sb = new StringBuilder();
|
||||
|
||||
newLine("// Generated by `ParallelActorCallGenerator.java`. DO NOT EDIT.");
|
||||
newLine("");
|
||||
newLine("package io.ray.api.parallelactor;");
|
||||
newLine("");
|
||||
newLine("import io.ray.api.ObjectRef;");
|
||||
|
||||
for (int i = 0; i <= MAX_PARAMETERS; i++) {
|
||||
newLine("import io.ray.api.function.RayFunc" + i + ";");
|
||||
}
|
||||
newLine("");
|
||||
|
||||
newLine("/**");
|
||||
newLine(" * This class provides type-safe interfaces for `ParallelActor.actor`.");
|
||||
newLine(" **/");
|
||||
newLine("class Call {");
|
||||
|
||||
newLine(1, "// ===========================");
|
||||
newLine(1, "// Methods for actor creation.");
|
||||
newLine(1, "// ===========================");
|
||||
for (int i = 0; i <= MAX_PARAMETERS; i++) {
|
||||
buildCalls(i, false, true, true);
|
||||
}
|
||||
newLine("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Returns Whole file content of `ActorCall.java`. */
|
||||
private String generateActorCallDotJava() {
|
||||
sb = new StringBuilder();
|
||||
|
||||
newLine("// Generated by `RayCallGenerator.java`. DO NOT EDIT.");
|
||||
newLine("");
|
||||
newLine("package io.ray.api.parallelactor;");
|
||||
newLine("");
|
||||
newLine("import io.ray.api.ObjectRef;");
|
||||
newLine("import io.ray.api.function.RayFuncVoid;");
|
||||
newLine("import io.ray.api.function.RayFuncR;");
|
||||
|
||||
for (int i = 1; i <= MAX_PARAMETERS; i++) {
|
||||
newLine("import io.ray.api.function.RayFunc" + i + ";");
|
||||
}
|
||||
for (int i = 1; i <= MAX_PARAMETERS; i++) {
|
||||
newLine("import io.ray.api.function.RayFuncVoid" + i + ";");
|
||||
}
|
||||
newLine("");
|
||||
newLine("/**");
|
||||
newLine(" * This class provides type-safe interfaces for remote actor calls.");
|
||||
newLine(" **/");
|
||||
newLine("interface ActorCall<A> {");
|
||||
newLine("");
|
||||
|
||||
{
|
||||
/// Generate buildVoidReturnCaller()
|
||||
newLine(
|
||||
0,
|
||||
" default VoidParallelActorTaskCaller buildVoidReturnCaller(RayFuncVoid func, Object[] args) {\n"
|
||||
+ " return new VoidParallelActorTaskCaller((ParallelActorInstance) this, func, args);\n"
|
||||
+ " }\n");
|
||||
|
||||
/// Generate buildCaller()
|
||||
newLine(
|
||||
0,
|
||||
" default <R> ParallelActorTaskCaller<R> buildCaller(RayFuncR<R> func, Object[] args) {\n"
|
||||
+ " return new ParallelActorTaskCaller<R>((ParallelActorInstance) this, func, args);\n"
|
||||
+ " }\n");
|
||||
}
|
||||
|
||||
for (int i = 0; i <= MAX_PARAMETERS - 1; i++) {
|
||||
buildCalls(i, true, false, true);
|
||||
buildCalls(i, true, false, false);
|
||||
}
|
||||
newLine("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build `Ray.call`, `Ray.createActor` and `actor.call` methods with the given number of
|
||||
* parameters.
|
||||
*
|
||||
* @param numParameters the number of parameters
|
||||
* @param forActor Build `actor.call` when true, otherwise build `Ray.call`.
|
||||
* @param hasReturn if true, Build api for functions with return.
|
||||
* @param forActorCreation Build `Ray.createActor` when true, otherwise build `Ray.call`.
|
||||
*/
|
||||
private void buildCalls(
|
||||
int numParameters, boolean forActor, boolean forActorCreation, boolean hasReturn) {
|
||||
String modifiers = forActor ? "default" : "public static";
|
||||
|
||||
// 1) Construct the `genericTypes` part, e.g. `<T0, T1, T2, R>`.
|
||||
String genericTypes = "";
|
||||
for (int i = 0; i < numParameters; i++) {
|
||||
genericTypes += "T" + i + ", ";
|
||||
}
|
||||
// Return generic type.
|
||||
if (forActorCreation) {
|
||||
genericTypes += "A, ";
|
||||
} else {
|
||||
if (hasReturn) {
|
||||
genericTypes += "R, ";
|
||||
}
|
||||
}
|
||||
if (!genericTypes.isEmpty()) {
|
||||
// Trim trailing ", ";
|
||||
genericTypes = genericTypes.substring(0, genericTypes.length() - 2);
|
||||
genericTypes = "<" + genericTypes + ">";
|
||||
}
|
||||
|
||||
// 2) Construct the `returnType` part.
|
||||
String returnType;
|
||||
if (forActorCreation) {
|
||||
returnType = "ParallelActorCreator<A>";
|
||||
} else {
|
||||
if (forActor) {
|
||||
returnType = hasReturn ? "ParallelActorTaskCaller<R>" : "VoidParallelActorTaskCaller";
|
||||
} else {
|
||||
// TODO(qwang): This should be removed since we don't have normal task calls for parallel
|
||||
// actor.
|
||||
returnType = hasReturn ? "TaskCaller<R>" : "VoidTaskCaller";
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Construct the `argsDeclaration` part.
|
||||
String rayFuncGenericTypes = genericTypes;
|
||||
if (forActor) {
|
||||
if (rayFuncGenericTypes.isEmpty()) {
|
||||
rayFuncGenericTypes = "<A>";
|
||||
} else {
|
||||
rayFuncGenericTypes = rayFuncGenericTypes.replace("<", "<A, ");
|
||||
}
|
||||
}
|
||||
String argsDeclarationPrefix =
|
||||
String.format(
|
||||
"RayFunc%s%d%s f, ",
|
||||
hasReturn ? "" : "Void",
|
||||
!forActor ? numParameters : numParameters + 1,
|
||||
rayFuncGenericTypes);
|
||||
|
||||
String callFunc = forActorCreation ? "actor" : "task";
|
||||
String caller;
|
||||
if (forActorCreation) {
|
||||
caller = "ParallelActorCreator<>";
|
||||
} else {
|
||||
if (forActor) {
|
||||
caller = hasReturn ? "buildCaller" : "buildVoidReturnCaller";
|
||||
} else {
|
||||
caller = hasReturn ? "buildCaller" : "buildVoidReturnCaller";
|
||||
}
|
||||
}
|
||||
|
||||
// Enumerate all combinations of the parameters.
|
||||
for (String param : generateParameters(numParameters)) {
|
||||
String argsDeclaration = argsDeclarationPrefix + param;
|
||||
// Trim trailing ", ";
|
||||
argsDeclaration = argsDeclaration.substring(0, argsDeclaration.length() - 2);
|
||||
// Print the first line (method signature).
|
||||
newLine(
|
||||
1,
|
||||
String.format(
|
||||
"%s%s %s %s(%s) {",
|
||||
modifiers,
|
||||
genericTypes.isEmpty() ? "" : " " + genericTypes,
|
||||
returnType,
|
||||
callFunc,
|
||||
argsDeclaration));
|
||||
|
||||
// 4) Construct the `args` part.
|
||||
String args = "";
|
||||
for (int i = 0; i < numParameters; i++) {
|
||||
args += "t" + i + ", ";
|
||||
}
|
||||
// Trim trailing ", ";
|
||||
if (!args.isEmpty()) {
|
||||
args = args.substring(0, args.length() - 2);
|
||||
}
|
||||
// Print the second line (local args declaration).
|
||||
newLine(2, String.format("Object[] args = new Object[] {%s};", args));
|
||||
|
||||
// 5) Construct the third line.
|
||||
String ctrArgs = "";
|
||||
// if (forActor) {
|
||||
// ctrArgs += "(ParallelActor) this, ";
|
||||
// }
|
||||
ctrArgs += "f, args, ";
|
||||
ctrArgs = ctrArgs.substring(0, ctrArgs.length() - 2);
|
||||
if (forActorCreation) {
|
||||
newLine(2, String.format("return new %s(%s);", caller, ctrArgs));
|
||||
} else {
|
||||
newLine(2, String.format("return %s(%s);", caller, ctrArgs));
|
||||
}
|
||||
newLine(1, "}");
|
||||
newLine("");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build `Ray.call`, `Ray.createActor` and `actor.call` methods with the given number of
|
||||
* parameters.
|
||||
*
|
||||
* @param numParameters the number of parameters
|
||||
* @param forActor Build `actor.call` when true, otherwise build `Ray.call`.
|
||||
* @param forActorCreation Build `Ray.createActor` when true, otherwise build `Ray.call`.
|
||||
*/
|
||||
private void buildPyCalls(int numParameters, boolean forActor, boolean forActorCreation) {
|
||||
String modifiers = forActor ? "default" : "public static";
|
||||
|
||||
String argList = "";
|
||||
String paramList = "";
|
||||
for (int i = 0; i < numParameters; i++) {
|
||||
paramList += "Object obj" + i + ", ";
|
||||
argList += "obj" + i + ", ";
|
||||
}
|
||||
if (argList.endsWith(", ")) {
|
||||
argList = argList.substring(0, argList.length() - 2);
|
||||
}
|
||||
if (paramList.endsWith(", ")) {
|
||||
paramList = paramList.substring(0, paramList.length() - 2);
|
||||
}
|
||||
|
||||
String paramPrefix = "";
|
||||
String funcArgs = "";
|
||||
if (forActorCreation) {
|
||||
paramPrefix += "PyActorClass pyActorClass";
|
||||
funcArgs += "pyActorClass";
|
||||
} else if (forActor) {
|
||||
paramPrefix += "PyActorMethod<R> pyActorMethod";
|
||||
funcArgs += "pyActorMethod";
|
||||
} else {
|
||||
paramPrefix += "PyFunction<R> pyFunction";
|
||||
funcArgs += "pyFunction";
|
||||
}
|
||||
if (numParameters > 0) {
|
||||
paramPrefix += ", ";
|
||||
}
|
||||
|
||||
String genericType = forActorCreation ? "" : " <R>";
|
||||
String returnType =
|
||||
forActorCreation
|
||||
? "ParallelActorCreator"
|
||||
: forActor ? "PyActorTaskCaller<R>" : "PyTaskCaller<R>";
|
||||
|
||||
String funcName = forActorCreation ? "actor" : "task";
|
||||
String caller =
|
||||
forActorCreation
|
||||
? "ParallelActorCreator"
|
||||
: forActor ? "PyActorTaskCaller<>" : "PyTaskCaller<>";
|
||||
funcArgs += ", args";
|
||||
// Method signature.
|
||||
newLine(
|
||||
1,
|
||||
String.format(
|
||||
"%s%s %s %s(%s) {",
|
||||
modifiers, genericType, returnType, funcName, paramPrefix + paramList));
|
||||
// Method body.
|
||||
newLine(2, String.format("Object[] args = new Object[] {%s};", argList));
|
||||
if (forActor) {
|
||||
newLine(2, String.format("return new %s((PyActorHandle)this, %s);", caller, funcArgs));
|
||||
} else {
|
||||
newLine(2, String.format("return new %s(%s);", caller, funcArgs));
|
||||
}
|
||||
newLine(1, "}");
|
||||
newLine("");
|
||||
}
|
||||
|
||||
private List<String> generateParameters(int numParams) {
|
||||
List<String> res = new ArrayList<>();
|
||||
dfs(0, numParams, "", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
private void dfs(int pos, int numParams, String cur, List<String> res) {
|
||||
if (pos >= numParams) {
|
||||
res.add(cur);
|
||||
return;
|
||||
}
|
||||
String nextParameter = String.format("T%d t%d, ", pos, pos);
|
||||
dfs(pos + 1, numParams, cur + nextParameter, res);
|
||||
nextParameter = String.format("ObjectRef<T%d> t%d, ", pos, pos);
|
||||
dfs(pos + 1, numParams, cur + nextParameter, res);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
String path =
|
||||
System.getProperty("user.dir") + "/api/src/main/java/io/ray/api/parallelactor/Call.java";
|
||||
FileUtils.write(
|
||||
new File(path),
|
||||
new ParallelActorCallGenerator().generateRayCallDotJava(),
|
||||
Charset.defaultCharset());
|
||||
path =
|
||||
System.getProperty("user.dir")
|
||||
+ "/api/src/main/java/io/ray/api/parallelactor/ActorCall.java";
|
||||
FileUtils.write(
|
||||
new File(path),
|
||||
new ParallelActorCallGenerator().generateActorCallDotJava(),
|
||||
Charset.defaultCharset());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
package io.ray.runtime.util.generator;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
/**
|
||||
* A util class that generates `RayCall.java` and `ActorCall.java`, which provide type-safe
|
||||
* interfaces for `Ray.call`, `Ray.createActor` and `actor.call`.
|
||||
*/
|
||||
public class RayCallGenerator extends BaseGenerator {
|
||||
|
||||
/** Returns Whole file content of `RayCall.java`. */
|
||||
private String generateRayCallDotJava() {
|
||||
sb = new StringBuilder();
|
||||
|
||||
newLine("// Generated by `RayCallGenerator.java`. DO NOT EDIT.");
|
||||
newLine("");
|
||||
newLine("package io.ray.api;");
|
||||
newLine("");
|
||||
newLine("import io.ray.api.call.ActorCreator;");
|
||||
newLine("import io.ray.api.call.CppActorCreator;");
|
||||
newLine("import io.ray.api.call.CppTaskCaller;");
|
||||
newLine("import io.ray.api.call.PyActorCreator;");
|
||||
newLine("import io.ray.api.call.PyTaskCaller;");
|
||||
newLine("import io.ray.api.call.TaskCaller;");
|
||||
newLine("import io.ray.api.call.VoidTaskCaller;");
|
||||
newLine("import io.ray.api.function.CppActorClass;");
|
||||
newLine("import io.ray.api.function.CppFunction;");
|
||||
newLine("import io.ray.api.function.PyActorClass;");
|
||||
newLine("import io.ray.api.function.PyFunction;");
|
||||
for (int i = 0; i <= MAX_PARAMETERS; i++) {
|
||||
newLine("import io.ray.api.function.RayFunc" + i + ";");
|
||||
}
|
||||
for (int i = 0; i <= MAX_PARAMETERS; i++) {
|
||||
newLine("import io.ray.api.function.RayFuncVoid" + i + ";");
|
||||
}
|
||||
newLine("");
|
||||
|
||||
newLine("/**");
|
||||
newLine(" * This class provides type-safe interfaces for `Ray.call` and `Ray.createActor`.");
|
||||
newLine(" **/");
|
||||
newLine("class RayCall {");
|
||||
newLine(1, "// =======================================");
|
||||
newLine(1, "// Methods for remote function invocation.");
|
||||
newLine(1, "// =======================================");
|
||||
for (int i = 0; i <= MAX_PARAMETERS; i++) {
|
||||
buildCalls(i, false, false, true);
|
||||
buildCalls(i, false, false, false);
|
||||
}
|
||||
|
||||
newLine(1, "// ===========================");
|
||||
newLine(1, "// Methods for actor creation.");
|
||||
newLine(1, "// ===========================");
|
||||
for (int i = 0; i <= MAX_PARAMETERS; i++) {
|
||||
buildCalls(i, false, true, true);
|
||||
}
|
||||
|
||||
newLine(1, "// ===========================");
|
||||
newLine(1, "// Cross-language methods.");
|
||||
newLine(1, "// ===========================");
|
||||
for (int i = 0; i <= MAX_PARAMETERS; i++) {
|
||||
buildPyCalls(i, false, false);
|
||||
}
|
||||
for (int i = 0; i <= MAX_PARAMETERS; i++) {
|
||||
buildPyCalls(i, false, true);
|
||||
}
|
||||
for (int i = 0; i <= MAX_PARAMETERS; i++) {
|
||||
buildCppCalls(i, false, false);
|
||||
}
|
||||
for (int i = 0; i <= MAX_PARAMETERS; i++) {
|
||||
buildCppCalls(i, false, true);
|
||||
}
|
||||
newLine("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Returns Whole file content of `ActorCall.java`. */
|
||||
private String generateActorCallDotJava() {
|
||||
sb = new StringBuilder();
|
||||
|
||||
newLine("// Generated by `RayCallGenerator.java`. DO NOT EDIT.");
|
||||
newLine("");
|
||||
newLine("package io.ray.api;");
|
||||
newLine("");
|
||||
newLine("import io.ray.api.call.ActorTaskCaller;");
|
||||
newLine("import io.ray.api.call.VoidActorTaskCaller;");
|
||||
for (int i = 1; i <= MAX_PARAMETERS; i++) {
|
||||
newLine("import io.ray.api.function.RayFunc" + i + ";");
|
||||
}
|
||||
for (int i = 1; i <= MAX_PARAMETERS; i++) {
|
||||
newLine("import io.ray.api.function.RayFuncVoid" + i + ";");
|
||||
}
|
||||
newLine("");
|
||||
newLine("/**");
|
||||
newLine(" * This class provides type-safe interfaces for remote actor calls.");
|
||||
newLine(" **/");
|
||||
newLine("interface ActorCall<A> {");
|
||||
newLine("");
|
||||
for (int i = 0; i <= MAX_PARAMETERS - 1; i++) {
|
||||
buildCalls(i, true, false, true);
|
||||
buildCalls(i, true, false, false);
|
||||
}
|
||||
newLine("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Returns Whole file content of `PyActorCall.java`. */
|
||||
private String generatePyActorCallDotJava() {
|
||||
sb = new StringBuilder();
|
||||
|
||||
newLine("// Generated by `RayCallGenerator.java`. DO NOT EDIT.");
|
||||
newLine("");
|
||||
newLine("package io.ray.api;");
|
||||
newLine("");
|
||||
newLine("import io.ray.api.call.PyActorTaskCaller;");
|
||||
newLine("import io.ray.api.function.PyActorMethod;");
|
||||
newLine("");
|
||||
newLine("/**");
|
||||
newLine(" * This class provides type-safe interfaces for remote actor calls.");
|
||||
newLine(" **/");
|
||||
newLine("interface PyActorCall {");
|
||||
newLine("");
|
||||
for (int i = 0; i <= MAX_PARAMETERS - 1; i++) {
|
||||
buildPyCalls(i, true, false);
|
||||
}
|
||||
newLine("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Returns Whole file content of `CppActorCall.java`. */
|
||||
private String generateCppActorCallDotJava() {
|
||||
sb = new StringBuilder();
|
||||
|
||||
newLine("// Generated by `RayCallGenerator.java`. DO NOT EDIT.");
|
||||
newLine("");
|
||||
newLine("package io.ray.api;");
|
||||
newLine("");
|
||||
newLine("import io.ray.api.call.CppActorTaskCaller;");
|
||||
newLine("import io.ray.api.function.CppActorMethod;");
|
||||
newLine("");
|
||||
newLine("/**");
|
||||
newLine(" * This class provides type-safe interfaces for remote actor calls.");
|
||||
newLine(" **/");
|
||||
newLine("interface CppActorCall {");
|
||||
newLine("");
|
||||
for (int i = 0; i <= MAX_PARAMETERS - 1; i++) {
|
||||
buildCppCalls(i, true, false);
|
||||
}
|
||||
newLine("}");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build `Ray.call`, `Ray.createActor` and `actor.call` methods with the given number of
|
||||
* parameters.
|
||||
*
|
||||
* @param numParameters the number of parameters
|
||||
* @param forActor Build `actor.call` when true, otherwise build `Ray.call`.
|
||||
* @param hasReturn if true, Build api for functions with return.
|
||||
* @param forActorCreation Build `Ray.createActor` when true, otherwise build `Ray.call`.
|
||||
*/
|
||||
private void buildCalls(
|
||||
int numParameters, boolean forActor, boolean forActorCreation, boolean hasReturn) {
|
||||
// Template of the generated function:
|
||||
// [modifiers] [genericTypes] [returnType] [callFunc]([argsDeclaration]) {
|
||||
// Objects[] args = new Object[]{[args]};
|
||||
// return new [Caller](func, args);
|
||||
// }
|
||||
|
||||
String modifiers = forActor ? "default" : "public static";
|
||||
|
||||
// 1) Construct the `genericTypes` part, e.g. `<T0, T1, T2, R>`.
|
||||
String genericTypes = "";
|
||||
for (int i = 0; i < numParameters; i++) {
|
||||
genericTypes += "T" + i + ", ";
|
||||
}
|
||||
// Return generic type.
|
||||
if (forActorCreation) {
|
||||
genericTypes += "A, ";
|
||||
} else {
|
||||
if (hasReturn) {
|
||||
genericTypes += "R, ";
|
||||
}
|
||||
}
|
||||
if (!genericTypes.isEmpty()) {
|
||||
// Trim trailing ", ";
|
||||
genericTypes = genericTypes.substring(0, genericTypes.length() - 2);
|
||||
genericTypes = "<" + genericTypes + ">";
|
||||
}
|
||||
|
||||
// 2) Construct the `returnType` part.
|
||||
String returnType;
|
||||
if (forActorCreation) {
|
||||
returnType = "ActorCreator<A>";
|
||||
} else {
|
||||
if (forActor) {
|
||||
returnType = hasReturn ? "ActorTaskCaller<R>" : "VoidActorTaskCaller";
|
||||
} else {
|
||||
returnType = hasReturn ? "TaskCaller<R>" : "VoidTaskCaller";
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Construct the `argsDeclaration` part.
|
||||
String rayFuncGenericTypes = genericTypes;
|
||||
if (forActor) {
|
||||
if (rayFuncGenericTypes.isEmpty()) {
|
||||
rayFuncGenericTypes = "<A>";
|
||||
} else {
|
||||
rayFuncGenericTypes = rayFuncGenericTypes.replace("<", "<A, ");
|
||||
}
|
||||
}
|
||||
String argsDeclarationPrefix =
|
||||
String.format(
|
||||
"RayFunc%s%d%s f, ",
|
||||
hasReturn ? "" : "Void",
|
||||
!forActor ? numParameters : numParameters + 1,
|
||||
rayFuncGenericTypes);
|
||||
|
||||
String callFunc = forActorCreation ? "actor" : "task";
|
||||
String caller;
|
||||
if (forActorCreation) {
|
||||
caller = "ActorCreator<>";
|
||||
} else {
|
||||
if (forActor) {
|
||||
caller = hasReturn ? "ActorTaskCaller<>" : "VoidActorTaskCaller";
|
||||
} else {
|
||||
caller = hasReturn ? "TaskCaller<>" : "VoidTaskCaller";
|
||||
}
|
||||
}
|
||||
|
||||
// Enumerate all combinations of the parameters.
|
||||
for (String param : generateParameters(numParameters)) {
|
||||
String argsDeclaration = argsDeclarationPrefix + param;
|
||||
// Trim trailing ", ";
|
||||
argsDeclaration = argsDeclaration.substring(0, argsDeclaration.length() - 2);
|
||||
// Print the first line (method signature).
|
||||
newLine(
|
||||
1,
|
||||
String.format(
|
||||
"%s%s %s %s(%s) {",
|
||||
modifiers,
|
||||
genericTypes.isEmpty() ? "" : " " + genericTypes,
|
||||
returnType,
|
||||
callFunc,
|
||||
argsDeclaration));
|
||||
|
||||
// 4) Construct the `args` part.
|
||||
String args = "";
|
||||
for (int i = 0; i < numParameters; i++) {
|
||||
args += "t" + i + ", ";
|
||||
}
|
||||
// Trim trailing ", ";
|
||||
if (!args.isEmpty()) {
|
||||
args = args.substring(0, args.length() - 2);
|
||||
}
|
||||
// Print the second line (local args declaration).
|
||||
newLine(2, String.format("Object[] args = new Object[]{%s};", args));
|
||||
|
||||
// 5) Construct the third line.
|
||||
String ctrArgs = "";
|
||||
if (forActor) {
|
||||
ctrArgs += "(ActorHandle) this, ";
|
||||
}
|
||||
ctrArgs += "f, args, ";
|
||||
ctrArgs = ctrArgs.substring(0, ctrArgs.length() - 2);
|
||||
newLine(2, String.format("return new %s(%s);", caller, ctrArgs));
|
||||
newLine(1, "}");
|
||||
newLine("");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build `Ray.call`, `Ray.createActor` and `actor.call` methods with the given number of
|
||||
* parameters.
|
||||
*
|
||||
* @param numParameters the number of parameters
|
||||
* @param forActor Build `actor.call` when true, otherwise build `Ray.call`.
|
||||
* @param forActorCreation Build `Ray.createActor` when true, otherwise build `Ray.call`.
|
||||
*/
|
||||
private void buildPyCalls(int numParameters, boolean forActor, boolean forActorCreation) {
|
||||
String modifiers = forActor ? "default" : "public static";
|
||||
|
||||
String argList = "";
|
||||
String paramList = "";
|
||||
for (int i = 0; i < numParameters; i++) {
|
||||
paramList += "Object obj" + i + ", ";
|
||||
argList += "obj" + i + ", ";
|
||||
}
|
||||
if (argList.endsWith(", ")) {
|
||||
argList = argList.substring(0, argList.length() - 2);
|
||||
}
|
||||
if (paramList.endsWith(", ")) {
|
||||
paramList = paramList.substring(0, paramList.length() - 2);
|
||||
}
|
||||
|
||||
String paramPrefix = "";
|
||||
String funcArgs = "";
|
||||
if (forActorCreation) {
|
||||
paramPrefix += "PyActorClass pyActorClass";
|
||||
funcArgs += "pyActorClass";
|
||||
} else if (forActor) {
|
||||
paramPrefix += "PyActorMethod<R> pyActorMethod";
|
||||
funcArgs += "pyActorMethod";
|
||||
} else {
|
||||
paramPrefix += "PyFunction<R> pyFunction";
|
||||
funcArgs += "pyFunction";
|
||||
}
|
||||
if (numParameters > 0) {
|
||||
paramPrefix += ", ";
|
||||
}
|
||||
|
||||
String genericType = forActorCreation ? "" : " <R>";
|
||||
String returnType =
|
||||
forActorCreation ? "PyActorCreator" : forActor ? "PyActorTaskCaller<R>" : "PyTaskCaller<R>";
|
||||
|
||||
String funcName = forActorCreation ? "actor" : "task";
|
||||
String caller =
|
||||
forActorCreation ? "PyActorCreator" : forActor ? "PyActorTaskCaller<>" : "PyTaskCaller<>";
|
||||
funcArgs += ", args";
|
||||
// Method signature.
|
||||
newLine(
|
||||
1,
|
||||
String.format(
|
||||
"%s%s %s %s(%s) {",
|
||||
modifiers, genericType, returnType, funcName, paramPrefix + paramList));
|
||||
// Method body.
|
||||
newLine(2, String.format("Object[] args = new Object[]{%s};", argList));
|
||||
if (forActor) {
|
||||
newLine(2, String.format("return new %s((PyActorHandle)this, %s);", caller, funcArgs));
|
||||
} else {
|
||||
newLine(2, String.format("return new %s(%s);", caller, funcArgs));
|
||||
}
|
||||
newLine(1, "}");
|
||||
newLine("");
|
||||
}
|
||||
|
||||
private void buildCppCalls(int numParameters, boolean forActor, boolean forActorCreation) {
|
||||
String modifiers = forActor ? "default" : "public static";
|
||||
|
||||
String argList = "";
|
||||
String paramList = "";
|
||||
for (int i = 0; i < numParameters; i++) {
|
||||
paramList += "Object obj" + i + ", ";
|
||||
argList += "obj" + i + ", ";
|
||||
}
|
||||
if (argList.endsWith(", ")) {
|
||||
argList = argList.substring(0, argList.length() - 2);
|
||||
}
|
||||
if (paramList.endsWith(", ")) {
|
||||
paramList = paramList.substring(0, paramList.length() - 2);
|
||||
}
|
||||
|
||||
String paramPrefix = "";
|
||||
String funcArgs = "";
|
||||
if (forActorCreation) {
|
||||
paramPrefix += "CppActorClass cppActorClass";
|
||||
funcArgs += "cppActorClass";
|
||||
} else if (forActor) {
|
||||
paramPrefix += "CppActorMethod<R> cppActorMethod";
|
||||
funcArgs += "cppActorMethod";
|
||||
} else {
|
||||
paramPrefix += "CppFunction<R> cppFunction";
|
||||
funcArgs += "cppFunction";
|
||||
}
|
||||
if (numParameters > 0) {
|
||||
paramPrefix += ", ";
|
||||
}
|
||||
|
||||
String genericType = forActorCreation ? "" : " <R>";
|
||||
String returnType =
|
||||
forActorCreation
|
||||
? "CppActorCreator"
|
||||
: forActor ? "CppActorTaskCaller<R>" : "CppTaskCaller<R>";
|
||||
|
||||
String funcName = forActorCreation ? "actor" : "task";
|
||||
String caller =
|
||||
forActorCreation
|
||||
? "CppActorCreator"
|
||||
: forActor ? "CppActorTaskCaller<>" : "CppTaskCaller<>";
|
||||
funcArgs += ", args";
|
||||
// Method signature.
|
||||
newLine(
|
||||
1,
|
||||
String.format(
|
||||
"%s%s %s %s(%s) {",
|
||||
modifiers, genericType, returnType, funcName, paramPrefix + paramList));
|
||||
// Method body.
|
||||
newLine(2, String.format("Object[] args = new Object[]{%s};", argList));
|
||||
if (forActor) {
|
||||
newLine(2, String.format("return new %s((CppActorHandle)this, %s);", caller, funcArgs));
|
||||
} else {
|
||||
newLine(2, String.format("return new %s(%s);", caller, funcArgs));
|
||||
}
|
||||
newLine(1, "}");
|
||||
newLine("");
|
||||
}
|
||||
|
||||
private List<String> generateParameters(int numParams) {
|
||||
List<String> res = new ArrayList<>();
|
||||
dfs(0, numParams, "", res);
|
||||
return res;
|
||||
}
|
||||
|
||||
private void dfs(int pos, int numParams, String cur, List<String> res) {
|
||||
if (pos >= numParams) {
|
||||
res.add(cur);
|
||||
return;
|
||||
}
|
||||
String nextParameter = String.format("T%d t%d, ", pos, pos);
|
||||
dfs(pos + 1, numParams, cur + nextParameter, res);
|
||||
nextParameter = String.format("ObjectRef<T%d> t%d, ", pos, pos);
|
||||
dfs(pos + 1, numParams, cur + nextParameter, res);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
String path = System.getProperty("user.dir") + "/api/src/main/java/io/ray/api/RayCall.java";
|
||||
FileUtils.write(
|
||||
new File(path), new RayCallGenerator().generateRayCallDotJava(), Charset.defaultCharset());
|
||||
path = System.getProperty("user.dir") + "/api/src/main/java/io/ray/api/ActorCall.java";
|
||||
FileUtils.write(
|
||||
new File(path),
|
||||
new RayCallGenerator().generateActorCallDotJava(),
|
||||
Charset.defaultCharset());
|
||||
path = System.getProperty("user.dir") + "/api/src/main/java/io/ray/api/PyActorCall.java";
|
||||
FileUtils.write(
|
||||
new File(path),
|
||||
new RayCallGenerator().generatePyActorCallDotJava(),
|
||||
Charset.defaultCharset());
|
||||
path = System.getProperty("user.dir") + "/api/src/main/java/io/ray/api/CppActorCall.java";
|
||||
FileUtils.write(
|
||||
new File(path),
|
||||
new RayCallGenerator().generateCppActorCallDotJava(),
|
||||
Charset.defaultCharset());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package io.ray.runtime.util.generator;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
/** A util class that generates all the RayFuncX classes under io.ray.api.function package. */
|
||||
public class RayFuncGenerator extends BaseGenerator {
|
||||
|
||||
private String generate(int numParameters, boolean hasReturn) {
|
||||
sb = new StringBuilder();
|
||||
|
||||
String genericTypes = "";
|
||||
String paramList = "";
|
||||
for (int i = 0; i < numParameters; i++) {
|
||||
genericTypes += "T" + i + ", ";
|
||||
if (i > 0) {
|
||||
paramList += ", ";
|
||||
}
|
||||
paramList += String.format("T%d t%d", i, i);
|
||||
}
|
||||
if (hasReturn) {
|
||||
genericTypes += "R, ";
|
||||
}
|
||||
if (!genericTypes.isEmpty()) {
|
||||
// Remove trailing ", ".
|
||||
genericTypes = genericTypes.substring(0, genericTypes.length() - 2);
|
||||
genericTypes = "<" + genericTypes + ">";
|
||||
}
|
||||
|
||||
newLine("// generated automatically, do not modify.");
|
||||
newLine("");
|
||||
newLine("package io.ray.api.function;");
|
||||
newLine("");
|
||||
newLine("/**");
|
||||
String comment =
|
||||
String.format(
|
||||
" * Functional interface for a remote function that has %d parameter%s.",
|
||||
numParameters, numParameters > 1 ? "s" : "");
|
||||
newLine(comment);
|
||||
newLine(" */");
|
||||
newLine("@FunctionalInterface");
|
||||
String className = "RayFunc" + (hasReturn ? "" : "Void") + numParameters;
|
||||
newLine(
|
||||
String.format(
|
||||
"public interface %s%s extends %s {",
|
||||
className, genericTypes, hasReturn ? "RayFuncR<R>" : "RayFuncVoid"));
|
||||
newLine("");
|
||||
indents(1);
|
||||
newLine(String.format("%s apply(%s) throws Exception;", hasReturn ? "R" : "void", paramList));
|
||||
newLine("}");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
String root = System.getProperty("user.dir") + "/api/src/main/java/io/ray/api/function/";
|
||||
RayFuncGenerator generator = new RayFuncGenerator();
|
||||
for (int i = 0; i <= MAX_PARAMETERS; i++) {
|
||||
// Functions that have return.
|
||||
String content = generator.generate(i, true);
|
||||
FileUtils.write(new File(root + "RayFunc" + i + ".java"), content, Charset.defaultCharset());
|
||||
// Functions that don't have return.
|
||||
content = generator.generate(i, false);
|
||||
FileUtils.write(
|
||||
new File(root + "RayFuncVoid" + i + ".java"), content, Charset.defaultCharset());
|
||||
}
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package io.ray.runtime.utils.parallelactor;
|
||||
|
||||
import io.ray.api.ActorHandle;
|
||||
import io.ray.api.ObjectRef;
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.api.concurrencygroup.ConcurrencyGroup;
|
||||
import io.ray.api.concurrencygroup.ConcurrencyGroupBuilder;
|
||||
import io.ray.api.function.RayFunc;
|
||||
import io.ray.api.function.RayFuncR;
|
||||
import io.ray.api.parallelactor.*;
|
||||
import io.ray.runtime.AbstractRayRuntime;
|
||||
import io.ray.runtime.functionmanager.FunctionManager;
|
||||
import io.ray.runtime.functionmanager.JavaFunctionDescriptor;
|
||||
|
||||
public class ParallelActorContextImpl implements ParallelActorContext {
|
||||
|
||||
@Override
|
||||
public <A> ParallelActorHandle<A> createParallelActorExecutor(
|
||||
int parallelism, RayFuncR<A> ctorFunc) {
|
||||
ConcurrencyGroup[] concurrencyGroups = new ConcurrencyGroup[parallelism];
|
||||
for (int i = 0; i < parallelism; ++i) {
|
||||
concurrencyGroups[i] =
|
||||
new ConcurrencyGroupBuilder<ParallelActorExecutorImpl>()
|
||||
.setName(String.format("PARALLEL_INSTANCE_%d", i))
|
||||
.setMaxConcurrency(1)
|
||||
.build();
|
||||
}
|
||||
|
||||
FunctionManager functionManager = ((AbstractRayRuntime) Ray.internal()).getFunctionManager();
|
||||
JavaFunctionDescriptor functionDescriptor =
|
||||
functionManager.getFunction(ctorFunc).getFunctionDescriptor();
|
||||
ActorHandle<ParallelActorExecutorImpl> parallelExecutorHandle =
|
||||
Ray.actor(ParallelActorExecutorImpl::new, parallelism, functionDescriptor)
|
||||
.setConcurrencyGroups(concurrencyGroups)
|
||||
.remote();
|
||||
|
||||
return new ParallelActorHandleImpl<>(parallelism, parallelExecutorHandle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A, R> ObjectRef<R> submitTask(
|
||||
ParallelActorHandle<A> parallelActorHandle, int instanceId, RayFunc func, Object[] args) {
|
||||
ActorHandle<ParallelActorExecutorImpl> parallelExecutor =
|
||||
((ParallelActorHandleImpl) parallelActorHandle).getExecutor();
|
||||
FunctionManager functionManager = ((AbstractRayRuntime) Ray.internal()).getFunctionManager();
|
||||
JavaFunctionDescriptor functionDescriptor =
|
||||
functionManager.getFunction(func).getFunctionDescriptor();
|
||||
ObjectRef<Object> ret =
|
||||
parallelExecutor
|
||||
.task(ParallelActorExecutorImpl::execute, instanceId, functionDescriptor, args)
|
||||
.setConcurrencyGroup(String.format("PARALLEL_INSTANCE_%d", instanceId))
|
||||
.remote();
|
||||
return (ObjectRef<R>) ret;
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package io.ray.runtime.utils.parallelactor;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import io.ray.api.Ray;
|
||||
import io.ray.runtime.AbstractRayRuntime;
|
||||
import io.ray.runtime.functionmanager.FunctionManager;
|
||||
import io.ray.runtime.functionmanager.JavaFunctionDescriptor;
|
||||
import io.ray.runtime.functionmanager.RayFunction;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ParallelActorExecutorImpl {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ParallelActorExecutorImpl.class);
|
||||
|
||||
private FunctionManager functionManager = null;
|
||||
|
||||
private ConcurrentHashMap<Integer, Object> instances = new ConcurrentHashMap<>();
|
||||
|
||||
public ParallelActorExecutorImpl(int parallelism, JavaFunctionDescriptor javaFunctionDescriptor)
|
||||
throws InvocationTargetException, IllegalAccessException {
|
||||
|
||||
functionManager = ((AbstractRayRuntime) Ray.internal()).getFunctionManager();
|
||||
RayFunction init = functionManager.getFunction(javaFunctionDescriptor);
|
||||
Thread.currentThread().setContextClassLoader(init.classLoader);
|
||||
for (int i = 0; i < parallelism; ++i) {
|
||||
Object instance = init.getMethod().invoke(null);
|
||||
instances.put(i, instance);
|
||||
}
|
||||
}
|
||||
|
||||
public Object execute(int instanceId, JavaFunctionDescriptor functionDescriptor, Object[] args)
|
||||
throws IllegalAccessException, InvocationTargetException {
|
||||
RayFunction func = functionManager.getFunction(functionDescriptor);
|
||||
Preconditions.checkState(instances.containsKey(instanceId));
|
||||
return func.getMethod().invoke(instances.get(instanceId), args);
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package io.ray.runtime.utils.parallelactor;
|
||||
|
||||
import io.ray.api.ActorHandle;
|
||||
import io.ray.api.parallelactor.ParallelActorHandle;
|
||||
import io.ray.api.parallelactor.ParallelActorInstance;
|
||||
import java.io.Serializable;
|
||||
|
||||
public class ParallelActorHandleImpl<A> implements ParallelActorHandle<A>, Serializable {
|
||||
|
||||
private int parallelism = 1;
|
||||
|
||||
private ActorHandle<ParallelActorExecutorImpl> parallelExecutorHandle = null;
|
||||
|
||||
// An empty ctor for FST serializing need.
|
||||
public ParallelActorHandleImpl() {}
|
||||
|
||||
public ParallelActorHandleImpl(int parallelism, ActorHandle<ParallelActorExecutorImpl> handle) {
|
||||
this.parallelism = parallelism;
|
||||
parallelExecutorHandle = handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParallelActorInstance<A> getInstance(int instanceId) {
|
||||
return new ParallelActorInstance<A>(this, instanceId);
|
||||
}
|
||||
|
||||
public ActorHandle<? extends ParallelActorExecutorImpl> getExecutor() {
|
||||
return parallelExecutorHandle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getParallelism() {
|
||||
return parallelism;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActorHandle<?> getHandle() {
|
||||
return parallelExecutorHandle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--NOTE: This configuration file is important, we shouldn't remove it.
|
||||
In log4j2, if we don't specify the log level, the default value is error.
|
||||
In our case that if we logged before `Ray.init()`, the logs will not printed.
|
||||
-->
|
||||
<Configuration status="info" name="RayDefaultLog4j2ConfigBeforeInit">
|
||||
<ThresholdFilter level="debug"/>
|
||||
|
||||
<Appenders>
|
||||
<Console name="STDOUT" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} %p %c{1} [%t]: %m%n"/>
|
||||
<ThresholdFilter level="debug"/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<Root level="info">
|
||||
<AppenderRef ref="STDOUT"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
|
||||
</Configuration>
|
||||
@@ -0,0 +1,127 @@
|
||||
// This file contains default values of all Ray configurations.
|
||||
// Users should define their own 'ray.conf' file in the classpath,
|
||||
// or use Java properties, to overwrite these values.
|
||||
|
||||
ray {
|
||||
// ----------------------
|
||||
// Basic configurations
|
||||
// ----------------------
|
||||
|
||||
// Address of the Ray cluster to connect to.
|
||||
// If not provided, a new Ray cluster will be created.
|
||||
address: ""
|
||||
|
||||
// Run mode, available options are:
|
||||
//
|
||||
// `LOCAL`: Ray is running in one single Java process, without Raylet backend,
|
||||
// object store, and GCS. It's useful for debug.
|
||||
// `CLUSTER`: Ray is running on one or more nodes, with multiple processes.
|
||||
run-mode: CLUSTER
|
||||
|
||||
// Configuration items about job.
|
||||
job {
|
||||
// If worker.mode is DRIVER, specify the job id.
|
||||
// If not provided, a random id will be used.
|
||||
id: ""
|
||||
// A list of directories or jar files separated by colon that specify the
|
||||
// search path for user code. This will be used as `CLASSPATH` in Java,
|
||||
// and `PYTHONPATH` in Python.
|
||||
code-search-path: ""
|
||||
/// The jvm options for java workers of the job.
|
||||
jvm-options: []
|
||||
|
||||
runtime-env: {
|
||||
// Environment variables to be set on worker processes in current job.
|
||||
"env-vars": {
|
||||
// key1: "value11"
|
||||
// key2: "value22"
|
||||
}
|
||||
|
||||
// The packages that for this job. Dashboard agent will download from the
|
||||
// urls and then the workers of this job will add them to classpath.
|
||||
// Note that it supports both jar packages and zip packages.
|
||||
"jars": [
|
||||
// "https://my_host/a.jar",
|
||||
// "https://my_host/b.jar"
|
||||
]
|
||||
|
||||
// The config of runtime env.
|
||||
"config": {
|
||||
// The timeout of runtime environment creation, timeout is in seconds.
|
||||
// The value `-1` means disable timeout logic, except `-1`, `setup_timeout_seconds`
|
||||
// cannot be less than or equal to 0. The default value of `setup_timeout_seconds`
|
||||
// is 600 seconds.
|
||||
// setup-timeout-seconds: 600
|
||||
// Indicates whether to install the runtime environment on the cluster at `ray.init()`
|
||||
// time, before the workers are leased. This flag is set to `True` by default.
|
||||
// eager-install: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// The namespace of this job. It's used for isolation between jobs.
|
||||
/// Jobs in different namespaces cannot access each other.
|
||||
/// If it's not specified, a randomized value will be used instead.
|
||||
namespace: ""
|
||||
|
||||
// The default lifetime of actors in this job.
|
||||
// If the lifetime of an actor is not specified explicitly at runtime, this
|
||||
// default value will be applied.
|
||||
// The available values are `NON_DETACHED` and `DETACHED`.
|
||||
default-actor-lifetime: NON_DETACHED
|
||||
}
|
||||
|
||||
// Configurations about worker
|
||||
worker {
|
||||
id: ""
|
||||
}
|
||||
|
||||
// Configurations about logging.
|
||||
logging {
|
||||
// Level of logging for Java workers.
|
||||
level: INFO
|
||||
// Pattern of log messages.
|
||||
pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS} %p %c{1} [%t]: %m%n"
|
||||
// Root directory of the log files.
|
||||
// If this is not set, the default one will be `${temp-dir}/session_xxx/logs`.
|
||||
dir: ""
|
||||
// Maximum size that a log file is allowed to reach before being rolled over to backup files.
|
||||
max-file-size: 500MB
|
||||
// Maximum number of backup files to keep around.
|
||||
max-backup-files: 10
|
||||
// log file name prefix of default logger
|
||||
// change it to something else other than "java-worker" if you dont want
|
||||
// ray log monitor to poll and publish log to gcs from the log file
|
||||
file-prefix: java-worker
|
||||
|
||||
// Configuration for the customized loggers.
|
||||
// For example, if you want to customize the file name and the log pattern for a logger
|
||||
// named "userlogger", you can add the following configuration, and then you will get
|
||||
// the custom log file `userlogger.log`.
|
||||
loggers: [
|
||||
// {
|
||||
// name: "userlogger"
|
||||
// file-name: "userlogger"
|
||||
// pattern: "%d{yyyy-MM-dd HH:mm:ss,SSS} %p %c{1} [%t]: %m%n"
|
||||
// }
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// Redis configurations
|
||||
// ----------------------
|
||||
redis {
|
||||
// The password used to connect to the redis server.
|
||||
username: "default"
|
||||
password: "5241590000000000"
|
||||
}
|
||||
|
||||
// Below args will be appended as parameters of the `ray start` command.
|
||||
// It takes effect only if Ray head is started by a driver.
|
||||
head-args: [
|
||||
// "--num-cpus=1",
|
||||
// "--num-gpus=1",
|
||||
// "--memory=1073741824"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package io.ray.runtime;
|
||||
|
||||
import io.ray.api.id.UniqueId;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class UniqueIdTest {
|
||||
|
||||
@Test
|
||||
public void testConstructUniqueId() {
|
||||
// Test `fromHexString()`
|
||||
UniqueId id1 =
|
||||
UniqueId.fromHexString("00000000123456789ABCDEF123456789ABCDEF0123456789ABCDEF00");
|
||||
Assert.assertEquals("00000000123456789abcdef123456789abcdef0123456789abcdef00", id1.toString());
|
||||
Assert.assertFalse(id1.isNil());
|
||||
|
||||
try {
|
||||
UniqueId id2 =
|
||||
UniqueId.fromHexString("000000123456789ABCDEF123456789ABCDEF0123456789ABCDEF00");
|
||||
// This shouldn't be happened.
|
||||
Assert.assertTrue(false);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Assert.assertTrue(true);
|
||||
}
|
||||
|
||||
try {
|
||||
UniqueId id3 = UniqueId.fromHexString("GGGGGGGGGGGGG");
|
||||
// This shouldn't be happened.
|
||||
Assert.assertTrue(false);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Assert.assertTrue(true);
|
||||
}
|
||||
|
||||
// Test `fromByteBuffer()`
|
||||
byte[] bytes =
|
||||
DatatypeConverter.parseHexBinary(
|
||||
"0123456789ABCDEF0123456789ABCDEF012345670123456789ABCDEF");
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes, 0, 28);
|
||||
UniqueId id4 = UniqueId.fromByteBuffer(byteBuffer);
|
||||
Assert.assertTrue(Arrays.equals(bytes, id4.getBytes()));
|
||||
Assert.assertEquals("0123456789abcdef0123456789abcdef012345670123456789abcdef", id4.toString());
|
||||
|
||||
// Test `genNil()`
|
||||
UniqueId id6 = UniqueId.NIL;
|
||||
Assert.assertEquals(
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF".toLowerCase(), id6.toString());
|
||||
Assert.assertTrue(id6.isNil());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.ray.runtime.config;
|
||||
|
||||
import io.ray.runtime.generated.Common.WorkerType;
|
||||
import java.util.Collections;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class RayConfigTest {
|
||||
|
||||
@Test
|
||||
public void testCreateRayConfig() {
|
||||
System.setProperty("ray.job.code-search-path", "path/to/ray/job/resource/path");
|
||||
RayConfig rayConfig = RayConfig.create();
|
||||
Assert.assertEquals(WorkerType.DRIVER, rayConfig.workerMode);
|
||||
Assert.assertEquals(
|
||||
Collections.singletonList("path/to/ray/job/resource/path"), rayConfig.codeSearchPath);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLogFilePrefix() {
|
||||
String key = "ray.logging.file-prefix";
|
||||
RayConfig rayConfig = RayConfig.create();
|
||||
Assert.assertEquals("java-worker", rayConfig.getInternalConfig().getString(key));
|
||||
System.setProperty(key, "raydp-java-worker");
|
||||
rayConfig = RayConfig.create();
|
||||
Assert.assertEquals("raydp-java-worker", rayConfig.getInternalConfig().getString(key));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
package io.ray.runtime.functionmanager;
|
||||
|
||||
import io.ray.api.function.RayFunc0;
|
||||
import io.ray.api.function.RayFunc1;
|
||||
import io.ray.api.id.JobId;
|
||||
import io.ray.runtime.functionmanager.FunctionManager.JobFunctionTable;
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.ToolProvider;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
/** Tests for {@link FunctionManager} */
|
||||
public class FunctionManagerTest {
|
||||
|
||||
public static Object foo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class ParentClass {
|
||||
|
||||
public Object foo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object bar() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ChildClassInterface {
|
||||
|
||||
default String interfaceName() {
|
||||
return getClass().getName();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ChildClass extends ParentClass implements ChildClassInterface {
|
||||
|
||||
public ChildClass() {}
|
||||
|
||||
@Override
|
||||
public Object bar() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object overloadFunction(int i) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object overloadFunction(double d) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final JobId JOB_ID = JobId.fromInt(1);
|
||||
|
||||
private static RayFunc0<Object> fooFunc;
|
||||
private static RayFunc1<ChildClass, Object> childClassBarFunc;
|
||||
private static RayFunc0<ChildClass> childClassConstructor;
|
||||
private static JavaFunctionDescriptor fooDescriptor;
|
||||
private static JavaFunctionDescriptor childClassBarDescriptor;
|
||||
private static JavaFunctionDescriptor childClassConstructorDescriptor;
|
||||
private static JavaFunctionDescriptor overloadFunctionDescriptorInt;
|
||||
private static JavaFunctionDescriptor overloadFunctionDescriptorDouble;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
fooFunc = FunctionManagerTest::foo;
|
||||
childClassConstructor = ChildClass::new;
|
||||
childClassBarFunc = ChildClass::bar;
|
||||
fooDescriptor =
|
||||
new JavaFunctionDescriptor(
|
||||
FunctionManagerTest.class.getName(), "foo", "()Ljava/lang/Object;");
|
||||
childClassBarDescriptor =
|
||||
new JavaFunctionDescriptor(ChildClass.class.getName(), "bar", "()Ljava/lang/Object;");
|
||||
childClassConstructorDescriptor =
|
||||
new JavaFunctionDescriptor(
|
||||
ChildClass.class.getName(), FunctionManager.CONSTRUCTOR_NAME, "()V");
|
||||
overloadFunctionDescriptorInt =
|
||||
new JavaFunctionDescriptor(
|
||||
FunctionManagerTest.class.getName(), "overloadFunction", "(I)Ljava/lang/Object;");
|
||||
overloadFunctionDescriptorDouble =
|
||||
new JavaFunctionDescriptor(
|
||||
FunctionManagerTest.class.getName(), "overloadFunction", "(D)Ljava/lang/Object;");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFunctionFromRayFunc() {
|
||||
final FunctionManager functionManager = new FunctionManager(null);
|
||||
// Test normal function.
|
||||
RayFunction func = functionManager.getFunction(fooFunc);
|
||||
Assert.assertFalse(func.isConstructor());
|
||||
Assert.assertEquals(func.getFunctionDescriptor(), fooDescriptor);
|
||||
|
||||
// Test actor method
|
||||
func = functionManager.getFunction(childClassBarFunc);
|
||||
Assert.assertFalse(func.isConstructor());
|
||||
Assert.assertEquals(func.getFunctionDescriptor(), childClassBarDescriptor);
|
||||
|
||||
// Test actor constructor
|
||||
func = functionManager.getFunction(childClassConstructor);
|
||||
Assert.assertTrue(func.isConstructor());
|
||||
Assert.assertEquals(func.getFunctionDescriptor(), childClassConstructorDescriptor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFunctionFromFunctionDescriptor() {
|
||||
final FunctionManager functionManager = new FunctionManager(null);
|
||||
// Test normal function.
|
||||
RayFunction func = functionManager.getFunction(fooDescriptor);
|
||||
Assert.assertFalse(func.isConstructor());
|
||||
Assert.assertEquals(func.getFunctionDescriptor(), fooDescriptor);
|
||||
|
||||
// Test actor method
|
||||
func = functionManager.getFunction(childClassBarDescriptor);
|
||||
Assert.assertFalse(func.isConstructor());
|
||||
Assert.assertEquals(func.getFunctionDescriptor(), childClassBarDescriptor);
|
||||
|
||||
// Test actor constructor
|
||||
func = functionManager.getFunction(childClassConstructorDescriptor);
|
||||
Assert.assertTrue(func.isConstructor());
|
||||
Assert.assertEquals(func.getFunctionDescriptor(), childClassConstructorDescriptor);
|
||||
|
||||
// Test raise overload exception
|
||||
Assert.expectThrows(
|
||||
RuntimeException.class,
|
||||
() -> {
|
||||
functionManager.getFunction(
|
||||
new JavaFunctionDescriptor(
|
||||
FunctionManagerTest.class.getName(), "overloadFunction", ""));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInheritance() {
|
||||
final FunctionManager functionManager = new FunctionManager(null);
|
||||
// Check inheritance can work and FunctionManager can find method in parent class.
|
||||
fooDescriptor =
|
||||
new JavaFunctionDescriptor(ParentClass.class.getName(), "foo", "()Ljava/lang/Object;");
|
||||
Assert.assertEquals(
|
||||
functionManager.getFunction(fooDescriptor).executable.getDeclaringClass(),
|
||||
ParentClass.class);
|
||||
RayFunction fooFunc =
|
||||
functionManager.getFunction(
|
||||
new JavaFunctionDescriptor(ChildClass.class.getName(), "foo", "()Ljava/lang/Object;"));
|
||||
Assert.assertEquals(fooFunc.executable.getDeclaringClass(), ParentClass.class);
|
||||
|
||||
// Check FunctionManager can use method in child class if child class methods overrides methods
|
||||
// in parent class.
|
||||
childClassBarDescriptor =
|
||||
new JavaFunctionDescriptor(ParentClass.class.getName(), "bar", "()Ljava/lang/Object;");
|
||||
Assert.assertEquals(
|
||||
functionManager.getFunction(childClassBarDescriptor).executable.getDeclaringClass(),
|
||||
ParentClass.class);
|
||||
RayFunction barFunc =
|
||||
functionManager.getFunction(
|
||||
new JavaFunctionDescriptor(ChildClass.class.getName(), "bar", "()Ljava/lang/Object;"));
|
||||
Assert.assertEquals(barFunc.executable.getDeclaringClass(), ChildClass.class);
|
||||
|
||||
// Check interface default methods.
|
||||
RayFunction interfaceNameFunc =
|
||||
functionManager.getFunction(
|
||||
new JavaFunctionDescriptor(
|
||||
ChildClass.class.getName(), "interfaceName", "()Ljava/lang/String;"));
|
||||
Assert.assertEquals(
|
||||
interfaceNameFunc.executable.getDeclaringClass(), ChildClassInterface.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadFunctionTableForClass() {
|
||||
JobFunctionTable functionTable = new JobFunctionTable(getClass().getClassLoader());
|
||||
Map<Pair<String, String>, Pair<RayFunction, Boolean>> res =
|
||||
functionTable.loadFunctionsForClass(ChildClass.class.getName());
|
||||
// The result should be 5 entries:
|
||||
// 1, the constructor with signature
|
||||
// 2, the constructor without signature
|
||||
// 3, bar with signature
|
||||
// 4, bar without signature
|
||||
// 5, bar with the number of signature acting as signature field (xlang)
|
||||
Assert.assertEquals(res.size(), 16);
|
||||
Assert.assertTrue(
|
||||
res.containsKey(
|
||||
ImmutablePair.of(childClassBarDescriptor.name, childClassBarDescriptor.signature)));
|
||||
Assert.assertTrue(
|
||||
res.containsKey(
|
||||
ImmutablePair.of(
|
||||
childClassConstructorDescriptor.name, childClassConstructorDescriptor.signature)));
|
||||
Assert.assertTrue(res.containsKey(ImmutablePair.of(childClassBarDescriptor.name, "")));
|
||||
Assert.assertTrue(res.containsKey(ImmutablePair.of(childClassConstructorDescriptor.name, "")));
|
||||
Assert.assertTrue(
|
||||
res.containsKey(
|
||||
ImmutablePair.of(
|
||||
overloadFunctionDescriptorInt.name, overloadFunctionDescriptorInt.signature)));
|
||||
Assert.assertTrue(
|
||||
res.containsKey(
|
||||
ImmutablePair.of(
|
||||
overloadFunctionDescriptorDouble.name,
|
||||
overloadFunctionDescriptorDouble.signature)));
|
||||
Assert.assertTrue(res.containsKey(ImmutablePair.of(overloadFunctionDescriptorInt.name, "")));
|
||||
Pair<String, String> overloadKey = ImmutablePair.of(overloadFunctionDescriptorInt.name, "");
|
||||
RayFunction func = res.get(overloadKey).getLeft();
|
||||
// The function is overloaded.
|
||||
Assert.assertTrue(res.containsKey(overloadKey));
|
||||
Assert.assertNull(func);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFunctionFromLocalResource() throws Exception {
|
||||
final String codeSearchPath = FileUtils.getTempDirectoryPath() + "/ray_test_resources/";
|
||||
File jobResourceDir = new File(codeSearchPath);
|
||||
FileUtils.deleteQuietly(jobResourceDir);
|
||||
jobResourceDir.mkdirs();
|
||||
jobResourceDir.deleteOnExit();
|
||||
|
||||
String demoJavaFile = "";
|
||||
demoJavaFile += "public class DemoApp {\n";
|
||||
demoJavaFile += " public static String hello() {\n";
|
||||
demoJavaFile += " return \"hello\";\n";
|
||||
demoJavaFile += " }\n";
|
||||
demoJavaFile += "}";
|
||||
|
||||
// Write the demo java file to the job code search path.
|
||||
String javaFilePath = codeSearchPath + "/DemoApp.java";
|
||||
Files.write(Paths.get(javaFilePath), demoJavaFile.getBytes());
|
||||
|
||||
// Compile the java file.
|
||||
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||
int result = compiler.run(null, null, null, "-d", codeSearchPath, javaFilePath);
|
||||
if (result != 0) {
|
||||
throw new RuntimeException("Couldn't compile Demo.java.");
|
||||
}
|
||||
|
||||
// Test loading the function.
|
||||
JavaFunctionDescriptor descriptor =
|
||||
new JavaFunctionDescriptor("DemoApp", "hello", "()Ljava/lang/String;");
|
||||
final FunctionManager functionManager =
|
||||
new FunctionManager(Collections.singletonList(codeSearchPath));
|
||||
RayFunction func = functionManager.getFunction(descriptor);
|
||||
Assert.assertEquals(func.getFunctionDescriptor(), descriptor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package io.ray.runtime.serializer;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class SerializerTest {
|
||||
|
||||
@Test
|
||||
public void testBasicSerialization() {
|
||||
// Test serialize / deserialize primitive types with type conversion.
|
||||
{
|
||||
Object[] foo =
|
||||
new Object[] {"hello", (byte) 1, 2.0, (short) 3, 4, 5L, new String[] {"hello", "world"}};
|
||||
Pair<byte[], Boolean> serialized = Serializer.encode(foo);
|
||||
Object[] bar = Serializer.decode(serialized.getLeft(), Object[].class);
|
||||
Assert.assertTrue(serialized.getRight());
|
||||
Assert.assertEquals(foo[0], bar[0]);
|
||||
Assert.assertEquals(((Number) foo[1]).byteValue(), ((Number) bar[1]).byteValue());
|
||||
Assert.assertEquals(foo[2], bar[2]);
|
||||
Assert.assertEquals(((Number) foo[3]).intValue(), ((Number) bar[3]).intValue());
|
||||
Assert.assertEquals(((Number) foo[4]).intValue(), ((Number) bar[4]).intValue());
|
||||
Assert.assertEquals(((Number) foo[5]).intValue(), ((Number) bar[5]).intValue());
|
||||
}
|
||||
// Test multidimensional array.
|
||||
{
|
||||
Object[][] foo = new Object[][] {{1, 2}, {"3", 4}};
|
||||
Assert.expectThrows(
|
||||
RuntimeException.class,
|
||||
() -> {
|
||||
Object[][] bar = Serializer.decode(Serializer.encode(foo).getLeft(), Integer[][].class);
|
||||
});
|
||||
Pair<byte[], Boolean> serialized = Serializer.encode(foo);
|
||||
Object[][] bar = Serializer.decode(serialized.getLeft(), Object[][].class);
|
||||
Assert.assertTrue(serialized.getRight());
|
||||
Assert.assertEquals(((Number) foo[0][1]).intValue(), ((Number) bar[0][1]).intValue());
|
||||
Assert.assertEquals(foo[1][0], bar[1][0]);
|
||||
}
|
||||
// Test List.
|
||||
{
|
||||
ArrayList<String> foo = new ArrayList<>();
|
||||
foo.add("1");
|
||||
foo.add("2");
|
||||
Pair<byte[], Boolean> serialized = Serializer.encode(foo);
|
||||
ArrayList<String> bar = Serializer.decode(serialized.getLeft(), String[].class);
|
||||
Assert.assertFalse(serialized.getRight());
|
||||
Assert.assertEquals(foo.get(0), bar.get(0));
|
||||
}
|
||||
// Test BigInteger.
|
||||
{
|
||||
BigInteger bi = BigInteger.valueOf(Long.MAX_VALUE);
|
||||
Pair<byte[], Boolean> serialized = Serializer.encode(bi);
|
||||
BigInteger newBi = Serializer.decode(serialized.getLeft(), BigInteger.class);
|
||||
Assert.assertTrue(serialized.getRight());
|
||||
Assert.assertEquals(bi, newBi);
|
||||
bi = bi.pow(2);
|
||||
serialized = Serializer.encode(bi);
|
||||
newBi = Serializer.decode(serialized.getLeft(), BigInteger.class);
|
||||
Assert.assertFalse(serialized.getRight());
|
||||
Assert.assertEquals(bi, newBi);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user