Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
selectors.py604 linesDownload Raw Back to Lib
1"""Selectors module.2 3This module allows high-level and efficient I/O multiplexing, built upon the4`select` module primitives.5"""6 7 8from abc import ABCMeta, abstractmethod9from collections import namedtuple10from collections.abc import Mapping11import math12import select13import sys14 15 16# generic events, that must be mapped to implementation-specific ones17EVENT_READ = (1 << 0)18EVENT_WRITE = (1 << 1)19 20 21def _fileobj_to_fd(fileobj):22    """Return a file descriptor from a file object.23 24    Parameters:25    fileobj -- file object or file descriptor26 27    Returns:28    corresponding file descriptor29 30    Raises:31    ValueError if the object is invalid32    """33    if isinstance(fileobj, int):34        fd = fileobj35    else:36        try:37            fd = int(fileobj.fileno())38        except (AttributeError, TypeError, ValueError):39            raise ValueError("Invalid file object: "40                             "{!r}".format(fileobj)) from None41    if fd < 0:42        raise ValueError("Invalid file descriptor: {}".format(fd))43    return fd44 45 46SelectorKey = namedtuple('SelectorKey', ['fileobj', 'fd', 'events', 'data'])47 48SelectorKey.__doc__ = """SelectorKey(fileobj, fd, events, data)49 50    Object used to associate a file object to its backing51    file descriptor, selected event mask, and attached data.52"""53SelectorKey.fileobj.__doc__ = 'File object registered.'54SelectorKey.fd.__doc__ = 'Underlying file descriptor.'55SelectorKey.events.__doc__ = 'Events that must be waited for on this file object.'56SelectorKey.data.__doc__ = ('''Optional opaque data associated to this file object.57For example, this could be used to store a per-client session ID.''')58 59 60class _SelectorMapping(Mapping):61    """Mapping of file objects to selector keys."""62 63    def __init__(self, selector):64        self._selector = selector65 66    def __len__(self):67        return len(self._selector._fd_to_key)68 69    def get(self, fileobj, default=None):70        fd = self._selector._fileobj_lookup(fileobj)71        return self._selector._fd_to_key.get(fd, default)72 73    def __getitem__(self, fileobj):74        fd = self._selector._fileobj_lookup(fileobj)75        key = self._selector._fd_to_key.get(fd)76        if key is None:77            raise KeyError("{!r} is not registered".format(fileobj))78        return key79 80    def __iter__(self):81        return iter(self._selector._fd_to_key)82 83 84class BaseSelector(metaclass=ABCMeta):85    """Selector abstract base class.86 87    A selector supports registering file objects to be monitored for specific88    I/O events.89 90    A file object is a file descriptor or any object with a `fileno()` method.91    An arbitrary object can be attached to the file object, which can be used92    for example to store context information, a callback, etc.93 94    A selector can use various implementations (select(), poll(), epoll()...)95    depending on the platform. The default `Selector` class uses the most96    efficient implementation on the current platform.97    """98 99    @abstractmethod100    def register(self, fileobj, events, data=None):101        """Register a file object.102 103        Parameters:104        fileobj -- file object or file descriptor105        events  -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE)106        data    -- attached data107 108        Returns:109        SelectorKey instance110 111        Raises:112        ValueError if events is invalid113        KeyError if fileobj is already registered114        OSError if fileobj is closed or otherwise is unacceptable to115                the underlying system call (if a system call is made)116 117        Note:118        OSError may or may not be raised119        """120        raise NotImplementedError121 122    @abstractmethod123    def unregister(self, fileobj):124        """Unregister a file object.125 126        Parameters:127        fileobj -- file object or file descriptor128 129        Returns:130        SelectorKey instance131 132        Raises:133        KeyError if fileobj is not registered134 135        Note:136        If fileobj is registered but has since been closed this does137        *not* raise OSError (even if the wrapped syscall does)138        """139        raise NotImplementedError140 141    def modify(self, fileobj, events, data=None):142        """Change a registered file object monitored events or attached data.143 144        Parameters:145        fileobj -- file object or file descriptor146        events  -- events to monitor (bitwise mask of EVENT_READ|EVENT_WRITE)147        data    -- attached data148 149        Returns:150        SelectorKey instance151 152        Raises:153        Anything that unregister() or register() raises154        """155        self.unregister(fileobj)156        return self.register(fileobj, events, data)157 158    @abstractmethod159    def select(self, timeout=None):160        """Perform the actual selection, until some monitored file objects are161        ready or a timeout expires.162 163        Parameters:164        timeout -- if timeout > 0, this specifies the maximum wait time, in165                   seconds166                   if timeout <= 0, the select() call won't block, and will167                   report the currently ready file objects168                   if timeout is None, select() will block until a monitored169                   file object becomes ready170 171        Returns:172        list of (key, events) for ready file objects173        `events` is a bitwise mask of EVENT_READ|EVENT_WRITE174        """175        raise NotImplementedError176 177    def close(self):178        """Close the selector.179 180        This must be called to make sure that any underlying resource is freed.181        """182        pass183 184    def get_key(self, fileobj):185        """Return the key associated to a registered file object.186 187        Returns:188        SelectorKey for this file object189        """190        mapping = self.get_map()191        if mapping is None:192            raise RuntimeError('Selector is closed')193        try:194            return mapping[fileobj]195        except KeyError:196            raise KeyError("{!r} is not registered".format(fileobj)) from None197 198    @abstractmethod199    def get_map(self):200        """Return a mapping of file objects to selector keys."""201        raise NotImplementedError202 203    def __enter__(self):204        return self205 206    def __exit__(self, *args):207        self.close()208 209 210class _BaseSelectorImpl(BaseSelector):211    """Base selector implementation."""212 213    def __init__(self):214        # this maps file descriptors to keys215        self._fd_to_key = {}216        # read-only mapping returned by get_map()217        self._map = _SelectorMapping(self)218 219    def _fileobj_lookup(self, fileobj):220        """Return a file descriptor from a file object.221 222        This wraps _fileobj_to_fd() to do an exhaustive search in case223        the object is invalid but we still have it in our map.  This224        is used by unregister() so we can unregister an object that225        was previously registered even if it is closed.  It is also226        used by _SelectorMapping.227        """228        try:229            return _fileobj_to_fd(fileobj)230        except ValueError:231            # Do an exhaustive search.232            for key in self._fd_to_key.values():233                if key.fileobj is fileobj:234                    return key.fd235            # Raise ValueError after all.236            raise237 238    def register(self, fileobj, events, data=None):239        if (not events) or (events & ~(EVENT_READ | EVENT_WRITE)):240            raise ValueError("Invalid events: {!r}".format(events))241 242        key = SelectorKey(fileobj, self._fileobj_lookup(fileobj), events, data)243 244        if key.fd in self._fd_to_key:245            raise KeyError("{!r} (FD {}) is already registered"246                           .format(fileobj, key.fd))247 248        self._fd_to_key[key.fd] = key249        return key250 251    def unregister(self, fileobj):252        try:253            key = self._fd_to_key.pop(self._fileobj_lookup(fileobj))254        except KeyError:255            raise KeyError("{!r} is not registered".format(fileobj)) from None256        return key257 258    def modify(self, fileobj, events, data=None):259        try:260            key = self._fd_to_key[self._fileobj_lookup(fileobj)]261        except KeyError:262            raise KeyError("{!r} is not registered".format(fileobj)) from None263        if events != key.events:264            self.unregister(fileobj)265            key = self.register(fileobj, events, data)266        elif data != key.data:267            # Use a shortcut to update the data.268            key = key._replace(data=data)269            self._fd_to_key[key.fd] = key270        return key271 272    def close(self):273        self._fd_to_key.clear()274        self._map = None275 276    def get_map(self):277        return self._map278 279 280 281class SelectSelector(_BaseSelectorImpl):282    """Select-based selector."""283 284    def __init__(self):285        super().__init__()286        self._readers = set()287        self._writers = set()288 289    def register(self, fileobj, events, data=None):290        key = super().register(fileobj, events, data)291        if events & EVENT_READ:292            self._readers.add(key.fd)293        if events & EVENT_WRITE:294            self._writers.add(key.fd)295        return key296 297    def unregister(self, fileobj):298        key = super().unregister(fileobj)299        self._readers.discard(key.fd)300        self._writers.discard(key.fd)301        return key302 303    if sys.platform == 'win32':304        def _select(self, r, w, _, timeout=None):305            r, w, x = select.select(r, w, w, timeout)306            return r, w + x, []307    else:308        _select = select.select309 310    def select(self, timeout=None):311        timeout = None if timeout is None else max(timeout, 0)312        ready = []313        try:314            r, w, _ = self._select(self._readers, self._writers, [], timeout)315        except InterruptedError:316            return ready317        r = frozenset(r)318        w = frozenset(w)319        rw = r | w320        fd_to_key_get = self._fd_to_key.get321        for fd in rw:322            key = fd_to_key_get(fd)323            if key:324                events = ((fd in r and EVENT_READ)325                          | (fd in w and EVENT_WRITE))326                ready.append((key, events & key.events))327        return ready328 329 330class _PollLikeSelector(_BaseSelectorImpl):331    """Base class shared between poll, epoll and devpoll selectors."""332    _selector_cls = None333    _EVENT_READ = None334    _EVENT_WRITE = None335 336    def __init__(self):337        super().__init__()338        self._selector = self._selector_cls()339 340    def register(self, fileobj, events, data=None):341        key = super().register(fileobj, events, data)342        poller_events = ((events & EVENT_READ and self._EVENT_READ)343                         | (events & EVENT_WRITE and self._EVENT_WRITE) )344        try:345            self._selector.register(key.fd, poller_events)346        except:347            super().unregister(fileobj)348            raise349        return key350 351    def unregister(self, fileobj):352        key = super().unregister(fileobj)353        try:354            self._selector.unregister(key.fd)355        except OSError:356            # This can happen if the FD was closed since it357            # was registered.358            pass359        return key360 361    def modify(self, fileobj, events, data=None):362        try:363            key = self._fd_to_key[self._fileobj_lookup(fileobj)]364        except KeyError:365            raise KeyError(f"{fileobj!r} is not registered") from None366 367        changed = False368        if events != key.events:369            selector_events = ((events & EVENT_READ and self._EVENT_READ)370                               | (events & EVENT_WRITE and self._EVENT_WRITE))371            try:372                self._selector.modify(key.fd, selector_events)373            except:374                super().unregister(fileobj)375                raise376            changed = True377        if data != key.data:378            changed = True379 380        if changed:381            key = key._replace(events=events, data=data)382            self._fd_to_key[key.fd] = key383        return key384 385    def select(self, timeout=None):386        # This is shared between poll() and epoll().387        # epoll() has a different signature and handling of timeout parameter.388        if timeout is None:389            timeout = None390        elif timeout <= 0:391            timeout = 0392        else:393            # poll() has a resolution of 1 millisecond, round away from394            # zero to wait *at least* timeout seconds.395            timeout = math.ceil(timeout * 1e3)396        ready = []397        try:398            fd_event_list = self._selector.poll(timeout)399        except InterruptedError:400            return ready401 402        fd_to_key_get = self._fd_to_key.get403        for fd, event in fd_event_list:404            key = fd_to_key_get(fd)405            if key:406                events = ((event & ~self._EVENT_READ and EVENT_WRITE)407                           | (event & ~self._EVENT_WRITE and EVENT_READ))408                ready.append((key, events & key.events))409        return ready410 411 412if hasattr(select, 'poll'):413 414    class PollSelector(_PollLikeSelector):415        """Poll-based selector."""416        _selector_cls = select.poll417        _EVENT_READ = select.POLLIN418        _EVENT_WRITE = select.POLLOUT419 420 421if hasattr(select, 'epoll'):422 423    _NOT_EPOLLIN = ~select.EPOLLIN424    _NOT_EPOLLOUT = ~select.EPOLLOUT425 426    class EpollSelector(_PollLikeSelector):427        """Epoll-based selector."""428        _selector_cls = select.epoll429        _EVENT_READ = select.EPOLLIN430        _EVENT_WRITE = select.EPOLLOUT431 432        def fileno(self):433            return self._selector.fileno()434 435        def select(self, timeout=None):436            if timeout is None:437                timeout = -1438            elif timeout <= 0:439                timeout = 0440            else:441                # epoll_wait() has a resolution of 1 millisecond, round away442                # from zero to wait *at least* timeout seconds.443                timeout = math.ceil(timeout * 1e3) * 1e-3444 445            # epoll_wait() expects `maxevents` to be greater than zero;446            # we want to make sure that `select()` can be called when no447            # FD is registered.448            max_ev = len(self._fd_to_key) or 1449 450            ready = []451            try:452                fd_event_list = self._selector.poll(timeout, max_ev)453            except InterruptedError:454                return ready455 456            fd_to_key = self._fd_to_key457            for fd, event in fd_event_list:458                key = fd_to_key.get(fd)459                if key:460                    events = ((event & _NOT_EPOLLIN and EVENT_WRITE)461                              | (event & _NOT_EPOLLOUT and EVENT_READ))462                    ready.append((key, events & key.events))463            return ready464 465        def close(self):466            self._selector.close()467            super().close()468 469 470if hasattr(select, 'devpoll'):471 472    class DevpollSelector(_PollLikeSelector):473        """Solaris /dev/poll selector."""474        _selector_cls = select.devpoll475        _EVENT_READ = select.POLLIN476        _EVENT_WRITE = select.POLLOUT477 478        def fileno(self):479            return self._selector.fileno()480 481        def close(self):482            self._selector.close()483            super().close()484 485 486if hasattr(select, 'kqueue'):487 488    class KqueueSelector(_BaseSelectorImpl):489        """Kqueue-based selector."""490 491        def __init__(self):492            super().__init__()493            self._selector = select.kqueue()494            self._max_events = 0495 496        def fileno(self):497            return self._selector.fileno()498 499        def register(self, fileobj, events, data=None):500            key = super().register(fileobj, events, data)501            try:502                if events & EVENT_READ:503                    kev = select.kevent(key.fd, select.KQ_FILTER_READ,504                                        select.KQ_EV_ADD)505                    self._selector.control([kev], 0, 0)506                    self._max_events += 1507                if events & EVENT_WRITE:508                    kev = select.kevent(key.fd, select.KQ_FILTER_WRITE,509                                        select.KQ_EV_ADD)510                    self._selector.control([kev], 0, 0)511                    self._max_events += 1512            except:513                super().unregister(fileobj)514                raise515            return key516 517        def unregister(self, fileobj):518            key = super().unregister(fileobj)519            if key.events & EVENT_READ:520                kev = select.kevent(key.fd, select.KQ_FILTER_READ,521                                    select.KQ_EV_DELETE)522                self._max_events -= 1523                try:524                    self._selector.control([kev], 0, 0)525                except OSError:526                    # This can happen if the FD was closed since it527                    # was registered.528                    pass529            if key.events & EVENT_WRITE:530                kev = select.kevent(key.fd, select.KQ_FILTER_WRITE,531                                    select.KQ_EV_DELETE)532                self._max_events -= 1533                try:534                    self._selector.control([kev], 0, 0)535                except OSError:536                    # See comment above.537                    pass538            return key539 540        def select(self, timeout=None):541            timeout = None if timeout is None else max(timeout, 0)542            # If max_ev is 0, kqueue will ignore the timeout. For consistent543            # behavior with the other selector classes, we prevent that here544            # (using max). See https://bugs.python.org/issue29255545            max_ev = self._max_events or 1546            ready = []547            try:548                kev_list = self._selector.control(None, max_ev, timeout)549            except InterruptedError:550                return ready551 552            fd_to_key_get = self._fd_to_key.get553            for kev in kev_list:554                fd = kev.ident555                flag = kev.filter556                key = fd_to_key_get(fd)557                if key:558                    events = ((flag == select.KQ_FILTER_READ and EVENT_READ)559                              | (flag == select.KQ_FILTER_WRITE and EVENT_WRITE))560                    ready.append((key, events & key.events))561            return ready562 563        def close(self):564            self._selector.close()565            super().close()566 567 568def _can_use(method):569    """Check if we can use the selector depending upon the570    operating system. """571    # Implementation based upon https://github.com/sethmlarson/selectors2/blob/master/selectors2.py572    selector = getattr(select, method, None)573    if selector is None:574        # select module does not implement method575        return False576    # check if the OS and Kernel actually support the method. Call may fail with577    # OSError: [Errno 38] Function not implemented578    try:579        selector_obj = selector()580        if method == 'poll':581            # check that poll actually works582            selector_obj.poll(0)583        else:584            # close epoll, kqueue, and devpoll fd585            selector_obj.close()586        return True587    except OSError:588        return False589 590 591# Choose the best implementation, roughly:592#    epoll|kqueue|devpoll > poll > select.593# select() also can't accept a FD > FD_SETSIZE (usually around 1024)594if _can_use('kqueue'):595    DefaultSelector = KqueueSelector596elif _can_use('epoll'):597    DefaultSelector = EpollSelector598elif _can_use('devpoll'):599    DefaultSelector = DevpollSelector600elif _can_use('poll'):601    DefaultSelector = PollSelector602else:603    DefaultSelector = SelectSelector604 
codekingpro/portable-devtools · Team Ai