Skip to content

Commit 28ea38b

Browse files
bpo-26819: Prevent proactor double read on resume (GH-6921)
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 <[email protected]>
1 parent f0af69f commit 28ea38b

File tree

3 files changed

+17
-2
lines changed

3 files changed

+17
-2
lines changed

‎Lib/asyncio/proactor_events.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ def __init__(self, loop, sock, protocol, waiter=None,
161161
extra=None, server=None):
162162
super().__init__(loop, sock, protocol, waiter, extra, server)
163163
self._paused = False
164+
self._reschedule_on_resume = False
164165

165166
if protocols._is_buffered_protocol(protocol):
166167
self._loop_reading = self._loop_reading__get_buffer
@@ -180,6 +181,7 @@ def pause_reading(self):
180181
if self._read_fut is not None and not self._read_fut.done():
181182
self._read_fut.cancel()
182183
self._read_fut = None
184+
self._reschedule_on_resume = True
183185

184186
if self._loop.get_debug():
185187
logger.debug("%r pauses reading", self)
@@ -188,7 +190,9 @@ def resume_reading(self):
188190
if self._closing or not self._paused:
189191
return
190192
self._paused = False
191-
self._loop.call_soon(self._loop_reading, self._read_fut)
193+
if self._reschedule_on_resume:
194+
self._loop.call_soon(self._loop_reading, self._read_fut)
195+
self._reschedule_on_resume = False
192196
if self._loop.get_debug():
193197
logger.debug("%r resumes reading", self)
194198

@@ -208,6 +212,7 @@ def _loop_reading__on_eof(self):
208212

209213
def _loop_reading__data_received(self, fut=None):
210214
if self._paused:
215+
self._reschedule_on_resume = True
211216
return
212217

213218
data = None
@@ -257,6 +262,7 @@ def _loop_reading__data_received(self, fut=None):
257262

258263
def _loop_reading__get_buffer(self, fut=None):
259264
if self._paused:
265+
self._reschedule_on_resume = True
260266
return
261267

262268
nbytes = None

‎Lib/test/test_asyncio/test_proactor_events.py‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ def test_write_eof_duplex_pipe(self):
334334
def test_pause_resume_reading(self):
335335
tr = self.socket_transport()
336336
futures = []
337-
for msg in [b'data1', b'data2', b'data3', b'data4', b'']:
337+
for msg in [b'data1', b'data2', b'data3', b'data4', b'data5', b'']:
338338
f = asyncio.Future(loop=self.loop)
339339
f.set_result(msg)
340340
futures.append(f)
@@ -364,6 +364,13 @@ def test_pause_resume_reading(self):
364364
self.protocol.data_received.assert_called_with(b'data3')
365365
self.loop._run_once()
366366
self.protocol.data_received.assert_called_with(b'data4')
367+
368+
tr.pause_reading()
369+
tr.resume_reading()
370+
self.loop.call_exception_handler = mock.Mock()
371+
self.loop._run_once()
372+
self.loop.call_exception_handler.assert_not_called()
373+
self.protocol.data_received.assert_called_with(b'data5')
367374
tr.close()
368375

369376
self.assertFalse(tr.is_reading())
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)