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
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)
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)
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
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
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
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)