We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Memory leak with unclosed span in opentelemetry integration
### How do you use Sentry?

Sentry Saas (sentry.io)

### Version

1.40.3

### Steps to Reproduce

`SentrySpanProcessor` stores all open span in `self.otel_span_map` dict. This leads to a memory leak if otel span is deleted without closing. E.g. due to a bug: https://github.com/open-telemetry/opentelemetry-python-contrib/issues/2149

### Expected Result

Sentry span should be deleted after otel span is deleted by GC.

### Actual Result

Sentry spans are stored in self.otel_span_map forever.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `sentry_sdk/scrubber.py`
Content:
```
1 try:
2     from typing import cast
3 except ImportError:
4     cast = lambda _, obj: obj
5 
6 from sentry_sdk.utils import (
7     capture_internal_exceptions,
8     AnnotatedValue,
9     iter_event_frames,
10 )
11 from sentry_sdk._types import TYPE_CHECKING
12 
13 if TYPE_CHECKING:
14     from sentry_sdk._types import Event
15     from typing import List
16     from typing import Optional
17 
18 
19 DEFAULT_DENYLIST = [
20     # stolen from relay
21     "password",
22     "passwd",
23     "secret",
24     "api_key",
25     "apikey",
26     "auth",
27     "credentials",
28     "mysql_pwd",
29     "privatekey",
30     "private_key",
31     "token",
32     "ip_address",
33     "session",
34     # django
35     "csrftoken",
36     "sessionid",
37     # wsgi
38     "remote_addr",
39     "x_csrftoken",
40     "x_forwarded_for",
41     "set_cookie",
42     "cookie",
43     "authorization",
44     "x_api_key",
45     "x_forwarded_for",
46     "x_real_ip",
47     # other common names used in the wild
48     "aiohttp_session",  # aiohttp
49     "connect.sid",  # Express
50     "csrf_token",  # Pyramid
51     "csrf",  # (this is a cookie name used in accepted answers on stack overflow)
52     "_csrf",  # Express
53     "_csrf_token",  # Bottle
54     "PHPSESSID",  # PHP
55     "_session",  # Sanic
56     "symfony",  # Symfony
57     "user_session",  # Vue
58     "_xsrf",  # Tornado
59     "XSRF-TOKEN",  # Angular, Laravel
60 ]
61 
62 
63 class EventScrubber(object):
64     def __init__(self, denylist=None, recursive=False):
65         # type: (Optional[List[str]], bool) -> None
66         self.denylist = DEFAULT_DENYLIST if denylist is None else denylist
67         self.denylist = [x.lower() for x in self.denylist]
68         self.recursive = recursive
69 
70     def scrub_list(self, lst):
71         # type: (object) -> None
72         """
73         If a list is passed to this method, the method recursively searches the list and any
74         nested lists for any dictionaries. The method calls scrub_dict on all dictionaries
75         it finds.
76         If the parameter passed to this method is not a list, the method does nothing.
77         """
78         if not isinstance(lst, list):
79             return
80 
81         for v in lst:
82             self.scrub_dict(v)  # no-op unless v is a dict
83             self.scrub_list(v)  # no-op unless v is a list
84 
85     def scrub_dict(self, d):
86         # type: (object) -> None
87         """
88         If a dictionary is passed to this method, the method scrubs the dictionary of any
89         sensitive data. The method calls itself recursively on any nested dictionaries (
90         including dictionaries nested in lists) if self.recursive is True.
91         This method does nothing if the parameter passed to it is not a dictionary.
92         """
93         if not isinstance(d, dict):
94             return
95 
96         for k, v in d.items():
97             if isinstance(k, str) and k.lower() in self.denylist:
98                 d[k] = AnnotatedValue.substituted_because_contains_sensitive_data()
99             elif self.recursive:
100                 self.scrub_dict(v)  # no-op unless v is a dict
101                 self.scrub_list(v)  # no-op unless v is a list
102 
103     def scrub_request(self, event):
104         # type: (Event) -> None
105         with capture_internal_exceptions():
106             if "request" in event:
107                 if "headers" in event["request"]:
108                     self.scrub_dict(event["request"]["headers"])
109                 if "cookies" in event["request"]:
110                     self.scrub_dict(event["request"]["cookies"])
111                 if "data" in event["request"]:
112                     self.scrub_dict(event["request"]["data"])
113 
114     def scrub_extra(self, event):
115         # type: (Event) -> None
116         with capture_internal_exceptions():
117             if "extra" in event:
118                 self.scrub_dict(event["extra"])
119 
120     def scrub_user(self, event):
121         # type: (Event) -> None
122         with capture_internal_exceptions():
123             if "user" in event:
124                 self.scrub_dict(event["user"])
125 
126     def scrub_breadcrumbs(self, event):
127         # type: (Event) -> None
128         with capture_internal_exceptions():
129             if "breadcrumbs" in event:
130                 if "values" in event["breadcrumbs"]:
131                     for value in event["breadcrumbs"]["values"]:
132                         if "data" in value:
133                             self.scrub_dict(value["data"])
134 
135     def scrub_frames(self, event):
136         # type: (Event) -> None
137         with capture_internal_exceptions():
138             for frame in iter_event_frames(event):
139                 if "vars" in frame:
140                     self.scrub_dict(frame["vars"])
141 
142     def scrub_spans(self, event):
143         # type: (Event) -> None
144         with capture_internal_exceptions():
145             if "spans" in event:
146                 for span in event["spans"]:
147                     if "data" in span:
148                         self.scrub_dict(span["data"])
149 
150     def scrub_event(self, event):
151         # type: (Event) -> None
152         self.scrub_request(event)
153         self.scrub_extra(event)
154         self.scrub_user(event)
155         self.scrub_breadcrumbs(event)
156         self.scrub_frames(event)
157         self.scrub_spans(event)
158
```
--- 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()

```

diff --git a/sentry_sdk/scrubber.py b/sentry_sdk/scrubber.py
--- a/sentry_sdk/scrubber.py
+++ b/sentry_sdk/scrubber.py
@@ -1,8 +1,3 @@
-try:
-    from typing import cast
-except ImportError:
-    cast = lambda _, obj: obj
-
 from sentry_sdk.utils import (
     capture_internal_exceptions,
     AnnotatedValue,
@@ -94,6 +89,8 @@
             return
 
         for k, v in d.items():
+            # The cast is needed because mypy is not smart enough to figure out that k must be a
+            # string after the isinstance check.
             if isinstance(k, str) and k.lower() in self.denylist:
                 d[k] = AnnotatedValue.substituted_because_contains_sensitive_data()
             elif self.recursive:

{"golden_diff": "diff --git a/sentry_sdk/scrubber.py b/sentry_sdk/scrubber.py\n--- a/sentry_sdk/scrubber.py\n+++ b/sentry_sdk/scrubber.py\n@@ -1,8 +1,3 @@\n-try:\n-    from typing import cast\n-except ImportError:\n-    cast = lambda _, obj: obj\n-\n from sentry_sdk.utils import (\n     capture_internal_exceptions,\n     AnnotatedValue,\n@@ -94,6 +89,8 @@\n             return\n \n         for k, v in d.items():\n+            # The cast is needed because mypy is not smart enough to figure out that k must be a\n+            # string after the isinstance check.\n             if isinstance(k, str) and k.lower() in self.denylist:\n                 d[k] = AnnotatedValue.substituted_because_contains_sensitive_data()\n             elif self.recursive:\n", "issue": "Memory leak with unclosed span in opentelemetry integration\n### How do you use Sentry?\n\nSentry Saas (sentry.io)\n\n### Version\n\n1.40.3\n\n### Steps to Reproduce\n\n`SentrySpanProcessor` stores all open span in `self.otel_span_map` dict. This leads to a memory leak if otel span is deleted without closing. E.g. due to a bug: https://github.com/open-telemetry/opentelemetry-python-contrib/issues/2149\n\n### Expected Result\n\nSentry span should be deleted after otel span is deleted by GC.\n\n### Actual Result\n\nSentry spans are stored in self.otel_span_map forever.\n", "before_files": [{"content": "try:\n    from typing import cast\nexcept ImportError:\n    cast = lambda _, obj: obj\n\nfrom sentry_sdk.utils import (\n    capture_internal_exceptions,\n    AnnotatedValue,\n    iter_event_frames,\n)\nfrom sentry_sdk._types import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from sentry_sdk._types import Event\n    from typing import List\n    from typing import Optional\n\n\nDEFAULT_DENYLIST = [\n    # stolen from relay\n    \"password\",\n    \"passwd\",\n    \"secret\",\n    \"api_key\",\n    \"apikey\",\n    \"auth\",\n    \"credentials\",\n    \"mysql_pwd\",\n    \"privatekey\",\n    \"private_key\",\n    \"token\",\n    \"ip_address\",\n    \"session\",\n    # django\n    \"csrftoken\",\n    \"sessionid\",\n    # wsgi\n    \"remote_addr\",\n    \"x_csrftoken\",\n    \"x_forwarded_for\",\n    \"set_cookie\",\n    \"cookie\",\n    \"authorization\",\n    \"x_api_key\",\n    \"x_forwarded_for\",\n    \"x_real_ip\",\n    # other common names used in the wild\n    \"aiohttp_session\",  # aiohttp\n    \"connect.sid\",  # Express\n    \"csrf_token\",  # Pyramid\n    \"csrf\",  # (this is a cookie name used in accepted answers on stack overflow)\n    \"_csrf\",  # Express\n    \"_csrf_token\",  # Bottle\n    \"PHPSESSID\",  # PHP\n    \"_session\",  # Sanic\n    \"symfony\",  # Symfony\n    \"user_session\",  # Vue\n    \"_xsrf\",  # Tornado\n    \"XSRF-TOKEN\",  # Angular, Laravel\n]\n\n\nclass EventScrubber(object):\n    def __init__(self, denylist=None, recursive=False):\n        # type: (Optional[List[str]], bool) -> None\n        self.denylist = DEFAULT_DENYLIST if denylist is None else denylist\n        self.denylist = [x.lower() for x in self.denylist]\n        self.recursive = recursive\n\n    def scrub_list(self, lst):\n        # type: (object) -> None\n        \"\"\"\n        If a list is passed to this method, the method recursively searches the list and any\n        nested lists for any dictionaries. The method calls scrub_dict on all dictionaries\n        it finds.\n        If the parameter passed to this method is not a list, the method does nothing.\n        \"\"\"\n        if not isinstance(lst, list):\n            return\n\n        for v in lst:\n            self.scrub_dict(v)  # no-op unless v is a dict\n            self.scrub_list(v)  # no-op unless v is a list\n\n    def scrub_dict(self, d):\n        # type: (object) -> None\n        \"\"\"\n        If a dictionary is passed to this method, the method scrubs the dictionary of any\n        sensitive data. The method calls itself recursively on any nested dictionaries (\n        including dictionaries nested in lists) if self.recursive is True.\n        This method does nothing if the parameter passed to it is not a dictionary.\n        \"\"\"\n        if not isinstance(d, dict):\n            return\n\n        for k, v in d.items():\n            if isinstance(k, str) and k.lower() in self.denylist:\n                d[k] = AnnotatedValue.substituted_because_contains_sensitive_data()\n            elif self.recursive:\n                self.scrub_dict(v)  # no-op unless v is a dict\n                self.scrub_list(v)  # no-op unless v is a list\n\n    def scrub_request(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            if \"request\" in event:\n                if \"headers\" in event[\"request\"]:\n                    self.scrub_dict(event[\"request\"][\"headers\"])\n                if \"cookies\" in event[\"request\"]:\n                    self.scrub_dict(event[\"request\"][\"cookies\"])\n                if \"data\" in event[\"request\"]:\n                    self.scrub_dict(event[\"request\"][\"data\"])\n\n    def scrub_extra(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            if \"extra\" in event:\n                self.scrub_dict(event[\"extra\"])\n\n    def scrub_user(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            if \"user\" in event:\n                self.scrub_dict(event[\"user\"])\n\n    def scrub_breadcrumbs(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            if \"breadcrumbs\" in event:\n                if \"values\" in event[\"breadcrumbs\"]:\n                    for value in event[\"breadcrumbs\"][\"values\"]:\n                        if \"data\" in value:\n                            self.scrub_dict(value[\"data\"])\n\n    def scrub_frames(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            for frame in iter_event_frames(event):\n                if \"vars\" in frame:\n                    self.scrub_dict(frame[\"vars\"])\n\n    def scrub_spans(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            if \"spans\" in event:\n                for span in event[\"spans\"]:\n                    if \"data\" in span:\n                        self.scrub_dict(span[\"data\"])\n\n    def scrub_event(self, event):\n        # type: (Event) -> None\n        self.scrub_request(event)\n        self.scrub_extra(event)\n        self.scrub_user(event)\n        self.scrub_breadcrumbs(event)\n        self.scrub_frames(event)\n        self.scrub_spans(event)\n", "path": "sentry_sdk/scrubber.py"}], "after_files": [{"content": "from sentry_sdk.utils import (\n    capture_internal_exceptions,\n    AnnotatedValue,\n    iter_event_frames,\n)\nfrom sentry_sdk._types import TYPE_CHECKING\n\nif TYPE_CHECKING:\n    from sentry_sdk._types import Event\n    from typing import List\n    from typing import Optional\n\n\nDEFAULT_DENYLIST = [\n    # stolen from relay\n    \"password\",\n    \"passwd\",\n    \"secret\",\n    \"api_key\",\n    \"apikey\",\n    \"auth\",\n    \"credentials\",\n    \"mysql_pwd\",\n    \"privatekey\",\n    \"private_key\",\n    \"token\",\n    \"ip_address\",\n    \"session\",\n    # django\n    \"csrftoken\",\n    \"sessionid\",\n    # wsgi\n    \"remote_addr\",\n    \"x_csrftoken\",\n    \"x_forwarded_for\",\n    \"set_cookie\",\n    \"cookie\",\n    \"authorization\",\n    \"x_api_key\",\n    \"x_forwarded_for\",\n    \"x_real_ip\",\n    # other common names used in the wild\n    \"aiohttp_session\",  # aiohttp\n    \"connect.sid\",  # Express\n    \"csrf_token\",  # Pyramid\n    \"csrf\",  # (this is a cookie name used in accepted answers on stack overflow)\n    \"_csrf\",  # Express\n    \"_csrf_token\",  # Bottle\n    \"PHPSESSID\",  # PHP\n    \"_session\",  # Sanic\n    \"symfony\",  # Symfony\n    \"user_session\",  # Vue\n    \"_xsrf\",  # Tornado\n    \"XSRF-TOKEN\",  # Angular, Laravel\n]\n\n\nclass EventScrubber(object):\n    def __init__(self, denylist=None, recursive=False):\n        # type: (Optional[List[str]], bool) -> None\n        self.denylist = DEFAULT_DENYLIST if denylist is None else denylist\n        self.denylist = [x.lower() for x in self.denylist]\n        self.recursive = recursive\n\n    def scrub_list(self, lst):\n        # type: (object) -> None\n        \"\"\"\n        If a list is passed to this method, the method recursively searches the list and any\n        nested lists for any dictionaries. The method calls scrub_dict on all dictionaries\n        it finds.\n        If the parameter passed to this method is not a list, the method does nothing.\n        \"\"\"\n        if not isinstance(lst, list):\n            return\n\n        for v in lst:\n            self.scrub_dict(v)  # no-op unless v is a dict\n            self.scrub_list(v)  # no-op unless v is a list\n\n    def scrub_dict(self, d):\n        # type: (object) -> None\n        \"\"\"\n        If a dictionary is passed to this method, the method scrubs the dictionary of any\n        sensitive data. The method calls itself recursively on any nested dictionaries (\n        including dictionaries nested in lists) if self.recursive is True.\n        This method does nothing if the parameter passed to it is not a dictionary.\n        \"\"\"\n        if not isinstance(d, dict):\n            return\n\n        for k, v in d.items():\n            # The cast is needed because mypy is not smart enough to figure out that k must be a\n            # string after the isinstance check.\n            if isinstance(k, str) and k.lower() in self.denylist:\n                d[k] = AnnotatedValue.substituted_because_contains_sensitive_data()\n            elif self.recursive:\n                self.scrub_dict(v)  # no-op unless v is a dict\n                self.scrub_list(v)  # no-op unless v is a list\n\n    def scrub_request(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            if \"request\" in event:\n                if \"headers\" in event[\"request\"]:\n                    self.scrub_dict(event[\"request\"][\"headers\"])\n                if \"cookies\" in event[\"request\"]:\n                    self.scrub_dict(event[\"request\"][\"cookies\"])\n                if \"data\" in event[\"request\"]:\n                    self.scrub_dict(event[\"request\"][\"data\"])\n\n    def scrub_extra(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            if \"extra\" in event:\n                self.scrub_dict(event[\"extra\"])\n\n    def scrub_user(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            if \"user\" in event:\n                self.scrub_dict(event[\"user\"])\n\n    def scrub_breadcrumbs(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            if \"breadcrumbs\" in event:\n                if \"values\" in event[\"breadcrumbs\"]:\n                    for value in event[\"breadcrumbs\"][\"values\"]:\n                        if \"data\" in value:\n                            self.scrub_dict(value[\"data\"])\n\n    def scrub_frames(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            for frame in iter_event_frames(event):\n                if \"vars\" in frame:\n                    self.scrub_dict(frame[\"vars\"])\n\n    def scrub_spans(self, event):\n        # type: (Event) -> None\n        with capture_internal_exceptions():\n            if \"spans\" in event:\n                for span in event[\"spans\"]:\n                    if \"data\" in span:\n                        self.scrub_dict(span[\"data\"])\n\n    def scrub_event(self, event):\n        # type: (Event) -> None\n        self.scrub_request(event)\n        self.scrub_extra(event)\n        self.scrub_user(event)\n        self.scrub_breadcrumbs(event)\n        self.scrub_frames(event)\n        self.scrub_spans(event)\n", "path": "sentry_sdk/scrubber.py"}]}