python / python/cpython

selectors' closing behaviors completely inconsistent across selector types

Open
#91,433 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

stdlib topic-socket
Dominant language
Python
Stars
77.2k
Forks
36k
PR merge metrics
PR metrics pending

Description

Several behaviors in selectors differ across implementations in major ways.

  1. What happens when a selector is selecting and is closed from a different thread?
  2. What happens when a selector is closed and select is called thereafter?
  3. What error (if any) is raised if a selector cannot be used after close.

While individual select implementations can exhibit selector-type-specific behaviors, selectors provides a high-level abstraction that should behave in a consistent fashion.

Examples of inconsistent behaviors (select-after-close):

Python 3.10.4 (main, Mar 28 2022, 17:39:22) [GCC 11.2.1 20220127 (Red Hat 11.2.1-9)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import selectors
>>> s = selectors.SelectSelector()
>>> s.close()
>>> s.select()
^CTraceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/arcivanov/.pyenv/versions/3.10.4/lib/python3.10/selectors.py", line 324, in select
    r, w, _ = self._select(self._readers, self._writers, [], timeout)
KeyboardInterrupt
>>> s = selectors.PollSelector()
>>> s.close()
>>> s.select()
^CTraceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/arcivanov/.pyenv/versions/3.10.4/lib/python3.10/selectors.py", line 416, in select
    fd_event_list = self._selector.poll(timeout)
KeyboardInterrupt
>>> s = selectors.EpollSelector()
>>> s.close()
>>> s.select()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/arcivanov/.pyenv/versions/3.10.4/lib/python3.10/selectors.py", line 469, in select
    fd_event_list = self._selector.poll(timeout, max_ev)
ValueError: I/O operation on closed epoll object

Proposal:

  1. Closing a selector (from a different thread) while it is blocked in a select will always result in select returning immediately with no ready keys. This indicates EOF if timeout=None or is indistinguishable from timeout otherwise. See [4].
  2. All selectors will have a fileno() regardless of implementation.
  3. In all cases where selector is call-based (select/poll) a sentinel file (os.pipe) will be used as a fileno() and never returned in the ready list.
  4. Selecting on a closed selector will result in an OSError irrespective of the underlying implementation.
  5. Analogous tofile object, closed property returns True when selector is closed.

In cases when a selector implementation is not backed by a file-based object, this will result in 2 more file handles used per selector created (either a pipe or a socketpair).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with selectors.py and compare SelectSelector, PollSelector, and EpollSelector, focusing on close(), select(), fileno(), and closed behavior. Review the proposed cross-thread wakeup and post-close semantics, then add coverage for each selector type and verify that closed selectors raise OSError and blocked selects return as specified.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
operating-systems
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.