prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Canceling async Redis command leaves connection open, in unsafe state for future commands
  
  **Version**: 4.5.3
  
  **Platform**: Python 3.8 on Ubuntu  / Generic
  
  **Description**: Canceling async Redis command leaves connection open, in unsafe state for future commands
  
  This is a reincarnation of #2624, which was closed with an incomplete fix and a possibly unreliable test case. This is the same issue that recently got a lot of attention due to ChatGPT outage, and that remains only partially fixed. The cancellation shielding introduced in #2641 addressed only the cancellation of Redis pipeline operation, but non-pipelined ops are still vulnerable.
  
  This time I am attaching a script that reproduces the issue reliably without relying on an external, slow Redis server. This is achieved by inserting a small TCP socket proxy between the Redis client and local Redis server, with the proxy introducing a 0.1 second delay when sending data in either direction. 
  
  Running this script with a Redis server running locally on port 6379 produces the following output:
  ```
  $ python redis_cancel.py 
  managed to cancel the task, connection is left open with unread response
  bar: b'foo'
  ping: False
  foo: b'PONG'
  ```
  
  ```python
  import asyncio
  
  from redis.asyncio import Redis
  
  
  async def pipe(reader: asyncio.StreamReader, writer: asyncio.StreamWriter, delay: float, name=''):
      while data := await reader.read(1000):
          # print(name, 'received:', data)
          await asyncio.sleep(delay)
          writer.write(data)
          await writer.drain()
  
  
  class DelayProxy:
  
      def __init__(self, addr, redis_addr, delay: float):
          self.addr = addr
          self.redis_addr = redis_addr
          self.delay = delay
  
      async def start(self):
          server = await asyncio.start_server(self.handle, *self.addr)
          asyncio.create_task(server.serve_forever())
  
      async def handle(self, reader, writer):
          # establish connection to redis
          redis_reader, redis_writer = await asyncio.open_connection(*self.redis_addr)
          pipe1 = asyncio.create_task(pipe(reader, redis_writer, self.delay, 'to redis:'))
          pipe2 = asyncio.create_task(pipe(redis_reader, writer, self.delay, 'from redis:'))
          await asyncio.gather(pipe1, pipe2)
  
  
  async def main():
  
      # create a tcp socket proxy that relays data to Redis and back, inserting 0.1 seconds of delay
      dp = DelayProxy(addr=('localhost', 6380), redis_addr=('localhost', 6379), delay=0.1)
      await dp.start()
  
      # note that we connect to proxy, rather than to Redis directly
      async with Redis(host='localhost', port=6380) as r:
  
          await r.set('foo', 'foo')
          await r.set('bar', 'bar')
  
          t = asyncio.create_task(r.get('foo'))
          await asyncio.sleep(0.050)
          t.cancel()
          try:
              await t
              print('try again, we did not cancel the task in time')
          except asyncio.CancelledError:
              print('managed to cancel the task, connection is left open with unread response')
  
          print('bar:', await r.get('bar'))
          print('ping:', await r.ping())
          print('foo:', await r.get('foo'))
  
  if __name__ == '__main__':
      asyncio.run(main())
  ```
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `setup.py`
  Content:
  ```
  1 #!/usr/bin/env python
  2 from setuptools import find_packages, setup
  3 
  4 setup(
  5     name="redis",
  6     description="Python client for Redis database and key-value store",
  7     long_description=open("README.md").read().strip(),
  8     long_description_content_type="text/markdown",
  9     keywords=["Redis", "key-value store", "database"],
  10     license="MIT",
  11     version="4.5.3",
  12     packages=find_packages(
  13         include=[
  14             "redis",
  15             "redis.asyncio",
  16             "redis.commands",
  17             "redis.commands.bf",
  18             "redis.commands.json",
  19             "redis.commands.search",
  20             "redis.commands.timeseries",
  21             "redis.commands.graph",
  22         ]
  23     ),
  24     url="https://github.com/redis/redis-py",
  25     project_urls={
  26         "Documentation": "https://redis.readthedocs.io/en/latest/",
  27         "Changes": "https://github.com/redis/redis-py/releases",
  28         "Code": "https://github.com/redis/redis-py",
  29         "Issue tracker": "https://github.com/redis/redis-py/issues",
  30     },
  31     author="Redis Inc.",
  32     author_email="oss@redis.com",
  33     python_requires=">=3.7",
  34     install_requires=[
  35         'importlib-metadata >= 1.0; python_version < "3.8"',
  36         'typing-extensions; python_version<"3.8"',
  37         'async-timeout>=4.0.2; python_version<="3.11.2"',
  38     ],
  39     classifiers=[
  40         "Development Status :: 5 - Production/Stable",
  41         "Environment :: Console",
  42         "Intended Audience :: Developers",
  43         "License :: OSI Approved :: MIT License",
  44         "Operating System :: OS Independent",
  45         "Programming Language :: Python",
  46         "Programming Language :: Python :: 3",
  47         "Programming Language :: Python :: 3 :: Only",
  48         "Programming Language :: Python :: 3.7",
  49         "Programming Language :: Python :: 3.8",
  50         "Programming Language :: Python :: 3.9",
  51         "Programming Language :: Python :: 3.10",
  52         "Programming Language :: Python :: 3.11",
  53         "Programming Language :: Python :: Implementation :: CPython",
  54         "Programming Language :: Python :: Implementation :: PyPy",
  55     ],
  56     extras_require={
  57         "hiredis": ["hiredis>=1.0.0"],
  58         "ocsp": ["cryptography>=36.0.1", "pyopenssl==20.0.1", "requests>=2.26.0"],
  59     },
  60 )
  61
  ```
  --- 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/setup.py b/setup.py
  --- a/setup.py
  +++ b/setup.py
  @@ -8,7 +8,7 @@
       long_description_content_type="text/markdown",
       keywords=["Redis", "key-value store", "database"],
       license="MIT",
  -    version="4.5.3",
  +    version="4.5.4",
       packages=find_packages(
           include=[
               "redis",
  
verification_info: {"golden_diff": "diff --git a/setup.py b/setup.py\n--- a/setup.py\n+++ b/setup.py\n@@ -8,7 +8,7 @@\n     long_description_content_type=\"text/markdown\",\n     keywords=[\"Redis\", \"key-value store\", \"database\"],\n     license=\"MIT\",\n-    version=\"4.5.3\",\n+    version=\"4.5.4\",\n     packages=find_packages(\n         include=[\n             \"redis\",\n", "issue": "Canceling async Redis command leaves connection open, in unsafe state for future commands\n\r\n**Version**: 4.5.3\r\n\r\n**Platform**: Python 3.8 on Ubuntu  / Generic\r\n\r\n**Description**: Canceling async Redis command leaves connection open, in unsafe state for future commands\r\n\r\nThis is a reincarnation of #2624, which was closed with an incomplete fix and a possibly unreliable test case. This is the same issue that recently got a lot of attention due to ChatGPT outage, and that remains only partially fixed. The cancellation shielding introduced in #2641 addressed only the cancellation of Redis pipeline operation, but non-pipelined ops are still vulnerable.\r\n\r\nThis time I am attaching a script that reproduces the issue reliably without relying on an external, slow Redis server. This is achieved by inserting a small TCP socket proxy between the Redis client and local Redis server, with the proxy introducing a 0.1 second delay when sending data in either direction. \r\n\r\nRunning this script with a Redis server running locally on port 6379 produces the following output:\r\n```\r\n$ python redis_cancel.py \r\nmanaged to cancel the task, connection is left open with unread response\r\nbar: b'foo'\r\nping: False\r\nfoo: b'PONG'\r\n```\r\n\r\n```python\r\nimport asyncio\r\n\r\nfrom redis.asyncio import Redis\r\n\r\n\r\nasync def pipe(reader: asyncio.StreamReader, writer: asyncio.StreamWriter, delay: float, name=''):\r\n    while data := await reader.read(1000):\r\n        # print(name, 'received:', data)\r\n        await asyncio.sleep(delay)\r\n        writer.write(data)\r\n        await writer.drain()\r\n\r\n\r\nclass DelayProxy:\r\n\r\n    def __init__(self, addr, redis_addr, delay: float):\r\n        self.addr = addr\r\n        self.redis_addr = redis_addr\r\n        self.delay = delay\r\n\r\n    async def start(self):\r\n        server = await asyncio.start_server(self.handle, *self.addr)\r\n        asyncio.create_task(server.serve_forever())\r\n\r\n    async def handle(self, reader, writer):\r\n        # establish connection to redis\r\n        redis_reader, redis_writer = await asyncio.open_connection(*self.redis_addr)\r\n        pipe1 = asyncio.create_task(pipe(reader, redis_writer, self.delay, 'to redis:'))\r\n        pipe2 = asyncio.create_task(pipe(redis_reader, writer, self.delay, 'from redis:'))\r\n        await asyncio.gather(pipe1, pipe2)\r\n\r\n\r\nasync def main():\r\n\r\n    # create a tcp socket proxy that relays data to Redis and back, inserting 0.1 seconds of delay\r\n    dp = DelayProxy(addr=('localhost', 6380), redis_addr=('localhost', 6379), delay=0.1)\r\n    await dp.start()\r\n\r\n    # note that we connect to proxy, rather than to Redis directly\r\n    async with Redis(host='localhost', port=6380) as r:\r\n\r\n        await r.set('foo', 'foo')\r\n        await r.set('bar', 'bar')\r\n\r\n        t = asyncio.create_task(r.get('foo'))\r\n        await asyncio.sleep(0.050)\r\n        t.cancel()\r\n        try:\r\n            await t\r\n            print('try again, we did not cancel the task in time')\r\n        except asyncio.CancelledError:\r\n            print('managed to cancel the task, connection is left open with unread response')\r\n\r\n        print('bar:', await r.get('bar'))\r\n        print('ping:', await r.ping())\r\n        print('foo:', await r.get('foo'))\r\n\r\nif __name__ == '__main__':\r\n    asyncio.run(main())\r\n```\r\n\n", "before_files": [{"content": "#!/usr/bin/env python\nfrom setuptools import find_packages, setup\n\nsetup(\n    name=\"redis\",\n    description=\"Python client for Redis database and key-value store\",\n    long_description=open(\"README.md\").read().strip(),\n    long_description_content_type=\"text/markdown\",\n    keywords=[\"Redis\", \"key-value store\", \"database\"],\n    license=\"MIT\",\n    version=\"4.5.3\",\n    packages=find_packages(\n        include=[\n            \"redis\",\n            \"redis.asyncio\",\n            \"redis.commands\",\n            \"redis.commands.bf\",\n            \"redis.commands.json\",\n            \"redis.commands.search\",\n            \"redis.commands.timeseries\",\n            \"redis.commands.graph\",\n        ]\n    ),\n    url=\"https://github.com/redis/redis-py\",\n    project_urls={\n        \"Documentation\": \"https://redis.readthedocs.io/en/latest/\",\n        \"Changes\": \"https://github.com/redis/redis-py/releases\",\n        \"Code\": \"https://github.com/redis/redis-py\",\n        \"Issue tracker\": \"https://github.com/redis/redis-py/issues\",\n    },\n    author=\"Redis Inc.\",\n    author_email=\"oss@redis.com\",\n    python_requires=\">=3.7\",\n    install_requires=[\n        'importlib-metadata >= 1.0; python_version < \"3.8\"',\n        'typing-extensions; python_version<\"3.8\"',\n        'async-timeout>=4.0.2; python_version<=\"3.11.2\"',\n    ],\n    classifiers=[\n        \"Development Status :: 5 - Production/Stable\",\n        \"Environment :: Console\",\n        \"Intended Audience :: Developers\",\n        \"License :: OSI Approved :: MIT License\",\n        \"Operating System :: OS Independent\",\n        \"Programming Language :: Python\",\n        \"Programming Language :: Python :: 3\",\n        \"Programming Language :: Python :: 3 :: Only\",\n        \"Programming Language :: Python :: 3.7\",\n        \"Programming Language :: Python :: 3.8\",\n        \"Programming Language :: Python :: 3.9\",\n        \"Programming Language :: Python :: 3.10\",\n        \"Programming Language :: Python :: 3.11\",\n        \"Programming Language :: Python :: Implementation :: CPython\",\n        \"Programming Language :: Python :: Implementation :: PyPy\",\n    ],\n    extras_require={\n        \"hiredis\": [\"hiredis>=1.0.0\"],\n        \"ocsp\": [\"cryptography>=36.0.1\", \"pyopenssl==20.0.1\", \"requests>=2.26.0\"],\n    },\n)\n", "path": "setup.py"}], "after_files": [{"content": "#!/usr/bin/env python\nfrom setuptools import find_packages, setup\n\nsetup(\n    name=\"redis\",\n    description=\"Python client for Redis database and key-value store\",\n    long_description=open(\"README.md\").read().strip(),\n    long_description_content_type=\"text/markdown\",\n    keywords=[\"Redis\", \"key-value store\", \"database\"],\n    license=\"MIT\",\n    version=\"4.5.4\",\n    packages=find_packages(\n        include=[\n            \"redis\",\n            \"redis.asyncio\",\n            \"redis.commands\",\n            \"redis.commands.bf\",\n            \"redis.commands.json\",\n            \"redis.commands.search\",\n            \"redis.commands.timeseries\",\n            \"redis.commands.graph\",\n        ]\n    ),\n    url=\"https://github.com/redis/redis-py\",\n    project_urls={\n        \"Documentation\": \"https://redis.readthedocs.io/en/latest/\",\n        \"Changes\": \"https://github.com/redis/redis-py/releases\",\n        \"Code\": \"https://github.com/redis/redis-py\",\n        \"Issue tracker\": \"https://github.com/redis/redis-py/issues\",\n    },\n    author=\"Redis Inc.\",\n    author_email=\"oss@redis.com\",\n    python_requires=\">=3.7\",\n    install_requires=[\n        'importlib-metadata >= 1.0; python_version < \"3.8\"',\n        'typing-extensions; python_version<\"3.8\"',\n        'async-timeout>=4.0.2; python_version<=\"3.11.2\"',\n    ],\n    classifiers=[\n        \"Development Status :: 5 - Production/Stable\",\n        \"Environment :: Console\",\n        \"Intended Audience :: Developers\",\n        \"License :: OSI Approved :: MIT License\",\n        \"Operating System :: OS Independent\",\n        \"Programming Language :: Python\",\n        \"Programming Language :: Python :: 3\",\n        \"Programming Language :: Python :: 3 :: Only\",\n        \"Programming Language :: Python :: 3.7\",\n        \"Programming Language :: Python :: 3.8\",\n        \"Programming Language :: Python :: 3.9\",\n        \"Programming Language :: Python :: 3.10\",\n        \"Programming Language :: Python :: 3.11\",\n        \"Programming Language :: Python :: Implementation :: CPython\",\n        \"Programming Language :: Python :: Implementation :: PyPy\",\n    ],\n    extras_require={\n        \"hiredis\": [\"hiredis>=1.0.0\"],\n        \"ocsp\": [\"cryptography>=36.0.1\", \"pyopenssl==20.0.1\", \"requests>=2.26.0\"],\n    },\n)\n", "path": "setup.py"}]}