Skip to main content

Modules/selectmodule.c

cpython 3.14 @ ab2d84fe1023/Modules/selectmodule.c

Modules/selectmodule.c implements the select module, exposing five I/O-readiness mechanisms as Python objects. POSIX select(2) is available everywhere; poll(2) on POSIX platforms other than macOS; epoll(7) on Linux; kqueue/kevent on BSD and macOS; devpoll on Solaris. Each mechanism is wrapped in its own type with register, unregister, modify, and poll methods. Platform guards in the C source enable only the mechanisms the host OS provides.

Map

LinesSymbolRole
1-120includes and platform guardsOS-specific headers, fd_set size constants
121-380select_selectModule-level select() wrapping fd_set
381-700pollObjectpoll(2) wrapper with dict-based fd registry
701-1100devpollObjectSolaris /dev/poll wrapper
1101-1500pyepollLinux epoll wrapper with EPOLLET / EPOLLONESHOT support
1501-1950kqueue, keventBSD/macOS kqueue/kevent objects
1951-2200module initPyInit_select, constant registration

Reading

select_select: fd_set assembly

The module-level select() function converts Python sequences of socket/file objects into C fd_set bitmaps, calls select(2) with the GIL released, then rebuilds Python lists from the returned fd_set masks.

// CPython: Modules/selectmodule.c:160 select_select_impl
static PyObject *
select_select_impl(PyObject *module, PyObject *iwtd, PyObject *owtd,
PyObject *ewtd, PyObject *timeout_obj)
{
fd_set ifdset, ofdset, efdset;
struct timeval tv, *tvp;
...
Py_BEGIN_ALLOW_THREADS
n = select(maxfd+1, &ifdset, &ofdset, &efdset, tvp);
Py_END_ALLOW_THREADS

The maximum file descriptor is tracked while building fd_set and passed as the first argument to select(2). File descriptors above FD_SETSIZE (typically 1024) raise ValueError because fd_set is a fixed-size bitmap.

pollObject: dict-based registry

pollObject stores the registered file descriptors in a C pollfd array rebuilt from a Python dict on each poll() call. The dict maps fd integers to event masks, allowing register, unregister, and modify to be O(1) dict operations.

// CPython: Modules/selectmodule.c:430 poll_register_impl
static PyObject *
poll_register_impl(pollObject *self, int fd, unsigned short eventmask)
{
PyObject *key, *value;
int err;

if (fd < 0) {
PyErr_SetString(PyExc_ValueError, "Invalid file descriptor: ...");
return NULL;
}
key = PyLong_FromLong(fd);
value = PyLong_FromLong(eventmask);
err = PyDict_SetItem(self->dict, key, value);
...
self->ufd_uptodate = 0; /* mark array as stale */

The ufd_uptodate flag triggers a rebuild of the pollfd array at the start of the next poll() call, amortizing the rebuild cost across multiple register calls.

pyepoll: edge-triggered and one-shot modes

The epoll wrapper exposes EPOLLET (edge-triggered) and EPOLLONESHOT as integer constants that callers OR into the event mask passed to register. The epoll_ctl syscall is called directly; the GIL is not released because epoll_ctl is expected to return immediately.

// CPython: Modules/selectmodule.c:1140 pyepoll_internal_ctl
static int
pyepoll_internal_ctl(int epfd, int op, int fd, unsigned int events)
{
struct epoll_event ev;
int result;

ev.events = events;
ev.data.fd = fd;
Py_BEGIN_ALLOW_THREADS
result = epoll_ctl(epfd, op, fd, &ev);
Py_END_ALLOW_THREADS
return result;
}

gopy notes

Not yet ported. The Go standard library provides syscall.Select and the golang.org/x/sys package exposes epoll and kqueue primitives. A module/select/ port would wrap these and expose select.select, select.poll, and select.epoll as Go types. In practice, gopy's event-loop integration would use select primarily to support asyncio.

CPython 3.14 changes

3.14 corrected kqueue.control to respect the timeout parameter on all BSD variants. The select.select function gained an audit event. No new polling mechanisms were added.