Example #1
0
    def __init__(self, original_class, method: Callable, method_name: str,
                 runtime_options: WorkflowStepRuntimeOptions):
        self._original_class = original_class
        self._original_method = method
        # Extract the signature of the method. This will be used
        # to catch some errors if the methods are called with inappropriate
        # arguments.

        # Whether or not this method requires binding of its first
        # argument. For class and static methods, we do not want to bind
        # the first argument, but we do for instance methods
        method = inspect.unwrap(method)
        is_bound = (is_class_method(method)
                    or is_static_method(original_class, method_name))

        # Print a warning message if the method signature is not
        # supported. We don't raise an exception because if the actor
        # inherits from a class that has a method whose signature we
        # don't support, there may not be much the user can do about it.
        self._signature = signature.extract_signature(
            method, ignore_first=not is_bound)

        self._method = method
        self._method_name = method_name
        self._options = runtime_options
        self._name = None
        self._user_metadata = {}

        # attach properties to the original function, so we can create a
        # workflow step with the original function inside a virtual actor.
        self._original_method.step = self.step
        self._original_method.options = self.options
Example #2
0
    def __init__(self, original_class: type):
        actor_methods = inspect.getmembers(original_class,
                                           is_function_or_method)
        self.cls = original_class
        self.module = original_class.__module__
        self.name = original_class.__name__
        self.qualname = original_class.__qualname__
        self.methods = dict(actor_methods)

        # Extract the signatures of each of the methods. This will be used
        # to catch some errors if the methods are called with inappropriate
        # arguments.
        self.signatures = {}
        for method_name, method in actor_methods:
            # Whether or not this method requires binding of its first
            # argument. For class and static methods, we do not want to bind
            # the first argument, but we do for instance methods
            method = inspect.unwrap(method)
            is_bound = (is_class_method(method)
                        or is_static_method(original_class, method_name))

            # Print a warning message if the method signature is not
            # supported. We don't raise an exception because if the actor
            # inherits from a class that has a method whose signature we
            # don't support, there may not be much the user can do about it.
            self.signatures[method_name] = signature.extract_signature(
                method, ignore_first=not is_bound)
Example #3
0
 def actor_method_executor(__ray_actor, *args, **kwargs):
     # Execute the assigned method.
     is_bound = (is_class_method(method)
                 or is_static_method(type(__ray_actor), method_name))
     if is_bound:
         return method(*args, **kwargs)
     else:
         return method(__ray_actor, *args, **kwargs)
Example #4
0
    def create(cls, modified_class, actor_creation_function_descriptor):
        # Try to create an instance from cache.
        cached_meta = cls._cache.get(actor_creation_function_descriptor)
        if cached_meta is not None:
            return cached_meta

        # Create an instance without __init__ called.
        self = cls.__new__(cls)

        actor_methods = inspect.getmembers(modified_class,
                                           is_function_or_method)
        self.methods = dict(actor_methods)

        # Extract the signatures of each of the methods. This will be used
        # to catch some errors if the methods are called with inappropriate
        # arguments.
        self.decorators = {}
        self.signatures = {}
        self.num_returns = {}
        self.concurrency_group_for_methods = {}

        for method_name, method in actor_methods:
            # Whether or not this method requires binding of its first
            # argument. For class and static methods, we do not want to bind
            # the first argument, but we do for instance methods
            method = inspect.unwrap(method)
            is_bound = (is_class_method(method)
                        or is_static_method(modified_class, method_name))

            # Print a warning message if the method signature is not
            # supported. We don't raise an exception because if the actor
            # inherits from a class that has a method whose signature we
            # don't support, there may not be much the user can do about it.
            self.signatures[method_name] = signature.extract_signature(
                method, ignore_first=not is_bound)
            # Set the default number of return values for this method.
            if hasattr(method, "__ray_num_returns__"):
                self.num_returns[method_name] = (method.__ray_num_returns__)
            else:
                self.num_returns[method_name] = (
                    ray_constants.DEFAULT_ACTOR_METHOD_NUM_RETURN_VALS)

            if hasattr(method, "__ray_invocation_decorator__"):
                self.decorators[method_name] = (
                    method.__ray_invocation_decorator__)

            if hasattr(method, "__ray_concurrency_group__"):
                self.concurrency_group_for_methods[method_name] = (
                    method.__ray_concurrency_group__)

        # Update cache.
        cls._cache[actor_creation_function_descriptor] = self
        return self
Example #5
0
 def __init__(self,
              actor_ref: ClientActorRef,
              actor_class: Optional[ClientActorClass] = None):
     self.actor_ref = actor_ref
     self._dir: Optional[List[str]] = None
     if actor_class is not None:
         self._method_num_returns = {}
         self._method_signatures = {}
         for method_name, method_obj in inspect.getmembers(
                 actor_class.actor_cls, is_function_or_method):
             self._method_num_returns[method_name] = getattr(
                 method_obj, "__ray_num_returns__", None)
             self._method_signatures[method_name] = inspect.Signature(
                 parameters=extract_signature(
                     method_obj,
                     ignore_first=(not (
                         is_class_method(method_obj) or is_static_method(
                             actor_class.actor_cls, method_name)))))
     else:
         self._method_num_returns = None
         self._method_signatures = None
Example #6
0
def _inject_tracing_into_class(_cls):
    """Given a class that will be made into an actor,
    inject tracing into all of the methods."""

    def span_wrapper(method: Callable[..., Any]) -> Any:
        def _resume_span(
                self: Any,
                *_args: Any,
                _ray_trace_ctx: Optional[Dict[str, Any]] = None,
                **_kwargs: Any,
        ) -> Any:
            """
            Wrap the user's function with a function that
            will extract the trace context
            """
            # If tracing feature flag is not on, perform a no-op
            if not is_tracing_enabled():
                return method(self, *_args, **_kwargs)

            tracer: trace.Tracer = trace.get_tracer(__name__)

            # Retrieves the context from the _ray_trace_ctx dictionary we
            # injected, or starts a new context
            if _ray_trace_ctx:
                with use_context(DictPropagator.extract(
                        _ray_trace_ctx)), tracer.start_as_current_span(
                            _actor_span_consumer_name(self.__class__.__name__,
                                                      method),
                            kind=trace.SpanKind.CONSUMER,
                            attributes=_actor_hydrate_span_args(
                                self.__class__.__name__, method),
                        ):
                    return method(self, *_args, **_kwargs)
            else:
                with tracer.start_as_current_span(
                        _actor_span_consumer_name(self.__class__.__name__,
                                                  method),
                        kind=trace.SpanKind.CONSUMER,
                        attributes=_actor_hydrate_span_args(
                            self.__class__.__name__, method),
                ):
                    return method(self, *_args, **_kwargs)

        return _resume_span

    def async_span_wrapper(method: Callable[..., Any]) -> Any:
        async def _resume_span(
                self: Any,
                *_args: Any,
                _ray_trace_ctx: Optional[Dict[str, Any]] = None,
                **_kwargs: Any,
        ) -> Any:
            """
            Wrap the user's function with a function that
            will extract the trace context
            """
            # If tracing feature flag is not on, perform a no-op
            if not is_tracing_enabled():
                return await method(self, *_args, **_kwargs)

            tracer = trace.get_tracer(__name__)

            # Retrieves the context from the _ray_trace_ctx dictionary we
            # injected, or starts a new context
            if _ray_trace_ctx:
                with use_context(DictPropagator.extract(
                        _ray_trace_ctx)), tracer.start_as_current_span(
                            _actor_span_consumer_name(self.__class__.__name__,
                                                      method.__name__),
                            kind=trace.SpanKind.CONSUMER,
                            attributes=_actor_hydrate_span_args(
                                self.__class__.__name__, method.__name__),
                        ):
                    return await method(self, *_args, **_kwargs)
            else:
                with tracer.start_as_current_span(
                        _actor_span_consumer_name(self._wrapped.__name__,
                                                  method.__name__),
                        kind=trace.SpanKind.CONSUMER,
                        attributes=_actor_hydrate_span_args(
                            self._wrapped.__name__, method.__name__),
                ):
                    return await method(self, *_args, **_kwargs)

        return _resume_span

    methods = inspect.getmembers(_cls, is_function_or_method)
    for name, method in methods:
        # Skip tracing for staticmethod or classmethod, because these method
        # might not be called directly by remote calls. Additionally, they are
        # tricky to get wrapped and unwrapped.
        if (is_static_method(_cls, name) or is_class_method(method)
                or not is_tracing_enabled()):
            continue

        # Add _ray_trace_ctx to method signature
        setattr(
            method, "__signature__",
            add_param_to_signature(
                method,
                inspect.Parameter(
                    "_ray_trace_ctx",
                    inspect.Parameter.KEYWORD_ONLY,
                    default=None)))

        if inspect.iscoroutinefunction(method):
            # If the method was async, swap out sync wrapper into async
            wrapped_method = wraps(method)(async_span_wrapper(method))
        else:
            wrapped_method = wraps(method)(span_wrapper(method))

        setattr(_cls, name, wrapped_method)

    return _cls
Example #7
0
    def __init__(self, original_class: type):
        actor_methods = inspect.getmembers(original_class,
                                           is_function_or_method)

        self.cls = original_class
        self.module = original_class.__module__
        self.name = original_class.__name__
        self.qualname = original_class.__qualname__
        self.methods = dict(actor_methods)

        # Extract the signatures of each of the methods. This will be used
        # to catch some errors if the methods are called with inappropriate
        # arguments.
        self.signatures = {}
        for method_name, method in actor_methods:
            # Whether or not this method requires binding of its first
            # argument. For class and static methods, we do not want to bind
            # the first argument, but we do for instance methods
            method = inspect.unwrap(method)
            is_bound = (is_class_method(method)
                        or is_static_method(original_class, method_name))

            # Print a warning message if the method signature is not
            # supported. We don't raise an exception because if the actor
            # inherits from a class that has a method whose signature we
            # don't support, there may not be much the user can do about it.
            self.signatures[method_name] = signature.extract_signature(
                method, ignore_first=not is_bound)

        for method_name, method in actor_methods:

            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,
                    user_metadata=None,
                )
                wf = Workflow(workflow_data)
                return wf

            method.step = functools.partial(step, method_name, method)