title: asyncio.wait_for does not propagate CancelledError, if waited on future is "done" before cancellation
body: <p>I'm working on an application that features tasks that can be started by the user. These tasks can also be aborted by the user. The notable thing is, that the cancellation can happen at any given time. I implemented this by using <code>asyncio.tasks.Task</code> which I cancel if the user aborts. I recently updated from Python 3.8.5 to 3.10 (on Windows 10), after which the cancellation of my <code>Task</code>s stopped working as expected.</p>
  <p>After some digging, I think I found where things go awry: These tasks naturally involve a lot of I/O that is also unreliable, so I'm working with timeouts in the form of <code>asyncio.wait_for()</code>. It seems that the <code>CancelledError</code> gets swallowed within <code>wait_for()</code> if the future that is waited on is already done at the time of cancellation. Consider this code snippet from Python 3.8.5:</p>
  <pre class="lang-py prettyprint-override"><code># asyncio.tasks.wait_for() / Python 3.8.5
  # wait until the future completes or the timeout
          try:
              await waiter
          except futures.CancelledError:
              fut.remove_done_callback(cb)
              fut.cancel()
              raise # reraise CancelledError -&gt; all is fine
  
          if fut.done():
              return fut.result()
          else:
              fut.remove_done_callback(cb)
              # We must ensure that the task is not running
              # after wait_for() returns.
              # See https://bugs.python.org/issue32751
              await _cancel_and_wait(fut, loop=loop)
              raise futures.TimeoutError()
  </code></pre>
  <p>versus Python 3.10:</p>
  <pre class="lang-py prettyprint-override"><code># asyncio.tasks.wait_for() / Python 3.10
  # wait until the future completes or the timeout
          try:
              await waiter
          except exceptions.CancelledError:
              if fut.done(): # future already done
                  return fut.result() # return result without raising -&gt; task is not aborted
              else:
                  fut.remove_done_callback(cb)
                  # We must ensure that the task is not running
                  # after wait_for() returns.
                  # See https://bugs.python.org/issue32751
                  await _cancel_and_wait(fut, loop=loop)
                  raise
  </code></pre>
  <p>This piece of code reproduces the error:</p>
  <pre class="lang-py prettyprint-override"><code>import asyncio
  
  async def im_done():
      print(&quot;done&quot;)
  
  async def nested_wait(timeout):
      await asyncio.wait_for(im_done(), timeout=timeout)
  
  async def test_cancel():
      task = asyncio.create_task(nested_wait(30))
      await asyncio.sleep(0)
      task.cancel()
      await asyncio.sleep(0)
      
      await task # raises on 3.8.5 / doesn't raise on 3.10
  
  asyncio.run(test_cancel())
  </code></pre>
  <p>Is this expected behaviour or is this a bug? Note that <code>im_done()</code> in my example is in reality a coroutine that does actual I/O. My tests for aborting always fail, but I don't fully understand how the &quot;finished&quot; state of the future comes to pass and why the <code>CancelledError</code> is raised within <code>wait_for()</code> if the waited on future is already done. If it is by pure chance and timing or if I'm actually using the API wrong. But since everything was working perfectly before that code change in <code>asyncio.tasks.wait_for</code> I'm guessing this could be a bug.</p>