Skip to content

eloop: Use kqueue or epoll to wait for a fd to become ready#612

Merged
rsmarples merged 2 commits into
masterfrom
eloop_wait
May 21, 2026
Merged

eloop: Use kqueue or epoll to wait for a fd to become ready#612
rsmarples merged 2 commits into
masterfrom
eloop_wait

Conversation

@rsmarples
Copy link
Copy Markdown
Member

On some platforms, poll/select don't work with RLIMIT_NOFILE = 0.
Luckily we can use kevent/epoll instead and it then works.

Add eloop_openfdwaiter() and eloop_closefdwaiter() so we can setup the new polling mechanism as it might not always be required.

Use RLIMIT_NOFILE = 0 on all platforms when using privsep.

rsmarples added 2 commits May 21, 2026 23:48
Use kqueue or epoll for eloop_waitfd().
This requires opening a new kqueue or epoll to handle this as
this is a one shot event we don't want to touch the eloop events.

This works with RLIMIT_NOFILE = 0.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Review Change Stack

Walkthrough

This PR refactors the event loop's fd-waiter implementation by introducing explicit fd-waiter management, centralizing backend event-bit translation helpers, refactoring backend-specific event registration to accept explicit fd, and updating initialization, cleanup, fork handling, and caller code accordingly. Privilege dropping and message framing logic are also updated.

Changes

Event Loop fd-Waiter API

Layer / File(s) Summary
Data model and public API contract
src/eloop.c, src/eloop.h
struct eloop gains waitfd member; header declares new eloop_openfdwaiter(), eloop_closefdwaiter(), and updated eloop_waitfd(struct eloop *, int) signature.
Backend event helpers and bit translation
src/eloop.c
kqueue and epoll event-registration helpers refactored to accept backend fd explicitly; new eloop_kqueueevents() and eloop_epollevents() helpers centralize native event structure to eloop bitmask translation.
Core fd-waiter API implementation
src/eloop.c
eloop_open() refactored to return fd directly; new eloop_openfdwaiter(), eloop_waitfd(struct eloop *, int), and eloop_closefdwaiter() functions implemented using active backend to manage and wait on external file descriptors.
Event loop lifecycle
src/eloop.c
eloop_new() initializes waitfd to -1 and opens backend fd; eloop_free() closes both waitfd and main fd; eloop_forked() reopens both fds on fork and re-registers existing events with refactored helper signatures.
Event loop run loop integration
src/eloop.c
eloop_event_add() updated to pass eloop->fd to refactored helpers; kqueue and epoll run loops refactored to call eloop_kqueueevents() and eloop_epollevents() for event-bit translation.
Caller updates: privsep-root and privsep fork
src/privsep-root.c, src/privsep.c
ps_root_readerrorcb() and ps_root_start() updated to use new eloop_waitfd() and eloop_openfdwaiter() signatures; ps_startprocess() adds eloop_closefdwaiter() call in non-parent fork path.
Privilege dropping and message handling updates
src/privsep.c
ps_dropprivs() simplifies RLIMIT_NOFILE logic by removing platform-specific compile-time gating; ps_recvpsmsg() adjusts expected message payload length calculation using psm fields.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: using kqueue or epoll instead of poll/select for fd waiting in the event loop.
Description check ✅ Passed The description clearly relates to the changeset by explaining the problem (poll/select failing with RLIMIT_NOFILE=0), the solution (using kqueue/epoll), and the new APIs being added.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch eloop_wait

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/eloop.c (1)

923-941: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Preprocessor macro mismatch: USE_POLL should be USE_PPOLL.

The guard at line 923 checks USE_POLL, but the codebase defines USE_PPOLL (line 54) for non-kqueue/non-epoll platforms. This means eloop_pollevents will never be compiled, causing a build failure on platforms that fall into the USE_PPOLL path since eloop_waitfd calls eloop_pollevents at line 992.

Proposed fix
-#elif defined(USE_POLL)
+#elif defined(USE_PPOLL)
 static unsigned short
 eloop_pollevents(struct pollfd *pfd)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/eloop.c` around lines 923 - 941, The conditional compilation guard for
eloop_pollevents is wrong: change the `#elif` defined(USE_POLL) to `#elif`
defined(USE_PPOLL) so eloop_pollevents is compiled when the project uses
USE_PPOLL; ensure the block containing eloop_pollevents (and its closing `#endif`)
matches the USE_PPOLL macro so calls from eloop_waitfd to eloop_pollevents
resolve correctly.
🧹 Nitpick comments (1)
src/eloop.c (1)

996-1006: 💤 Low value

Guard against double-close or calling without prior open.

If eloop_closefdwaiter is called when waitfd is already -1 (never opened, or already closed), close(-1) returns -1 with EBADF. Adding a guard would be consistent with eloop_free and make the API more defensive.

Proposed fix
 int
 eloop_closefdwaiter(struct eloop *eloop)
 {
 `#if` defined(USE_KQUEUE) || defined(USE_EPOLL)
-	int err = close(eloop->waitfd);
+	int err = 0;
+	if (eloop->waitfd != -1)
+		err = close(eloop->waitfd);
 	eloop->waitfd = -1;
 	return err;
 `#else`
 	return 0;
 `#endif`
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/eloop.c` around lines 996 - 1006, eloop_closefdwaiter should guard
against closing an invalid fd: check eloop->waitfd != -1 before calling
close(eloop->waitfd), only call close when valid, set eloop->waitfd = -1 after a
successful/attempted close, and return 0 if it was already -1; mirror the
defensive behavior used in eloop_free and keep the existing conditional
compilation around USE_KQUEUE/USE_EPOLL.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/eloop.c`:
- Around line 923-941: The conditional compilation guard for eloop_pollevents is
wrong: change the `#elif` defined(USE_POLL) to `#elif` defined(USE_PPOLL) so
eloop_pollevents is compiled when the project uses USE_PPOLL; ensure the block
containing eloop_pollevents (and its closing `#endif`) matches the USE_PPOLL macro
so calls from eloop_waitfd to eloop_pollevents resolve correctly.

---

Nitpick comments:
In `@src/eloop.c`:
- Around line 996-1006: eloop_closefdwaiter should guard against closing an
invalid fd: check eloop->waitfd != -1 before calling close(eloop->waitfd), only
call close when valid, set eloop->waitfd = -1 after a successful/attempted
close, and return 0 if it was already -1; mirror the defensive behavior used in
eloop_free and keep the existing conditional compilation around
USE_KQUEUE/USE_EPOLL.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 24cc2be4-6e87-4dc3-a7c2-a1cecefda416

📥 Commits

Reviewing files that changed from the base of the PR and between 61b64b9 and 38c2d33.

📒 Files selected for processing (4)
  • src/eloop.c
  • src/eloop.h
  • src/privsep-root.c
  • src/privsep.c

@rsmarples rsmarples merged commit 6201889 into master May 21, 2026
5 checks passed
@rsmarples rsmarples deleted the eloop_wait branch May 21, 2026 23:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant