Skip to main content

Modules/selectmodule.c (part 7)

Source:

cpython 3.14 @ ab2d84fe1023/Modules/selectmodule.c

This annotation covers I/O multiplexing syscalls. See modules_select6_detail for select.select basic usage, fd set construction, and result parsing.

Map

LinesSymbolRole
1-80select.select internalsfd_set construction, select(2) call
81-180select.pollLevel-triggered polling on Linux
181-280select.epollEdge/level-triggered with epoll_create/epoll_wait
281-380select.kqueue / keventBSD event notification
381-600select.devpoll/dev/poll on Solaris

Reading

select.select

// CPython: Modules/selectmodule.c:380 select_select_impl
static PyObject *
select_select_impl(PyObject *module, PyObject *rlist, PyObject *wlist,
PyObject *xlist, PyObject *timeout_obj)
{
fd_set ifdset, ofdset, efdset;
FD_ZERO(&ifdset); FD_ZERO(&ofdset); FD_ZERO(&efdset);
int maxfd = seq2set(rlist, &ifdset, ...) - 1;
...
Py_BEGIN_ALLOW_THREADS
n = select(maxfd + 1, &ifdset, &ofdset, &efdset, timeout);
Py_END_ALLOW_THREADS
return set2list(&ifdset, rlist), set2list(&ofdset, wlist), ...
}

select.select is limited to file descriptors < 1024 on most platforms (the fd_set bitmap size). For higher-numbered FDs or large sets, poll or epoll is required.

select.poll

// CPython: Modules/selectmodule.c:580 poll_poll
static PyObject *
poll_poll(pollObject *self, PyObject *args)
{
struct pollfd *ufds = PyMem_New(struct pollfd, self->nfds);
/* fill ufds from self->dict of {fd: events} */
Py_BEGIN_ALLOW_THREADS
retval = poll(ufds, self->nfds, timeout);
Py_END_ALLOW_THREADS
/* Convert results: list of (fd, revents) tuples */
for (Py_ssize_t i = 0; i < self->nfds; i++) {
if (ufds[i].revents) {
PyList_Append(result, Py_BuildValue("(ii)", ufds[i].fd, ufds[i].revents));
}
}
return result;
}

poll accepts any fd number and a set of event flags (POLLIN, POLLOUT, POLLERR). Returns (fd, events) tuples for ready fds. No fd limit, but O(n) per call for n registered fds.

select.epoll

// CPython: Modules/selectmodule.c:820 pyepoll_poll
static PyObject *
pyepoll_poll(pyEpoll_Object *self, PyObject *args, PyObject *kwds)
{
struct epoll_event *evs = PyMem_New(struct epoll_event, maxevents);
Py_BEGIN_ALLOW_THREADS
nfds = epoll_wait(self->epfd, evs, maxevents, timeout_ms);
Py_END_ALLOW_THREADS
for (int i = 0; i < nfds; i++) {
PyList_Append(elist, Py_BuildValue("(iI)", evs[i].data.fd, evs[i].events));
}
PyMem_Free(evs);
return elist;
}

epoll is O(1) for the wait call regardless of how many fds are registered. epoll_wait returns only the fds that are ready, not all registered fds. EPOLLET flag enables edge-triggered mode (only notifies on transitions).

select.kqueue

// CPython: Modules/selectmodule.c:1080 kqueue_queue_control
static PyObject *
kqueue_queue_control(kqueue_queue_Object *self, PyObject *args)
{
struct kevent *changelist = ..., *eventlist = ...;
Py_BEGIN_ALLOW_THREADS
gotevents = kevent(self->kqfd, changelist, nchanges,
eventlist, maxevents, ptimeout);
Py_END_ALLOW_THREADS
...
}

BSD kqueue can monitor file descriptors, sockets, files, processes, and signals with a single syscall. kevent both registers changes and waits for events. Available on macOS, FreeBSD; not on Linux.

gopy notes

select.select is module/select.Select in module/select/module.go. It calls syscall.Select. poll calls syscall.Poll. epoll uses golang.org/x/sys/unix.EpollCreate1 / EpollWait. kqueue uses syscall.Kqueue / syscall.Kevent on Darwin.