prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  gRPC server interceptor raises AttributeError
  When a gRPC service is not found, the Datadog interceptor raises an AttributeError:
  
  ```
  ERROR    grpc._server:_server.py:732 Exception servicing handler: 'NoneType' object has no attribute '_pin'
  ...
    File "/src/ddtrace/contrib/grpc/server_interceptor.py", line 153, in intercept_service
      return self._fn(continuation, handler_call_details)
    File "/src/ddtrace/contrib/grpc/server_interceptor.py", line 22, in interceptor_function
      return _TracedRpcMethodHandler(pin, handler_call_details, rpc_method_handler)
    File "/src/ddtrace/contrib/grpc/server_interceptor.py", line 58, in __init__
      self._pin = pin
  AttributeError: 'NoneType' object has no attribute '_pin'
  ```
  
  The gRPC documentation [points](https://grpc.github.io/grpc/python/grpc.html#grpc.ServerInterceptor.intercept_service) out that `continuation()` in a server-side interceptor can return a `None`, which is not handled by the Datadog interceptor.
  
  This applies to `ddtrace>=0.28.0` when the server interceptor was added.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `ddtrace/contrib/grpc/server_interceptor.py`
  Content:
  ```
  1 import grpc
  2 from ddtrace.vendor import wrapt
  3 
  4 from ddtrace import config
  5 from ddtrace.ext import errors
  6 from ddtrace.compat import to_unicode
  7 
  8 from ...constants import ANALYTICS_SAMPLE_RATE_KEY, SPAN_MEASURED_KEY
  9 from ...ext import SpanTypes
  10 from ...propagation.http import HTTPPropagator
  11 from . import constants
  12 from .utils import parse_method_path
  13 
  14 
  15 def create_server_interceptor(pin):
  16     def interceptor_function(continuation, handler_call_details):
  17         if not pin.enabled:
  18             return continuation(handler_call_details)
  19 
  20         rpc_method_handler = continuation(handler_call_details)
  21         return _TracedRpcMethodHandler(pin, handler_call_details, rpc_method_handler)
  22 
  23     return _ServerInterceptor(interceptor_function)
  24 
  25 
  26 def _handle_server_exception(server_context, span):
  27     if server_context is not None and \
  28        hasattr(server_context, '_state') and \
  29        server_context._state is not None:
  30         code = to_unicode(server_context._state.code)
  31         details = to_unicode(server_context._state.details)
  32         span.error = 1
  33         span.set_tag(errors.ERROR_MSG, details)
  34         span.set_tag(errors.ERROR_TYPE, code)
  35 
  36 
  37 def _wrap_response_iterator(response_iterator, server_context, span):
  38     try:
  39         for response in response_iterator:
  40             yield response
  41     except Exception:
  42         span.set_traceback()
  43         _handle_server_exception(server_context, span)
  44         raise
  45     finally:
  46         span.finish()
  47 
  48 
  49 class _TracedRpcMethodHandler(wrapt.ObjectProxy):
  50     def __init__(self, pin, handler_call_details, wrapped):
  51         super(_TracedRpcMethodHandler, self).__init__(wrapped)
  52         self._pin = pin
  53         self._handler_call_details = handler_call_details
  54 
  55     def _fn(self, method_kind, behavior, args, kwargs):
  56         if config.grpc_server.distributed_tracing_enabled:
  57             headers = dict(self._handler_call_details.invocation_metadata)
  58             propagator = HTTPPropagator()
  59             context = propagator.extract(headers)
  60 
  61             if context.trace_id:
  62                 self._pin.tracer.context_provider.activate(context)
  63 
  64         tracer = self._pin.tracer
  65 
  66         span = tracer.trace(
  67             'grpc',
  68             span_type=SpanTypes.GRPC,
  69             service=self._pin.service,
  70             resource=self._handler_call_details.method,
  71         )
  72         span.set_tag(SPAN_MEASURED_KEY)
  73 
  74         method_path = self._handler_call_details.method
  75         method_package, method_service, method_name = parse_method_path(method_path)
  76         span.set_tag(constants.GRPC_METHOD_PATH_KEY, method_path)
  77         span.set_tag(constants.GRPC_METHOD_PACKAGE_KEY, method_package)
  78         span.set_tag(constants.GRPC_METHOD_SERVICE_KEY, method_service)
  79         span.set_tag(constants.GRPC_METHOD_NAME_KEY, method_name)
  80         span.set_tag(constants.GRPC_METHOD_KIND_KEY, method_kind)
  81         span.set_tag(constants.GRPC_SPAN_KIND_KEY, constants.GRPC_SPAN_KIND_VALUE_SERVER)
  82 
  83         sample_rate = config.grpc_server.get_analytics_sample_rate()
  84         if sample_rate is not None:
  85             span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, sample_rate)
  86 
  87         # access server context by taking second argument as server context
  88         # if not found, skip using context to tag span with server state information
  89         server_context = args[1] if isinstance(args[1], grpc.ServicerContext) else None
  90 
  91         if self._pin.tags:
  92             span.set_tags(self._pin.tags)
  93 
  94         try:
  95             response_or_iterator = behavior(*args, **kwargs)
  96 
  97             if self.__wrapped__.response_streaming:
  98                 response_or_iterator = _wrap_response_iterator(response_or_iterator, server_context, span)
  99         except Exception:
  100             span.set_traceback()
  101             _handle_server_exception(server_context, span)
  102             raise
  103         finally:
  104             if not self.__wrapped__.response_streaming:
  105                 span.finish()
  106 
  107         return response_or_iterator
  108 
  109     def unary_unary(self, *args, **kwargs):
  110         return self._fn(
  111             constants.GRPC_METHOD_KIND_UNARY,
  112             self.__wrapped__.unary_unary,
  113             args,
  114             kwargs
  115         )
  116 
  117     def unary_stream(self, *args, **kwargs):
  118         return self._fn(
  119             constants.GRPC_METHOD_KIND_SERVER_STREAMING,
  120             self.__wrapped__.unary_stream,
  121             args,
  122             kwargs
  123         )
  124 
  125     def stream_unary(self, *args, **kwargs):
  126         return self._fn(
  127             constants.GRPC_METHOD_KIND_CLIENT_STREAMING,
  128             self.__wrapped__.stream_unary,
  129             args,
  130             kwargs
  131         )
  132 
  133     def stream_stream(self, *args, **kwargs):
  134         return self._fn(
  135             constants.GRPC_METHOD_KIND_BIDI_STREAMING,
  136             self.__wrapped__.stream_stream,
  137             args,
  138             kwargs
  139         )
  140 
  141 
  142 class _ServerInterceptor(grpc.ServerInterceptor):
  143     def __init__(self, interceptor_function):
  144         self._fn = interceptor_function
  145 
  146     def intercept_service(self, continuation, handler_call_details):
  147         return self._fn(continuation, handler_call_details)
  148
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/ddtrace/contrib/grpc/server_interceptor.py b/ddtrace/contrib/grpc/server_interceptor.py
  --- a/ddtrace/contrib/grpc/server_interceptor.py
  +++ b/ddtrace/contrib/grpc/server_interceptor.py
  @@ -18,7 +18,15 @@
               return continuation(handler_call_details)
   
           rpc_method_handler = continuation(handler_call_details)
  -        return _TracedRpcMethodHandler(pin, handler_call_details, rpc_method_handler)
  +
  +        # continuation returns an RpcMethodHandler instance if the RPC is
  +        # considered serviced, or None otherwise
  +        # https://grpc.github.io/grpc/python/grpc.html#grpc.ServerInterceptor.intercept_service
  +
  +        if rpc_method_handler:
  +            return _TracedRpcMethodHandler(pin, handler_call_details, rpc_method_handler)
  +
  +        return rpc_method_handler
   
       return _ServerInterceptor(interceptor_function)
  
verification_info: {"golden_diff": "diff --git a/ddtrace/contrib/grpc/server_interceptor.py b/ddtrace/contrib/grpc/server_interceptor.py\n--- a/ddtrace/contrib/grpc/server_interceptor.py\n+++ b/ddtrace/contrib/grpc/server_interceptor.py\n@@ -18,7 +18,15 @@\n             return continuation(handler_call_details)\n \n         rpc_method_handler = continuation(handler_call_details)\n-        return _TracedRpcMethodHandler(pin, handler_call_details, rpc_method_handler)\n+\n+        # continuation returns an RpcMethodHandler instance if the RPC is\n+        # considered serviced, or None otherwise\n+        # https://grpc.github.io/grpc/python/grpc.html#grpc.ServerInterceptor.intercept_service\n+\n+        if rpc_method_handler:\n+            return _TracedRpcMethodHandler(pin, handler_call_details, rpc_method_handler)\n+\n+        return rpc_method_handler\n \n     return _ServerInterceptor(interceptor_function)\n", "issue": "gRPC server interceptor raises AttributeError\nWhen a gRPC service is not found, the Datadog interceptor raises an AttributeError:\r\n\r\n```\r\nERROR    grpc._server:_server.py:732 Exception servicing handler: 'NoneType' object has no attribute '_pin'\r\n...\r\n  File \"/src/ddtrace/contrib/grpc/server_interceptor.py\", line 153, in intercept_service\r\n    return self._fn(continuation, handler_call_details)\r\n  File \"/src/ddtrace/contrib/grpc/server_interceptor.py\", line 22, in interceptor_function\r\n    return _TracedRpcMethodHandler(pin, handler_call_details, rpc_method_handler)\r\n  File \"/src/ddtrace/contrib/grpc/server_interceptor.py\", line 58, in __init__\r\n    self._pin = pin\r\nAttributeError: 'NoneType' object has no attribute '_pin'\r\n```\r\n\r\nThe gRPC documentation [points](https://grpc.github.io/grpc/python/grpc.html#grpc.ServerInterceptor.intercept_service) out that `continuation()` in a server-side interceptor can return a `None`, which is not handled by the Datadog interceptor.\r\n\r\nThis applies to `ddtrace>=0.28.0` when the server interceptor was added.\n", "before_files": [{"content": "import grpc\nfrom ddtrace.vendor import wrapt\n\nfrom ddtrace import config\nfrom ddtrace.ext import errors\nfrom ddtrace.compat import to_unicode\n\nfrom ...constants import ANALYTICS_SAMPLE_RATE_KEY, SPAN_MEASURED_KEY\nfrom ...ext import SpanTypes\nfrom ...propagation.http import HTTPPropagator\nfrom . import constants\nfrom .utils import parse_method_path\n\n\ndef create_server_interceptor(pin):\n    def interceptor_function(continuation, handler_call_details):\n        if not pin.enabled:\n            return continuation(handler_call_details)\n\n        rpc_method_handler = continuation(handler_call_details)\n        return _TracedRpcMethodHandler(pin, handler_call_details, rpc_method_handler)\n\n    return _ServerInterceptor(interceptor_function)\n\n\ndef _handle_server_exception(server_context, span):\n    if server_context is not None and \\\n       hasattr(server_context, '_state') and \\\n       server_context._state is not None:\n        code = to_unicode(server_context._state.code)\n        details = to_unicode(server_context._state.details)\n        span.error = 1\n        span.set_tag(errors.ERROR_MSG, details)\n        span.set_tag(errors.ERROR_TYPE, code)\n\n\ndef _wrap_response_iterator(response_iterator, server_context, span):\n    try:\n        for response in response_iterator:\n            yield response\n    except Exception:\n        span.set_traceback()\n        _handle_server_exception(server_context, span)\n        raise\n    finally:\n        span.finish()\n\n\nclass _TracedRpcMethodHandler(wrapt.ObjectProxy):\n    def __init__(self, pin, handler_call_details, wrapped):\n        super(_TracedRpcMethodHandler, self).__init__(wrapped)\n        self._pin = pin\n        self._handler_call_details = handler_call_details\n\n    def _fn(self, method_kind, behavior, args, kwargs):\n        if config.grpc_server.distributed_tracing_enabled:\n            headers = dict(self._handler_call_details.invocation_metadata)\n            propagator = HTTPPropagator()\n            context = propagator.extract(headers)\n\n            if context.trace_id:\n                self._pin.tracer.context_provider.activate(context)\n\n        tracer = self._pin.tracer\n\n        span = tracer.trace(\n            'grpc',\n            span_type=SpanTypes.GRPC,\n            service=self._pin.service,\n            resource=self._handler_call_details.method,\n        )\n        span.set_tag(SPAN_MEASURED_KEY)\n\n        method_path = self._handler_call_details.method\n        method_package, method_service, method_name = parse_method_path(method_path)\n        span.set_tag(constants.GRPC_METHOD_PATH_KEY, method_path)\n        span.set_tag(constants.GRPC_METHOD_PACKAGE_KEY, method_package)\n        span.set_tag(constants.GRPC_METHOD_SERVICE_KEY, method_service)\n        span.set_tag(constants.GRPC_METHOD_NAME_KEY, method_name)\n        span.set_tag(constants.GRPC_METHOD_KIND_KEY, method_kind)\n        span.set_tag(constants.GRPC_SPAN_KIND_KEY, constants.GRPC_SPAN_KIND_VALUE_SERVER)\n\n        sample_rate = config.grpc_server.get_analytics_sample_rate()\n        if sample_rate is not None:\n            span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, sample_rate)\n\n        # access server context by taking second argument as server context\n        # if not found, skip using context to tag span with server state information\n        server_context = args[1] if isinstance(args[1], grpc.ServicerContext) else None\n\n        if self._pin.tags:\n            span.set_tags(self._pin.tags)\n\n        try:\n            response_or_iterator = behavior(*args, **kwargs)\n\n            if self.__wrapped__.response_streaming:\n                response_or_iterator = _wrap_response_iterator(response_or_iterator, server_context, span)\n        except Exception:\n            span.set_traceback()\n            _handle_server_exception(server_context, span)\n            raise\n        finally:\n            if not self.__wrapped__.response_streaming:\n                span.finish()\n\n        return response_or_iterator\n\n    def unary_unary(self, *args, **kwargs):\n        return self._fn(\n            constants.GRPC_METHOD_KIND_UNARY,\n            self.__wrapped__.unary_unary,\n            args,\n            kwargs\n        )\n\n    def unary_stream(self, *args, **kwargs):\n        return self._fn(\n            constants.GRPC_METHOD_KIND_SERVER_STREAMING,\n            self.__wrapped__.unary_stream,\n            args,\n            kwargs\n        )\n\n    def stream_unary(self, *args, **kwargs):\n        return self._fn(\n            constants.GRPC_METHOD_KIND_CLIENT_STREAMING,\n            self.__wrapped__.stream_unary,\n            args,\n            kwargs\n        )\n\n    def stream_stream(self, *args, **kwargs):\n        return self._fn(\n            constants.GRPC_METHOD_KIND_BIDI_STREAMING,\n            self.__wrapped__.stream_stream,\n            args,\n            kwargs\n        )\n\n\nclass _ServerInterceptor(grpc.ServerInterceptor):\n    def __init__(self, interceptor_function):\n        self._fn = interceptor_function\n\n    def intercept_service(self, continuation, handler_call_details):\n        return self._fn(continuation, handler_call_details)\n", "path": "ddtrace/contrib/grpc/server_interceptor.py"}], "after_files": [{"content": "import grpc\nfrom ddtrace.vendor import wrapt\n\nfrom ddtrace import config\nfrom ddtrace.ext import errors\nfrom ddtrace.compat import to_unicode\n\nfrom ...constants import ANALYTICS_SAMPLE_RATE_KEY, SPAN_MEASURED_KEY\nfrom ...ext import SpanTypes\nfrom ...propagation.http import HTTPPropagator\nfrom . import constants\nfrom .utils import parse_method_path\n\n\ndef create_server_interceptor(pin):\n    def interceptor_function(continuation, handler_call_details):\n        if not pin.enabled:\n            return continuation(handler_call_details)\n\n        rpc_method_handler = continuation(handler_call_details)\n\n        # continuation returns an RpcMethodHandler instance if the RPC is\n        # considered serviced, or None otherwise\n        # https://grpc.github.io/grpc/python/grpc.html#grpc.ServerInterceptor.intercept_service\n\n        if rpc_method_handler:\n            return _TracedRpcMethodHandler(pin, handler_call_details, rpc_method_handler)\n\n        return rpc_method_handler\n\n    return _ServerInterceptor(interceptor_function)\n\n\ndef _handle_server_exception(server_context, span):\n    if server_context is not None and \\\n       hasattr(server_context, '_state') and \\\n       server_context._state is not None:\n        code = to_unicode(server_context._state.code)\n        details = to_unicode(server_context._state.details)\n        span.error = 1\n        span.set_tag(errors.ERROR_MSG, details)\n        span.set_tag(errors.ERROR_TYPE, code)\n\n\ndef _wrap_response_iterator(response_iterator, server_context, span):\n    try:\n        for response in response_iterator:\n            yield response\n    except Exception:\n        span.set_traceback()\n        _handle_server_exception(server_context, span)\n        raise\n    finally:\n        span.finish()\n\n\nclass _TracedRpcMethodHandler(wrapt.ObjectProxy):\n    def __init__(self, pin, handler_call_details, wrapped):\n        super(_TracedRpcMethodHandler, self).__init__(wrapped)\n        self._pin = pin\n        self._handler_call_details = handler_call_details\n\n    def _fn(self, method_kind, behavior, args, kwargs):\n        if config.grpc_server.distributed_tracing_enabled:\n            headers = dict(self._handler_call_details.invocation_metadata)\n            propagator = HTTPPropagator()\n            context = propagator.extract(headers)\n\n            if context.trace_id:\n                self._pin.tracer.context_provider.activate(context)\n\n        tracer = self._pin.tracer\n\n        span = tracer.trace(\n            'grpc',\n            span_type=SpanTypes.GRPC,\n            service=self._pin.service,\n            resource=self._handler_call_details.method,\n        )\n        span.set_tag(SPAN_MEASURED_KEY)\n\n        method_path = self._handler_call_details.method\n        method_package, method_service, method_name = parse_method_path(method_path)\n        span.set_tag(constants.GRPC_METHOD_PATH_KEY, method_path)\n        span.set_tag(constants.GRPC_METHOD_PACKAGE_KEY, method_package)\n        span.set_tag(constants.GRPC_METHOD_SERVICE_KEY, method_service)\n        span.set_tag(constants.GRPC_METHOD_NAME_KEY, method_name)\n        span.set_tag(constants.GRPC_METHOD_KIND_KEY, method_kind)\n        span.set_tag(constants.GRPC_SPAN_KIND_KEY, constants.GRPC_SPAN_KIND_VALUE_SERVER)\n\n        sample_rate = config.grpc_server.get_analytics_sample_rate()\n        if sample_rate is not None:\n            span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, sample_rate)\n\n        # access server context by taking second argument as server context\n        # if not found, skip using context to tag span with server state information\n        server_context = args[1] if isinstance(args[1], grpc.ServicerContext) else None\n\n        if self._pin.tags:\n            span.set_tags(self._pin.tags)\n\n        try:\n            response_or_iterator = behavior(*args, **kwargs)\n\n            if self.__wrapped__.response_streaming:\n                response_or_iterator = _wrap_response_iterator(response_or_iterator, server_context, span)\n        except Exception:\n            span.set_traceback()\n            _handle_server_exception(server_context, span)\n            raise\n        finally:\n            if not self.__wrapped__.response_streaming:\n                span.finish()\n\n        return response_or_iterator\n\n    def unary_unary(self, *args, **kwargs):\n        return self._fn(\n            constants.GRPC_METHOD_KIND_UNARY,\n            self.__wrapped__.unary_unary,\n            args,\n            kwargs\n        )\n\n    def unary_stream(self, *args, **kwargs):\n        return self._fn(\n            constants.GRPC_METHOD_KIND_SERVER_STREAMING,\n            self.__wrapped__.unary_stream,\n            args,\n            kwargs\n        )\n\n    def stream_unary(self, *args, **kwargs):\n        return self._fn(\n            constants.GRPC_METHOD_KIND_CLIENT_STREAMING,\n            self.__wrapped__.stream_unary,\n            args,\n            kwargs\n        )\n\n    def stream_stream(self, *args, **kwargs):\n        return self._fn(\n            constants.GRPC_METHOD_KIND_BIDI_STREAMING,\n            self.__wrapped__.stream_stream,\n            args,\n            kwargs\n        )\n\n\nclass _ServerInterceptor(grpc.ServerInterceptor):\n    def __init__(self, interceptor_function):\n        self._fn = interceptor_function\n\n    def intercept_service(self, continuation, handler_call_details):\n        return self._fn(continuation, handler_call_details)\n", "path": "ddtrace/contrib/grpc/server_interceptor.py"}]}