Repository: encode/starlette

Issue:
fix Unraisable ResourceWarnings
Hrm..we should probably fix these 🤔 curious that they all seem to be warnings for `family=AddressFamily.AF_UNIX`

_Originally posted by @JayH5 in https://github.com/encode/starlette/pull/1040#discussion_r484068578_
Test failure
### Checklist

<!-- Please make sure you check all these items before submitting your bug report. -->

- [x] The bug is reproducible against the latest release and/or `master`.
- [x] There are no similar issues or pull requests to fix it yet.

### Describe the bug

Test failure on NixOS and Fedora during the package build process.

```
============================= test session starts ==============================
platform linux -- Python 3.8.7, pytest-6.1.2, py-1.9.0, pluggy-0.13.1
rootdir: /build/source, configfile: setup.cfg
plugins: asyncio-0.14.0, cov-2.10.1
collected 240 items                                                            
[...]
=================================== FAILURES ===================================
_______________________________ test_debug_html ________________________________

    def test_debug_html():
        async def app(scope, receive, send):
            raise RuntimeError("Something went wrong")

        app = ServerErrorMiddleware(app, debug=True)
        client = TestClient(app, raise_server_exceptions=False)
        response = client.get("/", headers={"Accept": "text/html, */*"})
        assert response.status_code == 500
>       assert response.headers["content-type"].startswith("text/html")

tests/middleware/test_errors.py:42:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = {}, key = 'content-type'

    def __getitem__(self, key):
>       return self._store[key.lower()][1]
E       KeyError: 'content-type'

/nix/store/9bb3iygm8kkdzfsaj3j1j2q845nzja1p-python3.8-requests-2.25.0/lib/python3.8/site-packages/requests/structures.py:54: KeyError
```
Fedora uses different releases

```bash
============================= test session starts ==============================
platform linux -- Python 3.9.1, pytest-6.0.2, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
benchmark: 3.2.3 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/python-starlette/starlette-0.14.1, configfile: setup.cfg
plugins: cov-2.10.1, asyncio-0.14.0, requests-mock-1.7.0, httpx-0.10.0, mock-3.3.1, benchmark-3.2.3, aresponses-2.0.0, forked-1.3.0, xdist-1.34.0, django-3.8.0, vcr-1.0.2, datafiles-2.0, pyfakefs-3.5.8, httpbin-1.0.0, sugar-0.9.3, aiohttp-0.3.0
collecting ... collected 187 items
[...]
```

### To reproduce

Run with enabled unit tests.

<!-- Provide a *minimal* example with steps to reproduce the bug locally.

NOTE: try to keep any external dependencies *at an absolute minimum*
(middleware, servers, proxies, certificates...).
In other words, remove anything that doesn't make the bug go away.
-->

### Expected behavior

All tests passing.

### Actual behavior

<!-- A clear and concise description of what actually happens. -->

### Debugging material

<!-- Any tracebacks, screenshots, etc. that can help understanding the problem.

NOTE:
- Please list tracebacks in full (don't truncate them).
- Consider using `<details>` to make tracebacks/logs collapsible if they're very large (see https://gist.github.com/ericclemmons/b146fe5da72ca1f706b2ef72a20ac39d).
-->

### Environment

- OS: NixOS and Fedora <!-- eg Linux/Windows/macOS. -->
- Python version: 3.8.7 <!-- eg 3.8.5 (get it with `$ python -V`). -->
- Starlette version: 0.14.1 but as also present in 0.13.8 <!-- eg 0.13.8 (get it with `$ pip show starlette`). -->

### Additional context

<!-- Any additional information that can help understanding the problem.

Eg. linked issues, or a description of what you were trying to achieve. -->



Discussion:
it looks like various objects are not used in context managers, or pass on the `__exit__` to their super class
Possibly related: #947, #948 


Resolving patch:
diff --git a/starlette/middleware/errors.py b/starlette/middleware/errors.py
--- a/starlette/middleware/errors.py
+++ b/starlette/middleware/errors.py
@@ -217,15 +217,15 @@ def generate_html(self, exc: Exception, limit: int = 7) -> str:
         traceback_obj = traceback.TracebackException.from_exception(
             exc, capture_locals=True
         )
-        frames = inspect.getinnerframes(
-            traceback_obj.exc_traceback, limit  # type: ignore
-        )
 
         exc_html = ""
         is_collapsed = False
-        for frame in reversed(frames):
-            exc_html += self.generate_frame_html(frame, is_collapsed)
-            is_collapsed = True
+        exc_traceback = exc.__traceback__
+        if exc_traceback is not None:
+            frames = inspect.getinnerframes(exc_traceback, limit)
+            for frame in reversed(frames):
+                exc_html += self.generate_frame_html(frame, is_collapsed)
+                is_collapsed = True
 
         # escape error class and text
         error = (
diff --git a/starlette/testclient.py b/starlette/testclient.py
--- a/starlette/testclient.py
+++ b/starlette/testclient.py
@@ -268,7 +268,6 @@ def __init__(self, app: ASGI3App, scope: Scope) -> None:
         self.app = app
         self.scope = scope
         self.accepted_subprotocol = None
-        self._loop = asyncio.new_event_loop()
         self._receive_queue = queue.Queue()  # type: queue.Queue
         self._send_queue = queue.Queue()  # type: queue.Queue
         self._thread = threading.Thread(target=self._run)
@@ -293,13 +292,16 @@ def _run(self) -> None:
         """
         The sub-thread in which the websocket session runs.
         """
+        loop = asyncio.new_event_loop()
         scope = self.scope
         receive = self._asgi_receive
         send = self._asgi_send
         try:
-            self._loop.run_until_complete(self.app(scope, receive, send))
+            loop.run_until_complete(self.app(scope, receive, send))
         except BaseException as exc:
             self._send_queue.put(exc)
+        finally:
+            loop.close()
 
     async def _asgi_receive(self) -> Message:
         while self._receive_queue.empty():


Test patch:
diff --git a/tests/middleware/__init__.py b/tests/middleware/__init__.py
new file mode 100644