Skip to content

Commit 142e3c0

Browse files
authored
[3.6] bpo-32356: idempotent pause_/resume_reading (GH-4914) (GH-7629)
Backport note: don't add new is_reading() method from master to 3.6. (cherry picked from commit d757aaf)
1 parent 961332d commit 142e3c0

File tree

7 files changed

+43
-22
lines changed

7 files changed

+43
-22
lines changed

Doc/library/asyncio-protocol.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,19 @@ ReadTransport
124124
the protocol's :meth:`data_received` method until :meth:`resume_reading`
125125
is called.
126126

127+
.. versionchanged:: 3.6.7
128+
The method is idempotent, i.e. it can be called when the
129+
transport is already paused or closed.
130+
127131
.. method:: resume_reading()
128132

129133
Resume the receiving end. The protocol's :meth:`data_received` method
130134
will be called once again if some data is available for reading.
131135

136+
.. versionchanged:: 3.6.7
137+
The method is idempotent, i.e. it can be called when the
138+
transport is already reading.
139+
132140

133141
WriteTransport
134142
--------------

Lib/asyncio/proactor_events.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,20 +160,16 @@ def __init__(self, loop, sock, protocol, waiter=None,
160160
self._loop.call_soon(self._loop_reading)
161161

162162
def pause_reading(self):
163-
if self._closing:
164-
raise RuntimeError('Cannot pause_reading() when closing')
165-
if self._paused:
166-
raise RuntimeError('Already paused')
163+
if self._closing or self._paused:
164+
return
167165
self._paused = True
168166
if self._loop.get_debug():
169167
logger.debug("%r pauses reading", self)
170168

171169
def resume_reading(self):
172-
if not self._paused:
173-
raise RuntimeError('Not paused')
174-
self._paused = False
175-
if self._closing:
170+
if self._closing or not self._paused:
176171
return
172+
self._paused = False
177173
if self._reschedule_on_resume:
178174
self._loop.call_soon(self._loop_reading, self._read_fut)
179175
self._reschedule_on_resume = False

Lib/asyncio/selector_events.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -703,18 +703,16 @@ def __init__(self, loop, sock, protocol, waiter=None,
703703
waiter, None)
704704

705705
def pause_reading(self):
706-
if self._closing:
707-
raise RuntimeError('Cannot pause_reading() when closing')
708-
if self._paused:
709-
raise RuntimeError('Already paused')
706+
if self._closing or self._paused:
707+
return
710708
self._paused = True
711709
self._loop._remove_reader(self._sock_fd)
712710
if self._loop.get_debug():
713711
logger.debug("%r pauses reading", self)
714712

715713
def resume_reading(self):
716-
if not self._paused:
717-
raise RuntimeError('Not paused')
714+
if self._closing or not self._paused:
715+
return
718716
self._paused = False
719717
self._add_reader(self._sock_fd, self._read_ready)
720718
if self._loop.get_debug():

Lib/asyncio/test_utils.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -335,12 +335,19 @@ def _remove_reader(self, fd):
335335
return False
336336

337337
def assert_reader(self, fd, callback, *args):
338-
assert fd in self.readers, 'fd {} is not registered'.format(fd)
338+
if fd not in self.readers:
339+
raise AssertionError(f'fd {fd} is not registered')
339340
handle = self.readers[fd]
340-
assert handle._callback == callback, '{!r} != {!r}'.format(
341-
handle._callback, callback)
342-
assert handle._args == args, '{!r} != {!r}'.format(
343-
handle._args, args)
341+
if handle._callback != callback:
342+
raise AssertionError(
343+
f'unexpected callback: {handle._callback} != {callback}')
344+
if handle._args != args:
345+
raise AssertionError(
346+
f'unexpected callback args: {handle._args} != {args}')
347+
348+
def assert_no_reader(self, fd):
349+
if fd in self.readers:
350+
raise AssertionError(f'fd {fd} is registered')
344351

345352
def _add_writer(self, fd, callback, *args):
346353
self.writers[fd] = events.Handle(callback, args, self)

Lib/test/test_asyncio/test_proactor_events.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,18 +334,23 @@ def test_pause_resume_reading(self):
334334
f = asyncio.Future(loop=self.loop)
335335
f.set_result(msg)
336336
futures.append(f)
337+
337338
self.loop._proactor.recv.side_effect = futures
338339
self.loop._run_once()
339340
self.assertFalse(tr._paused)
340341
self.loop._run_once()
341342
self.protocol.data_received.assert_called_with(b'data1')
342343
self.loop._run_once()
343344
self.protocol.data_received.assert_called_with(b'data2')
345+
346+
tr.pause_reading()
344347
tr.pause_reading()
345348
self.assertTrue(tr._paused)
346349
for i in range(10):
347350
self.loop._run_once()
348351
self.protocol.data_received.assert_called_with(b'data2')
352+
353+
tr.resume_reading()
349354
tr.resume_reading()
350355
self.assertFalse(tr._paused)
351356
self.loop._run_once()

Lib/test/test_asyncio/test_selector_events.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def test_make_ssl_transport(self):
8181
with test_utils.disable_logger():
8282
transport = self.loop._make_ssl_transport(
8383
m, asyncio.Protocol(), m, waiter)
84+
8485
# execute the handshake while the logger is disabled
8586
# to ignore SSL handshake failure
8687
test_utils.run_briefly(self.loop)
@@ -884,14 +885,19 @@ def test_pause_resume_reading(self):
884885
test_utils.run_briefly(self.loop)
885886
self.assertFalse(tr._paused)
886887
self.loop.assert_reader(7, tr._read_ready)
888+
889+
tr.pause_reading()
887890
tr.pause_reading()
888891
self.assertTrue(tr._paused)
889-
self.assertFalse(7 in self.loop.readers)
892+
self.loop.assert_no_reader(7)
893+
894+
tr.resume_reading()
890895
tr.resume_reading()
891896
self.assertFalse(tr._paused)
892897
self.loop.assert_reader(7, tr._read_ready)
893-
with self.assertRaises(RuntimeError):
894-
tr.resume_reading()
898+
899+
tr.close()
900+
self.loop.assert_no_reader(7)
895901

896902
def test_read_ready(self):
897903
transport = self.socket_transport()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
asyncio.transport.resume_reading() and pause_reading() are now idempotent.

0 commit comments

Comments
 (0)