Bug report
Bug description:
Summary
Cancelling loop.sendfile() while its native implementation is waiting for the transport's write buffer to become empty leaves the transport in a partially modified state. The transport remains paused and retains its internal empty-buffer waiter. The selector implementation additionally leaves the transport absent from the event loop's transport registry.
The cancellation window exists in both BaseSelectorEventLoop._sendfile_native() and BaseProactorEventLoop._sendfile_native().
Reproduction Code
import asyncio
loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)
class Transport:
_sendfile_compatible = asyncio.constants._SendfileMode.TRY_NATIVE
_sock = None
def __init__(self):
self.paused = False
self.resumed = False
self.empty_waiter = None
def is_closing(self):
return False
def is_reading(self):
return True
def pause_reading(self):
self.paused = True
def resume_reading(self):
self.resumed = True
def _make_empty_waiter(self):
self.empty_waiter = loop.create_future()
return self.empty_waiter
def _reset_empty_waiter(self):
self.empty_waiter = None
async def main():
transport = Transport()
task = asyncio.create_task(loop.sendfile(transport, None))
await asyncio.sleep(0)
task.cancel()
await asyncio.gather(task, return_exceptions=True)
print(
transport.paused,
transport.resumed,
transport.empty_waiter is not None,
)
loop.run_until_complete(main())
loop.close()
Actual Behavior
The controlled transport is paused but never resumed, and its waiter is not reset:
On the selector implementation, the same cancellation point also leaves transport._sock_fd absent from loop._transports.
Expected Behavior
Cancellation should propagate without leaving the transport in a partially modified state. Reading should be restored when it was active before sendfile(), the empty-buffer waiter should be reset, and the selector implementation should restore the transport registry entry.
CPython versions tested on:
CPython main branch
Operating systems tested on:
Windows
Linked PRs
Bug report
Bug description:
Summary
Cancelling
loop.sendfile()while its native implementation is waiting for the transport's write buffer to become empty leaves the transport in a partially modified state. The transport remains paused and retains its internal empty-buffer waiter. The selector implementation additionally leaves the transport absent from the event loop's transport registry.The cancellation window exists in both
BaseSelectorEventLoop._sendfile_native()andBaseProactorEventLoop._sendfile_native().Reproduction Code
Actual Behavior
The controlled transport is paused but never resumed, and its waiter is not reset:
On the selector implementation, the same cancellation point also leaves
transport._sock_fdabsent fromloop._transports.Expected Behavior
Cancellation should propagate without leaving the transport in a partially modified state. Reading should be restored when it was active before
sendfile(), the empty-buffer waiter should be reset, and the selector implementation should restore the transport registry entry.CPython versions tested on:
CPython main branch
Operating systems tested on:
Windows
Linked PRs