epoll_ctl(2)

๐Ÿ’– epoll_ctl โ€” bestie's guide to the control interface for an epoll file descriptor ๐Ÿ’–

okay so like. this is epoll_ctl(2), from the System Calls Manual, and honestly? it's giving main character energy for anyone doing async I/O on Linux. sit down, grab a boba, we're doing this together ๐Ÿง‹โœจ


๐ŸŒธ NAME ๐ŸŒธ

epoll_ctl โ€” the control interface for an epoll file descriptor. that's it, that's the vibe. it's how you tell your epoll instance what to care about ๐Ÿ‘‘


๐Ÿ“š LIBRARY ๐Ÿ“š

Standard C library โ€” <libc>, aka -lc. classic. iconic. no cap.


โœจ SYNOPSIS โœจ

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd,
              struct epoll_event *_Nullable event);

look at her. four args, zero drama (wellโ€ฆ some drama, keep reading) ๐Ÿ’…


๐Ÿฆ‹ DESCRIPTION ๐Ÿฆ‹

so this syscall is the one that adds, modifies, or removes entries in the interest list of the epoll(7) instance that the file descriptor epfd is pointing at. you're basically saying "hey, please do op to the target file descriptor fd", and the kernel is like "say less" ๐Ÿซถ

the valid values for the op argument are:

  • EPOLL_CTL_ADD โ€” adds an entry to the interest list of the epoll file descriptor epfd. and what goes in that entry? the file descriptor fd, a reference to the matching open file description (peep epoll(7) and open(2)), plus all the settings you specified in event. new friend registered โœจ
  • EPOLL_CTL_MOD โ€” changes the settings hanging out with fd in the interest list, swapping them for the new settings in event. glow-up time ๐Ÿ’…
  • EPOLL_CTL_DEL โ€” removes (deregisters) the target file descriptor fd from the interest list. the event argument gets ignored and can be NULL (but see BUGS below, bestie, there's a plot twist ๐Ÿ‘€). it's not you, it's justโ€ฆ you're not in the list anymore.

the event argument describes the object linked to the file descriptor fd. the struct epoll_event is described over in epoll_event(3type) if you wanna go deeper ๐Ÿ“–

the data member of the epoll_event structure is where you stash data that the kernel should save, and then hand back to you (via epoll_wait(2)) when that file descriptor becomes ready. it's like leaving a lil note for future you ๐ŸŽ€

the events member of the epoll_event structure is a bit mask, and you build it by ORing together zero or more event types (which epoll_wait(2) returns) and input flags (which change behavior but aren't returned). so, the available event types:

  • EPOLLIN โ€” the associated file is available for read(2) operations. it's readable, go get it ๐Ÿ“ฅ
  • EPOLLOUT โ€” the associated file is available for write(2) operations. it's writable, go off ๐Ÿ“ค
  • EPOLLRDHUP (since Linux 2.6.17) โ€” stream socket peer closed the connection, or shut down the writing half of the connection. (this flag is especially useful for writing simple code that detects peer shutdown when you're doing edge-triggered monitoring. tiny but mighty ๐Ÿงš)
  • EPOLLPRI โ€” there's an exceptional condition on the file descriptor. go read the discussion of POLLPRI in poll(2) for the lore ๐Ÿšจ
  • EPOLLERR โ€” an error condition happened on the associated file descriptor. this one is also reported for the write end of a pipe when the read end has been closed. epoll_wait(2) will always report for this event; you do not need to set it in events when calling epoll_ctl(). it shows up whether you invited it or not ๐Ÿ’€
  • EPOLLHUP โ€” hang up happened on the associated file descriptor. epoll_wait(2) will always wait for this event; you do not need to set it in events when calling epoll_ctl(). also a permanent plus-one. note that when you're reading from a channel like a pipe or a stream socket, this event merely indicates that the peer closed its end of the channel โ€” subsequent reads from the channel will return 0 (end of file) only after all the outstanding data in the channel has been consumed. so don't panic-drain, okay? ๐Ÿ˜ญ

and the available input flags:

  • EPOLLET โ€” requests edge-triggered notification for the associated file descriptor. the default behavior for epoll is level-triggered. go see epoll(7) for the more detailed tea on edge-triggered vs level-triggered notification ๐Ÿต
  • EPOLLONESHOT (since Linux 2.6.2) โ€” requests one-shot notification for the associated file descriptor. meaning: after one event is notified for that file descriptor by epoll_wait(2), the file descriptor is disabled in the interest list and no other events will be reported by the epoll interface. you have to call epoll_ctl() with EPOLL_CTL_MOD to rearm the file descriptor with a new event mask. one and done, then you re-up ๐Ÿ”
  • EPOLLWAKEUP (since Linux 3.5) โ€” if EPOLLONESHOT and EPOLLET are clear and the process has the CAP_BLOCK_SUSPEND capability, make sure the system does not enter "suspend" or "hibernate" while this event is pending or being processed. the event counts as "processed" from the moment it's returned by a call to epoll_wait(2) until the next call to epoll_wait(2) on the same epoll(7) file descriptor, the closure of that file descriptor, the removal of the event file descriptor with EPOLL_CTL_DEL, or the clearing of EPOLLWAKEUP for the event file descriptor with EPOLL_CTL_MOD. see also BUGS ๐Ÿ‘€
  • EPOLLEXCLUSIVE (since Linux 4.5) โ€” sets an exclusive wakeup mode for the epoll file descriptor being attached to the target file descriptor fd. when a wakeup event happens and multiple epoll file descriptors are attached to the same target file using EPOLLEXCLUSIVE, one or more of those epoll file descriptors will receive an event with epoll_wait(2). the default in that scenario (when EPOLLEXCLUSIVE is not set) is for all epoll file descriptors to receive an event. so EPOLLEXCLUSIVE is useful for avoiding thundering herd problems in certain scenarios. nobody likes a stampede ๐Ÿ˜
    • if the same file descriptor is in multiple epoll instances, some with the EPOLLEXCLUSIVE flag and others without, then events will be provided to all epoll instances that did not specify EPOLLEXCLUSIVE, and to at least one of the epoll instances that did specify EPOLLEXCLUSIVE.
    • these values may be specified in conjunction with EPOLLEXCLUSIVE: EPOLLIN, EPOLLOUT, EPOLLWAKEUP, and EPOLLET. EPOLLHUP and EPOLLERR can also be specified, but that's not required: as usual, these events are always reported if they occur, regardless of whether they're specified in events. attempts to specify other values in events yield the error EINVAL ๐Ÿšซ
    • EPOLLEXCLUSIVE may be used only in an EPOLL_CTL_ADD operation; attempts to use it with EPOLL_CTL_MOD yield an error. if EPOLLEXCLUSIVE has been set using epoll_ctl(), then a subsequent EPOLL_CTL_MOD on the same epfd, fd pair yields an error. a call to epoll_ctl() that specifies EPOLLEXCLUSIVE in events and specifies the target file descriptor fd as an epoll instance will likewise fail. the error in all of these cases is EINVAL. the rules are the rules, bestie ๐Ÿ˜ค

๐ŸŒˆ RETURN VALUE ๐ŸŒˆ

when it works: epoll_ctl() returns zero. when something goes wrong: epoll_ctl() returns -1 and errno is set to tell you what happened. classic king behavior ๐Ÿ‘‘


๐Ÿ’€ ERRORS ๐Ÿ’€

  • EBADF โ€” epfd or fd is not a valid file descriptor.
  • EEXIST โ€” op was EPOLL_CTL_ADD, and the supplied file descriptor fd is already registered with this epoll instance. she's already on the guest list, babe ๐ŸŽŸ๏ธ
  • EINVAL โ€” epfd is not an epoll file descriptor, or fd is the same as epfd, or the requested operation op is not supported by this interface.
  • EINVAL โ€” an invalid event type was specified along with EPOLLEXCLUSIVE in events.
  • EINVAL โ€” op was EPOLL_CTL_MOD and events included EPOLLEXCLUSIVE.
  • EINVAL โ€” op was EPOLL_CTL_MOD and the EPOLLEXCLUSIVE flag has previously been applied to this epfd, fd pair.
  • EINVAL โ€” EPOLLEXCLUSIVE was specified in event and fd refers to an epoll instance.
  • ELOOP โ€” fd refers to an epoll instance and this EPOLL_CTL_ADD operation would result in a circular loop of epoll instances monitoring one another, or a nesting depth of epoll instances greater than 5. no ouroboros energy allowed, the limit is 5, periodt ๐ŸŒ€
  • ENOENT โ€” op was EPOLL_CTL_MOD or EPOLL_CTL_DEL, and fd is not registered with this epoll instance. who? never heard of her ๐Ÿ’…
  • ENOMEM โ€” there was insufficient memory to handle the requested op control operation.
  • ENOSPC โ€” the limit imposed by /proc/sys/fs/epoll/max_user_watches was encountered while trying to register (EPOLL_CTL_ADD) a new file descriptor on an epoll instance. see epoll(7) for further details.
  • EPERM โ€” the target file fd does not support epoll. this error can occur if fd refers to, for example, a regular file or a directory. sorry babe, not everyone gets to play ๐Ÿšซ

๐Ÿ“œ STANDARDS ๐Ÿ“œ

Linux. that's the whole section. Linux only, iykyk ๐Ÿง


๐Ÿ•ฐ๏ธ HISTORY ๐Ÿ•ฐ๏ธ

Linux 2.6, glibc 2.3.2. old money โœจ


๐Ÿ“ NOTES ๐Ÿ“

the epoll interface supports all file descriptors that support poll(2). equality, honestly ๐Ÿซถ


๐Ÿ› BUGS ๐Ÿ›

listen. before Linux 2.6.9, the EPOLL_CTL_DEL operation required a non-null pointer in event, even though that argument is ignored. since Linux 2.6.9, event can be specified as NULL when using EPOLL_CTL_DEL. so if your application needs to be portable to kernels before Linux 2.6.9, you should specify a non-null pointer in event. ancient-kernel girlies, this one's for you ๐Ÿ™ˆ

and second: if EPOLLWAKEUP is specified in flags, but the caller does not have the CAP_BLOCK_SUSPEND capability, then the EPOLLWAKEUP flag is silently ignored. why, you ask? because this unfortunate behavior is necessary: no validity checks were performed on the flags argument in the original implementation, and adding EPOLLWAKEUP with a check that made the call fail if the caller didn't have CAP_BLOCK_SUSPEND caused a breakage in at least one existing user-space application that happened to randomly (and uselessly) specify this bit. so yeah. a robust application should therefore double check that it has the CAP_BLOCK_SUSPEND capability if it's attempting to use the EPOLLWAKEUP flag. trust but verify, bestie ๐Ÿ”


๐Ÿ‘€ SEE ALSO ๐Ÿ‘€

epoll_create(2), epoll_wait(2), ioctl_eventpoll(2), poll(2), epoll(7). go read the whole friend group, they're all iconic ๐Ÿ’–


okay that's the whole page, and for the cute sign-off: this was epoll_ctl(2) from Linux man-pages 6.20, dated 2025-12-25 ๐ŸŽ€ happy epolling, go rearm responsibly, and remember โ€” if epoll_wait(2) says it's ready, it's ready. bye bestie ๐Ÿ’…โœจ๐Ÿฆ‹

๐Ÿ“ฆ where this page comes from
  • arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
  • nixpkgs unstable: man-pages 6.20
  • ๐Ÿ“œ the page's license: GPL-2.0-or-later
  • ๐Ÿ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms