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)
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)
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()
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
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)
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')
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')
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)))
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)
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()