[xlang] Cross language Python support (#6709)

This commit is contained in:
fyrestone
2020-02-08 13:01:28 +08:00
committed by GitHub
parent f146d05b36
commit 0648bd28ef
59 changed files with 1412 additions and 580 deletions
+5
View File
@@ -107,6 +107,7 @@ from ray._raylet import (
ObjectID,
TaskID,
UniqueID,
Language,
) # noqa: E402
_config = _Config()
@@ -141,6 +142,7 @@ import ray.projects # noqa: E402
import ray.actor # noqa: F401
from ray.actor import method # noqa: E402
from ray.runtime_context import _get_runtime_context # noqa: E402
from ray.cross_language import java_function, java_actor_class # noqa: E402
# Ray version string.
__version__ = "0.9.0.dev0"
@@ -182,6 +184,9 @@ __all__ = [
"shutdown",
"show_in_webui",
"wait",
"Language",
"java_function",
"java_actor_class",
]
# ID types
+6 -1
View File
@@ -20,6 +20,9 @@ from ray.includes.unique_ids cimport (
CObjectID,
CActorID
)
from ray.includes.function_descriptor cimport (
CFunctionDescriptor,
)
cdef class Buffer:
cdef:
@@ -66,4 +69,6 @@ cdef class CoreWorker:
self, worker, outputs, const c_vector[CObjectID] return_ids,
c_vector[shared_ptr[CRayObject]] *returns)
cdef c_vector[c_string] string_vector_from_list(list string_list)
cdef class FunctionDescriptor:
cdef:
CFunctionDescriptor descriptor
+23 -32
View File
@@ -2,6 +2,7 @@
# distutils: language = c++
# cython: embedsignature = True
# cython: language_level = 3
# cython: c_string_encoding = default
from cpython.exc cimport PyErr_CheckSignals
@@ -87,7 +88,6 @@ from ray.exceptions import (
RayTimeoutError,
)
from ray.experimental.no_return import NoReturn
from ray.function_manager import FunctionDescriptor
from ray.utils import decode
from ray.ray_constants import (
DEFAULT_PUT_OBJECT_DELAY,
@@ -106,6 +106,7 @@ cimport cpython
include "includes/unique_ids.pxi"
include "includes/ray_config.pxi"
include "includes/function_descriptor.pxi"
include "includes/task.pxi"
include "includes/buffer.pxi"
include "includes/common.pxi"
@@ -206,6 +207,7 @@ def compute_task_id(ObjectID object_id):
return TaskID(object_id.native().TaskId().Binary())
@cython.auto_pickle(False)
cdef class Language:
cdef CLanguage lang
@@ -218,7 +220,7 @@ cdef class Language:
def __eq__(self, other):
return (isinstance(other, Language) and
(<int32_t>self.lang) == (<int32_t>other.lang))
(<int32_t>self.lang) == (<int32_t>(<Language>other).lang))
def __repr__(self):
if <int32_t>self.lang == <int32_t>LANGUAGE_PYTHON:
@@ -230,11 +232,12 @@ cdef class Language:
else:
raise Exception("Unexpected error")
def __reduce__(self):
return Language, (<int32_t>self.lang,)
# Programming language enum values.
cdef Language LANG_PYTHON = Language.from_native(LANGUAGE_PYTHON)
cdef Language LANG_CPP = Language.from_native(LANGUAGE_CPP)
cdef Language LANG_JAVA = Language.from_native(LANGUAGE_JAVA)
PYTHON = Language.from_native(LANGUAGE_PYTHON)
CPP = Language.from_native(LANGUAGE_CPP)
JAVA = Language.from_native(LANGUAGE_JAVA)
cdef int prepare_resources(
@@ -261,17 +264,8 @@ cdef int prepare_resources(
return 0
cdef c_vector[c_string] string_vector_from_list(list string_list):
cdef:
c_vector[c_string] out
for s in string_list:
if not isinstance(s, bytes):
raise TypeError("string_list elements must be bytes")
out.push_back(s)
return out
cdef void prepare_args(
CoreWorker core_worker, list args, c_vector[CTaskArg] *args_vector):
CoreWorker core_worker, args, c_vector[CTaskArg] *args_vector):
cdef:
size_t size
int64_t put_threshold
@@ -345,11 +339,10 @@ cdef execute_task(
# Automatically restrict the GPUs available to this task.
ray.utils.set_cuda_visible_devices(ray.get_gpu_ids())
descriptor = tuple(ray_function.GetFunctionDescriptor())
function_descriptor = CFunctionDescriptorToPython(
ray_function.GetFunctionDescriptor())
if <int>task_type == <int>TASK_TYPE_ACTOR_CREATION_TASK:
function_descriptor = FunctionDescriptor.from_bytes_list(
ray_function.GetFunctionDescriptor())
actor_class = manager.load_actor_class(job_id, function_descriptor)
actor_id = core_worker.get_actor_id()
worker.actors[actor_id] = actor_class.__new__(actor_class)
@@ -359,13 +352,11 @@ cdef execute_task(
last_checkpoint_timestamp=int(1000 * time.time()),
checkpoint_ids=[]))
execution_info = execution_infos.get(descriptor)
execution_info = execution_infos.get(function_descriptor)
if not execution_info:
function_descriptor = FunctionDescriptor.from_bytes_list(
ray_function.GetFunctionDescriptor())
execution_info = manager.get_execution_info(
job_id, function_descriptor)
execution_infos[descriptor] = execution_info
execution_infos[function_descriptor] = execution_info
function_name = execution_info.function_name
extra_data = (b'{"name": ' + function_name.encode("ascii") +
@@ -500,9 +491,6 @@ cdef execute_task(
ray_signal.reset()
if execution_info.max_calls != 0:
function_descriptor = FunctionDescriptor.from_bytes_list(
ray_function.GetFunctionDescriptor())
# Reset the state of the worker for the next task to execute.
# Increase the task execution counter.
manager.increase_task_counter(job_id, function_descriptor)
@@ -772,7 +760,8 @@ cdef class CoreWorker:
message.decode("utf-8")))
def submit_task(self,
function_descriptor,
Language language,
FunctionDescriptor function_descriptor,
args,
int num_return_vals,
c_bool is_direct_call,
@@ -790,7 +779,7 @@ cdef class CoreWorker:
task_options = CTaskOptions(
num_return_vals, is_direct_call, c_resources)
ray_function = CRayFunction(
LANGUAGE_PYTHON, string_vector_from_list(function_descriptor))
language.lang, function_descriptor.descriptor)
prepare_args(self, args, &args_vector)
with nogil:
@@ -801,7 +790,8 @@ cdef class CoreWorker:
return VectorToObjectIDs(return_ids)
def create_actor(self,
function_descriptor,
Language language,
FunctionDescriptor function_descriptor,
args,
uint64_t max_reconstructions,
resources,
@@ -822,7 +812,7 @@ cdef class CoreWorker:
prepare_resources(resources, &c_resources)
prepare_resources(placement_resources, &c_placement_resources)
ray_function = CRayFunction(
LANGUAGE_PYTHON, string_vector_from_list(function_descriptor))
language.lang, function_descriptor.descriptor)
prepare_args(self, args, &args_vector)
with nogil:
@@ -837,8 +827,9 @@ cdef class CoreWorker:
return ActorID(c_actor_id.Binary())
def submit_actor_task(self,
Language language,
ActorID actor_id,
function_descriptor,
FunctionDescriptor function_descriptor,
args,
int num_return_vals,
double num_method_cpus):
@@ -856,7 +847,7 @@ cdef class CoreWorker:
c_resources[b"CPU"] = num_method_cpus
task_options = CTaskOptions(num_return_vals, False, c_resources)
ray_function = CRayFunction(
LANGUAGE_PYTHON, string_vector_from_list(function_descriptor))
language.lang, function_descriptor.descriptor)
prepare_args(self, args, &args_vector)
with nogil:
+140 -67
View File
@@ -7,12 +7,13 @@ import weakref
from abc import ABCMeta, abstractmethod
from collections import namedtuple
from ray.function_manager import FunctionDescriptor
import ray.ray_constants as ray_constants
import ray._raylet
import ray.signature as signature
import ray.worker
from ray import ActorID, ActorClassID
from ray import ActorID, ActorClassID, Language
from ray._raylet import PythonFunctionDescriptor
from ray import cross_language
logger = logging.getLogger(__name__)
@@ -143,8 +144,11 @@ class ActorClassMetadata:
"""Metadata for an actor class.
Attributes:
language: The actor language, e.g. Python, Java.
modified_class: The original class that was decorated (with some
additional methods added like __ray_terminate__).
actor_creation_function_descriptor: The function descriptor for
the actor creation task.
class_id: The ID of this actor class.
class_name: The name of this class.
num_cpus: The default number of CPUs required by the actor creation
@@ -154,7 +158,6 @@ class ActorClassMetadata:
memory: The heap memory quota for this actor.
object_store_memory: The object store memory quota for this actor.
resources: The default resources required by the actor creation task.
actor_method_cpus: The number of CPUs required by actor method tasks.
last_export_session_and_job: A pair of the last exported session
and job to help us to know whether this function was exported.
This is an imperfect mechanism used to determine if we need to
@@ -172,11 +175,17 @@ class ActorClassMetadata:
each actor method.
"""
def __init__(self, modified_class, class_id, max_reconstructions, num_cpus,
num_gpus, memory, object_store_memory, resources):
def __init__(self, language, modified_class,
actor_creation_function_descriptor, class_id,
max_reconstructions, num_cpus, num_gpus, memory,
object_store_memory, resources):
self.language = language
self.modified_class = modified_class
self.actor_creation_function_descriptor = \
actor_creation_function_descriptor
self.class_name = actor_creation_function_descriptor.class_name
self.is_cross_language = language != Language.PYTHON
self.class_id = class_id
self.class_name = modified_class.__name__
self.max_reconstructions = max_reconstructions
self.num_cpus = num_cpus
self.num_gpus = num_gpus
@@ -192,7 +201,8 @@ class ActorClassMetadata:
]
constructor_name = "__init__"
if constructor_name not in self.actor_method_names:
if not self.is_cross_language and \
constructor_name not in self.actor_method_names:
# Add __init__ if it does not exist.
# Actor creation will be executed with __init__ together.
@@ -290,7 +300,10 @@ class ActorClass:
def _ray_from_modified_class(cls, modified_class, class_id,
max_reconstructions, num_cpus, num_gpus,
memory, object_store_memory, resources):
for attribute in ["remote", "_remote", "_ray_from_modified_class"]:
for attribute in [
"remote", "_remote", "_ray_from_modified_class",
"_ray_from_function_descriptor"
]:
if hasattr(modified_class, attribute):
logger.warning("Creating an actor from class {} overwrites "
"attribute {} of that class".format(
@@ -307,10 +320,28 @@ class ActorClass:
DerivedActorClass.__qualname__ = name
# Construct the base object.
self = DerivedActorClass.__new__(DerivedActorClass)
# Actor creation function descriptor.
actor_creation_function_descriptor = PythonFunctionDescriptor(
modified_class.__module__, "__init__", modified_class.__name__)
self.__ray_metadata__ = ActorClassMetadata(
modified_class, class_id, max_reconstructions, num_cpus, num_gpus,
memory, object_store_memory, resources)
Language.PYTHON, modified_class,
actor_creation_function_descriptor, class_id, max_reconstructions,
num_cpus, num_gpus, memory, object_store_memory, resources)
return self
@classmethod
def _ray_from_function_descriptor(cls, language,
actor_creation_function_descriptor,
max_reconstructions, num_cpus, num_gpus,
memory, object_store_memory, resources):
self = ActorClass.__new__(ActorClass)
self.__ray_metadata__ = ActorClassMetadata(
language, None, actor_creation_function_descriptor, None,
max_reconstructions, num_cpus, num_gpus, memory,
object_store_memory, resources)
return self
@@ -460,29 +491,33 @@ class ActorClass:
if meta.num_cpus is None else meta.num_cpus)
actor_method_cpu = ray_constants.DEFAULT_ACTOR_METHOD_CPU_SPECIFIED
function_name = "__init__"
function_descriptor = FunctionDescriptor(
meta.modified_class.__module__, function_name,
meta.modified_class.__name__)
# Do not export the actor class or the actor if run in LOCAL_MODE
# Instead, instantiate the actor locally and add it to the worker's
# dictionary
if worker.mode == ray.LOCAL_MODE:
assert not meta.is_cross_language, \
"Cross language ActorClass cannot be executed locally."
actor_id = ActorID.from_random()
worker.actors[actor_id] = meta.modified_class(
*copy.deepcopy(args), **copy.deepcopy(kwargs))
else:
# Export the actor.
if (meta.last_export_session_and_job !=
worker.current_session_and_job):
if not meta.is_cross_language and (meta.last_export_session_and_job
!=
worker.current_session_and_job):
# If this actor class was not exported in this session and job,
# we need to export this function again, because current GCS
# doesn't have it.
meta.last_export_session_and_job = (
worker.current_session_and_job)
# After serialize / deserialize modified class, the __module__
# of modified class will be ray.cloudpickle.cloudpickle.
# So, here pass actor_creation_function_descriptor to make
# sure export actor class correct.
worker.function_actor_manager.export_actor_class(
meta.modified_class, meta.actor_method_names)
meta.modified_class,
meta.actor_creation_function_descriptor,
meta.actor_method_names)
resources = ray.utils.resources_from_resource_arguments(
cpus_to_use, meta.num_gpus, meta.memory,
@@ -497,24 +532,28 @@ class ActorClass:
if actor_method_cpu == 1:
actor_placement_resources = resources.copy()
actor_placement_resources["CPU"] += 1
function_signature = meta.method_signatures[function_name]
creation_args = signature.flatten_args(function_signature, args,
kwargs)
if meta.is_cross_language:
creation_args = cross_language.format_args(
worker, args, kwargs)
else:
function_signature = meta.method_signatures["__init__"]
creation_args = signature.flatten_args(function_signature,
args, kwargs)
actor_id = worker.core_worker.create_actor(
function_descriptor.get_function_descriptor_list(),
meta.language, meta.actor_creation_function_descriptor,
creation_args, meta.max_reconstructions, resources,
actor_placement_resources, is_direct_call, max_concurrency,
detached, is_asyncio)
actor_handle = ActorHandle(
meta.language,
actor_id,
meta.modified_class.__module__,
meta.class_name,
meta.actor_method_names,
meta.method_decorators,
meta.method_signatures,
meta.actor_method_num_return_vals,
actor_method_cpu,
meta.is_cross_language,
meta.actor_creation_function_descriptor,
worker.current_session_and_job,
original_handle=True)
@@ -536,9 +575,8 @@ class ActorHandle:
cloudpickle).
Attributes:
_ray_actor_language: The actor language.
_ray_actor_id: Actor ID.
_ray_module_name: The module name of this actor.
_ray_actor_method_names: The names of the actor methods.
_ray_method_decorators: Optional decorators for the function
invocation. This can be used to change the behavior on the
invocation side, whereas a regular decorator can be used to change
@@ -546,48 +584,55 @@ class ActorHandle:
_ray_method_signatures: The signatures of the actor methods.
_ray_method_num_return_vals: The default number of return values for
each method.
_ray_class_name: The name of the actor class.
_ray_actor_method_cpus: The number of CPUs required by actor methods.
_ray_original_handle: True if this is the original actor handle for a
given actor. If this is true, then the actor will be destroyed when
this handle goes out of scope.
_ray_is_cross_language: Whether this actor is cross language.
_ray_actor_creation_function_descriptor: The function descriptor
of the actor creation task.
"""
def __init__(self,
language,
actor_id,
module_name,
class_name,
actor_method_names,
method_decorators,
method_signatures,
method_num_return_vals,
actor_method_cpus,
is_cross_language,
actor_creation_function_descriptor,
session_and_job,
original_handle=False):
self._ray_actor_language = language
self._ray_actor_id = actor_id
self._ray_module_name = module_name
self._ray_original_handle = original_handle
self._ray_actor_method_names = actor_method_names
self._ray_method_decorators = method_decorators
self._ray_method_signatures = method_signatures
self._ray_method_num_return_vals = method_num_return_vals
self._ray_class_name = class_name
self._ray_actor_method_cpus = actor_method_cpus
self._ray_session_and_job = session_and_job
self._ray_function_descriptor_lists = {
method_name: FunctionDescriptor(
self._ray_module_name, method_name,
self._ray_class_name).get_function_descriptor_list()
for method_name in self._ray_method_signatures.keys()
}
self._ray_is_cross_language = is_cross_language
self._ray_actor_creation_function_descriptor = \
actor_creation_function_descriptor
self._ray_function_descriptor = {}
for method_name in actor_method_names:
method = ActorMethod(
self,
method_name,
self._ray_method_num_return_vals[method_name],
decorator=self._ray_method_decorators.get(method_name))
setattr(self, method_name, method)
if not self._ray_is_cross_language:
assert isinstance(actor_creation_function_descriptor,
PythonFunctionDescriptor)
module_name = actor_creation_function_descriptor.module_name
class_name = actor_creation_function_descriptor.class_name
for method_name in self._ray_method_signatures.keys():
function_descriptor = PythonFunctionDescriptor(
module_name, method_name, class_name)
self._ray_function_descriptor[
method_name] = function_descriptor
method = ActorMethod(
self,
method_name,
self._ray_method_num_return_vals[method_name],
decorator=self._ray_method_decorators.get(method_name))
setattr(self, method_name, method)
def _actor_method_call(self,
method_name,
@@ -615,22 +660,34 @@ class ActorHandle:
args = args or []
kwargs = kwargs or {}
function_signature = self._ray_method_signatures[method_name]
if not args and not kwargs and not function_signature:
list_args = []
if self._ray_is_cross_language:
list_args = cross_language.format_args(worker, args, kwargs)
function_descriptor = \
cross_language.get_function_descriptor_for_actor_method(
self._ray_actor_language,
self._ray_actor_creation_function_descriptor, method_name)
else:
list_args = signature.flatten_args(function_signature, args,
kwargs)
function_signature = self._ray_method_signatures[method_name]
if not args and not kwargs and not function_signature:
list_args = []
else:
list_args = signature.flatten_args(function_signature, args,
kwargs)
function_descriptor = self._ray_function_descriptor[method_name]
if worker.mode == ray.LOCAL_MODE:
assert not self._ray_is_cross_language,\
"Cross language remote actor method " \
"cannot be executed locally."
function = getattr(worker.actors[self._actor_id], method_name)
object_ids = worker.local_mode_manager.execute(
function, method_name, args, kwargs, num_return_vals)
else:
object_ids = worker.core_worker.submit_actor_task(
self._ray_actor_id,
self._ray_function_descriptor_lists[method_name], list_args,
num_return_vals, self._ray_actor_method_cpus)
self._ray_actor_language, self._ray_actor_id,
function_descriptor, list_args, num_return_vals,
self._ray_actor_method_cpus)
if len(object_ids) == 1:
object_ids = object_ids[0]
@@ -639,13 +696,28 @@ class ActorHandle:
return object_ids
def __getattr__(self, item):
if not self._ray_is_cross_language:
raise AttributeError("'{}' object has no attribute '{}'".format(
type(self).__name__, item))
return ActorMethod(
self,
item,
ray_constants.
# Currently, we use default num returns
DEFAULT_ACTOR_METHOD_NUM_RETURN_VALS,
# Currently, cross-lang actor method not support decorator
decorator=None)
# Make tab completion work.
def __dir__(self):
return self._ray_actor_method_names
return self._ray_method_signatures.keys()
def __repr__(self):
return "Actor({}, {})".format(self._ray_class_name,
self._actor_id.hex())
return "Actor({}, {})".format(
self._ray_actor_creation_function_descriptor.class_name,
self._actor_id.hex())
def __del__(self):
"""Terminate the worker that is running this actor."""
@@ -666,7 +738,7 @@ class ActorHandle:
logger.warning(
"Actor is garbage collected in the wrong driver." +
" Actor id = %s, class name = %s.", self._ray_actor_id,
self._ray_class_name)
self._ray_actor_creation_function_descriptor.class_name)
return
if worker.connected and self._ray_original_handle:
# Note: in py2 the weakref is destroyed prior to calling __del__
@@ -708,17 +780,18 @@ class ActorHandle:
worker = ray.worker.get_global_worker()
worker.check_connected()
state = {
"actor_language": self._ray_actor_language,
# Local mode just uses the actor ID.
"core_handle": worker.core_worker.serialize_actor_handle(
self._ray_actor_id)
if hasattr(worker, "core_worker") else self._ray_actor_id,
"module_name": self._ray_module_name,
"class_name": self._ray_class_name,
"actor_method_names": self._ray_actor_method_names,
"method_decorators": self._ray_method_decorators,
"method_signatures": self._ray_method_signatures,
"method_num_return_vals": self._ray_method_num_return_vals,
"actor_method_cpus": self._ray_actor_method_cpus
"actor_method_cpus": self._ray_actor_method_cpus,
"is_cross_language": self._ray_is_cross_language,
"actor_creation_function_descriptor": self.
_ray_actor_creation_function_descriptor,
}
return state
@@ -738,16 +811,16 @@ class ActorHandle:
# TODO(swang): Accessing the worker's current task ID is not
# thread-safe.
# Local mode just uses the actor ID.
state["actor_language"],
worker.core_worker.deserialize_and_register_actor_handle(
state["core_handle"])
if hasattr(worker, "core_worker") else state["core_handle"],
state["module_name"],
state["class_name"],
state["actor_method_names"],
state["method_decorators"],
state["method_signatures"],
state["method_num_return_vals"],
state["actor_method_cpus"],
state["is_cross_language"],
state["actor_creation_function_descriptor"],
worker.current_session_and_job)
def __getstate__(self):
+84
View File
@@ -0,0 +1,84 @@
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from ray import Language
from ray._raylet import JavaFunctionDescriptor
__all__ = [
"java_function",
"java_actor_class",
]
def format_args(worker, args, kwargs):
"""Format args for various languages.
Args:
worker: The global worker instance.
args: The arguments for cross language.
kwargs: The keyword arguments for cross language.
Returns:
List of args and kwargs (if supported).
"""
if not worker.load_code_from_local:
raise Exception("Cross language feature needs "
"--load-code-from-local to be set.")
if kwargs:
raise Exception("Cross language remote functions "
"does not support kwargs.")
return args
def get_function_descriptor_for_actor_method(
language, actor_creation_function_descriptor, method_name):
"""Get function descriptor for cross language actor method call.
Args:
language: Target language.
actor_creation_function_descriptor:
The function signature for actor creation.
method_name: The name of actor method.
Returns:
Function descriptor for cross language actor method call.
"""
if language == Language.JAVA:
return JavaFunctionDescriptor(
actor_creation_function_descriptor.class_name,
method_name,
# Currently not support call actor method with signature.
"")
else:
raise NotImplementedError("Cross language remote actor method "
"not support language {}".format(language))
def java_function(class_name, function_name):
from ray.remote_function import RemoteFunction
return RemoteFunction(
Language.JAVA,
lambda *args, **kwargs: None,
JavaFunctionDescriptor(class_name, function_name, ""),
None, # num_cpus,
None, # num_gpus,
None, # memory,
None, # object_store_memory,
None, # resources,
None, # num_return_vals,
None, # max_calls,
None) # max_retries)
def java_actor_class(class_name):
from ray.actor import ActorClass
return ActorClass._ray_from_function_descriptor(
Language.JAVA,
JavaFunctionDescriptor(class_name, "<init>", ""),
0, # max_reconstructions,
None, # num_cpus,
None, # num_gpus,
None, # memory,
None, # object_store_memory,
None) # resources,
-8
View File
@@ -441,12 +441,6 @@ class NodeStats(threading.Thread):
if addr in self._addr_to_actor_id:
actor_info = flattened_tree[self._addr_to_actor_id[
addr]]
if "currentTaskFuncDesc" in core_worker_stats:
core_worker_stats[
"currentTaskFuncDesc"] = list(
map(
b64_decode, core_worker_stats[
"currentTaskFuncDesc"]))
format_reply_id(core_worker_stats)
actor_info.update(core_worker_stats)
actor_info["averageTaskExecutionSpeed"] = round(
@@ -464,8 +458,6 @@ class NodeStats(threading.Thread):
caller_id = self._addr_to_actor_id.get(caller_addr, "root")
child_to_parent[actor_id] = caller_id
infeasible_task["state"] = -1
infeasible_task["functionDescriptor"] = list(
map(b64_decode, infeasible_task["functionDescriptor"]))
format_reply_id(infeasible_tasks)
flattened_tree[actor_id] = infeasible_task
+8 -228
View File
@@ -17,8 +17,8 @@ import ray
from ray import profiling
from ray import ray_constants
from ray import cloudpickle as pickle
from ray._raylet import PythonFunctionDescriptor
from ray.utils import (
binary_to_hex,
is_function_or_method,
is_class_method,
is_static_method,
@@ -36,226 +36,6 @@ FunctionExecutionInfo = namedtuple("FunctionExecutionInfo",
logger = logging.getLogger(__name__)
class FunctionDescriptor:
"""A class used to describe a python function.
Attributes:
module_name: the module name that the function belongs to.
class_name: the class name that the function belongs to if exists.
It could be empty is the function is not a class method.
function_name: the function name of the function.
function_hash: the hash code of the function source code if the
function code is available.
function_id: the function id calculated from this descriptor.
is_for_driver_task: whether this descriptor is for driver task.
"""
def __init__(self,
module_name,
function_name,
class_name="",
function_source_hash=b""):
self._module_name = module_name
self._class_name = class_name
self._function_name = function_name
self._function_source_hash = function_source_hash
self._function_id = self._get_function_id()
def __repr__(self):
return ("FunctionDescriptor:" + self._module_name + "." +
self._class_name + "." + self._function_name + "." +
binary_to_hex(self._function_source_hash))
@classmethod
def from_bytes_list(cls, function_descriptor_list):
"""Create a FunctionDescriptor instance from list of bytes.
This function is used to create the function descriptor from
backend data.
Args:
cls: Current class which is required argument for classmethod.
function_descriptor_list: list of bytes to represent the
function descriptor.
Returns:
The FunctionDescriptor instance created from the bytes list.
"""
assert isinstance(function_descriptor_list, list)
if len(function_descriptor_list) == 0:
# This is a function descriptor of driver task.
return FunctionDescriptor.for_driver_task()
elif (len(function_descriptor_list) == 3
or len(function_descriptor_list) == 4):
module_name = ensure_str(function_descriptor_list[0])
class_name = ensure_str(function_descriptor_list[1])
function_name = ensure_str(function_descriptor_list[2])
if len(function_descriptor_list) == 4:
return cls(module_name, function_name, class_name,
function_descriptor_list[3])
else:
return cls(module_name, function_name, class_name)
else:
raise Exception(
"Invalid input for FunctionDescriptor.from_bytes_list")
@classmethod
def from_function(cls, function, pickled_function):
"""Create a FunctionDescriptor from a function instance.
This function is used to create the function descriptor from
a python function. If a function is a class function, it should
not be used by this function.
Args:
cls: Current class which is required argument for classmethod.
function: the python function used to create the function
descriptor.
pickled_function: This is factored in to ensure that any
modifications to the function result in a different function
descriptor.
Returns:
The FunctionDescriptor instance created according to the function.
"""
module_name = function.__module__
function_name = function.__name__
class_name = ""
pickled_function_hash = hashlib.sha1(pickled_function).digest()
return cls(module_name, function_name, class_name,
pickled_function_hash)
@classmethod
def from_class(cls, target_class):
"""Create a FunctionDescriptor from a class.
Args:
cls: Current class which is required argument for classmethod.
target_class: the python class used to create the function
descriptor.
Returns:
The FunctionDescriptor instance created according to the class.
"""
module_name = target_class.__module__
class_name = target_class.__name__
return cls(module_name, "__init__", class_name)
@classmethod
def for_driver_task(cls):
"""Create a FunctionDescriptor instance for a driver task."""
return cls("", "", "", b"")
@property
def is_for_driver_task(self):
"""See whether this function descriptor is for a driver or not.
Returns:
True if this function descriptor is for driver tasks.
"""
return all(
len(x) == 0
for x in [self.module_name, self.class_name, self.function_name])
@property
def module_name(self):
"""Get the module name of current function descriptor.
Returns:
The module name of the function descriptor.
"""
return self._module_name
@property
def class_name(self):
"""Get the class name of current function descriptor.
Returns:
The class name of the function descriptor. It could be
empty if the function is not a class method.
"""
return self._class_name
@property
def function_name(self):
"""Get the function name of current function descriptor.
Returns:
The function name of the function descriptor.
"""
return self._function_name
@property
def function_hash(self):
"""Get the hash code of the function source code.
Returns:
The bytes with length of ray_constants.ID_SIZE if the source
code is available. Otherwise, the bytes length will be 0.
"""
return self._function_source_hash
@property
def function_id(self):
"""Get the function id calculated from this descriptor.
Returns:
The value of ray.ObjectID that represents the function id.
"""
return self._function_id
def _get_function_id(self):
"""Calculate the function id of current function descriptor.
This function id is calculated from all the fields of function
descriptor.
Returns:
ray.ObjectID to represent the function descriptor.
"""
if self.is_for_driver_task:
return ray.FunctionID.nil()
function_id_hash = hashlib.sha1()
# Include the function module and name in the hash.
function_id_hash.update(self.module_name.encode("ascii"))
function_id_hash.update(self.function_name.encode("ascii"))
function_id_hash.update(self.class_name.encode("ascii"))
function_id_hash.update(self._function_source_hash)
# Compute the function ID.
function_id = function_id_hash.digest()
return ray.FunctionID(function_id)
def get_function_descriptor_list(self):
"""Return a list of bytes representing the function descriptor.
This function is used to pass this function descriptor to backend.
Returns:
A list of bytes.
"""
descriptor_list = []
if self.is_for_driver_task:
# Driver task returns an empty list.
return descriptor_list
else:
descriptor_list.append(self.module_name.encode("ascii"))
descriptor_list.append(self.class_name.encode("ascii"))
descriptor_list.append(self.function_name.encode("ascii"))
if len(self._function_source_hash) != 0:
descriptor_list.append(self._function_source_hash)
return descriptor_list
def is_actor_method(self):
"""Wether this function descriptor is an actor method.
Returns:
True if it's an actor method, False if it's a normal function.
"""
return len(self._class_name) > 0
class FunctionActorManager:
"""A class used to export/load remote functions and actors.
@@ -488,7 +268,7 @@ class FunctionActorManager:
self._num_task_executions[job_id][function_id] = 0
except Exception:
logger.exception(
"Failed to load function %s.".format(function_name))
"Failed to load function {}.".format(function_name))
raise Exception(
"Function {} failed to be loaded from local code.".format(
function_descriptor))
@@ -551,10 +331,10 @@ class FunctionActorManager:
self._worker.redis_client.hmset(key, actor_class_info)
self._worker.redis_client.rpush("Exports", key)
def export_actor_class(self, Class, actor_method_names):
def export_actor_class(self, Class, actor_creation_function_descriptor,
actor_method_names):
if self._worker.load_code_from_local:
return
function_descriptor = FunctionDescriptor.from_class(Class)
# `current_job_id` shouldn't be NIL, unless:
# 1) This worker isn't an actor;
# 2) And a previous task started a background thread, which didn't
@@ -565,10 +345,10 @@ class FunctionActorManager:
"please make sure the thread finishes before the task finishes.")
job_id = self._worker.current_job_id
key = (b"ActorClass:" + job_id.binary() + b":" +
function_descriptor.function_id.binary())
actor_creation_function_descriptor.function_id.binary())
actor_class_info = {
"class_name": Class.__name__,
"module": Class.__module__,
"class_name": actor_creation_function_descriptor.class_name,
"module": actor_creation_function_descriptor.module_name,
"class": pickle.dumps(Class),
"job_id": job_id.binary(),
"collision_identifier": self.compute_collision_identifier(Class),
@@ -617,7 +397,7 @@ class FunctionActorManager:
actor_methods = inspect.getmembers(
actor_class, predicate=is_function_or_method)
for actor_method_name, actor_method in actor_methods:
method_descriptor = FunctionDescriptor(
method_descriptor = PythonFunctionDescriptor(
module_name, actor_method_name, actor_class_name)
method_id = method_descriptor.function_id
executor = self._make_actor_method_executor(
+5 -2
View File
@@ -13,6 +13,9 @@ from ray.includes.unique_ids cimport (
CObjectID,
CTaskID,
)
from ray.includes.function_descriptor cimport (
CFunctionDescriptor,
)
cdef extern from * namespace "polyfill":
@@ -201,9 +204,9 @@ cdef extern from "ray/core_worker/common.h" nogil:
cdef cppclass CRayFunction "ray::RayFunction":
CRayFunction()
CRayFunction(CLanguage language,
const c_vector[c_string] function_descriptor)
const CFunctionDescriptor &function_descriptor)
CLanguage GetLanguage()
const c_vector[c_string]& GetFunctionDescriptor()
const CFunctionDescriptor GetFunctionDescriptor()
cdef cppclass CTaskArg "ray::TaskArg":
@staticmethod
@@ -0,0 +1,69 @@
from libc.stdint cimport uint8_t, uint64_t
from libcpp cimport bool as c_bool
from libcpp.memory cimport unique_ptr, shared_ptr
from libcpp.string cimport string as c_string
from libcpp.unordered_map cimport unordered_map
from libcpp.vector cimport vector as c_vector
from ray.includes.common cimport (
CLanguage,
ResourceSet,
)
from ray.includes.unique_ids cimport (
CActorID,
CJobID,
CObjectID,
CTaskID,
)
cdef extern from "ray/protobuf/common.pb.h" nogil:
cdef cppclass CFunctionDescriptorType \
"ray::FunctionDescriptorType":
pass
cdef CFunctionDescriptorType EmptyFunctionDescriptorType \
"ray::FunctionDescriptorType::FUNCTION_DESCRIPTOR_NOT_SET"
cdef CFunctionDescriptorType JavaFunctionDescriptorType \
"ray::FunctionDescriptorType::kJavaFunctionDescriptor"
cdef CFunctionDescriptorType PythonFunctionDescriptorType \
"ray::FunctionDescriptorType::kPythonFunctionDescriptor"
cdef extern from "ray/common/function_descriptor.h" nogil:
cdef cppclass CFunctionDescriptorInterface \
"ray::CFunctionDescriptorInterface":
CFunctionDescriptorType Type()
c_string ToString()
c_string Serialize()
ctypedef shared_ptr[CFunctionDescriptorInterface] CFunctionDescriptor \
"ray::FunctionDescriptor"
cdef cppclass CFunctionDescriptorBuilder "ray::FunctionDescriptorBuilder":
@staticmethod
CFunctionDescriptor Empty()
@staticmethod
CFunctionDescriptor BuildJava(const c_string &class_name,
const c_string &function_name,
const c_string &signature)
@staticmethod
CFunctionDescriptor BuildPython(const c_string &module_name,
const c_string &class_name,
const c_string &function_name,
const c_string &function_source_hash)
@staticmethod
CFunctionDescriptor Deserialize(const c_string &serialized_binary)
cdef cppclass CJavaFunctionDescriptor "ray::JavaFunctionDescriptor":
c_string ClassName()
c_string FunctionName()
c_string Signature()
cdef cppclass CPythonFunctionDescriptor "ray::PythonFunctionDescriptor":
c_string ModuleName()
c_string ClassName()
c_string FunctionName()
c_string FunctionHash()
+285
View File
@@ -0,0 +1,285 @@
from ray.includes.function_descriptor cimport (
CFunctionDescriptor,
CFunctionDescriptorBuilder,
CPythonFunctionDescriptor,
CJavaFunctionDescriptor,
EmptyFunctionDescriptorType,
JavaFunctionDescriptorType,
PythonFunctionDescriptorType,
)
import hashlib
import cython
import inspect
ctypedef object (*FunctionDescriptor_from_cpp)(const CFunctionDescriptor &)
cdef unordered_map[int, FunctionDescriptor_from_cpp] \
FunctionDescriptor_constructor_map
cdef CFunctionDescriptorToPython(CFunctionDescriptor function_descriptor):
cdef int function_descriptor_type = <int>function_descriptor.get().Type()
it = FunctionDescriptor_constructor_map.find(function_descriptor_type)
if it == FunctionDescriptor_constructor_map.end():
raise Exception("Can't construct FunctionDescriptor from type {}"
.format(function_descriptor_type))
else:
constructor = dereference(it).second
return constructor(function_descriptor)
@cython.auto_pickle(False)
cdef class FunctionDescriptor:
def __cinit__(self, *args, **kwargs):
if type(self) == FunctionDescriptor:
raise Exception("type {} is abstract".format(type(self).__name__))
def __hash__(self):
return hash(self.descriptor.get().ToString())
def __eq__(self, other):
return (type(self) == type(other) and
self.descriptor.get().ToString() ==
(<FunctionDescriptor>other).descriptor.get().ToString())
def __repr__(self):
return <str>self.descriptor.get().ToString()
def to_dict(self):
d = {"type": type(self).__name__}
for k, v in vars(type(self)).items():
if inspect.isgetsetdescriptor(v):
d[k] = v.__get__(self)
return d
FunctionDescriptor_constructor_map[<int>EmptyFunctionDescriptorType] = \
EmptyFunctionDescriptor.from_cpp
@cython.auto_pickle(False)
cdef class EmptyFunctionDescriptor(FunctionDescriptor):
def __cinit__(self):
self.descriptor = CFunctionDescriptorBuilder.Empty()
def __reduce__(self):
return EmptyFunctionDescriptor, ()
@staticmethod
cdef from_cpp(const CFunctionDescriptor &c_function_descriptor):
return EmptyFunctionDescriptor()
FunctionDescriptor_constructor_map[<int>JavaFunctionDescriptorType] = \
JavaFunctionDescriptor.from_cpp
@cython.auto_pickle(False)
cdef class JavaFunctionDescriptor(FunctionDescriptor):
cdef:
CJavaFunctionDescriptor *typed_descriptor
def __cinit__(self,
class_name,
function_name,
signature):
self.descriptor = CFunctionDescriptorBuilder.BuildJava(
class_name, function_name, signature)
self.typed_descriptor = <CJavaFunctionDescriptor*>(
self.descriptor.get())
def __reduce__(self):
return JavaFunctionDescriptor, (self.typed_descriptor.ClassName(),
self.typed_descriptor.FunctionName(),
self.typed_descriptor.Signature())
@staticmethod
cdef from_cpp(const CFunctionDescriptor &c_function_descriptor):
cdef CJavaFunctionDescriptor *typed_descriptor = \
<CJavaFunctionDescriptor*>(c_function_descriptor.get())
return JavaFunctionDescriptor(typed_descriptor.ClassName(),
typed_descriptor.FunctionName(),
typed_descriptor.Signature())
@property
def class_name(self):
"""Get the class name of current function descriptor.
Returns:
The class name of the function descriptor. It could be
empty if the function is not a class method.
"""
return <str>self.typed_descriptor.ClassName()
@property
def function_name(self):
"""Get the function name of current function descriptor.
Returns:
The function name of the function descriptor.
"""
return <str>self.typed_descriptor.FunctionName()
@property
def signature(self):
"""Get the signature of current function descriptor.
Returns:
The signature of the function descriptor.
"""
return <str>self.typed_descriptor.Signature()
FunctionDescriptor_constructor_map[<int>PythonFunctionDescriptorType] = \
PythonFunctionDescriptor.from_cpp
@cython.auto_pickle(False)
cdef class PythonFunctionDescriptor(FunctionDescriptor):
cdef:
CPythonFunctionDescriptor *typed_descriptor
object _function_id
def __cinit__(self,
module_name,
function_name,
class_name="",
function_source_hash=""):
self.descriptor = CFunctionDescriptorBuilder.BuildPython(
module_name, class_name, function_name, function_source_hash)
self.typed_descriptor = <CPythonFunctionDescriptor*>(
self.descriptor.get())
def __reduce__(self):
return PythonFunctionDescriptor, (self.typed_descriptor.ModuleName(),
self.typed_descriptor.FunctionName(),
self.typed_descriptor.ClassName(),
self.typed_descriptor.FunctionHash())
@staticmethod
cdef from_cpp(const CFunctionDescriptor &c_function_descriptor):
cdef CPythonFunctionDescriptor *typed_descriptor = \
<CPythonFunctionDescriptor*>(c_function_descriptor.get())
return PythonFunctionDescriptor(typed_descriptor.ModuleName(),
typed_descriptor.FunctionName(),
typed_descriptor.ClassName(),
typed_descriptor.FunctionHash())
@classmethod
def from_function(cls, function, pickled_function):
"""Create a FunctionDescriptor from a function instance.
This function is used to create the function descriptor from
a python function. If a function is a class function, it should
not be used by this function.
Args:
cls: Current class which is required argument for classmethod.
function: the python function used to create the function
descriptor.
pickled_function: This is factored in to ensure that any
modifications to the function result in a different function
descriptor.
Returns:
The FunctionDescriptor instance created according to the function.
"""
module_name = function.__module__
function_name = function.__name__
class_name = ""
pickled_function_hash = hashlib.sha1(pickled_function).hexdigest()
return cls(module_name, function_name, class_name,
pickled_function_hash)
@classmethod
def from_class(cls, target_class):
"""Create a FunctionDescriptor from a class.
Args:
cls: Current class which is required argument for classmethod.
target_class: the python class used to create the function
descriptor.
Returns:
The FunctionDescriptor instance created according to the class.
"""
module_name = target_class.__module__
class_name = target_class.__name__
return cls(module_name, "__init__", class_name)
@property
def module_name(self):
"""Get the module name of current function descriptor.
Returns:
The module name of the function descriptor.
"""
return <str>self.typed_descriptor.ModuleName()
@property
def class_name(self):
"""Get the class name of current function descriptor.
Returns:
The class name of the function descriptor. It could be
empty if the function is not a class method.
"""
return <str>self.typed_descriptor.ClassName()
@property
def function_name(self):
"""Get the function name of current function descriptor.
Returns:
The function name of the function descriptor.
"""
return <str>self.typed_descriptor.FunctionName()
@property
def function_hash(self):
"""Get the hash string of the function source code.
Returns:
The hex of function hash if the source code is available.
Otherwise, it will be an empty string.
"""
return <str>self.typed_descriptor.FunctionHash()
@property
def function_id(self):
"""Get the function id calculated from this descriptor.
Returns:
The value of ray.ObjectID that represents the function id.
"""
if not self._function_id:
self._function_id = self._get_function_id()
return self._function_id
def _get_function_id(self):
"""Calculate the function id of current function descriptor.
This function id is calculated from all the fields of function
descriptor.
Returns:
ray.ObjectID to represent the function descriptor.
"""
function_id_hash = hashlib.sha1()
# Include the function module and name in the hash.
function_id_hash.update(self.typed_descriptor.ModuleName())
function_id_hash.update(self.typed_descriptor.FunctionName())
function_id_hash.update(self.typed_descriptor.ClassName())
function_id_hash.update(self.typed_descriptor.FunctionHash())
# Compute the function ID.
function_id = function_id_hash.digest()
return ray.FunctionID(function_id)
def is_actor_method(self):
"""Wether this function descriptor is an actor method.
Returns:
True if it's an actor method, False if it's a normal function.
"""
return not self.typed_descriptor.ClassName().empty()
+4 -1
View File
@@ -15,6 +15,9 @@ from ray.includes.unique_ids cimport (
CObjectID,
CTaskID,
)
from ray.includes.function_descriptor cimport (
CFunctionDescriptor,
)
cdef extern from "ray/protobuf/common.pb.h" nogil:
cdef cppclass RpcTaskSpec "ray::rpc::TaskSpec":
@@ -44,7 +47,7 @@ cdef extern from "ray/common/task/task_spec.h" nogil:
CJobID JobId() const
CTaskID ParentTaskId() const
uint64_t ParentCounter() const
c_vector[c_string] FunctionDescriptor() const
CFunctionDescriptor FunctionDescriptor() const
c_string FunctionDescriptorString() const
uint64_t NumArgs() const
uint64_t NumReturns() const
+2 -6
View File
@@ -64,14 +64,10 @@ cdef class TaskSpec:
"""Return the parent counter of this task."""
return self.task_spec.get().ParentCounter()
def function_descriptor_list(self):
def function_descriptor(self):
"""Return the function descriptor for this task."""
cdef c_vector[c_string] function_descriptor = (
return CFunctionDescriptorToPython(
self.task_spec.get().FunctionDescriptor())
results = []
for i in range(function_descriptor.size()):
results.append(function_descriptor[i])
return results
def arguments(self):
"""Return the arguments for the task."""
+22 -12
View File
@@ -3,7 +3,8 @@ from functools import wraps
from ray import cloudpickle as pickle
from ray import ray_constants
from ray.function_manager import FunctionDescriptor
from ray._raylet import PythonFunctionDescriptor
from ray import cross_language, Language
import ray.signature
# Default parameters for remote functions.
@@ -23,6 +24,7 @@ class RemoteFunction:
This is a decorated function. It can be used to spawn tasks.
Attributes:
_language: The target language.
_function: The original function.
_function_descriptor: The function descriptor. This is not defined
until the remote function is first invoked because that is when the
@@ -57,12 +59,15 @@ class RemoteFunction:
different workers.
"""
def __init__(self, function, num_cpus, num_gpus, memory,
object_store_memory, resources, num_return_vals, max_calls,
max_retries):
def __init__(self, language, function, function_descriptor, num_cpus,
num_gpus, memory, object_store_memory, resources,
num_return_vals, max_calls, max_retries):
self._language = language
self._function = function
self._function_name = (
self._function.__module__ + "." + self._function.__name__)
self._function_descriptor = function_descriptor
self._is_cross_language = language != Language.PYTHON
self._num_cpus = (DEFAULT_REMOTE_FUNCTION_CPUS
if num_cpus is None else num_cpus)
self._num_gpus = num_gpus
@@ -80,11 +85,11 @@ class RemoteFunction:
if max_retries is None else max_retries)
self._decorator = getattr(function, "__ray_invocation_decorator__",
None)
self._function_signature = ray.signature.extract_signature(
self._function)
self._last_export_session_and_job = None
# Override task.remote's signature and docstring
@wraps(function)
def _remote_proxy(*args, **kwargs):
@@ -152,7 +157,9 @@ class RemoteFunction:
# If this function was not exported in this session and job, we need to
# export this function again, because the current GCS doesn't have it.
if self._last_export_session_and_job != worker.current_session_and_job:
if not self._is_cross_language and \
self._last_export_session_and_job != \
worker.current_session_and_job:
# There is an interesting question here. If the remote function is
# used by a subsequent driver (in the same script), should the
# second driver pickle the function again? If yes, then the remote
@@ -164,10 +171,8 @@ class RemoteFunction:
# which we do here.
self._pickled_function = pickle.dumps(self._function)
self._function_descriptor = FunctionDescriptor.from_function(
self._function_descriptor = PythonFunctionDescriptor.from_function(
self._function, self._pickled_function)
self._function_descriptor_list = (
self._function_descriptor.get_function_descriptor_list())
self._last_export_session_and_job = worker.current_session_and_job
worker.function_actor_manager.export(self)
@@ -188,20 +193,25 @@ class RemoteFunction:
memory, object_store_memory, resources)
def invocation(args, kwargs):
if not args and not kwargs and not self._function_signature:
if self._is_cross_language:
list_args = cross_language.format_args(worker, args, kwargs)
elif not args and not kwargs and not self._function_signature:
list_args = []
else:
list_args = ray.signature.flatten_args(
self._function_signature, args, kwargs)
if worker.mode == ray.worker.LOCAL_MODE:
assert not self._is_cross_language, \
"Cross language remote function " \
"cannot be executed locally."
object_ids = worker.local_mode_manager.execute(
self._function, self._function_descriptor, args, kwargs,
num_return_vals)
else:
object_ids = worker.core_worker.submit_task(
self._function_descriptor_list, list_args, num_return_vals,
is_direct_call, resources, max_retries)
self._language, self._function_descriptor, list_args,
num_return_vals, is_direct_call, resources, max_retries)
if len(object_ids) == 1:
return object_ids[0]
+2 -9
View File
@@ -5,7 +5,6 @@ import sys
import time
import ray
from ray.function_manager import FunctionDescriptor
from ray import (
gcs_utils,
@@ -393,9 +392,7 @@ class GlobalState:
task = ray._raylet.TaskSpec.from_string(
task_table_data.task.task_spec.SerializeToString())
function_descriptor_list = task.function_descriptor_list()
function_descriptor = FunctionDescriptor.from_bytes_list(
function_descriptor_list)
function_descriptor = task.function_descriptor()
task_spec_info = {
"JobID": task.job_id().hex(),
@@ -412,11 +409,7 @@ class GlobalState:
"Args": task.arguments(),
"ReturnObjectIDs": task.returns(),
"RequiredResources": task.required_resources(),
"FunctionID": function_descriptor.function_id.hex(),
"FunctionHash": binary_to_hex(function_descriptor.function_hash),
"ModuleName": function_descriptor.module_name,
"ClassName": function_descriptor.class_name,
"FunctionName": function_descriptor.function_name,
"FunctionDescriptor": function_descriptor.to_dict(),
}
execution_spec = ray._raylet.TaskExecutionSpec.from_string(
+2 -3
View File
@@ -151,14 +151,13 @@ def test_global_state_api(shutdown_only):
assert len(task_table) == 1
assert driver_task_id == list(task_table.keys())[0]
task_spec = task_table[driver_task_id]["TaskSpec"]
nil_unique_id_hex = ray.UniqueID.nil().hex()
nil_actor_id_hex = ray.ActorID.nil().hex()
assert task_spec["TaskID"] == driver_task_id
assert task_spec["ActorID"] == nil_actor_id_hex
assert task_spec["Args"] == []
assert task_spec["JobID"] == job_id.hex()
assert task_spec["FunctionID"] == nil_unique_id_hex
assert task_spec["FunctionDescriptor"]["type"] == "EmptyFunctionDescriptor"
assert task_spec["ReturnObjectIDs"] == []
client_table = ray.nodes()
@@ -172,7 +171,7 @@ def test_global_state_api(shutdown_only):
def __init__(self):
pass
_ = Actor.remote()
_ = Actor.remote() # noqa: F841
# Wait for actor to be created
wait_for_num_actors(1)
+18
View File
@@ -9,6 +9,7 @@ import string
import sys
import threading
import time
import pickle
import numpy as np
import pytest
@@ -351,6 +352,23 @@ def test_complex_serialization_with_pickle(shutdown_only):
complex_serialization(use_pickle=True)
def test_function_descriptor():
python_descriptor = ray._raylet.PythonFunctionDescriptor(
"module_name", "function_name", "class_name", "function_hash")
python_descriptor2 = pickle.loads(pickle.dumps(python_descriptor))
assert python_descriptor == python_descriptor2
assert hash(python_descriptor) == hash(python_descriptor2)
assert python_descriptor.function_id == python_descriptor2.function_id
java_descriptor = ray._raylet.JavaFunctionDescriptor(
"class_name", "function_name", "signature")
java_descriptor2 = pickle.loads(pickle.dumps(java_descriptor))
assert java_descriptor == java_descriptor2
assert python_descriptor != java_descriptor
assert python_descriptor != object()
d = {python_descriptor: 123}
assert d.get(python_descriptor2) == 123
def test_nested_functions(ray_start_regular):
# Make sure that remote functions can use other values that are defined
# after the remote function but before the first function invocation.
+15
View File
@@ -0,0 +1,15 @@
import pytest
import ray
import ray.cluster_utils
import ray.test_utils
def test_cross_language_raise_kwargs(shutdown_only):
ray.init(load_code_from_local=True, include_java=True)
with pytest.raises(Exception, match="kwargs"):
ray.java_function("a", "b").remote(x="arg1")
with pytest.raises(Exception, match="kwargs"):
ray.java_actor_class("a").remote(x="arg1")
+7 -1
View File
@@ -189,7 +189,13 @@ def test_raylet_info_endpoint(shutdown_only):
try:
webui_url = addresses["webui_url"]
webui_url = webui_url.replace("localhost", "http://127.0.0.1")
raylet_info = requests.get(webui_url + "/api/raylet_info").json()
response = requests.get(webui_url + "/api/raylet_info")
response.raise_for_status()
try:
raylet_info = response.json()
except Exception as ex:
print("failed response: {}".format(response.text))
raise ex
actor_info = raylet_info["result"]["actors"]
try:
assert len(actor_info) == 1
+7 -3
View File
@@ -36,6 +36,7 @@ from ray import (
ActorID,
JobID,
ObjectID,
Language,
)
from ray import import_thread
from ray import profiling
@@ -553,6 +554,7 @@ def init(address=None,
redis_password=ray_constants.REDIS_DEFAULT_PASSWORD,
plasma_directory=None,
huge_pages=False,
include_java=False,
include_webui=None,
webui_host="localhost",
job_id=None,
@@ -632,6 +634,7 @@ def init(address=None,
be created.
huge_pages: Boolean flag indicating whether to start the Object
Store with hugetlbfs support. Requires plasma_directory.
include_java: Boolean flag indicating whether to enable java worker.
include_webui: Boolean flag indicating whether to start the web
UI, which displays the status of the Ray cluster. If this argument
is None, then the UI will be started if the relevant dependencies
@@ -725,6 +728,7 @@ def init(address=None,
redis_password=redis_password,
plasma_directory=plasma_directory,
huge_pages=huge_pages,
include_java=include_java,
include_webui=include_webui,
webui_host=webui_host,
memory=memory,
@@ -1684,9 +1688,9 @@ def make_decorator(num_return_vals=None,
"allowed for remote functions.")
return ray.remote_function.RemoteFunction(
function_or_class, num_cpus, num_gpus, memory,
object_store_memory, resources, num_return_vals, max_calls,
max_retries)
Language.PYTHON, function_or_class, None, num_cpus, num_gpus,
memory, object_store_memory, resources, num_return_vals,
max_calls, max_retries)
if inspect.isclass(function_or_class):
if num_return_vals is not None: