Skip to main content

Modules/selectmodule.c

Source:

cpython 3.14 @ ab2d84fe1023/Modules/selectmodule.c

select provides access to the OS I/O multiplexing interfaces used to wait for multiple file descriptors simultaneously.

Map

LinesSymbolRole
1-200select_selectPOSIX select(): fd_set arrays, timeout
201-500poll typePOSIX poll(): event-based, no fd limit
501-800epoll typeLinux epoll: edge/level triggered
801-1100kqueue typeBSD/macOS kqueue: kevent registration
1101-1400devpoll typeSolaris /dev/poll
1401-2200Helpersfileno() extraction, timeout conversion

Reading

select.select()

// CPython: Modules/selectmodule.c:88 select_select
static PyObject *
select_select(PyObject *self, PyObject *args)
{
/* rlist, wlist, xlist = sequences of fd-like objects */
/* timeout = float seconds or None (blocking) */
fd_set ifdset, ofdset, efdset;
FD_ZERO(&ifdset); FD_ZERO(&ofdset); FD_ZERO(&efdset);
int max_fd = 0;
/* Fill fd_sets from Python lists */
for each fd in rlist: FD_SET(fd, &ifdset);
...
Py_BEGIN_ALLOW_THREADS
n = select(max_fd + 1, &ifdset, &ofdset, &efdset, timeout_ptr);
Py_END_ALLOW_THREADS
/* Build result lists from ready fd_sets */
...
}

select() has a hard limit of FD_SETSIZE (1024 on Linux) file descriptors. Use poll or epoll for more.

poll object

// CPython: Modules/selectmodule.c:280 poll_register
static PyObject *
poll_register(pollObject *self, PyObject *args)
{
int fd;
unsigned short events = POLLIN | POLLPRI | POLLOUT;
PyArg_ParseTuple(args, "O&|H:register", fileno, &fd, &events);
struct pollfd pfd = { fd, events, 0 };
/* Store in a list; rebuild on every poll() call */
...
}

static PyObject *
poll_poll(pollObject *self, PyObject *args)
{
double timeout = -1;
/* Build pollfd array from registered fds */
...
Py_BEGIN_ALLOW_THREADS
n = poll(self->ufds, self->ufd_len, (int)(timeout * 1000));
Py_END_ALLOW_THREADS
/* Collect ready fds into result list of (fd, event) tuples */
...
}

epoll (Linux)

// CPython: Modules/selectmodule.c:620 pyepoll_register
static PyObject *
pyepoll_register(pyEpoll_Object *self, PyObject *args, PyObject *kwds)
{
struct epoll_event ev;
ev.events = eventmask; /* EPOLLIN | EPOLLOUT | EPOLLET, etc. */
ev.data.fd = fd;
Py_BEGIN_ALLOW_THREADS
res = epoll_ctl(self->epfd, EPOLL_CTL_ADD, fd, &ev);
Py_END_ALLOW_THREADS
...
}

static PyObject *
pyepoll_poll(pyEpoll_Object *self, PyObject *args)
{
struct epoll_event evs[maxevents];
Py_BEGIN_ALLOW_THREADS
nfds = epoll_wait(self->epfd, evs, maxevents, (int)(timeout_ms));
Py_END_ALLOW_THREADS
/* Return list of (fd, event) tuples */
...
}

EPOLLET (edge-triggered) fires only on state changes; level-triggered fires as long as data is available.

kqueue (macOS/BSD)

// CPython: Modules/selectmodule.c:980 kqueue_queue_control
static PyObject *
kqueue_queue_control(kqueueObject *self, PyObject *args)
{
/* changelist: list of kevent to register/deregister */
/* eventlist: max events to return */
Py_BEGIN_ALLOW_THREADS
gotevents = kevent(self->kqfd, clist, nchanges, elist, nevents, timeout_ptr);
Py_END_ALLOW_THREADS
/* Return list of triggered kevent objects */
...
}

gopy notes

select is in module/select/. select.select() uses syscall.Select. poll uses syscall.Poll. epoll uses syscall.EpollCreate1, EpollCtl, EpollWait (Linux only). kqueue uses syscall.Kqueue, Kevent (macOS/BSD only). asyncio uses epoll/kqueue via these bindings.