def compute_actor_handle_id_non_forked(actor_handle_id, current_task_id): """Deterministically compute an actor handle ID in the non-forked case. This code path is used whenever an actor handle is pickled and unpickled (for example, if a remote function closes over an actor handle). Then, whenever the actor handle is used, a new actor handle ID will be generated on the fly as a deterministic function of the actor ID, the previous actor handle ID and the current task ID. TODO(rkn): It may be possible to cause problems by closing over multiple actor handles in a remote function, which then get unpickled and give rise to the same actor handle IDs. Args: actor_handle_id: The original actor handle ID. current_task_id: The ID of the task that is unpickling the handle. Returns: An ID for the new actor handle. """ assert isinstance(actor_handle_id, ActorHandleID) assert isinstance(current_task_id, TaskID) handle_id_hash = hashlib.sha1() handle_id_hash.update(actor_handle_id.binary()) handle_id_hash.update(current_task_id.binary()) handle_id = handle_id_hash.digest() return ActorHandleID(handle_id)
def _serialization_helper(self, ray_forking): """This is defined in order to make pickling work. Args: ray_forking: True if this is being called because Ray is forking the actor handle and false if it is being called by pickling. Returns: A dictionary of the information needed to reconstruct the object. """ if ray_forking: actor_handle_id = compute_actor_handle_id( self._ray_actor_handle_id, self._ray_actor_forks) else: actor_handle_id = self._ray_actor_handle_id # Note: _ray_actor_cursor and _ray_actor_creation_dummy_object_id # could be None. state = { "actor_id": self._ray_actor_id, "actor_handle_id": actor_handle_id, "module_name": self._ray_module_name, "class_name": self._ray_class_name, "actor_cursor": self._ray_actor_cursor, "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, # Actors in local mode don't have dummy objects. "actor_creation_dummy_object_id": self. _ray_actor_creation_dummy_object_id, "actor_method_cpus": self._ray_actor_method_cpus, "actor_job_id": self._ray_actor_job_id, "ray_forking": ray_forking } if ray_forking: self._ray_actor_forks += 1 new_actor_handle_id = actor_handle_id else: # The execution dependency for a pickled actor handle is never safe # to release, since it could be unpickled and submit another # dependent task at any time. Therefore, we notify the backend of a # random handle ID that will never actually be used. new_actor_handle_id = ActorHandleID.from_random() # Notify the backend to expect this new actor handle. The backend will # not release the cursor for any new handles until the first task for # each of the new handles is submitted. # NOTE(swang): There is currently no garbage collection for actor # handles until the actor itself is removed. self._ray_new_actor_handles.append(new_actor_handle_id) return state
def compute_actor_handle_id(actor_handle_id, num_forks): """Deterministically compute an actor handle ID. A new actor handle ID is generated when it is forked from another actor handle. The new handle ID is computed as hash(old_handle_id || num_forks). Args: actor_handle_id (common.ObjectID): The original actor handle ID. num_forks: The number of times the original actor handle has been forked so far. Returns: An ID for the new actor handle. """ assert isinstance(actor_handle_id, ActorHandleID) handle_id_hash = hashlib.sha1() handle_id_hash.update(actor_handle_id.binary()) handle_id_hash.update(str(num_forks).encode("ascii")) handle_id = handle_id_hash.digest() return ActorHandleID(handle_id)
def __init__(self, actor_id, module_name, class_name, actor_cursor, actor_method_names, method_decorators, method_signatures, method_num_return_vals, actor_creation_dummy_object_id, actor_method_cpus, actor_job_id, session_and_job, actor_handle_id=None): assert isinstance(actor_id, ActorID) assert isinstance(actor_job_id, ray.JobID) self._ray_actor_id = actor_id self._ray_module_name = module_name # False if this actor handle was created by forking or pickling. True # if it was created by the _serialization_helper function. self._ray_original_handle = actor_handle_id is None if self._ray_original_handle: self._ray_actor_handle_id = ActorHandleID.nil() else: assert isinstance(actor_handle_id, ActorHandleID) self._ray_actor_handle_id = actor_handle_id self._ray_actor_cursor = actor_cursor self._ray_actor_counter = 0 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_forks = 0 self._ray_actor_creation_dummy_object_id = ( actor_creation_dummy_object_id) self._ray_actor_method_cpus = actor_method_cpus self._ray_actor_job_id = actor_job_id self._ray_session_and_job = session_and_job self._ray_new_actor_handles = [] self._ray_actor_lock = threading.Lock()
def __init__(self, actor_id, module_name, class_name, actor_cursor, actor_method_names, method_signatures, method_num_return_vals, actor_creation_dummy_object_id, actor_method_cpus, actor_driver_id, actor_handle_id=None): assert isinstance(actor_id, ActorID) assert isinstance(actor_driver_id, DriverID) self._ray_actor_id = actor_id self._ray_module_name = module_name # False if this actor handle was created by forking or pickling. True # if it was created by the _serialization_helper function. self._ray_original_handle = actor_handle_id is None if self._ray_original_handle: self._ray_actor_handle_id = ActorHandleID.nil() else: assert isinstance(actor_handle_id, ActorHandleID) self._ray_actor_handle_id = actor_handle_id self._ray_actor_cursor = actor_cursor self._ray_actor_counter = 0 self._ray_actor_method_names = actor_method_names 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_forks = 0 self._ray_actor_creation_dummy_object_id = ( actor_creation_dummy_object_id) self._ray_actor_method_cpus = actor_method_cpus self._ray_actor_driver_id = actor_driver_id self._ray_new_actor_handles = [] self._ray_actor_lock = threading.Lock()
def _remote(self, args=None, kwargs=None, num_cpus=None, num_gpus=None, memory=None, object_store_memory=None, resources=None): """Create an actor. This method allows more flexibility than the remote method because resource requirements can be specified and override the defaults in the decorator. Args: args: The arguments to forward to the actor constructor. kwargs: The keyword arguments to forward to the actor constructor. num_cpus: The number of CPUs required by the actor creation task. num_gpus: The number of GPUs required by the actor creation task. memory: Restrict the heap memory usage of this actor. object_store_memory: Restrict the object store memory used by this actor when creating objects. resources: The custom resources required by the actor creation task. Returns: A handle to the newly created actor. """ if args is None: args = [] if kwargs is None: kwargs = {} worker = ray.worker.get_global_worker() if worker.mode is None: raise Exception("Actors cannot be created before ray.init() " "has been called.") meta = self.__ray_metadata__ # Set the actor's default resources if not already set. First three # conditions are to check that no resources were specified in the # decorator. Last three conditions are to check that no resources were # specified when _remote() was called. if (meta.num_cpus is None and meta.num_gpus is None and meta.resources is None and num_cpus is None and num_gpus is None and resources is None): # In the default case, actors acquire no resources for # their lifetime, and actor methods will require 1 CPU. cpus_to_use = ray_constants.DEFAULT_ACTOR_CREATION_CPU_SIMPLE actor_method_cpu = ray_constants.DEFAULT_ACTOR_METHOD_CPU_SIMPLE else: # If any resources are specified (here or in decorator), then # all resources are acquired for the actor's lifetime and no # resources are associated with methods. cpus_to_use = (ray_constants.DEFAULT_ACTOR_CREATION_CPU_SPECIFIED 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: actor_id = ActorID.of(worker.current_job_id, worker.current_task_id, worker.task_context.task_index + 1) worker.actors[actor_id] = meta.modified_class( *copy.deepcopy(args), **copy.deepcopy(kwargs)) core_handle = ray._raylet.ActorHandle( actor_id, ActorHandleID.nil(), worker.current_job_id, function_descriptor.get_function_descriptor_list()) else: # Export the actor. if (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) worker.function_actor_manager.export_actor_class( meta.modified_class, meta.actor_method_names) resources = ray.utils.resources_from_resource_arguments( cpus_to_use, meta.num_gpus, meta.memory, meta.object_store_memory, meta.resources, num_cpus, num_gpus, memory, object_store_memory, resources) # If the actor methods require CPU resources, then set the required # placement resources. If actor_placement_resources is empty, then # the required placement resources will be the same as resources. actor_placement_resources = {} assert actor_method_cpu in [0, 1] 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.extend_args(function_signature, args, kwargs) core_handle = worker.core_worker.create_actor( function_descriptor.get_function_descriptor_list(), creation_args, meta.max_reconstructions, resources, actor_placement_resources) actor_handle = ActorHandle(core_handle, 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, worker.current_session_and_job, original_handle=True) return actor_handle