Skip to content

Commit 749afe8

Browse files
CtrlZviasvetlov
authored andcommitted
[3.6] bpo-26819: Prevent proactor double read on resume (GH-6921) (#7110)
The proactor event loop has a race condition when reading with pausing/resuming. `resume_reading()` unconditionally schedules the read function to read from the current future. If `resume_reading()` was called before the previously scheduled done callback fires, this results in two attempts to get the data from the most recent read and an assertion failure. This commit tracks whether or not `resume_reading` needs to reschedule the callback to restart the loop, preventing a second attempt to read the data.. (cherry picked from commit 4151061) Co-authored-by: CtrlZvi <viz+github@flippedperspective.com>
1 parent 36f066a commit 749afe8

3 files changed

Lines changed: 15 additions & 2 deletions

File tree

Lib/asyncio/proactor_events.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ def __init__(self, loop, sock, protocol, waiter=None,
156156
extra=None, server=None):
157157
super().__init__(loop, sock, protocol, waiter, extra, server)
158158
self._paused = False
159+
self._reschedule_on_resume = False
159160
self._loop.call_soon(self._loop_reading)
160161

161162
def pause_reading(self):
@@ -173,12 +174,15 @@ def resume_reading(self):
173174
self._paused = False
174175
if self._closing:
175176
return
176-
self._loop.call_soon(self._loop_reading, self._read_fut)
177+
if self._reschedule_on_resume:
178+
self._loop.call_soon(self._loop_reading, self._read_fut)
179+
self._reschedule_on_resume = False
177180
if self._loop.get_debug():
178181
logger.debug("%r resumes reading", self)
179182

180183
def _loop_reading(self, fut=None):
181184
if self._paused:
185+
self._reschedule_on_resume = True
182186
return
183187
data = None
184188

Lib/test/test_asyncio/test_proactor_events.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ def test_write_eof_duplex_pipe(self):
330330
def test_pause_resume_reading(self):
331331
tr = self.socket_transport()
332332
futures = []
333-
for msg in [b'data1', b'data2', b'data3', b'data4', b'']:
333+
for msg in [b'data1', b'data2', b'data3', b'data4', b'data5', b'']:
334334
f = asyncio.Future(loop=self.loop)
335335
f.set_result(msg)
336336
futures.append(f)
@@ -352,6 +352,13 @@ def test_pause_resume_reading(self):
352352
self.protocol.data_received.assert_called_with(b'data3')
353353
self.loop._run_once()
354354
self.protocol.data_received.assert_called_with(b'data4')
355+
356+
tr.pause_reading()
357+
tr.resume_reading()
358+
self.loop.call_exception_handler = mock.Mock()
359+
self.loop._run_once()
360+
self.loop.call_exception_handler.assert_not_called()
361+
self.protocol.data_received.assert_called_with(b'data5')
355362
tr.close()
356363

357364

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix race condition with `ReadTransport.resume_reading` in Windows proactor
2+
event loop.

0 commit comments

Comments
 (0)