mirror of
https://github.com/wassname/ray.git
synced 2026-07-07 15:28:52 +08:00
add documentation and refactor cluster.py (#238)
This commit is contained in:
committed by
Philipp Moritz
parent
0487b05111
commit
80526f7777
+73
-59
@@ -43,16 +43,16 @@ def new_objstore_port():
|
||||
return 20000 + objstore_port_counter
|
||||
|
||||
def cleanup():
|
||||
"""
|
||||
"""When running in local mode, shutdown the Ray processes.
|
||||
|
||||
This method is used to shutdown processes that were started with
|
||||
services.start_ray_local(). It kills all scheduler, object store, and worker
|
||||
processes that were started by this services module. It disconnects driver
|
||||
processes but does not kill them. Users should not invoke this manually. It
|
||||
will automatically run at the end when a Python process that imports
|
||||
services exits. It is ok to run this twice in a row. Note that we manaully
|
||||
call services.cleanup() in the tests because we need to start and stop many
|
||||
clusters in the tests, but in the tests, services is only imported and only
|
||||
exits once.
|
||||
services.start_ray_local(). It kills all scheduler, object store, and worker
|
||||
processes that were started by this services module. It disconnects driver
|
||||
processes but does not kill them. This will automatically run at the end when
|
||||
a Python process that imports services exits. It is ok to run this twice in a
|
||||
row. Note that we manually call services.cleanup() in the tests because we
|
||||
need to start and stop many clusters in the tests, but in the tests, services
|
||||
is only imported and only exits once.
|
||||
"""
|
||||
global drivers
|
||||
for driver in drivers:
|
||||
@@ -84,43 +84,47 @@ def cleanup():
|
||||
atexit.register(cleanup)
|
||||
|
||||
def start_scheduler(scheduler_address, local):
|
||||
"""
|
||||
This method starts a scheduler process.
|
||||
"""This method starts a scheduler process.
|
||||
|
||||
:param scheduler_address: The ip address and port to use for the scheduler.
|
||||
:param local: True if using Ray in local mode. If local is true, then this
|
||||
process will be killed by serices.cleanup() when the Python process that
|
||||
imported services exits.
|
||||
Args:
|
||||
scheduler_address (str): The ip address and port to use for the scheduler.
|
||||
local (bool): True if using Ray in local mode. If local is true, then this
|
||||
process will be killed by serices.cleanup() when the Python process that
|
||||
imported services exits.
|
||||
"""
|
||||
p = subprocess.Popen(["scheduler", scheduler_address, "--log-file-name", config.get_log_file_path("scheduler.log")], env=_services_env)
|
||||
if local:
|
||||
all_processes.append((p, scheduler_address))
|
||||
|
||||
def start_objstore(scheduler_address, objstore_address, local):
|
||||
"""
|
||||
This method starts an object store process.
|
||||
"""This method starts an object store process.
|
||||
|
||||
:param scheduler_address: The ip address and port of the scheduler to connect to.
|
||||
:param objstore_address: The ip address and port to use for the object store.
|
||||
:param local: True if using Ray in local mode. If local is true, then this
|
||||
process will be killed by serices.cleanup() when the Python process that
|
||||
imported services exits.
|
||||
Args:
|
||||
scheduler_address (str): The ip address and port of the scheduler to connect
|
||||
to.
|
||||
objstore_address (str): The ip address and port to use for the object store.
|
||||
local (bool): True if using Ray in local mode. If local is true, then this
|
||||
process will be killed by serices.cleanup() when the Python process that
|
||||
imported services exits.
|
||||
"""
|
||||
p = subprocess.Popen(["objstore", scheduler_address, objstore_address, "--log-file-name", config.get_log_file_path("-".join(["objstore", objstore_address]) + ".log")], env=_services_env)
|
||||
if local:
|
||||
all_processes.append((p, objstore_address))
|
||||
|
||||
def start_worker(worker_path, scheduler_address, objstore_address, worker_address, local):
|
||||
"""
|
||||
This method starts a worker process.
|
||||
"""This method starts a worker process.
|
||||
|
||||
:param worker_path: The path of the source code which the worker process will run.
|
||||
:param scheduler_address: The ip address and port of the scheduler to connect to.
|
||||
:param objstore_address: The ip address and port of the object store to connect to.
|
||||
:param worker_address: The ip address and port to use for the worker.
|
||||
:param local: True if using Ray in local mode. If local is true, then this
|
||||
process will be killed by serices.cleanup() when the Python process that
|
||||
imported services exits.
|
||||
Args:
|
||||
worker_path (str): The path of the source code which the worker process will
|
||||
run.
|
||||
scheduler_address (str): The ip address and port of the scheduler to connect
|
||||
to.
|
||||
objstore_address (str): The ip address and port of the object store to
|
||||
connect to.
|
||||
worker_address (str): The ip address and port to use for the worker.
|
||||
local (bool): True if using Ray in local mode. If local is true, then this
|
||||
process will be killed by serices.cleanup() when the Python process that
|
||||
imported services exits.
|
||||
"""
|
||||
p = subprocess.Popen(["python",
|
||||
worker_path,
|
||||
@@ -131,14 +135,18 @@ def start_worker(worker_path, scheduler_address, objstore_address, worker_addres
|
||||
all_processes.append((p, worker_address))
|
||||
|
||||
def start_node(scheduler_address, node_ip_address, num_workers, worker_path=None):
|
||||
"""
|
||||
Start an object store and associated workers that will be part of a larger cluster.
|
||||
Assumes the scheduler has already been started.
|
||||
"""Start an object store and associated workers in the cluster setting.
|
||||
|
||||
:param scheduler_address: ip address and port of the scheduler (which may run on a different node)
|
||||
:param node_ip_address: ip address (without port) of the node this function is run on
|
||||
:param num_workers: the number of workers to be started on this node
|
||||
:param worker_path: path of the source code that will be run on the worker
|
||||
This starts an object store and the associated workers when Ray is being used
|
||||
in the cluster setting. This assumes the scheduler has already been started.
|
||||
|
||||
Args:
|
||||
scheduler_address (str): ip address and port of the scheduler (which may run
|
||||
on a different node)
|
||||
node_ip_address (str): ip address (without port) of the node this function
|
||||
is run on
|
||||
num_workers (int): the number of workers to be started on this node
|
||||
worker_path (str): path of the source code that will be run on the worker
|
||||
"""
|
||||
objstore_address = address(node_ip_address, new_objstore_port())
|
||||
start_objstore(scheduler_address, objstore_address, local=False)
|
||||
@@ -148,35 +156,41 @@ def start_node(scheduler_address, node_ip_address, num_workers, worker_path=None
|
||||
time.sleep(0.5)
|
||||
|
||||
def start_workers(scheduler_address, objstore_address, num_workers, worker_path):
|
||||
"""
|
||||
Start a new set of workers on this node. This assumes that the scheduler is
|
||||
already running and that the object store on this node is already running.
|
||||
The intended use case is that a developer wants to update the code running
|
||||
on the worker processes so first kills all of the workers and then runs this
|
||||
method.
|
||||
"""Start a new set of workers on this node.
|
||||
|
||||
:param scheduler_address: ip address and port of the scheduler (which may run on a different node)
|
||||
:param objstore_address: ip address and port of the object store (which runs on the same node)
|
||||
:param num_workers: the number of workers to be started on this node
|
||||
:param worker_path: path of the source code that will be run on the worker
|
||||
Start a new set of workers on this node. This assumes that the scheduler is
|
||||
already running and that the object store on this node is already running. The
|
||||
intended use case is that a developer wants to update the code running on the
|
||||
worker processes so first kills all of the workers and then runs this method.
|
||||
|
||||
Args:
|
||||
scheduler_address (str): ip address and port of the scheduler (which may run
|
||||
on a different node)
|
||||
objstore_address (str): ip address and port of the object store (which runs
|
||||
on the same node)
|
||||
num_workers (int): the number of workers to be started on this node
|
||||
worker_path (str): path of the source code that will be run on the worker
|
||||
"""
|
||||
node_ip_address = objstore_address.split(":")[0]
|
||||
for _ in range(num_workers):
|
||||
start_worker(worker_path, scheduler_address, objstore_address, address(node_ip_address, new_worker_port()), local=False)
|
||||
|
||||
def start_ray_local(num_workers=0, worker_path=None, driver_mode=ray.SCRIPT_MODE):
|
||||
"""
|
||||
This method starts Ray in local mode (as opposed to cluster mode, which is
|
||||
handled by cluster.py).
|
||||
"""Start Ray in local mode.
|
||||
|
||||
:param num_workers: The number of workers to start.
|
||||
:param worker_path: The path of the source code that will be run by the worker
|
||||
:param driver_mode: The mode for the driver, this only affects the printing of
|
||||
error messages. This should be ray.SCRIPT_MODE if the driver is being run in
|
||||
a script. It should be ray.SHELL_MODE if it is being used interactively in
|
||||
the shell. It should be ray.PYTHON_MODE to run things in a manner eqivalent
|
||||
to serial Python code. It should be ray.WORKER_MODE to surpress the printing
|
||||
of error messages.
|
||||
This method starts Ray in local mode (as opposed to cluster mode, which is
|
||||
handled by cluster.py).
|
||||
|
||||
Args:
|
||||
num_workers (int): The number of workers to start.
|
||||
worker_path (str): The path of the source code that will be run by the
|
||||
worker
|
||||
driver_mode: The mode for the driver, this only affects the printing of
|
||||
error messages. This should be ray.SCRIPT_MODE if the driver is being run
|
||||
in a script. It should be ray.SHELL_MODE if it is being used interactively
|
||||
in the shell. It should be ray.PYTHON_MODE to run things in a manner
|
||||
equivalent to serial Python code. It should be ray.WORKER_MODE to surpress
|
||||
the printing of error messages.
|
||||
"""
|
||||
start_services_local(num_objstores=1, num_workers_per_objstore=num_workers, worker_path=worker_path, driver_mode=driver_mode)
|
||||
|
||||
|
||||
+347
-74
@@ -17,38 +17,146 @@ import ray.graph
|
||||
import services
|
||||
|
||||
class RayFailedObject(object):
|
||||
"""If a task throws an exception during execution, a RayFailedObject is stored in the object store for each of the tasks outputs."""
|
||||
"""An object used internally to represent a task that threw an exception.
|
||||
|
||||
If a task throws an exception during execution, a RayFailedObject is stored in
|
||||
the object store for each of the tasks outputs. When an object is retrieved
|
||||
from the object store, the Python method that retrieved it should check to see
|
||||
if the object is a RayFailedObject and if it is then an exception should be
|
||||
thrown containing the error message.
|
||||
|
||||
Attributes
|
||||
error_message (str): The error message raised by the task that failed.
|
||||
"""
|
||||
|
||||
def __init__(self, error_message=None):
|
||||
"""Initialize a RayFailedObject.
|
||||
|
||||
Args:
|
||||
error_message (str): The error message raised by the task for which a
|
||||
RayFailedObject is being created.
|
||||
"""
|
||||
self.error_message = error_message
|
||||
|
||||
def deserialize(self, primitives):
|
||||
"""Create a RayFailedObject from a primitive object.
|
||||
|
||||
This initializes a RayFailedObject from a primitive object created by the
|
||||
serialize method. This method is required in order for Ray to serialize
|
||||
custom Python classes.
|
||||
|
||||
Note:
|
||||
This method should not be called by users.
|
||||
|
||||
Args:
|
||||
primitives (str): The object's error message.
|
||||
"""
|
||||
self.error_message = primitives
|
||||
|
||||
def serialize(self):
|
||||
"""Turn a RayFailedObject into a primitive object.
|
||||
|
||||
This method is required in order for Ray to serialize
|
||||
custom Python classes.
|
||||
|
||||
Note:
|
||||
The output of this method should only be used by the deserialize method.
|
||||
This method should not be called by users.
|
||||
|
||||
Args:
|
||||
primitives (str): The object's error message.
|
||||
|
||||
Returns:
|
||||
A primitive representation of a RayFailedObject.
|
||||
"""
|
||||
return self.error_message
|
||||
|
||||
class RayDealloc(object):
|
||||
"""An object used internally to properly implement reference counting.
|
||||
|
||||
When we call get_object with a particular object reference, we create a
|
||||
RayDealloc object with the information necessary to properly handle closing
|
||||
the relevant memory segment when the object is no longer needed by the worker.
|
||||
The RayDealloc object is stored as a field in the object returned by
|
||||
get_object so that its destructor is only called when the worker no longer has
|
||||
any references to the object.
|
||||
|
||||
Attributes
|
||||
handle (worker capsule): A Python object wrapping a C++ Worker object.
|
||||
segmentid (int): The id of the segment that contains the object that holds
|
||||
this RayDealloc object.
|
||||
"""
|
||||
|
||||
def __init__(self, handle, segmentid):
|
||||
"""Initialize a RayDealloc object.
|
||||
|
||||
Args:
|
||||
handle (worker capsule): A Python object wrapping a C++ Worker object.
|
||||
segmentid (int): The id of the segment that contains the object that holds
|
||||
this RayDealloc object.
|
||||
"""
|
||||
self.handle = handle
|
||||
self.segmentid = segmentid
|
||||
|
||||
def __del__(self):
|
||||
"""Deallocate the relevant segment to avoid a memory leak."""
|
||||
ray.lib.unmap_object(self.handle, self.segmentid)
|
||||
|
||||
class Worker(object):
|
||||
"""The methods in this class are considered unexposed to the user. The functions outside of this class are considered exposed."""
|
||||
"""A class used to define the control flow of a worker process.
|
||||
|
||||
Note:
|
||||
The methods in this class are considered unexposed to the user. The
|
||||
functions outside of this class are considered exposed.
|
||||
|
||||
Attributes:
|
||||
functions (Dict[str, Callable]): A dictionary mapping the name of a remote
|
||||
function to the remote function itself. This is the set of remote
|
||||
functions that can be executed by this worker.
|
||||
handle (worker capsule): A Python object wrapping a C++ Worker object.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize a Worker object."""
|
||||
self.functions = {}
|
||||
self.handle = None
|
||||
|
||||
def set_mode(self, mode):
|
||||
"""Set the mode of the worker.
|
||||
|
||||
The mode ray.SCRIPT_MODE should be used if this Worker is a driver that is
|
||||
being run as a Python script. It will print information about task failures.
|
||||
|
||||
The mode ray.SHELL_MODE should be used if this Worker is a driver that is
|
||||
being run interactively in a Python shell. It will print information about
|
||||
task failures and successes.
|
||||
|
||||
The mode ray.WORKER_MODE should be used if this Worker is not a driver. It
|
||||
will not print information about tasks.
|
||||
|
||||
The mode ray.PYTHON_MODE should be used if this Worker is a driver and if
|
||||
you want to run the driver in a manner equivalent to serial Python for
|
||||
debugging purposes. It will not send remote function calls to the scheduler
|
||||
and will insead execute them in a blocking fashion.
|
||||
|
||||
args:
|
||||
mode: One of ray.SCRIPT_MODE, ray.WORKER_MODE, ray.SHELL_MODE, and
|
||||
ray.PYTHON_MODE.
|
||||
"""
|
||||
self.mode = mode
|
||||
colorama.init()
|
||||
|
||||
def put_object(self, objref, value):
|
||||
"""Put `value` in the local object store with objref `objref`. This assumes that the value for `objref` has not yet been placed in the local object store."""
|
||||
"""Put value in the local object store with object reference objref.
|
||||
|
||||
This assumes that the value for objref has not yet been placed in the
|
||||
local object store.
|
||||
|
||||
Args:
|
||||
objref (ray.ObjRef): The object reference of the value to be put.
|
||||
value (serializable object): The value to put in the object store.
|
||||
"""
|
||||
if serialization.is_arrow_serializable(value):
|
||||
ray.lib.put_arrow(self.handle, objref, value)
|
||||
else:
|
||||
@@ -56,11 +164,13 @@ class Worker(object):
|
||||
ray.lib.put_object(self.handle, objref, object_capsule, contained_objrefs)
|
||||
|
||||
def get_object(self, objref):
|
||||
"""
|
||||
Return the value from the local object store for objref `objref`. This will
|
||||
block until the value for `objref` has been written to the local object store.
|
||||
"""Get the value in the local object store associated with objref.
|
||||
|
||||
WARNING: get_object can only be called on a canonical objref.
|
||||
Return the value from the local object store for objref. This will block
|
||||
until the value for objref has been written to the local object store.
|
||||
|
||||
Args:
|
||||
objref (ray.ObjRef): The object reference of the value to retrieve.
|
||||
"""
|
||||
if ray.lib.is_arrow(self.handle, objref):
|
||||
result, segmentid = ray.lib.get_arrow(self.handle, objref)
|
||||
@@ -97,27 +207,55 @@ class Worker(object):
|
||||
return result
|
||||
|
||||
def alias_objrefs(self, alias_objref, target_objref):
|
||||
"""Make `alias_objref` refer to the same object that `target_objref` refers to."""
|
||||
"""Make two object references refer to the same object."""
|
||||
ray.lib.alias_objrefs(self.handle, alias_objref, target_objref)
|
||||
|
||||
def register_function(self, function):
|
||||
"""Notify the scheduler that this worker can execute the function with name `func_name`. Store the function `function` locally."""
|
||||
"""Register a function with the scheduler.
|
||||
|
||||
Notify the scheduler that this worker can execute the function with name
|
||||
func_name. After this call, the scheduler can send tasks for executing
|
||||
the function to this worker.
|
||||
|
||||
Args:
|
||||
function (Callable): The remote function that this worker can execute.
|
||||
"""
|
||||
ray.lib.register_function(self.handle, function.func_name, len(function.return_types))
|
||||
self.functions[function.func_name] = function
|
||||
|
||||
def submit_task(self, func_name, args):
|
||||
"""Tell the scheduler to schedule the execution of the function with name `func_name` with arguments `args`. Retrieve object references for the outputs of the function from the scheduler and immediately return them."""
|
||||
"""Submit a remote task to the scheduler.
|
||||
|
||||
Tell the scheduler to schedule the execution of the function with name
|
||||
func_name with arguments args. Retrieve object references for the outputs of
|
||||
the function from the scheduler and immediately return them.
|
||||
|
||||
Args:
|
||||
func_name (str): The name of the function to be executed.
|
||||
args (List[Any]): The arguments to pass into the function. Arguments can
|
||||
be object references or they can be values. If they are values, they
|
||||
must be serializable objecs.
|
||||
"""
|
||||
task_capsule = serialization.serialize_task(self.handle, func_name, args)
|
||||
objrefs = ray.lib.submit_task(self.handle, task_capsule)
|
||||
if self.mode == ray.SHELL_MODE or self.mode == ray.SCRIPT_MODE:
|
||||
print_task_info(ray.lib.task_info(self.handle), self.mode)
|
||||
return objrefs
|
||||
|
||||
# We make `global_worker` a global variable so that there is one worker per worker process.
|
||||
global_worker = Worker()
|
||||
"""Worker: The global Worker object for this worker process.
|
||||
|
||||
We use a global Worker object to ensure that there is a single worker object
|
||||
per worker process.
|
||||
"""
|
||||
|
||||
# This is a helper method. It should not be called by users.
|
||||
def print_failed_task(task_status):
|
||||
"""Print information about failed tasks.
|
||||
|
||||
Args:
|
||||
task_status (Dict): A dictionary containing the name, operationid, and
|
||||
error message for a failed task.
|
||||
"""
|
||||
print """
|
||||
Error: Task failed
|
||||
Function Name: {}
|
||||
@@ -125,8 +263,14 @@ def print_failed_task(task_status):
|
||||
Error Message: \n{}
|
||||
""".format(task_status["function_name"], task_status["operationid"], task_status["error_message"])
|
||||
|
||||
# This is a helper method. It should not be called by users.
|
||||
def print_task_info(task_data, mode):
|
||||
"""Print information about tasks.
|
||||
|
||||
Args:
|
||||
task_data (Dict): A dictionary containing information about tasks that have
|
||||
failed, succeeded, or are still running.
|
||||
mode: The mode of the Worker object.
|
||||
"""
|
||||
num_tasks_succeeded = task_data["num_succeeded"]
|
||||
num_tasks_in_progress = len(task_data["running_tasks"])
|
||||
num_tasks_failed = len(task_data["failed_tasks"])
|
||||
@@ -146,26 +290,26 @@ def print_task_info(task_data, mode):
|
||||
print ", ".join(info_strings)
|
||||
|
||||
def scheduler_info(worker=global_worker):
|
||||
"""Return information about the state of the scheduler."""
|
||||
return ray.lib.scheduler_info(worker.handle)
|
||||
|
||||
def visualize_computation_graph(file_path=None, view=False, worker=global_worker):
|
||||
"""
|
||||
Write the computation graph to a pdf file.
|
||||
"""Write the computation graph to a pdf file.
|
||||
|
||||
Args:
|
||||
file_path: A .pdf file that the rendered computation graph will be written to
|
||||
file_path (str): The name of a pdf file that the rendered computation graph
|
||||
will be written to. If this argument is None, a temporary path will be
|
||||
used.
|
||||
view (bool): If true, the result the python graphviz package will try to
|
||||
open the result in a viewer.
|
||||
|
||||
view: If true, the result the python graphviz package will try to open the
|
||||
result in a viewer
|
||||
Examples:
|
||||
In ray/scripts, call "python shell.py" and try the following code.
|
||||
|
||||
Example:
|
||||
In ray/scripts, call "python shell.py" and paste in the following code.
|
||||
|
||||
x = da.zeros([20, 20])
|
||||
y = da.zeros([20, 20])
|
||||
z = da.dot(x, y)
|
||||
|
||||
ray.visualize_computation_graph("computation_graph.pdf")
|
||||
>>> x = da.zeros([20, 20])
|
||||
>>> y = da.zeros([20, 20])
|
||||
>>> z = da.dot(x, y)
|
||||
>>> ray.visualize_computation_graph(view=True)
|
||||
"""
|
||||
|
||||
if file_path is None:
|
||||
@@ -186,15 +330,17 @@ def visualize_computation_graph(file_path=None, view=False, worker=global_worker
|
||||
print "Wrote computation graph to file {}.pdf".format(base_path)
|
||||
|
||||
def task_info(worker=global_worker):
|
||||
"""Tell the scheduler to return task information. Currently includes a list of all failed tasks since the start of the cluster."""
|
||||
"""Return information about failed tasks."""
|
||||
return ray.lib.task_info(worker.handle)
|
||||
|
||||
def register_module(module, worker=global_worker):
|
||||
"""
|
||||
This registers each remote function in the module with the scheduler, so tasks
|
||||
with those functions can be scheduled on this worker.
|
||||
"""Register each remote function in a module with the scheduler.
|
||||
|
||||
:param module: The module of functions to register.
|
||||
This registers each remote function in the module with the scheduler, so tasks
|
||||
with those functions can be scheduled on this worker.
|
||||
|
||||
args:
|
||||
module (module): The module of functions to register.
|
||||
"""
|
||||
logging.info("registering functions in module {}.".format(module.__name__))
|
||||
for name in dir(module):
|
||||
@@ -204,6 +350,17 @@ def register_module(module, worker=global_worker):
|
||||
worker.register_function(val)
|
||||
|
||||
def connect(scheduler_address, objstore_address, worker_address, is_driver=False, worker=global_worker, mode=ray.WORKER_MODE):
|
||||
"""Connect this worker to the scheduler and an object store.
|
||||
|
||||
Args:
|
||||
scheduler_address (str): The ip address and port of the scheduler.
|
||||
objstore_address (str): The ip address and port of the local object store.
|
||||
worker_address (str): The ip address and port of this worker. The port can
|
||||
be chosen arbitrarily.
|
||||
is_driver (bool): True if this worker is a driver and false otherwise.
|
||||
mode: The mode of the worker. One of ray.SCRIPT_MODE, ray.WORKER_MODE,
|
||||
ray.SHELL_MODE, and ray.PYTHON_MODE.
|
||||
"""
|
||||
if hasattr(worker, "handle"):
|
||||
del worker.handle
|
||||
worker.scheduler_address = scheduler_address
|
||||
@@ -216,15 +373,23 @@ def connect(scheduler_address, objstore_address, worker_address, is_driver=False
|
||||
ray.lib.set_log_config(config.get_log_file_path("-".join(["worker", worker_address, "c++"]) + ".log"))
|
||||
|
||||
def disconnect(worker=global_worker):
|
||||
"""Disconnect this worker from the scheduler and object store."""
|
||||
if worker.handle is not None:
|
||||
ray.lib.disconnect(worker.handle)
|
||||
|
||||
def get(objref, worker=global_worker):
|
||||
"""
|
||||
Get a remote object from an object store.
|
||||
"""Get a remote object from an object store.
|
||||
|
||||
:param objref: Object reference to the object you want to get
|
||||
:rtype: A Python value
|
||||
This method blocks until the object corresponding to objref is available in
|
||||
the local object store. If this object is not in the local object store, it
|
||||
will be shipped from an object store that has it (once the object has been
|
||||
created).
|
||||
|
||||
Args:
|
||||
objref (ray.ObjRef): Object reference to the object to get.
|
||||
|
||||
Returns:
|
||||
A Python object
|
||||
"""
|
||||
if worker.mode == ray.PYTHON_MODE:
|
||||
return objref # In ray.PYTHON_MODE, ray.get is the identity operation (the input will actually be a value not an objref)
|
||||
@@ -237,11 +402,13 @@ def get(objref, worker=global_worker):
|
||||
return value
|
||||
|
||||
def put(value, worker=global_worker):
|
||||
"""
|
||||
Store an object in the object store.
|
||||
"""Store an object in the object store.
|
||||
|
||||
:param value: The Python object to be stored
|
||||
:rtype: Object reference
|
||||
Args:
|
||||
value (serializable object): The Python object to be stored.
|
||||
|
||||
Returns:
|
||||
The object reference assigned to this value.
|
||||
"""
|
||||
if worker.mode == ray.PYTHON_MODE:
|
||||
return value # In ray.PYTHON_MODE, ray.put is the identity operation
|
||||
@@ -252,35 +419,52 @@ def put(value, worker=global_worker):
|
||||
return objref
|
||||
|
||||
def kill_workers(worker=global_worker):
|
||||
"""
|
||||
This method kills all of the workers in the cluster. It does not kill drivers.
|
||||
"""Kill all of the workers in the cluster. This does not kill drivers.
|
||||
|
||||
Note:
|
||||
Currently, we only support killing workers if all submitted tasks have been
|
||||
run. If some workers are still running tasks or if the scheduler still has
|
||||
tasks in its queue, then this method will not do anything.
|
||||
|
||||
Returns:
|
||||
True if workers were successfully killed. False otherwise.
|
||||
"""
|
||||
success = ray.lib.kill_workers(worker.handle)
|
||||
if not success:
|
||||
print "Could not kill all workers. Check that there are no tasks currently running."
|
||||
print "Could not kill all workers. We currently do not support killing workers when tasks are running."
|
||||
return success
|
||||
|
||||
def restart_workers_local(num_workers, worker_path, worker=global_worker):
|
||||
"""
|
||||
This method kills all of the workers and starts new workers locally on the
|
||||
same node as the driver. This is intended for use in the case where Ray is
|
||||
being used on a single node.
|
||||
"""Restart workers locally.
|
||||
|
||||
:param num_workers: the number of workers to be started
|
||||
:param worker_path: path of the source code that will be run on the worker
|
||||
This method kills all of the workers and starts new workers locally on the
|
||||
same node as the driver. This is intended for use in the case where Ray is
|
||||
being used on a single node.
|
||||
|
||||
Args:
|
||||
num_workers (int): The number of workers to be started.
|
||||
worker_path (str): The path of the source code that workers will run.
|
||||
|
||||
Returns:
|
||||
True if workers were successfully restarted. False otherwise.
|
||||
"""
|
||||
if not kill_workers(worker):
|
||||
return False
|
||||
services.start_workers(worker.scheduler_address, worker.objstore_address, num_workers, worker_path)
|
||||
return True
|
||||
|
||||
def format_error_message(exception_message):
|
||||
"""
|
||||
This method takes an backtrace from an exception and makes it nicer by
|
||||
removing a few uninformative lines and adding some space to indent the
|
||||
remaining lines nicely.
|
||||
"""Improve the formatting of an exception thrown by a remote function.
|
||||
|
||||
:param exception_message: a string generated by traceback.format_exc()
|
||||
:rtype: a string
|
||||
This method takes an backtrace from an exception and makes it nicer by
|
||||
removing a few uninformative lines and adding some space to indent the
|
||||
remaining lines nicely.
|
||||
|
||||
Args:
|
||||
exception_message (str): A message generated by traceback.format_exc().
|
||||
|
||||
Returns:
|
||||
A string of the formatted exception message.
|
||||
"""
|
||||
lines = exception_message.split("\n")
|
||||
# Remove lines 1, 2, 3, and 4, which are always the same, they just contain
|
||||
@@ -290,6 +474,21 @@ def format_error_message(exception_message):
|
||||
return "\n".join(lines)
|
||||
|
||||
def main_loop(worker=global_worker):
|
||||
"""The main loop a worker runs to receive and execute tasks.
|
||||
|
||||
This method is an infinite loop. It waits to receive tasks from the scheduler.
|
||||
When it receives a task, it first deserializes the task. Then it retrieves the
|
||||
values for any arguments that were passed in as object references. Then it
|
||||
passes the arguments to the actual function. Then it stores the outputs of the
|
||||
function in the local object store. Then it notifies the scheduler that it
|
||||
completed the task.
|
||||
|
||||
If the process of getting the arguments for execution (which does some type
|
||||
checking) or the process of executing the task fail, then the main loop will
|
||||
catch the exception and store RayFailedObject objects containing the relevant
|
||||
error messages in the object store in place of the actual outputs. These
|
||||
objects are used to propagate the error messages.
|
||||
"""
|
||||
if not ray.lib.connected(worker.handle):
|
||||
raise Exception("Worker is attempting to enter main_loop but has not been connected yet.")
|
||||
ray.lib.start_worker_service(worker.handle)
|
||||
@@ -321,15 +520,15 @@ def main_loop(worker=global_worker):
|
||||
process_task(task)
|
||||
|
||||
def remote(arg_types, return_types, worker=global_worker):
|
||||
"""
|
||||
This is a decorator to indicate that a Python function is to be executed remotely.
|
||||
"""This decorator is used to create remote functions.
|
||||
|
||||
:param arg_types: List of Python types of the function arguments
|
||||
:param return_types: List of Python types of the return values
|
||||
Args:
|
||||
arg_types (List[type]): List of Python types of the function arguments.
|
||||
return_types (List[type]): List of Python types of the return values.
|
||||
"""
|
||||
def remote_decorator(func):
|
||||
def func_executor(arguments):
|
||||
"""This is what gets executed remotely on a worker after a remote function is scheduled by the scheduler."""
|
||||
"""This gets run when the remote function is executed."""
|
||||
logging.info("Calling function {}".format(func.__name__))
|
||||
start_time = time.time()
|
||||
result = func(*arguments)
|
||||
@@ -338,7 +537,7 @@ def remote(arg_types, return_types, worker=global_worker):
|
||||
logging.info("Finished executing function {}, it took {} seconds".format(func.__name__, end_time - start_time))
|
||||
return result
|
||||
def func_call(*args, **kwargs):
|
||||
"""This is what gets run immediately when a worker calls a remote function."""
|
||||
"""This gets run immediately when a worker calls a remote function."""
|
||||
args = list(args)
|
||||
args.extend([kwargs[keyword] if kwargs.has_key(keyword) else default for keyword, default in func_call.keyword_defaults[len(args):]]) # fill in the remaining arguments
|
||||
if worker.mode == ray.PYTHON_MODE:
|
||||
@@ -365,10 +564,18 @@ def remote(arg_types, return_types, worker=global_worker):
|
||||
return func_call
|
||||
return remote_decorator
|
||||
|
||||
# helper method, this should not be called by the user
|
||||
# we currently do not support the functionality that we test for in this method,
|
||||
# but in the future we could
|
||||
def check_signature_supported(function):
|
||||
"""Check if we support the signature of this function.
|
||||
|
||||
We currently do not allow remote functions to have **kwargs. We also do not
|
||||
support keyword argumens in conjunction with a *args argument.
|
||||
|
||||
Args:
|
||||
function (Callable): The function to check.
|
||||
|
||||
Raises:
|
||||
Exception: An exception is raised if the signature is not supported.
|
||||
"""
|
||||
# check if the user specified kwargs
|
||||
if function.has_kwargs_param:
|
||||
raise "Function {} has a **kwargs argument, which is currently not supported.".format(function.__name__)
|
||||
@@ -377,8 +584,18 @@ def check_signature_supported(function):
|
||||
raise "Function {} has a *args argument as well as a keyword argument, which is currently not supported.".format(function.__name__)
|
||||
|
||||
|
||||
# helper method, this should not be called by the user
|
||||
def check_return_values(function, result):
|
||||
"""Check the types and number of return values.
|
||||
|
||||
Args:
|
||||
function (Callable): The remote function whose outputs are being checked.
|
||||
result: The value returned by an invocation of the remote function. The
|
||||
expected types and number are defined in the remote decorator.
|
||||
|
||||
Raises:
|
||||
Exception: An exception is raised if the return values have incorrect types
|
||||
or the function returned the wrong number of return values.
|
||||
"""
|
||||
# If the @remote decorator declares that the function has no return values,
|
||||
# then all we do is check that there were in fact no return values.
|
||||
if len(function.return_types) == 0:
|
||||
@@ -403,6 +620,17 @@ def check_return_values(function, result):
|
||||
raise Exception("The {}th return value for function {} has type {}, but the @remote decorator expected a return value of type {} or an ObjRef.".format(i, function.__name__, type(result[i]), function.return_types[i]))
|
||||
|
||||
def typecheck_arg(arg, expected_type, i, function):
|
||||
"""Check that an argument has the expected type.
|
||||
|
||||
Args:
|
||||
arg: An argument to function.
|
||||
expected_type (type): The expected type of arg.
|
||||
i (int): The position of the argument to the function.
|
||||
function (Callable): The remote function whose argument is being checked.
|
||||
|
||||
Raises:
|
||||
Exception: An exception is raised if arg does not have the expected type.
|
||||
"""
|
||||
if issubclass(type(arg), expected_type):
|
||||
# Passed the type-checck
|
||||
# TODO(rkn): This check doesn't really work, e.g., issubclass(type([1, 2, 3]), typing.List[str]) == True
|
||||
@@ -413,8 +641,19 @@ def typecheck_arg(arg, expected_type, i, function):
|
||||
else:
|
||||
raise Exception("Argument {} for function {} has type {} but an argument of type {} was expected.".format(i, function.__name__, type(arg), expected_type))
|
||||
|
||||
# helper method, this should not be called by the user
|
||||
def check_arguments(function, args):
|
||||
"""Check that the arguments to the remote function have the right types.
|
||||
|
||||
This is called by the worker that calls the remote function (not the worker
|
||||
that executes the remote function).
|
||||
|
||||
Args:
|
||||
function (Callable): The remote function whose arguments are being checked.
|
||||
args (List): The arguments to the function
|
||||
|
||||
Raises:
|
||||
Exception: An exception is raised the args do not all have the right types.
|
||||
"""
|
||||
# check the number of args
|
||||
if len(args) != len(function.arg_types) and not function.has_vararg_param:
|
||||
raise Exception("Function {} expects {} arguments, but received {}.".format(function.__name__, len(function.arg_types), len(args)))
|
||||
@@ -435,17 +674,33 @@ def check_arguments(function, args):
|
||||
else:
|
||||
typecheck_arg(arg, expected_type, i, function)
|
||||
|
||||
# helper method, this should not be called by the user
|
||||
def get_arguments_for_execution(function, args, worker=global_worker):
|
||||
"""Retrieve the arguments for the remote function.
|
||||
|
||||
This retrieves the values for the arguments to the remote function that were
|
||||
passed in as object references. Argumens that were passed by value are not
|
||||
changed. This also does some type checking. This is called by the worker that
|
||||
is executing the remote function.
|
||||
|
||||
Args:
|
||||
function (Callable): The remote function whose arguments are being
|
||||
retrieved.
|
||||
args (List): The arguments to the function.
|
||||
|
||||
Returns:
|
||||
The retrieved arguments in addition to the arguments that were passed by
|
||||
value.
|
||||
|
||||
Raises:
|
||||
Exception: An exception is raised the args do not all have the right types.
|
||||
"""
|
||||
# TODO(rkn): Eventually, all of the type checking can be put in `check_arguments` above so that the error will happen immediately when calling a remote function.
|
||||
arguments = []
|
||||
"""
|
||||
# check the number of args
|
||||
if len(args) != len(function.arg_types) and function.arg_types[-1] is not None:
|
||||
raise Exception("Function {} expects {} arguments, but received {}.".format(function.__name__, len(function.arg_types), len(args)))
|
||||
elif len(args) < len(function.arg_types) - 1 and function.arg_types[-1] is None:
|
||||
raise Exception("Function {} expects at least {} arguments, but received {}.".format(function.__name__, len(function.arg_types) - 1, len(args)))
|
||||
"""
|
||||
# # check the number of args
|
||||
# if len(args) != len(function.arg_types) and function.arg_types[-1] is not None:
|
||||
# raise Exception("Function {} expects {} arguments, but received {}.".format(function.__name__, len(function.arg_types), len(args)))
|
||||
# elif len(args) < len(function.arg_types) - 1 and function.arg_types[-1] is None:
|
||||
# raise Exception("Function {} expects at least {} arguments, but received {}.".format(function.__name__, len(function.arg_types) - 1, len(args)))
|
||||
|
||||
for (i, arg) in enumerate(args):
|
||||
if i <= len(function.arg_types) - 1:
|
||||
@@ -468,8 +723,26 @@ def get_arguments_for_execution(function, args, worker=global_worker):
|
||||
arguments.append(argument)
|
||||
return arguments
|
||||
|
||||
# helper method, this should not be called by the user
|
||||
def store_outputs_in_objstore(objrefs, outputs, worker=global_worker):
|
||||
"""Store the outputs of a remote function in the local object store.
|
||||
|
||||
This stores the values that were returned by a remote function in the local
|
||||
object store. If any of the return values are object references, then these
|
||||
object references are aliased with the object references that the scheduler
|
||||
assigned for the return values. This is called by the worker that executes the
|
||||
remote function.
|
||||
|
||||
Note:
|
||||
The arguments objrefs and outputs should have the same length.
|
||||
|
||||
Args:
|
||||
objrefs (List[ray.ObjRef]): The object references that were assigned to the
|
||||
outputs of the remote function call.
|
||||
outputs (Tuple): The value returned by the remote function. If the remote
|
||||
function was supposed to only return one value, then its output was
|
||||
wrapped in a tuple with one element prior to being passed into this
|
||||
function.
|
||||
"""
|
||||
for i in range(len(objrefs)):
|
||||
if isinstance(outputs[i], ray.lib.ObjRef):
|
||||
# An ObjRef is being returned, so we must alias objrefs[i] so that it refers to the same object that outputs[i] refers to
|
||||
|
||||
Reference in New Issue
Block a user