Commit 0c6b4cb0 authored by Jason Madden's avatar Jason Madden

Fix libuv multiplex io watchers from polling too much when one event has been...

Fix libuv multiplex io watchers from polling too much when one event has been completely turned off. Fixes #1144.
parent 34d56d26
...@@ -38,6 +38,10 @@ ...@@ -38,6 +38,10 @@
- Hub objects now include the value of their ``name`` attribute in - Hub objects now include the value of their ``name`` attribute in
their repr. their repr.
- Fix libuv io watchers polling for events that only stopped watchers
are interested in, reducing CPU usage. Reported in :issue:`1144` by
wwqgtxx.
1.3a2 (2018-03-06) 1.3a2 (2018-03-06)
================== ==================
......
...@@ -263,6 +263,8 @@ class io(_base.IoMixin, watcher): ...@@ -263,6 +263,8 @@ class io(_base.IoMixin, watcher):
assert self._handle is not None assert self._handle is not None
self._watcher_start(self._watcher, self._events, self._watcher_callback) self._watcher_start(self._watcher, self._events, self._watcher_callback)
events = property(_get_events, _set_events)
def _watcher_ffi_start(self): def _watcher_ffi_start(self):
_dbg("Starting watcher", self, "with events", self._events) _dbg("Starting watcher", self, "with events", self._events)
self._watcher_start(self._watcher, self._events, self._watcher_callback) self._watcher_start(self._watcher, self._events, self._watcher_callback)
...@@ -326,8 +328,12 @@ class io(_base.IoMixin, watcher): ...@@ -326,8 +328,12 @@ class io(_base.IoMixin, watcher):
self.args = args self.args = args
watcher = self._watcher_ref watcher = self._watcher_ref
if watcher is not None and not watcher.active: if watcher is not None:
watcher._io_start() if not watcher.active:
watcher._io_start()
else:
# Make sure we're in the event mask
watcher._calc_and_update_events()
def stop(self): def stop(self):
_dbg("Stopping IO multiplex watcher for", self.fd, _dbg("Stopping IO multiplex watcher for", self.fd,
...@@ -360,6 +366,8 @@ class io(_base.IoMixin, watcher): ...@@ -360,6 +366,8 @@ class io(_base.IoMixin, watcher):
lambda self, nv: self._watcher_ref._set_fd(nv)) lambda self, nv: self._watcher_ref._set_fd(nv))
def _io_maybe_stop(self): def _io_maybe_stop(self):
_dbg("IO maybe stop on behalf of multiplex", self, "fd", self._fd, "events", self._events)
self._calc_and_update_events()
for w in self._multiplex_watchers: for w in self._multiplex_watchers:
if w.callback is not None: if w.callback is not None:
# There's still a reference to it, and it's started, # There's still a reference to it, and it's started,
...@@ -371,12 +379,15 @@ class io(_base.IoMixin, watcher): ...@@ -371,12 +379,15 @@ class io(_base.IoMixin, watcher):
def _io_start(self): def _io_start(self):
_dbg("IO start on behalf of multiplex", self, "fd", self._fd, "events", self._events) _dbg("IO start on behalf of multiplex", self, "fd", self._fd, "events", self._events)
self._calc_and_update_events()
self.start(self._io_callback, pass_events=True) self.start(self._io_callback, pass_events=True)
def _calc_and_update_events(self): def _calc_and_update_events(self):
events = 0 events = 0
for watcher in self._multiplex_watchers: for watcher in self._multiplex_watchers:
events |= watcher.events if watcher.callback is not None:
# Only ask for events that are active.
events |= watcher.events
self._set_events(events) self._set_events(events)
......
...@@ -69,6 +69,7 @@ from greentest.skipping import skipOnPyPy ...@@ -69,6 +69,7 @@ from greentest.skipping import skipOnPyPy
from greentest.skipping import skipOnPyPyOnCI from greentest.skipping import skipOnPyPyOnCI
from greentest.skipping import skipOnPyPy3 from greentest.skipping import skipOnPyPy3
from greentest.skipping import skipIf from greentest.skipping import skipIf
from greentest.skipping import skipOnLibev
from greentest.skipping import skipOnLibuv from greentest.skipping import skipOnLibuv
from greentest.skipping import skipOnLibuvOnWin from greentest.skipping import skipOnLibuvOnWin
from greentest.skipping import skipOnLibuvOnCI from greentest.skipping import skipOnLibuvOnCI
......
...@@ -269,6 +269,15 @@ if LIBUV: ...@@ -269,6 +269,15 @@ if LIBUV:
'test_socket.GeneralModuleTests.test_uknown_socket_family_repr', 'test_socket.GeneralModuleTests.test_uknown_socket_family_repr',
] ]
if RUN_COVERAGE:
disabled_tests += [
# Starting with #1145 this test (actually
# TestTLS_FTPClassMixin) becomes sensitive to timings
# under coverage.
'test_ftplib.TestFTPClass.test_storlines',
]
if sys.platform.startswith('linux'): if sys.platform.startswith('linux'):
disabled_tests += [ disabled_tests += [
......
...@@ -49,6 +49,7 @@ skipOnLibuvOnCI = _do_not_skip ...@@ -49,6 +49,7 @@ skipOnLibuvOnCI = _do_not_skip
skipOnLibuvOnCIOnPyPy = _do_not_skip skipOnLibuvOnCIOnPyPy = _do_not_skip
skipOnLibuvOnPyPyOnWin = _do_not_skip skipOnLibuvOnPyPyOnWin = _do_not_skip
skipOnLibev = _do_not_skip
if sysinfo.WIN: if sysinfo.WIN:
skipOnWindows = unittest.skip skipOnWindows = unittest.skip
...@@ -99,3 +100,5 @@ if sysinfo.LIBUV: ...@@ -99,3 +100,5 @@ if sysinfo.LIBUV:
skipOnLibuvOnWin = unittest.skip skipOnLibuvOnWin = unittest.skip
if sysinfo.PYPY: if sysinfo.PYPY:
skipOnLibuvOnPyPyOnWin = unittest.skip skipOnLibuvOnPyPyOnWin = unittest.skip
else:
skipOnLibev = unittest.skip
...@@ -71,6 +71,41 @@ class TestWatchers(unittest.TestCase): ...@@ -71,6 +71,41 @@ class TestWatchers(unittest.TestCase):
io.start(lambda: None) io.start(lambda: None)
io.close() io.close()
@greentest.skipOnLibev("libuv-specific")
@greentest.skipOnWindows("Destroying the loop somehow fails")
def test_io_multiplex_events(self):
# pylint:disable=no-member
import socket
sock = socket.socket()
fd = sock.fileno()
read = self.loop.io(fd, core.READ)
write = self.loop.io(fd, core.WRITE)
try:
real_watcher = read._watcher_ref
read.start(lambda: None)
self.assertEqual(real_watcher.events, core.READ)
write.start(lambda: None)
self.assertEqual(real_watcher.events, core.READ | core.WRITE)
write.stop()
self.assertEqual(real_watcher.events, core.READ)
write.start(lambda: None)
self.assertEqual(real_watcher.events, core.READ | core.WRITE)
read.stop()
self.assertEqual(real_watcher.events, core.WRITE)
write.stop()
self.assertEqual(real_watcher.events, 0)
finally:
read.close()
write.close()
sock.close()
def test_timer_constructor(self): def test_timer_constructor(self):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment