We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
lifespan.shutdown.failed message is not implemented
there is no use that is made of the "lifespan.shutdown.failed" in the `async def send`. Is that an omission ? Readin the spec we should deal with it, not sure how yet, any ideas ?

_Originally posted by @euri10 in https://github.com/encode/uvicorn/pull/751#issuecomment-674366666_

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `uvicorn/lifespan/on.py`
Content:
```
1 import asyncio
2 import logging
3 from asyncio import Queue
4 
5 from uvicorn import Config
6 from uvicorn._types import LifespanReceiveMessage, LifespanScope, LifespanSendMessage
7 
8 STATE_TRANSITION_ERROR = "Got invalid state transition on lifespan protocol."
9 
10 
11 class LifespanOn:
12     def __init__(self, config: Config) -> None:
13         if not config.loaded:
14             config.load()
15 
16         self.config = config
17         self.logger = logging.getLogger("uvicorn.error")
18         self.startup_event = asyncio.Event()
19         self.shutdown_event = asyncio.Event()
20         self.receive_queue: "Queue[LifespanReceiveMessage]" = asyncio.Queue()
21         self.error_occured = False
22         self.startup_failed = False
23         self.should_exit = False
24 
25     async def startup(self) -> None:
26         self.logger.info("Waiting for application startup.")
27 
28         loop = asyncio.get_event_loop()
29         loop.create_task(self.main())
30 
31         await self.receive_queue.put({"type": "lifespan.startup"})
32         await self.startup_event.wait()
33 
34         if self.startup_failed or (self.error_occured and self.config.lifespan == "on"):
35             self.logger.error("Application startup failed. Exiting.")
36             self.should_exit = True
37         else:
38             self.logger.info("Application startup complete.")
39 
40     async def shutdown(self) -> None:
41         if self.error_occured:
42             return
43         self.logger.info("Waiting for application shutdown.")
44         await self.receive_queue.put({"type": "lifespan.shutdown"})
45         await self.shutdown_event.wait()
46         self.logger.info("Application shutdown complete.")
47 
48     async def main(self) -> None:
49         try:
50             app = self.config.loaded_app
51             scope: LifespanScope = {
52                 "type": "lifespan",
53                 "asgi": {"version": self.config.asgi_version, "spec_version": "2.0"},
54             }
55             await app(scope, self.receive, self.send)
56         except BaseException as exc:
57             self.asgi = None
58             self.error_occured = True
59             if self.startup_failed:
60                 return
61             if self.config.lifespan == "auto":
62                 msg = "ASGI 'lifespan' protocol appears unsupported."
63                 self.logger.info(msg)
64             else:
65                 msg = "Exception in 'lifespan' protocol\n"
66                 self.logger.error(msg, exc_info=exc)
67         finally:
68             self.startup_event.set()
69             self.shutdown_event.set()
70 
71     async def send(self, message: LifespanSendMessage) -> None:
72         assert message["type"] in (
73             "lifespan.startup.complete",
74             "lifespan.startup.failed",
75             "lifespan.shutdown.complete",
76         )
77 
78         if message["type"] == "lifespan.startup.complete":
79             assert not self.startup_event.is_set(), STATE_TRANSITION_ERROR
80             assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR
81             self.startup_event.set()
82 
83         elif message["type"] == "lifespan.startup.failed":
84             assert not self.startup_event.is_set(), STATE_TRANSITION_ERROR
85             assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR
86             self.startup_event.set()
87             self.startup_failed = True
88             if message.get("message"):
89                 self.logger.error(message["message"])
90 
91         elif message["type"] == "lifespan.shutdown.complete":
92             assert self.startup_event.is_set(), STATE_TRANSITION_ERROR
93             assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR
94             self.shutdown_event.set()
95 
96     async def receive(self) -> LifespanReceiveMessage:
97         return await self.receive_queue.get()
98
```
--- 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/uvicorn/lifespan/on.py b/uvicorn/lifespan/on.py
--- a/uvicorn/lifespan/on.py
+++ b/uvicorn/lifespan/on.py
@@ -20,6 +20,7 @@
         self.receive_queue: "Queue[LifespanReceiveMessage]" = asyncio.Queue()
         self.error_occured = False
         self.startup_failed = False
+        self.shutdown_failed = False
         self.should_exit = False
 
     async def startup(self) -> None:
@@ -43,7 +44,14 @@
         self.logger.info("Waiting for application shutdown.")
         await self.receive_queue.put({"type": "lifespan.shutdown"})
         await self.shutdown_event.wait()
-        self.logger.info("Application shutdown complete.")
+
+        if self.shutdown_failed or (
+            self.error_occured and self.config.lifespan == "on"
+        ):
+            self.logger.error("Application shutdown failed. Exiting.")
+            self.should_exit = True
+        else:
+            self.logger.info("Application shutdown complete.")
 
     async def main(self) -> None:
         try:
@@ -56,7 +64,7 @@
         except BaseException as exc:
             self.asgi = None
             self.error_occured = True
-            if self.startup_failed:
+            if self.startup_failed or self.shutdown_failed:
                 return
             if self.config.lifespan == "auto":
                 msg = "ASGI 'lifespan' protocol appears unsupported."
@@ -73,6 +81,7 @@
             "lifespan.startup.complete",
             "lifespan.startup.failed",
             "lifespan.shutdown.complete",
+            "lifespan.shutdown.failed",
         )
 
         if message["type"] == "lifespan.startup.complete":
@@ -93,5 +102,13 @@
             assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR
             self.shutdown_event.set()
 
+        elif message["type"] == "lifespan.shutdown.failed":
+            assert self.startup_event.is_set(), STATE_TRANSITION_ERROR
+            assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR
+            self.shutdown_event.set()
+            self.shutdown_failed = True
+            if message.get("message"):
+                self.logger.error(message["message"])
+
     async def receive(self) -> LifespanReceiveMessage:
         return await self.receive_queue.get()

{"golden_diff": "diff --git a/uvicorn/lifespan/on.py b/uvicorn/lifespan/on.py\n--- a/uvicorn/lifespan/on.py\n+++ b/uvicorn/lifespan/on.py\n@@ -20,6 +20,7 @@\n         self.receive_queue: \"Queue[LifespanReceiveMessage]\" = asyncio.Queue()\n         self.error_occured = False\n         self.startup_failed = False\n+        self.shutdown_failed = False\n         self.should_exit = False\n \n     async def startup(self) -> None:\n@@ -43,7 +44,14 @@\n         self.logger.info(\"Waiting for application shutdown.\")\n         await self.receive_queue.put({\"type\": \"lifespan.shutdown\"})\n         await self.shutdown_event.wait()\n-        self.logger.info(\"Application shutdown complete.\")\n+\n+        if self.shutdown_failed or (\n+            self.error_occured and self.config.lifespan == \"on\"\n+        ):\n+            self.logger.error(\"Application shutdown failed. Exiting.\")\n+            self.should_exit = True\n+        else:\n+            self.logger.info(\"Application shutdown complete.\")\n \n     async def main(self) -> None:\n         try:\n@@ -56,7 +64,7 @@\n         except BaseException as exc:\n             self.asgi = None\n             self.error_occured = True\n-            if self.startup_failed:\n+            if self.startup_failed or self.shutdown_failed:\n                 return\n             if self.config.lifespan == \"auto\":\n                 msg = \"ASGI 'lifespan' protocol appears unsupported.\"\n@@ -73,6 +81,7 @@\n             \"lifespan.startup.complete\",\n             \"lifespan.startup.failed\",\n             \"lifespan.shutdown.complete\",\n+            \"lifespan.shutdown.failed\",\n         )\n \n         if message[\"type\"] == \"lifespan.startup.complete\":\n@@ -93,5 +102,13 @@\n             assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR\n             self.shutdown_event.set()\n \n+        elif message[\"type\"] == \"lifespan.shutdown.failed\":\n+            assert self.startup_event.is_set(), STATE_TRANSITION_ERROR\n+            assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR\n+            self.shutdown_event.set()\n+            self.shutdown_failed = True\n+            if message.get(\"message\"):\n+                self.logger.error(message[\"message\"])\n+\n     async def receive(self) -> LifespanReceiveMessage:\n         return await self.receive_queue.get()\n", "issue": "lifespan.shutdown.failed message is not implemented\nthere is no use that is made of the \"lifespan.shutdown.failed\" in the `async def send`. Is that an omission ? Readin the spec we should deal with it, not sure how yet, any ideas ?\r\n\r\n_Originally posted by @euri10 in https://github.com/encode/uvicorn/pull/751#issuecomment-674366666_\n", "before_files": [{"content": "import asyncio\nimport logging\nfrom asyncio import Queue\n\nfrom uvicorn import Config\nfrom uvicorn._types import LifespanReceiveMessage, LifespanScope, LifespanSendMessage\n\nSTATE_TRANSITION_ERROR = \"Got invalid state transition on lifespan protocol.\"\n\n\nclass LifespanOn:\n    def __init__(self, config: Config) -> None:\n        if not config.loaded:\n            config.load()\n\n        self.config = config\n        self.logger = logging.getLogger(\"uvicorn.error\")\n        self.startup_event = asyncio.Event()\n        self.shutdown_event = asyncio.Event()\n        self.receive_queue: \"Queue[LifespanReceiveMessage]\" = asyncio.Queue()\n        self.error_occured = False\n        self.startup_failed = False\n        self.should_exit = False\n\n    async def startup(self) -> None:\n        self.logger.info(\"Waiting for application startup.\")\n\n        loop = asyncio.get_event_loop()\n        loop.create_task(self.main())\n\n        await self.receive_queue.put({\"type\": \"lifespan.startup\"})\n        await self.startup_event.wait()\n\n        if self.startup_failed or (self.error_occured and self.config.lifespan == \"on\"):\n            self.logger.error(\"Application startup failed. Exiting.\")\n            self.should_exit = True\n        else:\n            self.logger.info(\"Application startup complete.\")\n\n    async def shutdown(self) -> None:\n        if self.error_occured:\n            return\n        self.logger.info(\"Waiting for application shutdown.\")\n        await self.receive_queue.put({\"type\": \"lifespan.shutdown\"})\n        await self.shutdown_event.wait()\n        self.logger.info(\"Application shutdown complete.\")\n\n    async def main(self) -> None:\n        try:\n            app = self.config.loaded_app\n            scope: LifespanScope = {\n                \"type\": \"lifespan\",\n                \"asgi\": {\"version\": self.config.asgi_version, \"spec_version\": \"2.0\"},\n            }\n            await app(scope, self.receive, self.send)\n        except BaseException as exc:\n            self.asgi = None\n            self.error_occured = True\n            if self.startup_failed:\n                return\n            if self.config.lifespan == \"auto\":\n                msg = \"ASGI 'lifespan' protocol appears unsupported.\"\n                self.logger.info(msg)\n            else:\n                msg = \"Exception in 'lifespan' protocol\\n\"\n                self.logger.error(msg, exc_info=exc)\n        finally:\n            self.startup_event.set()\n            self.shutdown_event.set()\n\n    async def send(self, message: LifespanSendMessage) -> None:\n        assert message[\"type\"] in (\n            \"lifespan.startup.complete\",\n            \"lifespan.startup.failed\",\n            \"lifespan.shutdown.complete\",\n        )\n\n        if message[\"type\"] == \"lifespan.startup.complete\":\n            assert not self.startup_event.is_set(), STATE_TRANSITION_ERROR\n            assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR\n            self.startup_event.set()\n\n        elif message[\"type\"] == \"lifespan.startup.failed\":\n            assert not self.startup_event.is_set(), STATE_TRANSITION_ERROR\n            assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR\n            self.startup_event.set()\n            self.startup_failed = True\n            if message.get(\"message\"):\n                self.logger.error(message[\"message\"])\n\n        elif message[\"type\"] == \"lifespan.shutdown.complete\":\n            assert self.startup_event.is_set(), STATE_TRANSITION_ERROR\n            assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR\n            self.shutdown_event.set()\n\n    async def receive(self) -> LifespanReceiveMessage:\n        return await self.receive_queue.get()\n", "path": "uvicorn/lifespan/on.py"}], "after_files": [{"content": "import asyncio\nimport logging\nfrom asyncio import Queue\n\nfrom uvicorn import Config\nfrom uvicorn._types import LifespanReceiveMessage, LifespanScope, LifespanSendMessage\n\nSTATE_TRANSITION_ERROR = \"Got invalid state transition on lifespan protocol.\"\n\n\nclass LifespanOn:\n    def __init__(self, config: Config) -> None:\n        if not config.loaded:\n            config.load()\n\n        self.config = config\n        self.logger = logging.getLogger(\"uvicorn.error\")\n        self.startup_event = asyncio.Event()\n        self.shutdown_event = asyncio.Event()\n        self.receive_queue: \"Queue[LifespanReceiveMessage]\" = asyncio.Queue()\n        self.error_occured = False\n        self.startup_failed = False\n        self.shutdown_failed = False\n        self.should_exit = False\n\n    async def startup(self) -> None:\n        self.logger.info(\"Waiting for application startup.\")\n\n        loop = asyncio.get_event_loop()\n        loop.create_task(self.main())\n\n        await self.receive_queue.put({\"type\": \"lifespan.startup\"})\n        await self.startup_event.wait()\n\n        if self.startup_failed or (self.error_occured and self.config.lifespan == \"on\"):\n            self.logger.error(\"Application startup failed. Exiting.\")\n            self.should_exit = True\n        else:\n            self.logger.info(\"Application startup complete.\")\n\n    async def shutdown(self) -> None:\n        if self.error_occured:\n            return\n        self.logger.info(\"Waiting for application shutdown.\")\n        await self.receive_queue.put({\"type\": \"lifespan.shutdown\"})\n        await self.shutdown_event.wait()\n\n        if self.shutdown_failed or (\n            self.error_occured and self.config.lifespan == \"on\"\n        ):\n            self.logger.error(\"Application shutdown failed. Exiting.\")\n            self.should_exit = True\n        else:\n            self.logger.info(\"Application shutdown complete.\")\n\n    async def main(self) -> None:\n        try:\n            app = self.config.loaded_app\n            scope: LifespanScope = {\n                \"type\": \"lifespan\",\n                \"asgi\": {\"version\": self.config.asgi_version, \"spec_version\": \"2.0\"},\n            }\n            await app(scope, self.receive, self.send)\n        except BaseException as exc:\n            self.asgi = None\n            self.error_occured = True\n            if self.startup_failed or self.shutdown_failed:\n                return\n            if self.config.lifespan == \"auto\":\n                msg = \"ASGI 'lifespan' protocol appears unsupported.\"\n                self.logger.info(msg)\n            else:\n                msg = \"Exception in 'lifespan' protocol\\n\"\n                self.logger.error(msg, exc_info=exc)\n        finally:\n            self.startup_event.set()\n            self.shutdown_event.set()\n\n    async def send(self, message: LifespanSendMessage) -> None:\n        assert message[\"type\"] in (\n            \"lifespan.startup.complete\",\n            \"lifespan.startup.failed\",\n            \"lifespan.shutdown.complete\",\n            \"lifespan.shutdown.failed\",\n        )\n\n        if message[\"type\"] == \"lifespan.startup.complete\":\n            assert not self.startup_event.is_set(), STATE_TRANSITION_ERROR\n            assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR\n            self.startup_event.set()\n\n        elif message[\"type\"] == \"lifespan.startup.failed\":\n            assert not self.startup_event.is_set(), STATE_TRANSITION_ERROR\n            assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR\n            self.startup_event.set()\n            self.startup_failed = True\n            if message.get(\"message\"):\n                self.logger.error(message[\"message\"])\n\n        elif message[\"type\"] == \"lifespan.shutdown.complete\":\n            assert self.startup_event.is_set(), STATE_TRANSITION_ERROR\n            assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR\n            self.shutdown_event.set()\n\n        elif message[\"type\"] == \"lifespan.shutdown.failed\":\n            assert self.startup_event.is_set(), STATE_TRANSITION_ERROR\n            assert not self.shutdown_event.is_set(), STATE_TRANSITION_ERROR\n            self.shutdown_event.set()\n            self.shutdown_failed = True\n            if message.get(\"message\"):\n                self.logger.error(message[\"message\"])\n\n    async def receive(self) -> LifespanReceiveMessage:\n        return await self.receive_queue.get()\n", "path": "uvicorn/lifespan/on.py"}]}