Skip to main content

Modules/selectmodule.c (part 6)

Source:

cpython 3.14 @ ab2d84fe1023/Modules/selectmodule.c

This annotation covers the platform-specific I/O event interfaces. See modules_select5_detail for select.select, select.error, and the FD_SET mechanics.

Map

LinesSymbolRole
1-80select.poll (POSIX)poll() syscall: no fd-count limit
81-180select.epoll (Linux)Edge/level-triggered event notification
181-280select.kqueue (macOS/BSD)Kernel queue event notification
281-380epoll.register / epoll.modifyAdd/change fd interest
381-600epoll.pollWait for events with timeout

Reading

select.poll

// CPython: Modules/selectmodule.c:680 poll_poll
static PyObject *
poll_poll(pollObject *self, PyObject *args)
{
double timeout_d = -1.0;
int timeout;
if (timeout_d < 0) timeout = -1;
else timeout = (int)(timeout_d * 1000.0); /* ms */

Py_BEGIN_ALLOW_THREADS
poll_result = poll(self->ufds, self->ufd_len, timeout);
Py_END_ALLOW_THREADS

PyObject *result_list = PyList_New(0);
for (int i = 0; i < self->ufd_len; i++) {
if (self->ufds[i].revents != 0) {
PyList_Append(result_list,
Py_BuildValue("(ii)", self->ufds[i].fd,
self->ufds[i].revents));
}
}
return result_list;
}

poll() uses a flat array of pollfd structs instead of three separate bitmasks. It handles more than 1024 file descriptors (the FD_SETSIZE limit of select). The timeout is in milliseconds; -1 means block indefinitely.

select.epoll

// CPython: Modules/selectmodule.c:1040 pyepoll_poll
static PyObject *
pyepoll_poll(pyEpoll_Object *self, PyObject *args, PyObject *kwds)
{
int maxevents = -1;
double dtimeout = -1.0;
...
struct epoll_event *evs = PyMem_New(struct epoll_event, maxevents);
int nfds;
Py_BEGIN_ALLOW_THREADS
nfds = epoll_wait(self->epfd, evs, maxevents, timeout_ms);
Py_END_ALLOW_THREADS

PyObject *result = PyList_New(nfds);
for (int i = 0; i < nfds; i++) {
PyList_SET_ITEM(result, i,
Py_BuildValue("(iI)", evs[i].data.fd, evs[i].events));
}
return result;
}

epoll.poll(timeout, maxevents) calls epoll_wait. maxevents limits the kernel's result list. epoll is O(1) for both register and poll operations, unlike select/poll which are O(n) in the number of watched fds.

epoll.register

// CPython: Modules/selectmodule.c:960 pyepoll_register
static PyObject *
pyepoll_register(pyEpoll_Object *self, PyObject *args, PyObject *kwds)
{
int fd;
unsigned int events = EPOLLIN | EPOLLOUT;
struct epoll_event ev;
ev.events = events;
ev.data.fd = fd;
int result;
Py_BEGIN_ALLOW_THREADS
result = epoll_ctl(self->epfd, EPOLL_CTL_ADD, fd, &ev);
Py_END_ALLOW_THREADS
if (result == -1) return PyErr_SetFromErrno(PyExc_OSError);
Py_RETURN_NONE;
}

EPOLLIN | EPOLLOUT means "notify when readable or writable". EPOLLET adds edge-triggered mode: the event fires only on state transitions, not while the fd remains readable. Edge-triggered mode requires non-blocking fds.

gopy notes

select.poll is module/select.Poll in module/select/module.go using syscall.Poll. select.epoll uses syscall.EpollCreate1, EpollCtl, EpollWait. select.kqueue uses syscall.Kqueue, Kevent. On non-Linux platforms, epoll is not available and kqueue is used instead.