Skip to main content

Modules/selectmodule.c (part 2)

Source:

cpython 3.14 @ ab2d84fe1023/Modules/selectmodule.c

This annotation covers platform-specific I/O multiplexing. See modules_select_detail for select.select, file descriptor validation, and timeout handling.

Map

LinesSymbolRole
1-100select.pollLinux/BSD: register fds and poll for events
101-240select.epollLinux: edge/level-triggered event notification
241-380select.kqueuemacOS/BSD: kernel event queue
381-500selectors.DefaultSelectorHigh-level wrapper that chooses the best backend
501-600Event constantsPOLLIN, POLLOUT, EPOLLIN, EPOLLET, etc.

Reading

select.poll

// CPython: Modules/selectmodule.c:640 poll_register
static PyObject *
poll_register(pollObject *self, PyObject *args)
{
PyObject *o;
int fd, events = POLLIN | POLLPRI | POLLOUT;
if (!PyArg_ParseTuple(args, "O|i:register", &o, &events)) return NULL;
fd = PyObject_AsFileDescriptor(o);
if (fd < 0) return NULL;
/* Store (fd, events) in self->dict */
PyDict_SetItem(self->dict, PyLong_FromLong(fd), PyLong_FromLong(events));
Py_RETURN_NONE;
}

static PyObject *
poll_poll(pollObject *self, PyObject *args)
{
/* Build pollfd[] array, call poll(2), return list of (fd, events) */
Py_ssize_t num_fds = PyDict_GET_SIZE(self->dict);
struct pollfd *ufds = calloc(num_fds, sizeof(struct pollfd));
...
Py_BEGIN_ALLOW_THREADS
int retval = poll(ufds, num_fds, timeout);
Py_END_ALLOW_THREADS
...
}

poll() releases the GIL during the blocking call. The dict stores registered fds; poll.poll(timeout_ms) returns only the fds that are ready, as (fd, event) pairs.

select.epoll

// CPython: Modules/selectmodule.c:1040 pyepoll_register
static PyObject *
pyepoll_register(pyEpoll_Object *self, PyObject *args, PyObject *kwds)
{
int fd, events = EPOLLIN | EPOLLOUT | EPOLLPRI;
PyArg_ParseTupleAndKeywords(args, kwds, "O|i:register", kwlist, &fdobj, &events);
fd = PyObject_AsFileDescriptor(fdobj);
struct epoll_event ev = {.events = events, .data.fd = fd};
if (epoll_ctl(self->epfd, EPOLL_CTL_ADD, fd, &ev) < 0) {
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
Py_RETURN_NONE;
}

epoll is O(1) for event detection regardless of the number of registered fds, making it suitable for servers with thousands of connections. Edge-triggered (EPOLLET) mode requires draining the fd completely before waiting again.

selectors.DefaultSelector

# CPython: Lib/selectors.py:580 DefaultSelector
if 'EpollSelector' in globals():
DefaultSelector = EpollSelector # Linux
elif 'KqueueSelector' in globals():
DefaultSelector = KqueueSelector # macOS/BSD
elif 'DevpollSelector' in globals():
DefaultSelector = DevpollSelector # Solaris
elif 'PollSelector' in globals():
DefaultSelector = PollSelector
else:
DefaultSelector = SelectSelector # Windows fallback

selectors.DefaultSelector automatically picks the most efficient backend. asyncio uses it internally for non-blocking I/O. The SelectorEventLoop wraps DefaultSelector for the event loop.

gopy notes

select.poll is module/select.Poll in module/select/module.go. On Linux, it calls syscall.Poll. select.epoll uses golang.org/x/sys/unix.EpollCreate1 and EpollCtl. selectors.DefaultSelector dispatches to the appropriate backend at init time based on OS constants.