Issue #19842: Refactor BaseSelector to make it an actual usable ABC.

parent be0708f0
...@@ -142,9 +142,23 @@ def make_test_protocol(base): ...@@ -142,9 +142,23 @@ def make_test_protocol(base):
class TestSelector(selectors.BaseSelector): class TestSelector(selectors.BaseSelector):
def __init__(self):
self.keys = {}
def register(self, fileobj, events, data=None):
key = selectors.SelectorKey(fileobj, 0, events, data)
self.keys[fileobj] = key
return key
def unregister(self, fileobj):
return self.keys.pop(fileobj)
def select(self, timeout): def select(self, timeout):
return [] return []
def get_map(self):
return self.keys
class TestLoop(base_events.BaseEventLoop): class TestLoop(base_events.BaseEventLoop):
"""Loop for unittests. """Loop for unittests.
......
...@@ -64,7 +64,7 @@ class _SelectorMapping(Mapping): ...@@ -64,7 +64,7 @@ class _SelectorMapping(Mapping):
class BaseSelector(metaclass=ABCMeta): class BaseSelector(metaclass=ABCMeta):
"""Base selector class. """Selector abstract base class.
A selector supports registering file objects to be monitored for specific A selector supports registering file objects to be monitored for specific
I/O events. I/O events.
...@@ -78,12 +78,7 @@ class BaseSelector(metaclass=ABCMeta): ...@@ -78,12 +78,7 @@ class BaseSelector(metaclass=ABCMeta):
performant implementation on the current platform. performant implementation on the current platform.
""" """
def __init__(self): @abstractmethod
# this maps file descriptors to keys
self._fd_to_key = {}
# read-only mapping returned by get_map()
self._map = _SelectorMapping(self)
def register(self, fileobj, events, data=None): def register(self, fileobj, events, data=None):
"""Register a file object. """Register a file object.
...@@ -95,18 +90,9 @@ class BaseSelector(metaclass=ABCMeta): ...@@ -95,18 +90,9 @@ class BaseSelector(metaclass=ABCMeta):
Returns: Returns:
SelectorKey instance SelectorKey instance
""" """
if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)): raise NotImplementedError
raise ValueError("Invalid events: {!r}".format(events))
key = SelectorKey(fileobj, _fileobj_to_fd(fileobj), events, data)
if key.fd in self._fd_to_key:
raise KeyError("{!r} (FD {}) is already "
"registered".format(fileobj, key.fd))
self._fd_to_key[key.fd] = key
return key
@abstractmethod
def unregister(self, fileobj): def unregister(self, fileobj):
"""Unregister a file object. """Unregister a file object.
...@@ -116,11 +102,7 @@ class BaseSelector(metaclass=ABCMeta): ...@@ -116,11 +102,7 @@ class BaseSelector(metaclass=ABCMeta):
Returns: Returns:
SelectorKey instance SelectorKey instance
""" """
try: raise NotImplementedError
key = self._fd_to_key.pop(_fileobj_to_fd(fileobj))
except KeyError:
raise KeyError("{!r} is not registered".format(fileobj)) from None
return key
def modify(self, fileobj, events, data=None): def modify(self, fileobj, events, data=None):
"""Change a registered file object monitored events or attached data. """Change a registered file object monitored events or attached data.
...@@ -133,19 +115,8 @@ class BaseSelector(metaclass=ABCMeta): ...@@ -133,19 +115,8 @@ class BaseSelector(metaclass=ABCMeta):
Returns: Returns:
SelectorKey instance SelectorKey instance
""" """
# TODO: Subclasses can probably optimize this even further. self.unregister(fileobj)
try: return self.register(fileobj, events, data)
key = self._fd_to_key[_fileobj_to_fd(fileobj)]
except KeyError:
raise KeyError("{!r} is not registered".format(fileobj)) from None
if events != key.events:
self.unregister(fileobj)
key = self.register(fileobj, events, data)
elif data != key.data:
# Use a shortcut to update the data.
key = key._replace(data=data)
self._fd_to_key[key.fd] = key
return key
@abstractmethod @abstractmethod
def select(self, timeout=None): def select(self, timeout=None):
...@@ -164,14 +135,14 @@ class BaseSelector(metaclass=ABCMeta): ...@@ -164,14 +135,14 @@ class BaseSelector(metaclass=ABCMeta):
list of (key, events) for ready file objects list of (key, events) for ready file objects
`events` is a bitwise mask of EVENT_READ|EVENT_WRITE `events` is a bitwise mask of EVENT_READ|EVENT_WRITE
""" """
raise NotImplementedError() raise NotImplementedError
def close(self): def close(self):
"""Close the selector. """Close the selector.
This must be called to make sure that any underlying resource is freed. This must be called to make sure that any underlying resource is freed.
""" """
self._fd_to_key.clear() pass
def get_key(self, fileobj): def get_key(self, fileobj):
"""Return the key associated to a registered file object. """Return the key associated to a registered file object.
...@@ -179,14 +150,16 @@ class BaseSelector(metaclass=ABCMeta): ...@@ -179,14 +150,16 @@ class BaseSelector(metaclass=ABCMeta):
Returns: Returns:
SelectorKey for this file object SelectorKey for this file object
""" """
mapping = self.get_map()
try: try:
return self._fd_to_key[_fileobj_to_fd(fileobj)] return mapping[fileobj]
except KeyError: except KeyError:
raise KeyError("{!r} is not registered".format(fileobj)) from None raise KeyError("{!r} is not registered".format(fileobj)) from None
@abstractmethod
def get_map(self): def get_map(self):
"""Return a mapping of file objects to selector keys.""" """Return a mapping of file objects to selector keys."""
return self._map raise NotImplementedError
def __enter__(self): def __enter__(self):
return self return self
...@@ -194,6 +167,57 @@ class BaseSelector(metaclass=ABCMeta): ...@@ -194,6 +167,57 @@ class BaseSelector(metaclass=ABCMeta):
def __exit__(self, *args): def __exit__(self, *args):
self.close() self.close()
class _BaseSelectorImpl(BaseSelector):
"""Base selector implementation."""
def __init__(self):
# this maps file descriptors to keys
self._fd_to_key = {}
# read-only mapping returned by get_map()
self._map = _SelectorMapping(self)
def register(self, fileobj, events, data=None):
if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)):
raise ValueError("Invalid events: {!r}".format(events))
key = SelectorKey(fileobj, _fileobj_to_fd(fileobj), events, data)
if key.fd in self._fd_to_key:
raise KeyError("{!r} (FD {}) is already "
"registered".format(fileobj, key.fd))
self._fd_to_key[key.fd] = key
return key
def unregister(self, fileobj):
try:
key = self._fd_to_key.pop(_fileobj_to_fd(fileobj))
except KeyError:
raise KeyError("{!r} is not registered".format(fileobj)) from None
return key
def modify(self, fileobj, events, data=None):
# TODO: Subclasses can probably optimize this even further.
try:
key = self._fd_to_key[_fileobj_to_fd(fileobj)]
except KeyError:
raise KeyError("{!r} is not registered".format(fileobj)) from None
if events != key.events:
self.unregister(fileobj)
key = self.register(fileobj, events, data)
elif data != key.data:
# Use a shortcut to update the data.
key = key._replace(data=data)
self._fd_to_key[key.fd] = key
return key
def close(self):
self._fd_to_key.clear()
def get_map(self):
return self._map
def _key_from_fd(self, fd): def _key_from_fd(self, fd):
"""Return the key associated to a given file descriptor. """Return the key associated to a given file descriptor.
...@@ -209,7 +233,7 @@ class BaseSelector(metaclass=ABCMeta): ...@@ -209,7 +233,7 @@ class BaseSelector(metaclass=ABCMeta):
return None return None
class SelectSelector(BaseSelector): class SelectSelector(_BaseSelectorImpl):
"""Select-based selector.""" """Select-based selector."""
def __init__(self): def __init__(self):
...@@ -262,7 +286,7 @@ class SelectSelector(BaseSelector): ...@@ -262,7 +286,7 @@ class SelectSelector(BaseSelector):
if hasattr(select, 'poll'): if hasattr(select, 'poll'):
class PollSelector(BaseSelector): class PollSelector(_BaseSelectorImpl):
"""Poll-based selector.""" """Poll-based selector."""
def __init__(self): def __init__(self):
...@@ -306,7 +330,7 @@ if hasattr(select, 'poll'): ...@@ -306,7 +330,7 @@ if hasattr(select, 'poll'):
if hasattr(select, 'epoll'): if hasattr(select, 'epoll'):
class EpollSelector(BaseSelector): class EpollSelector(_BaseSelectorImpl):
"""Epoll-based selector.""" """Epoll-based selector."""
def __init__(self): def __init__(self):
...@@ -358,7 +382,7 @@ if hasattr(select, 'epoll'): ...@@ -358,7 +382,7 @@ if hasattr(select, 'epoll'):
if hasattr(select, 'kqueue'): if hasattr(select, 'kqueue'):
class KqueueSelector(BaseSelector): class KqueueSelector(_BaseSelectorImpl):
"""Kqueue-based selector.""" """Kqueue-based selector."""
def __init__(self): def __init__(self):
......
...@@ -114,7 +114,6 @@ class TelnetAlike(telnetlib.Telnet): ...@@ -114,7 +114,6 @@ class TelnetAlike(telnetlib.Telnet):
class MockSelector(selectors.BaseSelector): class MockSelector(selectors.BaseSelector):
def __init__(self): def __init__(self):
super().__init__()
self.keys = {} self.keys = {}
def register(self, fileobj, events, data=None): def register(self, fileobj, events, data=None):
...@@ -123,8 +122,7 @@ class MockSelector(selectors.BaseSelector): ...@@ -123,8 +122,7 @@ class MockSelector(selectors.BaseSelector):
return key return key
def unregister(self, fileobj): def unregister(self, fileobj):
key = self.keys.pop(fileobj) return self.keys.pop(fileobj)
return key
def select(self, timeout=None): def select(self, timeout=None):
block = False block = False
...@@ -137,6 +135,9 @@ class MockSelector(selectors.BaseSelector): ...@@ -137,6 +135,9 @@ class MockSelector(selectors.BaseSelector):
else: else:
return [(key, key.events) for key in self.keys.values()] return [(key, key.events) for key in self.keys.values()]
def get_map(self):
return self.keys
@contextlib.contextmanager @contextlib.contextmanager
def test_socket(reads): def test_socket(reads):
......
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