def _actor_method_call(self, method_name: str, args, kwargs) -> "ObjectRef": flattened_args = self._metadata.flatten_args(method_name, args, kwargs) cls = self._metadata.cls method = getattr(cls, method_name, None) if method is None: raise AttributeError(f"Method '{method_name}' does not exist.") workflow_inputs = serialization_context.make_workflow_inputs( flattened_args) readonly = getattr(method, "__virtual_actor_readonly__", False) with workflow_context.workflow_step_context(self._actor_id, self._storage.storage_url): if readonly: _readonly_actor_method = _wrap_readonly_actor_method( self._actor_id, cls, method_name) # TODO(suquark): Support actor options. workflow_data = WorkflowData( func_body=_readonly_actor_method, inputs=workflow_inputs, max_retries=1, catch_exceptions=False, ray_options={}, ) wf = Workflow(workflow_data) return execute_virtual_actor_step(wf.id, workflow_data, True) else: raise NotImplementedError( "Virtual actor writer mode has not been supported yet.")
def step(method_name, method, *args, **kwargs): readonly = getattr(method, "__virtual_actor_readonly__", False) flattened_args = self.flatten_args(method_name, args, kwargs) actor_id = workflow_context.get_current_workflow_id() if not readonly: if method_name == "__init__": state_ref = None else: ws = WorkflowStorage(actor_id, get_global_storage()) state_ref = WorkflowRef(ws.get_entrypoint_step_id()) # This is a hack to insert a positional argument. flattened_args = [signature.DUMMY_TYPE, state_ref ] + flattened_args workflow_inputs = serialization_context.make_workflow_inputs( flattened_args) if readonly: _actor_method = _wrap_readonly_actor_method( actor_id, self.cls, method_name) step_type = StepType.READONLY_ACTOR_METHOD else: _actor_method = _wrap_actor_method(self.cls, method_name) step_type = StepType.ACTOR_METHOD # TODO(suquark): Support actor options. workflow_data = WorkflowData( func_body=_actor_method, step_type=step_type, inputs=workflow_inputs, max_retries=1, catch_exceptions=False, ray_options={}, name=None, ) wf = Workflow(workflow_data) return wf
def _build_workflow(*args, **kwargs) -> Workflow: # validate if the input arguments match the signature of the # original function. reconstructed_signature = inspect.Signature( parameters=self._func_signature) try: reconstructed_signature.bind(*args, **kwargs) except TypeError as exc: # capture a friendlier stacktrace raise TypeError(str(exc)) from None workflows: List[Workflow] = [] object_refs: List[ObjectRef] = [] with serialization_context.workflow_args_serialization_context( workflows, object_refs): # NOTE: When calling 'ray.put', we trigger python object # serialization. Under our serialization context, # Workflows and ObjectRefs are separated from the arguments, # leaving a placeholder object with all other python objects. # Then we put the placeholder object to object store, # so it won't be mutated later. This guarantees correct # semantics. See "tests/test_variable_mutable.py" as # an example. input_placeholder: ObjectRef = ray.put((args, kwargs)) if object_refs: raise ValueError( "There are ObjectRefs in workflow inputs. However " "workflow currently does not support checkpointing " "ObjectRefs.") return Workflow(self._func, self._run_step, input_placeholder, workflows, object_refs, self._step_max_retries, self._catch_exceptions, self._ray_options)
def _actor_method_call(self, method_name: str, args, kwargs) -> "ObjectRef": cls = self._metadata.cls method = getattr(cls, method_name, None) if method is None: raise AttributeError(f"Method '{method_name}' does not exist.") readonly = getattr(method, "__virtual_actor_readonly__", False) flattened_args = self._metadata.flatten_args(method_name, args, kwargs) if not readonly: if method_name == "__init__": state_ref = None else: ws = WorkflowStorage(self._actor_id, self._storage) state_ref = WorkflowRef(ws.get_entrypoint_step_id()) # This is a hack to insert a positional argument. flattened_args = [signature.DUMMY_TYPE, state_ref] + flattened_args workflow_inputs = serialization_context.make_workflow_inputs( flattened_args) if readonly: _actor_method = _wrap_readonly_actor_method( self._actor_id, cls, method_name) step_type = StepType.READONLY_ACTOR_METHOD else: _actor_method = _wrap_actor_method(cls, method_name) step_type = StepType.ACTOR_METHOD # TODO(suquark): Support actor options. workflow_data = WorkflowData( func_body=_actor_method, step_type=step_type, inputs=workflow_inputs, max_retries=1, catch_exceptions=False, ray_options={}, ) wf = Workflow(workflow_data) with workflow_context.workflow_step_context(self._actor_id, self._storage.storage_url): if readonly: return execute_workflow(wf).volatile_output else: return wf.run_async(self._actor_id)
def save_subworkflow(self, workflow: Workflow) -> None: """Save the DAG and inputs of the sub-workflow. Args: workflow: A sub-workflow. Could be a nested workflow inside a workflow step. """ assert not workflow.executed for w in workflow.iter_workflows_in_dag(): self._write_step_inputs(w.id, w.get_inputs())
def save_subworkflow(self, workflow: Workflow) -> None: """Save the DAG and inputs of the sub-workflow. Args: workflow: A sub-workflow. Could be a nested workflow inside a workflow step. """ assert not workflow.executed tasks = [ self._write_step_inputs(w.id, w.data) for w in workflow.iter_workflows_in_dag() ] asyncio_run(asyncio.gather(*tasks))
def _build_workflow(*args, **kwargs) -> Workflow: flattened_args = signature.flatten_args(self._func_signature, args, kwargs) workflow_inputs = serialization_context.make_workflow_inputs( flattened_args) workflow_data = WorkflowData( func_body=self._func, inputs=workflow_inputs, max_retries=self._max_retries, catch_exceptions=self._catch_exceptions, ray_options=self._ray_options, ) return Workflow(workflow_data)
def _build_workflow(*args, **kwargs) -> Workflow: flattened_args = signature.flatten_args(self._func_signature, args, kwargs) def prepare_inputs(): ensure_ray_initialized() return serialization_context.make_workflow_inputs( flattened_args) workflow_data = WorkflowData( func_body=self._func, step_type=StepType.FUNCTION, inputs=None, max_retries=self._max_retries, catch_exceptions=self._catch_exceptions, ray_options=self._ray_options, name=self._name, ) return Workflow(workflow_data, prepare_inputs)
def execute_workflow( workflow: Workflow, outer_most_step_id: Optional[str] = None) -> ray.ObjectRef: """Execute workflow. To fully explain what we are doing, we need to introduce some syntax first. The syntax for dependencies between workflow steps "A.step(B.step())" is "A - B"; the syntax for nested workflow steps "def A(): return B.step()" is "A / B". In a chain/DAG of step dependencies, the "output step" is the step of last (topological) order. For example, in "A - B - C", C is the output step. In a chain of nested workflow steps, the initial "output step" is called the "outer most step" for other "output steps". For example, in "A / B / C / D", "A" is the outer most step for "B", "C", "D"; in the hybrid workflow "((A - B) / C / D) - (E / (F - G) / H)", "B" is the outer most step for "C", "D"; "E" is the outer most step for "G", "H". Args: workflow: The workflow to be executed. outer_most_step_id: The ID of the outer most workflow. None if it does not exists. See "step_executor.execute_workflow" for detailed explanation. Returns: An object ref that represent the result. """ if outer_most_step_id is None: # The current workflow step returns a nested workflow, and # there is no outer step for the current step. So the current # step is the outer most step for the inner nested workflow # steps. outer_most_step_id = workflow_context.get_current_step_id() # Passing down outer most step so inner nested steps would # access the same outer most step. return workflow.execute(outer_most_step_id)