Files
2026-07-13 13:17:40 +08:00

907 lines
35 KiB
Python

import logging
import random
import time
import uuid
from collections import Counter, defaultdict
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, Union
import ray
from ray.air.execution._internal.event_manager import RayEventManager
from ray.air.execution._internal.tracked_actor import TrackedActor
from ray.air.execution._internal.tracked_actor_task import TrackedActorTask
from ray.air.execution.resources import (
AcquiredResources,
ResourceManager,
ResourceRequest,
)
from ray.exceptions import RayActorError, RayTaskError
logger = logging.getLogger(__name__)
class RayActorManager:
"""Management class for Ray actors and actor tasks.
This class provides an event-based management interface for actors, and
actor tasks.
The manager can be used to start actors, stop actors, and schedule and
track task futures on these actors.
The manager will then invoke callbacks related to the tracked entities.
For instance, when an actor is added with
:meth:`add_actor() <RayActorManager.add_actor>`,
a :ref:`TrackedActor <ray.air.execution._internal.tracked_actor.TrackedActor`
object is returned. An ``on_start`` callback can be specified that is invoked
once the actor successfully started. Similarly, ``on_stop`` and ``on_error``
can be used to specify callbacks relating to the graceful or ungraceful
end of an actor's lifetime.
When scheduling an actor task using
:meth:`schedule_actor_task()
<ray.air.execution._internal.actor_manager.RayActorManager.schedule_actor_task>`,
an ``on_result`` callback can be specified that is invoked when the task
successfully resolves, and an ``on_error`` callback will resolve when the
task fails.
The RayActorManager does not implement any true asynchronous processing. Control
has to be explicitly yielded to the event manager via :meth:`RayActorManager.next`.
Callbacks will only be invoked when control is with the RayActorManager, and
callbacks will always be executed sequentially in order of arriving events.
Args:
resource_manager: Resource manager used to request resources for the actors.
Example:
.. code-block:: python
from ray.air.execution import ResourceRequest
from ray.air.execution._internal import RayActorManager
actor_manager = RayActorManager()
# Request an actor
tracked_actor = actor_manager.add_actor(
ActorClass,
kwargs={},
resource_request=ResourceRequest([{"CPU": 1}]),
on_start=actor_start_callback,
on_stop=actor_stop_callback,
on_error=actor_error_callback
)
# Yield control to event manager to start actor
actor_manager.next()
# Start task on the actor (ActorClass.foo.remote())
tracked_actor_task = actor_manager.schedule_actor_task(
tracked_actor,
method_name="foo",
on_result=task_result_callback,
on_error=task_error_callback
)
# Again yield control to event manager to process task futures
actor_manager.wait()
"""
def __init__(self, resource_manager: ResourceManager):
self._resource_manager: ResourceManager = resource_manager
self._actor_state_events = RayEventManager()
self._actor_task_events = RayEventManager()
# ---
# Tracked actor futures.
# This maps TrackedActor objects to their futures. We use this to see if an
# actor has any futures scheduled and to remove them when we terminate an actor.
# Actors to actor task futures
self._tracked_actors_to_task_futures: Dict[
TrackedActor, Set[ray.ObjectRef]
] = defaultdict(set)
# Actors to actor state futures (start/terminate)
self._tracked_actors_to_state_futures: Dict[
TrackedActor, Set[ray.ObjectRef]
] = defaultdict(set)
# ---
# Pending actors.
# We use three dicts for actors that are requested but not yet started.
# This dict keeps a list of actors associated with each resource request.
# We use this to start actors in the correct order when their resources
# become available.
self._resource_request_to_pending_actors: Dict[
ResourceRequest, List[TrackedActor]
] = defaultdict(list)
# This dict stores the actor class, kwargs, and resource request of
# pending actors. Once the resources are available, we start the remote
# actor class with its args. We need the resource request to cancel it
# if needed.
self._pending_actors_to_attrs: Dict[
TrackedActor, Tuple[Type, Dict[str, Any], ResourceRequest]
] = {}
# This dict keeps track of cached actor tasks. We can't schedule actor
# tasks before the actor is actually scheduled/live. So when the caller
# tries to schedule a task, we cache it here, and schedule it once the
# actor is started.
self._pending_actors_to_enqueued_actor_tasks: Dict[
TrackedActor, List[Tuple[TrackedActorTask, str, Tuple[Any], Dict[str, Any]]]
] = defaultdict(list)
# ---
# Live actors.
# We keep one dict for actors that are currently running and a set of
# actors that we should forcefully kill.
# This dict associates the TrackedActor object with the Ray actor handle
# and the resources associated to the actor. We use it to schedule the
# actual ray tasks, and to return the resources when the actor stopped.
self._live_actors_to_ray_actors_resources: Dict[
TrackedActor, Tuple[ray.actor.ActorHandle, AcquiredResources]
] = {}
self._live_resource_cache: Optional[Dict[str, Any]] = None
# This dict contains all actors that should be killed (after calling
# `remove_actor()`). Kill requests will be handled in wait().
self._live_actors_to_kill: Set[TrackedActor] = set()
# Track failed actors
self._failed_actor_ids: Set[int] = set()
def next(self, timeout: Optional[Union[int, float]] = None) -> bool:
"""Yield control to event manager to await the next event and invoke callbacks.
Calling this method will wait for up to ``timeout`` seconds for the next
event to arrive.
When events arrive, callbacks relating to the events will be
invoked. A timeout of ``None`` will block until the next event arrives.
Note:
If an actor task fails with a ``RayActorError``, this is one event,
but it may trigger _two_ `on_error` callbacks: One for the actor,
and one for the task.
Note:
The ``timeout`` argument is used for pure waiting time for events. It does
not include time spent on processing callbacks. Depending on the processing
time of the callbacks, it can take much longer for this function to
return than the specified timeout.
Args:
timeout: Timeout in seconds to wait for next event.
Returns:
True if at least one event was processed.
"""
# First issue any pending forceful actor kills
actor_killed = self._try_kill_actor()
# We always try to start actors as this won't trigger an event callback
self._try_start_actors()
# If an actor was killed, this was our event, and we return.
if actor_killed:
return True
# Otherwise, collect all futures and await the next.
resource_futures = self._resource_manager.get_resource_futures()
actor_state_futures = self._actor_state_events.get_futures()
actor_task_futures = self._actor_task_events.get_futures()
# Shuffle state futures
shuffled_state_futures = list(actor_state_futures)
random.shuffle(shuffled_state_futures)
# Shuffle task futures
shuffled_task_futures = list(actor_task_futures)
random.shuffle(shuffled_task_futures)
# Prioritize resource futures over actor state over task futures
all_futures = resource_futures + shuffled_state_futures + shuffled_task_futures
start_wait = time.monotonic()
ready, _ = ray.wait(all_futures, num_returns=1, timeout=timeout)
if not ready:
return False
[future] = ready
if future in actor_state_futures:
self._actor_state_events.resolve_future(future)
elif future in actor_task_futures:
self._actor_task_events.resolve_future(future)
else:
self._handle_ready_resource_future()
# Ready resource futures don't count as one event as they don't trigger
# any callbacks. So we repeat until we hit anything that is not a resource
# future.
time_taken = time.monotonic() - start_wait
return self.next(
timeout=max(1e-9, timeout - time_taken) if timeout is not None else None
)
self._try_start_actors()
return True
def _actor_start_resolved(self, tracked_actor: TrackedActor, future: ray.ObjectRef):
"""Callback to be invoked when actor started"""
self._tracked_actors_to_state_futures[tracked_actor].remove(future)
if tracked_actor._on_start:
tracked_actor._on_start(tracked_actor)
def _actor_stop_resolved(self, tracked_actor: TrackedActor):
"""Callback to be invoked when actor stopped"""
self._cleanup_actor(tracked_actor=tracked_actor)
if tracked_actor._on_stop:
tracked_actor._on_stop(tracked_actor)
def _actor_start_failed(self, tracked_actor: TrackedActor, exception: Exception):
"""Callback to be invoked when actor start/stop failed"""
self._failed_actor_ids.add(tracked_actor.actor_id)
self._cleanup_actor(tracked_actor=tracked_actor)
if tracked_actor._on_error:
tracked_actor._on_error(tracked_actor, exception)
def _actor_task_failed(
self, tracked_actor_task: TrackedActorTask, exception: Exception
):
"""Handle an actor task future that became ready.
- On actor error, trigger actor error callback AND error task error callback
- On task error, trigger actor task error callback
- On success, trigger actor task result callback
"""
tracked_actor = tracked_actor_task._tracked_actor
if isinstance(exception, RayActorError):
self._failed_actor_ids.add(tracked_actor.actor_id)
# Clean up any references to the actor and its futures
self._cleanup_actor(tracked_actor=tracked_actor)
# Handle actor state callbacks
if tracked_actor._on_error:
tracked_actor._on_error(tracked_actor, exception)
# Then trigger actor task error callback
if tracked_actor_task._on_error:
tracked_actor_task._on_error(tracked_actor, exception)
elif isinstance(exception, RayTaskError):
# Otherwise only the task failed. Invoke callback
if tracked_actor_task._on_error:
tracked_actor_task._on_error(tracked_actor, exception)
else:
raise RuntimeError(
f"Caught unexpected exception: {exception}"
) from exception
def _actor_task_resolved(self, tracked_actor_task: TrackedActorTask, result: Any):
tracked_actor = tracked_actor_task._tracked_actor
# Trigger actor task result callback
if tracked_actor_task._on_result:
tracked_actor_task._on_result(tracked_actor, result)
def _handle_ready_resource_future(self):
"""Handle a resource future that became ready.
- Update state of the resource manager
- Try to start one actor
"""
# Force resource manager to update internal state
self._resource_manager.update_state()
# We handle resource futures one by one, so only try to start 1 actor at a time
self._try_start_actors(max_actors=1)
def _try_start_actors(self, max_actors: Optional[int] = None) -> int:
"""Try to start up to ``max_actors`` actors.
This function will iterate through all resource requests we collected for
pending actors. As long as a resource request can be fulfilled (resources
are available), we try to start as many actors as possible.
This will schedule a `Actor.__ray_ready__()` future which, once resolved,
will trigger the `TrackedActor.on_start` callback.
"""
started_actors = 0
# Iterate through all resource requests
for resource_request in self._resource_request_to_pending_actors:
if max_actors is not None and started_actors >= max_actors:
break
# While we have resources ready and there are actors left to schedule
while (
self._resource_manager.has_resources_ready(resource_request)
and self._resource_request_to_pending_actors[resource_request]
):
# Acquire resources for actor
acquired_resources = self._resource_manager.acquire_resources(
resource_request
)
assert acquired_resources
# Get tracked actor to start
candidate_actors = self._resource_request_to_pending_actors[
resource_request
]
assert candidate_actors
tracked_actor = candidate_actors.pop(0)
# Get actor class and arguments
actor_cls, kwargs, _ = self._pending_actors_to_attrs.pop(tracked_actor)
if not isinstance(actor_cls, ray.actor.ActorClass):
actor_cls = ray.remote(actor_cls)
# Associate to acquired resources
[remote_actor_cls] = acquired_resources.annotate_remote_entities(
[actor_cls]
)
# Start Ray actor
actor = remote_actor_cls.remote(**kwargs)
# Track
self._live_actors_to_ray_actors_resources[tracked_actor] = (
actor,
acquired_resources,
)
self._live_resource_cache = None
# Schedule ready future
future = actor.__ray_ready__.remote()
self._tracked_actors_to_state_futures[tracked_actor].add(future)
# We need to create the callbacks in a function so tracked_actors
# are captured correctly.
def create_callbacks(
tracked_actor: TrackedActor, future: ray.ObjectRef
):
def on_actor_start(result: Any):
self._actor_start_resolved(
tracked_actor=tracked_actor, future=future
)
def on_error(exception: Exception):
self._actor_start_failed(
tracked_actor=tracked_actor, exception=exception
)
return on_actor_start, on_error
on_actor_start, on_error = create_callbacks(
tracked_actor=tracked_actor, future=future
)
self._actor_state_events.track_future(
future=future,
on_result=on_actor_start,
on_error=on_error,
)
self._enqueue_cached_actor_tasks(tracked_actor=tracked_actor)
started_actors += 1
return started_actors
def _enqueue_cached_actor_tasks(self, tracked_actor: TrackedActor):
assert tracked_actor in self._live_actors_to_ray_actors_resources
# Enqueue cached futures
cached_tasks = self._pending_actors_to_enqueued_actor_tasks.pop(
tracked_actor, []
)
for tracked_actor_task, method_name, args, kwargs in cached_tasks:
self._schedule_tracked_actor_task(
tracked_actor_task=tracked_actor_task,
method_name=method_name,
args=args,
kwargs=kwargs,
)
def _try_kill_actor(self) -> bool:
"""Try to kill actor scheduled for termination."""
if not self._live_actors_to_kill:
return False
tracked_actor = self._live_actors_to_kill.pop()
# Remove from tracked actors
(
ray_actor,
acquired_resources,
) = self._live_actors_to_ray_actors_resources[tracked_actor]
# Hard kill if requested
ray.kill(ray_actor)
self._cleanup_actor_futures(tracked_actor)
self._actor_stop_resolved(tracked_actor)
return True
def _cleanup_actor(self, tracked_actor: TrackedActor):
self._cleanup_actor_futures(tracked_actor)
# Remove from tracked actors
(
ray_actor,
acquired_resources,
) = self._live_actors_to_ray_actors_resources.pop(tracked_actor)
self._live_resource_cache = None
# Return resources
self._resource_manager.free_resources(acquired_resource=acquired_resources)
@property
def all_actors(self) -> List[TrackedActor]:
"""Return all ``TrackedActor`` objects managed by this manager instance."""
return self.live_actors + self.pending_actors
@property
def live_actors(self) -> List[TrackedActor]:
"""Return all ``TrackedActor`` objects that are currently alive."""
return list(self._live_actors_to_ray_actors_resources)
@property
def pending_actors(self) -> List[TrackedActor]:
"""Return all ``TrackedActor`` objects that are currently pending."""
return list(self._pending_actors_to_attrs)
@property
def num_live_actors(self):
"""Return number of started actors."""
return len(self.live_actors)
@property
def num_pending_actors(self) -> int:
"""Return number of pending (not yet started) actors."""
return len(self.pending_actors)
@property
def num_total_actors(self):
"""Return number of total actors."""
return len(self.all_actors)
@property
def num_actor_tasks(self):
"""Return number of pending tasks"""
return self._actor_task_events.num_futures
def get_live_actors_resources(self):
if self._live_resource_cache:
return self._live_resource_cache
counter = Counter()
for _, acq in self._live_actors_to_ray_actors_resources.values():
for bdl in acq.resource_request.bundles:
counter.update(bdl)
self._live_resource_cache = dict(counter)
return self._live_resource_cache
def add_actor(
self,
cls: Union[Type, ray.actor.ActorClass],
kwargs: Dict[str, Any],
resource_request: ResourceRequest,
*,
on_start: Optional[Callable[[TrackedActor], None]] = None,
on_stop: Optional[Callable[[TrackedActor], None]] = None,
on_error: Optional[Callable[[TrackedActor, Exception], None]] = None,
) -> TrackedActor:
"""Add an actor to be tracked.
This method will request resources to start the actor. Once the resources
are available, the actor will be started and the
:meth:`TrackedActor.on_start
<ray.air.execution._internal.tracked_actor.TrackedActor.on_start>` callback
will be invoked.
Args:
cls: Actor class to schedule.
kwargs: Keyword arguments to pass to actor class on construction.
resource_request: Resources required to start the actor.
on_start: Callback to invoke when the actor started.
on_stop: Callback to invoke when the actor stopped.
on_error: Callback to invoke when the actor failed.
Returns:
Tracked actor object to reference actor in subsequent API calls.
"""
tracked_actor = TrackedActor(
uuid.uuid4().int, on_start=on_start, on_stop=on_stop, on_error=on_error
)
self._pending_actors_to_attrs[tracked_actor] = cls, kwargs, resource_request
self._resource_request_to_pending_actors[resource_request].append(tracked_actor)
self._resource_manager.request_resources(resource_request=resource_request)
return tracked_actor
def remove_actor(
self,
tracked_actor: TrackedActor,
kill: bool = False,
stop_future: Optional[ray.ObjectRef] = None,
) -> bool:
"""Remove a tracked actor.
If the actor has already been started, this will stop the actor. This will
trigger the :meth:`TrackedActor.on_stop
<ray.air.execution._internal.tracked_actor.TrackedActor.on_stop>`
callback once the actor stopped.
If the actor has only been requested, but not started, yet, this will cancel
the actor request. This will not trigger any callback.
If ``kill=True``, this will use ``ray.kill()`` to forcefully terminate the
actor. Otherwise, graceful actor deconstruction will be scheduled after
all currently tracked futures are resolved.
This method returns a boolean, indicating if a stop future is tracked and
the ``on_stop`` callback will be invoked. If the actor has been alive,
this will be ``True``. If the actor hasn't been scheduled, yet, or failed
(and triggered the ``on_error`` callback), this will be ``False``.
Args:
tracked_actor: Tracked actor to be removed.
kill: If set, will forcefully terminate the actor instead of gracefully
scheduling termination.
stop_future: If set, use this future to track actor termination.
Otherwise, schedule a ``__ray_terminate__`` future.
Returns:
Boolean indicating if the actor was previously alive, and thus whether
a callback will be invoked once it is terminated.
"""
if tracked_actor.actor_id in self._failed_actor_ids:
logger.debug(
f"Tracked actor already failed, no need to remove: {tracked_actor}"
)
return False
elif tracked_actor in self._live_actors_to_ray_actors_resources:
# Ray actor is running.
if not kill:
# Schedule __ray_terminate__ future
ray_actor, _ = self._live_actors_to_ray_actors_resources[tracked_actor]
# Clear state futures here to avoid resolving __ray_ready__ futures
for future in list(
self._tracked_actors_to_state_futures[tracked_actor]
):
self._actor_state_events.discard_future(future)
self._tracked_actors_to_state_futures[tracked_actor].remove(future)
# If the __ray_ready__ future hasn't resolved yet, but we already
# scheduled the actor via Actor.remote(), we just want to stop
# it but not trigger any callbacks. This is in accordance with
# the contract defined in the docstring.
tracked_actor._on_start = None
tracked_actor._on_stop = None
tracked_actor._on_error = None
def on_actor_stop(*args, **kwargs):
self._actor_stop_resolved(tracked_actor=tracked_actor)
if stop_future:
# If the stop future was schedule via the actor manager,
# discard (track it as state future instead).
self._actor_task_events.discard_future(stop_future)
else:
stop_future = ray_actor.__ray_terminate__.remote()
self._actor_state_events.track_future(
future=stop_future,
on_result=on_actor_stop,
on_error=on_actor_stop,
)
self._tracked_actors_to_state_futures[tracked_actor].add(stop_future)
else:
# kill = True
self._live_actors_to_kill.add(tracked_actor)
return True
elif tracked_actor in self._pending_actors_to_attrs:
# Actor is pending, stop
_, _, resource_request = self._pending_actors_to_attrs.pop(tracked_actor)
self._resource_request_to_pending_actors[resource_request].remove(
tracked_actor
)
self._resource_manager.cancel_resource_request(
resource_request=resource_request
)
return False
else:
raise ValueError(f"Unknown tracked actor: {tracked_actor}")
def is_actor_started(self, tracked_actor: TrackedActor) -> bool:
"""Returns True if the actor has been started.
Args:
tracked_actor: Tracked actor object.
Returns:
True if the actor has been started, False otherwise.
"""
return (
tracked_actor in self._live_actors_to_ray_actors_resources
and tracked_actor.actor_id not in self._failed_actor_ids
)
def is_actor_failed(self, tracked_actor: TrackedActor) -> bool:
return tracked_actor.actor_id in self._failed_actor_ids
def get_actor_resources(
self, tracked_actor: TrackedActor
) -> Optional[AcquiredResources]:
"""Returns the acquired resources of an actor that has been started.
This will return ``None`` if the actor has not been started, yet.
Args:
tracked_actor: Tracked actor object.
Returns:
The acquired resources of the actor, or ``None`` if the actor has not
been started yet.
"""
if not self.is_actor_started(tracked_actor):
return None
return self._live_actors_to_ray_actors_resources[tracked_actor][1]
def schedule_actor_task(
self,
tracked_actor: TrackedActor,
method_name: str,
args: Optional[Tuple] = None,
kwargs: Optional[Dict] = None,
on_result: Optional[Callable[[TrackedActor, Any], None]] = None,
on_error: Optional[Callable[[TrackedActor, Exception], None]] = None,
_return_future: bool = False,
) -> Optional[ray.ObjectRef]:
"""Schedule and track a task on an actor.
This method will schedule a remote task ``method_name`` on the
``tracked_actor``.
This method accepts two optional callbacks that will be invoked when
their respective events are triggered.
The ``on_result`` callback is triggered when a task resolves successfully.
It should accept two arguments: The actor for which the
task resolved, and the result received from the remote call.
The ``on_error`` callback is triggered when a task fails.
It should accept two arguments: The actor for which the
task threw an error, and the exception.
Args:
tracked_actor: Actor to schedule task on.
method_name: Remote method name to invoke on the actor. If this is
e.g. ``foo``, then ``actor.foo.remote(*args, **kwargs)`` will be
scheduled.
args: Arguments to pass to the task.
kwargs: Keyword arguments to pass to the task.
on_result: Callback to invoke when the task resolves.
on_error: Callback to invoke when the task fails.
_return_future: If True, return the scheduled task's ``ObjectRef`` for
advanced callers. Defaults to False.
Raises:
ValueError: If the ``tracked_actor`` is not managed by this event manager.
Returns:
The scheduled task's ``ObjectRef`` if ``_return_future`` is True,
otherwise ``None``.
"""
args = args or tuple()
kwargs = kwargs or {}
if tracked_actor.actor_id in self._failed_actor_ids:
return
tracked_actor_task = TrackedActorTask(
tracked_actor=tracked_actor, on_result=on_result, on_error=on_error
)
if tracked_actor not in self._live_actors_to_ray_actors_resources:
# Actor is not started, yet
if tracked_actor not in self._pending_actors_to_attrs:
raise ValueError(
f"Tracked actor is not managed by this event manager: "
f"{tracked_actor}"
)
# Cache tasks for future execution
self._pending_actors_to_enqueued_actor_tasks[tracked_actor].append(
(tracked_actor_task, method_name, args, kwargs)
)
else:
res = self._schedule_tracked_actor_task(
tracked_actor_task=tracked_actor_task,
method_name=method_name,
args=args,
kwargs=kwargs,
_return_future=_return_future,
)
if _return_future:
return res[1]
def _schedule_tracked_actor_task(
self,
tracked_actor_task: TrackedActorTask,
method_name: str,
*,
args: Optional[Tuple] = None,
kwargs: Optional[Dict] = None,
_return_future: bool = False,
) -> Union[TrackedActorTask, Tuple[TrackedActorTask, ray.ObjectRef]]:
tracked_actor = tracked_actor_task._tracked_actor
ray_actor, _ = self._live_actors_to_ray_actors_resources[tracked_actor]
try:
remote_fn = getattr(ray_actor, method_name)
except AttributeError as e:
raise AttributeError(
f"Remote function `{method_name}()` does not exist for this actor."
) from e
def on_result(result: Any):
self._actor_task_resolved(
tracked_actor_task=tracked_actor_task, result=result
)
def on_error(exception: Exception):
self._actor_task_failed(
tracked_actor_task=tracked_actor_task, exception=exception
)
future = remote_fn.remote(*args, **kwargs)
self._actor_task_events.track_future(
future=future, on_result=on_result, on_error=on_error
)
self._tracked_actors_to_task_futures[tracked_actor].add(future)
if _return_future:
return tracked_actor_task, future
return tracked_actor_task
def schedule_actor_tasks(
self,
tracked_actors: List[TrackedActor],
method_name: str,
*,
args: Optional[Union[Tuple, List[Tuple]]] = None,
kwargs: Optional[Union[Dict, List[Dict]]] = None,
on_result: Optional[Callable[[TrackedActor, Any], None]] = None,
on_error: Optional[Callable[[TrackedActor, Exception], None]] = None,
) -> None:
"""Schedule and track tasks on a list of actors.
This method will schedule a remote task ``method_name`` on all
``tracked_actors``.
``args`` and ``kwargs`` can be a single tuple/dict, in which case the same
(keyword) arguments are passed to all actors. If a list is passed instead,
they are mapped to the respective actors. In that case, the list of
(keyword) arguments must be the same length as the list of actors.
This method accepts two optional callbacks that will be invoked when
their respective events are triggered.
The ``on_result`` callback is triggered when a task resolves successfully.
It should accept two arguments: The actor for which the
task resolved, and the result received from the remote call.
The ``on_error`` callback is triggered when a task fails.
It should accept two arguments: The actor for which the
task threw an error, and the exception.
Args:
tracked_actors: List of actors to schedule tasks on.
method_name: Remote actor method to invoke on the actors. If this is
e.g. ``foo``, then ``actor.foo.remote(*args, **kwargs)`` will be
scheduled on all actors.
args: Arguments to pass to the task.
kwargs: Keyword arguments to pass to the task.
on_result: Callback to invoke when the task resolves.
on_error: Callback to invoke when the task fails.
"""
if not isinstance(args, List):
args_list = [args] * len(tracked_actors)
else:
if len(tracked_actors) != len(args):
raise ValueError(
f"Length of args must be the same as tracked_actors "
f"list. Got `len(kwargs)={len(kwargs)}` and "
f"`len(tracked_actors)={len(tracked_actors)}"
)
args_list = args
if not isinstance(kwargs, List):
kwargs_list = [kwargs] * len(tracked_actors)
else:
if len(tracked_actors) != len(kwargs):
raise ValueError(
f"Length of kwargs must be the same as tracked_actors "
f"list. Got `len(args)={len(args)}` and "
f"`len(tracked_actors)={len(tracked_actors)}"
)
kwargs_list = kwargs
for tracked_actor, args, kwargs in zip(tracked_actors, args_list, kwargs_list):
self.schedule_actor_task(
tracked_actor=tracked_actor,
method_name=method_name,
args=args,
kwargs=kwargs,
on_result=on_result,
on_error=on_error,
)
def clear_actor_task_futures(self, tracked_actor: TrackedActor):
"""Discard all actor task futures from a tracked actor."""
futures = self._tracked_actors_to_task_futures.pop(tracked_actor, [])
for future in futures:
self._actor_task_events.discard_future(future)
def _cleanup_actor_futures(self, tracked_actor: TrackedActor):
# Remove all actor task futures
self.clear_actor_task_futures(tracked_actor=tracked_actor)
# Remove all actor state futures
futures = self._tracked_actors_to_state_futures.pop(tracked_actor, [])
for future in futures:
self._actor_state_events.discard_future(future)
def cleanup(self):
for (
actor,
acquired_resources,
) in self._live_actors_to_ray_actors_resources.values():
ray.kill(actor)
self._resource_manager.free_resources(acquired_resources)
for (
resource_request,
pending_actors,
) in self._resource_request_to_pending_actors.items():
for i in range(len(pending_actors)):
self._resource_manager.cancel_resource_request(resource_request)
self._resource_manager.clear()
self.__init__(resource_manager=self._resource_manager)