Example #1
0
def test_eintr_retry_call(tmpdir):
    import os, signal, select

    def handle_alarm(signum, frame):
        # pylint: disable=unused-argument
        pass

    orig_alarm = signal.getsignal(signal.SIGALRM)

    # Open an empty file and use it to wait for exceptions which should
    # never happen
    filename = tmpdir.join('test')
    filename.ensure(file=True)
    fd = os.open(str(filename), os.O_RDONLY)

    try:
        signal.signal(signal.SIGALRM, handle_alarm)

        # Ensure that a signal raises EINTR on Python < 3.5
        if sys.version_info < (3, 5):
            with pytest.raises(select.error):
                signal.alarm(1)
                select.select([], [], [fd], 2)

        # Ensure that wrapping the call does not raise EINTR
        signal.alarm(1)
        assert _util.eintr_retry_call(select.select, [], [], [3],
                                      2) == ([], [], [])
    finally:
        os.close(fd)
        signal.signal(signal.SIGALRM, orig_alarm)
Example #2
0
def test_eintr_retry_call(tmpdir):
    import os, signal, select

    def handle_alarm(signum, frame):
        pass
    orig_alarm = signal.getsignal(signal.SIGALRM)

    # Open an empty file and use it to wait for exceptions which should
    # never happen
    filename = tmpdir.join('test')
    filename.ensure(file=True)
    fd = os.open(str(filename), os.O_RDONLY)

    try:
        signal.signal(signal.SIGALRM, handle_alarm)

        # Ensure that a signal raises EINTR on Python < 3.5
        if sys.version_info < (3, 5):
            with pytest.raises(select.error) as e:
                signal.alarm(1)
                select.select([], [], [fd], 2)

        # Ensure that wrapping the call does not raise EINTR
        signal.alarm(1)
        assert _util.eintr_retry_call(select.select, [], [], [3], 2) == ([], [], [])
    finally:
        os.close(fd)
        signal.signal(signal.SIGALRM, orig_alarm)
Example #3
0
    def send_stop(self):
        """
        Send a stop signal to the background thread.

        The background thread will eventually exit, but it may still be running
        when this method returns.  This method is essentially the asynchronous
        equivalent to :meth:`stop()`.

        .. note::

           The underlying :attr:`monitor` is *not* stopped.
        """
        if self._stop_event is None:
            return
        with self._stop_event.sink:
            # emit a stop event to the thread
            eintr_retry_call(self._stop_event.sink.write, b'\x01')
            self._stop_event.sink.flush()
Example #4
0
    def send_stop(self):
        """
        Send a stop signal to the background thread.

        The background thread will eventually exit, but it may still be running
        when this method returns.  This method is essentially the asynchronous
        equivalent to :meth:`stop()`.

        .. note::

           The underlying :attr:`monitor` is *not* stopped.
        """
        if self._stop_event is None:
            return
        with self._stop_event.sink:
            # emit a stop event to the thread
            eintr_retry_call(self._stop_event.sink.write, b'\x01')
            self._stop_event.sink.flush()
Example #5
0
    def poll(self, timeout=None):
        """
        Poll for a device event.

        You can use this method together with :func:`iter()` to synchronously
        monitor events in the current thread::

           for device in iter(monitor.poll, None):
               print('{0.action} on {0.device_path}'.format(device))

        Since this method will never return ``None`` if no ``timeout`` is
        specified, this is effectively an endless loop. With
        :func:`functools.partial()` you can also create a loop that only waits
        for a specified time::

           for device in iter(partial(monitor.poll, 3), None):
               print('{0.action} on {0.device_path}'.format(device))

        This loop will only wait three seconds for a new device event. If no
        device event occurred after three seconds, the loop will exit.

        ``timeout`` is a floating point number that specifies a time-out in
        seconds. If omitted or ``None``, this method blocks until a device
        event is available. If ``0``, this method just polls and will never
        block.

        .. note::

           This method implicitly calls :meth:`start()`.

        Return the received :class:`Device`, or ``None`` if a timeout
        occurred. Raise :exc:`~exceptions.EnvironmentError` if event retrieval
        failed.

        .. seealso::

           :attr:`Device.action`
              The action that created this event.

           :attr:`Device.sequence_number`
              The sequence number of this event.

        .. versionadded:: 0.16
        """
        if timeout is not None and timeout > 0:
            # .poll() takes timeout in milliseconds
            timeout = int(timeout * 1000)
        self.start()
        if eintr_retry_call(poll.Poll.for_events((self, 'r')).poll, timeout):
            return self._receive_device()
        else:
            return None
Example #6
0
    def poll(self, timeout=None):
        """
        Poll for a device event.

        You can use this method together with :func:`iter()` to synchronously
        monitor events in the current thread::

           for device in iter(monitor.poll, None):
               print('{0.action} on {0.device_path}'.format(device))

        Since this method will never return ``None`` if no ``timeout`` is
        specified, this is effectively an endless loop. With
        :func:`functools.partial()` you can also create a loop that only waits
        for a specified time::

           for device in iter(partial(monitor.poll, 3), None):
               print('{0.action} on {0.device_path}'.format(device))

        This loop will only wait three seconds for a new device event. If no
        device event occurred after three seconds, the loop will exit.

        ``timeout`` is a floating point number that specifies a time-out in
        seconds. If omitted or ``None``, this method blocks until a device
        event is available. If ``0``, this method just polls and will never
        block.

        .. note::

           This method implicitly calls :meth:`start()`.

        Return the received :class:`Device`, or ``None`` if a timeout
        occurred. Raise :exc:`~exceptions.EnvironmentError` if event retrieval
        failed.

        .. seealso::

           :attr:`Device.action`
              The action that created this event.

           :attr:`Device.sequence_number`
              The sequence number of this event.

        .. versionadded:: 0.16
        """
        if timeout is not None and timeout > 0:
            # .poll() takes timeout in milliseconds
            timeout = int(timeout * 1000)
        self.start()
        if eintr_retry_call(poll.Poll.for_events((self, 'r')).poll, timeout):
            return self._receive_device()
        else:
            return None
Example #7
0
    def for_events(cls, *events):
        """Listen for ``events``.

        ``events`` is a list of ``(fd, event)`` pairs, where ``fd`` is a file
        descriptor or file object and ``event`` either ``'r'`` or ``'w'``.  If
        ``r``, listen for whether that is ready to be read.  If ``w``, listen
        for whether the channel is ready to be written to.

        """
        notifier = eintr_retry_call(select.poll)
        for fd, event in events:
            mask = cls._EVENT_TO_MASK.get(event)
            if not mask:
                raise ValueError('Unknown event type: {0!r}'.format(event))
            notifier.register(fd, mask)
        return cls(notifier)
Example #8
0
 def run(self):
     self.monitor.start()
     notifier = poll.Poll.for_events(
         (self.monitor, 'r'), (self._stop_event.source, 'r'))
     while True:
         for file_descriptor, event in eintr_retry_call(notifier.poll):
             if file_descriptor == self._stop_event.source.fileno():
                 # in case of a stop event, close our pipe side, and
                 # return from the thread
                 self._stop_event.source.close()
                 return
             elif file_descriptor == self.monitor.fileno() and event == 'r':
                 read_device = partial(eintr_retry_call, self.monitor.poll, timeout=0)
                 for device in iter(read_device, None):
                     self._callback(device)
             else:
                 raise EnvironmentError('Observed monitor hung up')
Example #9
0
 def run(self):
     self.monitor.start()
     notifier = Poll.for_events(
         (self.monitor, 'r'), (self._stop_event.source, 'r'))
     while True:
         for file_descriptor, event in eintr_retry_call(notifier.poll):
             if file_descriptor == self._stop_event.source.fileno():
                 # in case of a stop event, close our pipe side, and
                 # return from the thread
                 self._stop_event.source.close()
                 return
             elif file_descriptor == self.monitor.fileno() and event == 'r':
                 read_device = partial(eintr_retry_call, self.monitor.poll, timeout=0)
                 for device in iter(read_device, None):
                     self._callback(device)
             else:
                 raise EnvironmentError('Observed monitor hung up')
Example #10
0
    def poll(self, timeout=None):
        """Poll for events.

        ``timeout`` is an integer specifying how long to wait for events (in
        milliseconds).  If omitted, ``None`` or negative, wait until an event
        occurs.

        Return a list of all events that occurred before ``timeout``, where
        each event is a pair ``(fd, event)``. ``fd`` is the integral file
        descriptor, and ``event`` a string indicating the event type.  If
        ``'r'``, there is data to read from ``fd``.  If ``'w'``, ``fd`` is
        writable without blocking now.  If ``'h'``, the file descriptor was
        hung up (i.e. the remote side of a pipe was closed).

        """
        # Return a list to allow clients to determine whether there are any
        # events at all with a simple truthiness test.
        return list(self._parse_events(eintr_retry_call(self._notifier.poll, timeout)))
Example #11
0
 def run(self):
     self.monitor.enable_receiving()
     with closing(select.epoll()) as notifier:
         # poll on the stop event fd
         notifier.register(self._stop_event_source, select.EPOLLIN)
         # and on the monitor
         notifier.register(self.monitor, select.EPOLLIN)
         while True:
             for fd, _ in eintr_retry_call(notifier.poll):
                 if fd == self._stop_event_source:
                     # in case of a stop event, close our pipe side, and
                     # return from the thread
                     os.close(self._stop_event_source)
                     return
                 else:
                     event = self.monitor.receive_device()
                     if event:
                         action, device = event
                         self._handle_event(action, device)
Example #12
0
    def __iter__(self):
        """
        Wait for incoming events and receive them upon arrival.

        This methods implicitly calls :meth:`enable_receiving`, and starts
        polling the :meth:`fileno` of this monitor.  If a event comes in, it
        receives the corresponding device and yields it to the caller.

        The returned iterator is endless, and continues receiving devices
        without ever stopping.

        Yields ``(action, device)`` (see :meth:`receive_device` for a
        description).
        """
        self.enable_receiving()
        with closing(select.epoll()) as notifier:
            notifier.register(self, select.EPOLLIN)
            while True:
                events = eintr_retry_call(notifier.poll)
                for event in events:
                    yield self.receive_device()