chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:17:40 +08:00
commit f1825c8ceb
10096 changed files with 2364182 additions and 0 deletions
+133
View File
@@ -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(" ");
}
}
}
@@ -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());
}
}
@@ -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());
}
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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);
}
}
}